Claude Code à Unity CLI Loop ã§ãã¥ãŒããªã¢ã«ã®E2Eãã¹ããèªååããŠã¿ã
ãã¥ãŒããªã¢ã«ç¢ºèªãæ¯åæåã§åãã®ããã©ãåé¡
ã²ãŒã éçºãããŠãããšããã¥ãŒããªã¢ã«ã®ååž°ãã¹ãã¯é¿ããŠéããªãäœæ¥ã§ãã
ã»ãŒãããŒã¿ãåæåããŠãèµ·åããŠãèŠçŽã«åæããŠãããã²ãŒã ãã¯ãªã¢ããŠãã¡ã€ã³ã²ãŒã ã®ãã¥ãŒããªã¢ã«ãã²ãšéãããªããŠããã¹ãåããŠâŠâŠ
1åéãã ãã§æ°åããããã®ãããŒããã³ãŒãã倿Žãããã³ã«æåã§ç¢ºèªããã®ã¯ãªããªãããã©ãã§ãã
ããã§è©Šãã«ãClaude Code ãš Unity CLI Loop ãçµã¿åãããŠãååèµ·åãã¥ãŒããªã¢ã«ã®å®èµ°ãäžžããšèªååãã E2E ãã¹ãåºç€ãæ§ç¯ããŠã¿ãŸããã
ãŸã æé©è§£ãèŠã€ãããšã¯èšããŸããããã²ãšã€ã®ã¢ãããŒããšããŠããããªããæ¹ããããããšãã玹ä»ãã§ããã°ãšæããŸãã
ãªããUnity CLI LoopïŒæ§ïŒuLoopMCPïŒã«ã€ããŠã¯éå»èšäºã§ç޹ä»ããŠããã®ã§ãåºæ¬çãªä»çµã¿ã¯ãã¡ããåç §ããŠãã ããã
æ¬èšäºã§ã¯ã以äžã®å 容ã玹ä»ããŸãã
- E2Eãã¹ãåºç€ã®å šäœã¢ãŒããã¯ãã£ãšèšèšæ¹é
- ååèµ·åãã¥ãŒããªã¢ã«ã®èªååã·ããªãª
- CLI åŒã³åºãã®ãªãŒããŒãããã57%åæžãããç¶æ ããªããžæ¹åŒã
- ã¹ã¯ãªãŒã³ã·ã§ããã»åç»é²ç»ã«ããã¢ãŠãããã
å šäœã¢ãŒããã¯ãã£
ä»åæ§ç¯ããE2Eãã¹ãåºç€ã¯ã以äžã®4å±€æ§é ã«ãªã£ãŠããŸãã
| å±€ | æ åœ | ç¹åŸŽ |
|---|---|---|
| Claude Code Skill | ã·ããªãªèªã¿èŸŒã¿ãã¹ã¯ãªããå®è¡é å¶åŸ¡ãçµæå€å®ã»ã¬ããŒã | æå°éã®å€æã®ã¿ããã¹ãããžãã¯ã«ã¯é¢äžããªã |
| ã·ã§ã«ã¹ã¯ãªãã | ãã¹ããããŒå¶åŸ¡ãã«ãŒããsleepããªãã©ã€ | 決å®çã«åäœãåçŸæ§ãé«ã |
| unity-cli-loop | Unity Editor ãžã®åœä»€éä¿¡ |
execute-dynamic-code ã§ C# ãåçå®è¡ |
| Unity å C# | ãã¿ã³ã¯ãªãã¯ãç¶æ æ€ç¥ãããªããžæžã蟌㿠| ã²ãŒã å ãªããžã§ã¯ããçŽæ¥æäœ |
ãã¹ã察象ïŒååèµ·åãã¥ãŒããªã¢ã«
ã²ãŒã æŠèŠ
ä»åã®ãã¹ã察象ã¯ãæ¥åã§éçºããŠããã¢ãã€ã«åãã®ã«ãžã¥ã¢ã«RPGã§ããã¡ã€ã³ã²ãŒã ã§ã¯ã©ã³ãã ã€ãã³ãïŒããã«ãã¢ã€ãã ç²åŸãªã©ïŒãæ¶åããªããã¹ããŒãžãé²ããŠããä»çµã¿ã§ãååèµ·åæã«ã¯ãã®ã²ãŒã ã·ã¹ãã ãã²ãšéãäœéšãããã¥ãŒããªã¢ã«ãçšæãããŠããŸãã
ãã¥ãŒããªã¢ã«ã®æµã
BootïŒèµ·åïŒ â å©çšèŠçŽåæ â OpeningæŒåº â ããã²ãŒã
â ã¡ã€ã³ã²ãŒã ïŒè€æ°ã¹ãããïŒ â ãã¹æç Ž â æ¬¡ã¹ããŒãžãž
ãã¥ãŒããªã¢ã«äžã«ã¯ãã³ã€ã³ç²åŸãããã«ãè£ åç²åŸã仲éæåºã匷åãè£ å亀æãªã©ãã²ãŒã ã®äž»èŠæ©èœãã»ãŒå šãŠç»å ŽããŸããéäžã§ãããã¢ãããã€ã¢ãã°ã衚瀺ãããããå ±é ¬ã®åãåããæãŸã£ãããšãç¶æ é·ç§»ãããªãè€éã§ãã
ãªããã¥ãŒããªã¢ã«ãE2Eãã¹ãã«ããã
E2Eãã¹ãã®å¯Ÿè±¡ãšããŠã¯ä»ã®æ©èœãåè£ã«ãããŸãããããããããAI ã䜿ã£ãèªååã§ã©ããŸã§ã§ããã®ããã®å¡©æ¢ ãã€ãã¿ãããšããç®çããããŸããã
ãã®ç¹ã§ãååèµ·åãã¥ãŒããªã¢ã«ã¯æåã®é¡æãšããŠé©åã ãšå€æããŸããã
- ã²ãŒã ã®äž»èŠæ©èœãã²ãšéãç¶²çŸ ãããããŒãªã®ã§ãèªåã§éããã°åºç¯å²ã®ååž°ãã¹ãã«ãªã
- ã»ãŒãããŒã¿ãåæåããã°æ¯ååãç¶æ ããéå§ã§ãããããåçŸæ§ã®ç¢ºä¿ã容æ
- ãããŒãåºå®ãããŠããã®ã§ãèªååã®é£æåºŠãèŠæ¥µãããã
èšèšæ¹éïŒAIã«äœãããããŠãäœãããããªãã
æåã®ã¢ãããŒãïŒAI ããã¹ãŠå€æããæ¹åŒ
åœåã¯ãClaude ãæ¯ã¹ããã倿ããŠã€ã³ã¿ã©ã¯ãã£ãã«æäœãããæ¹åŒã§å®è£ ããŠããŸããã
ã€ãã¬ãŒã·ã§ã³ããšã«ç»é¢äžã®ããã¹ããUIèŠçŽ ã execute-dynamic-code ã§ååŸãããã®çµæãèŠãŠ AI ããæ¬¡ã¯ãã®ãã¿ã³ãã¯ãªãã¯ããããã¯èªåé²è¡ãªã®ã§åŸ
ã€ããšå€æããèšèšã§ãã
ããã¯ããã§åããŸãããAI ãã²ãŒã ç»é¢ã®ç¶æ³ãããã¹ãæ å ±ãšããŠåãåããããŒãã«ã«ç §ãããŠæ¬¡ã®ã¢ã¯ã·ã§ã³ã決å®ãããæè»æ§ã¯é«ããæ°ãããã€ã¢ãã°ãåºãŠã AI ãèšæ©å¿å€ã«å¯ŸåŠã§ããŸãã
ãã ããæ¬¡ã®ãããªåé¡ããããŸããã
- ããŒã¯ã³æ¶è²»ãæ¿ãã: æ¯ã€ãã¬ãŒã·ã§ã³ã§ç¶æ ååŸçµæã AI ã«æž¡ããŠå€æãä»°ãããããã¥ãŒããªã¢ã«1åã®å®èµ°ã§ããªãã®ããŒã¯ã³ãæ¶è²»ãã
- å®è¡æéãé·ã: AI ã®æšè«åŸ ã¡ + CLI åŒã³åºãã®ãªãŒããŒãããã§ãæãè€éãªã¡ã€ã³ã«ãŒãéšåã ãã§çŽ240ç§ããã
- åçŸæ§ãäœã: AI ã®å€æãæ¯å埮åŠã«ç°ãªããããåãã·ããªãªã§ãæåããã¬ãããšããã
æ¹é転æïŒãã¹ãããžãã¯ãã·ã§ã«ã¹ã¯ãªããã«åºãã
ããã§æ¹éã転æãããã¹ãããžãã¯ããã¹ãŠã·ã§ã«ã¹ã¯ãªããã«åºããããšã«ããŸããã
- ã·ã§ã«ã¹ã¯ãªãã: äœãã©ã®é ã§å®è¡ããã©ãåå²ãããã¯ãã¹ãŠã¹ã¯ãªããå ã§å®çµãã«ãŒãåæ°ã»sleep æéã»ãªãã©ã€äžéãåºå®å€
-
AIæšè«ïŒClaude Code SkillïŒ: ã·ããªãªãã¡ã€ã«ïŒ
.mdïŒãèªãã§ã¹ã¯ãªãããé æ¬¡ bash å®è¡ããæçµçµæãã¬ããŒãã«ãŸãšããã ã
AI ã®ãæè»ãªå€æåãã¯äŸ¿å©ã§ããããã¥ãŒããªã¢ã«ã®E2Eãã¹ãã«ãããŠã¯ãããŒãåºå®ãããŠããã®ã§ã倿ãå¿ èŠãªå Žé¢ã¯ã»ãšãã©ãããŸããã
ãã€ã¢ãã°ã®ãã¿ãŒã³ãšãã¿ã³ã®å¯Ÿå¿ã C# ã®ã³ãŒãã«æžãäžããŠããŸãã°ãAI ã«èããŸã§ããªã決å®çã«åŠçã§ããŸãã
ãã®è»¢æã«ãã£ãŠãå è¿°ã®3ã€ã®èª²é¡ãããããæ¹åããŸããã
- ããŒã¯ã³æ¶è²»: AI ã¯åã¹ãããã®ã·ã§ã«ã¹ã¯ãªãããé æ¬¡ bash å®è¡ + ã¹ã¯ãªãŒã³ã·ã§ããç¢ºèª + ã¬ããŒãçæããããªããããå ¥å çŽ17,000ã20,000 / åºå çŽ1,500 çšåºŠã«åãŸãããã«ãªããŸãã
- å®è¡æé: AI ã®æšè«åŸ ã¡ããªããªã£ãåãæãè€éãªã¡ã€ã³ã«ãŒãéšåã 240ç§ â 104ç§ã«ççž®ãããã«åŸè¿°ãããç¶æ ããªããžæ¹åŒãã®å°å ¥ã§ã²ãŒã å ããŒãªã³ã°ãé«éåããŠããŸã
- åçŸæ§: åå²ããžãã¯ããã¹ãŠã¹ã¯ãªããã«åºå®ãããŠãããããåãã·ããªãªã¯æ¯ååãæé ã§å®è¡ãããŸããAI ã®å€æãã¬ã«æ©ãŸãããããšããªããªããŸãã
æ°åéã®ã²ãŒã ãã¬ã€ãäžžããšèªååããŠãããããããŒã¯ã³ã¯ Claude Code ã§ç°¡åãªè³ªåããã2ã3ååçšåºŠã§ãããã AI 倿æ¹åŒã®ãŸãŸã ã£ããã80å以äžã®ã€ãã¬ãŒã·ã§ã³ã§æ¯åã³ã³ããã¹ããèç©ãããŠãããããæ¡éãã®æ¶è²»éã«ãªã£ãŠããã§ãããã
UIæäœã®ã¢ãããŒãæ¯èŒïŒæ±ºãæã¡ vs AIèªåŸå€æ
ä»åã®E2Eãã¹ãã§ã¯ GameObject.Find("ãã¿ã³å") + onClick.Invoke() ã§ãã¿ã³ãååæå®ããŠã¯ãªãã¯ããæ¹åŒãæ¡çšããŠããŸããäžæ¹ã§ãAIãšãŒãžã§ã³ãã« Unity UI ãèªåèªèã»æäœãããã¢ãããŒããååšããŸãã
| èŠ³ç¹ | 決ãæã¡æ¹åŒïŒä»åïŒ | AI èªåŸå€ææ¹åŒ |
|---|---|---|
| UI èŠçŽ ã®ç¹å® |
GameObject.Find ã§ååæå® |
Selectable å šåé + Raycast ã§æ±çšçã«æ€åº |
| æäœå€æ | ã¹ã¯ãªããã«åºå®ããžã㯠| AI ãæ¯åã¡ã¿ããŒã¿ãèŠãŠå€æ |
| æè»æ§ | ãã¿ã³å倿޿ã¯ã¹ã¯ãªããä¿®æ£ãå¿ èŠ | UI 倿Žã« AI ãé©å¿ã§ããå¯èœæ§ãã |
| åçŸæ§ | 決å®çãæ¯ååã | AI ã®å€æã§ãã¬ãã |
| ããŒã¯ã³æ¶è²» | AI ã¯ã¹ã¯ãªããå®è¡ã®ã¿ãäœã³ã¹ã | æ¯ã¹ãããã§ UI ã¡ã¿ããŒã¿ â AI 倿ãé«ã³ã¹ã |
| é床 | ã·ã§ã«ã¹ã¯ãªããã§é«éå®è¡ | æ¯å AI æšè« + UI å šåéã®ãªãŒããŒããã |
ãã¥ãŒããªã¢ã«ã®ããã«ãããŒãåºå®ãããŠãããã¹ãã§ã¯ãã©ã®ãã€ã¢ãã°ãåºãŠã©ã®ãã¿ã³ãæŒãããäºåã«åãã£ãŠãããããAI ã«æ¯å倿ãããã¡ãªããã¯èããšèããŠããŸããåçŸæ§ã»é床ã»ã³ã¹ãã®é¢ã§æ±ºãæã¡æ¹åŒãé©åãšå€æããŸããã
éã« AI èªåŸå€ææ¹åŒã掻ããã®ã¯ããããŒã確å®ããåã®æ¢çŽ¢çãã¹ãããUI ã®å€æŽãé »ç¹ã§ãã¿ãŒã³ã®äºåå®çŸ©ã远ãã€ããªãã±ãŒã¹ã§ãããã
ã·ããªãªãšã¹ã¯ãªãã矀
ãã¡ã€ã«æ§æ
.claude/skills/e2e-test/
âââ SKILL.md # Skillå®çŸ©ïŒåææ¡ä»¶ã»å
±éæé ïŒ
âââ scenarios/
â âââ boot-to-main.md # åºæ¬ã·ããªãªïŒBootâMainé·ç§»ç¢ºèªïŒ
â âââ first-launch-tutorial.md # ãã¥ãŒããªã¢ã«å®èµ°ã·ããªãª
âââ scripts/
âââ lib/
â âââ uloop-helpers.sh # å
±é颿°ã©ã€ãã©ãª
âââ e2e-save-reset.sh # Step 0: ã»ãŒãããŒã¿éé¿+ãªã»ãã
âââ e2e-boot-playmode.sh # Step 1: BootâPlayModeéå§
âââ e2e-terms-and-record.sh # Step 2: å©çšèŠçŽåæ+é²ç»éå§
âââ e2e-opening-skip.sh # Step 3: Opening ã¹ããã
âââ e2e-minigame.sh # Step 4: ããã²ãŒã ãã¥ãŒããªã¢ã«
âââ e2e-main-loop.sh # Step 5: ã¡ã€ã³ã²ãŒã ã«ãŒãèªåå
âââ e2e-cleanup.sh # Step 7-8: ãšã©ãŒç¢ºèª+ã¯ãªãŒã³ã¢ãã
ã·ããªãªãã¡ã€ã«ïŒ.mdïŒããäœããã¹ããããããå®çŸ©ããã·ã§ã«ã¹ã¯ãªããïŒ.shïŒããã©ãå®è¡ãããããæ
ããŸããClaude Code 㯠.md ãèªã¿èŸŒãã§ .sh ãé æ¬¡å®è¡ããã ãã§ãã
å®è¡ãããŒ
| Step | ã¹ã¯ãªãã | ããããš |
|---|---|---|
| 0 | e2e-save-reset.sh |
ã»ãŒãDBéé¿ â DBåé€ â PlayerPrefsåé€ |
| 1 | e2e-boot-playmode.sh |
Boot ã·ãŒã³ãéã â PlayMode éå§ |
| 2 | e2e-terms-and-record.sh |
å©çšèŠçŽãã¿ã³ã¯ãªã㯠â MP4 é²ç»éå§ |
| 3 | e2e-opening-skip.sh |
Opening æŒåºãã¹ããã |
| 4 | e2e-minigame.sh |
ããã²ãŒã ãã¥ãŒããªã¢ã«ãå®è¡ |
| 5 | e2e-main-loop.sh |
ã¡ã€ã³ã²ãŒã ã®ãã¥ãŒããªã¢ã«ã¹ããããèªåæ¶å |
| 6 | â | ã¹ã¯ãªãŒã³ã·ã§ããã§ãã¥ãŒããªã¢ã«å®äºãç®èŠç¢ºèª |
| 7-8 | e2e-cleanup.sh |
ãšã©ãŒãã°ç¢ºèª â é²ç»åæ¢ â PlayMode çµäº â ã»ãŒã埩å |
åã¹ã¯ãªããã®èŠç¹
Step 0: ã»ãŒãããŒã¿ã®ãªã»ãã
æ¯åãååèµ·åãç¶æ ãäœãããã«ãã·ã§ã«ã³ãã³ãã§DBãšPlayerPrefsãçŽæ¥åé€ããŠããŸãã
# çŸåšã®ã»ãŒãããŒã¿ãããã¯ã¢ããã«éé¿
if [ -f "$DB_DIR/MyGame.db" ]; then
cp "$DB_DIR/MyGame.db" "$DB_DIR/MyGame_backup.db"
fi
# DBåé€ + PlayerPrefsåé€
rm -f "$DB_DIR/MyGame.db"
defaults delete "unity.MyCompany.MyGame" 2>/dev/null
ãã¹ãå®äºåŸïŒStep 8ïŒã«ããã¯ã¢ãããã埩å ããã®ã§ãéçºäžã®ã»ãŒãããŒã¿ãæ¶ããå¿é ã¯ãããŸããã
Step 1: PlayMode éå§
unity-cli-loop çµç±ã§ Boot ã·ãŒã³ãéããPlayMode ãéå§ããŸãã
uloop_exec 'UnityEditor.SceneManagement.EditorSceneManager.OpenScene("Assets/.../Boot.unity");'
uloop clear-console
uloop control-play-mode --action Play
sleep 1
close_remote_config # éçºçšãã€ã¢ãã°ãéãã
close_remote_config ã¯éçºãã«ãç¹æã®ãã€ã¢ãã°ãéãããã«ããŒé¢æ°ã§ããååã® CloseButton ãæã€å¥ã®UIãšè¡çªããªããããCanvas åã§ç¹å®ããŠããã¯ãªãã¯ããå®è£
ã«ãªã£ãŠããŸãããªãã察象ã®ãªããžã§ã¯ãã«åºæã® Tag ãæ¯ã£ãŠããã° GameObject.FindWithTag ã§äžæã«ç¹å®ã§ããã®ã§ããã¡ãã®ã»ããã·ã³ãã«ã«è§£æ±ºã§ããã±ãŒã¹ããããŸãã
Step 2-3: å©çšèŠçŽåæãš Opening ã¹ããã
ããã§ã¯ poll_and_click ãšãããã«ããŒé¢æ°ã掻èºããŸããåºå®ã® sleep ã§ã¯ãªãããã¿ã³ãåºçŸãããŸã§ããŒãªã³ã°ããŠããã¯ãªãã¯ããæ¹åŒã§ãã
poll_and_click "StartButton" 1 10 # 1ç§ééãæå€§10åããŒãªã³ã°
start_recording "first_launch_tutorial" # MP4é²ç»éå§
poll_and_click "SkipButton" 0.5 20 # 0.5ç§ééãæå€§20å
åºå® sleep ã ãšãç»é¢é·ç§»ãæ©ããã°ç¡é§ã«åŸ ã€ãé ããã°ã¿ã€ã ã¢ãŠããã«ãªããã¡ã§ãããããŒãªã³ã°æ¹åŒãªãé·ç§»å®äºãæ€ç¥ããŠå³åº§ã«æ¬¡ã®ã¢ã¯ã·ã§ã³ã«ç§»ããŸãã
Step 4: ããã²ãŒã
ãã¥ãŒããªã¢ã«ã®åºç€ã«ããããã²ãŒã ã¯ãå°ãåã£ãå¶åŸ¡ãå¿ èŠã§ãããç©çæŒç®ã䜿ã£ãã€ã³ã¿ã©ã¯ã·ã§ã³ããããPointerDown/Up ã€ãã³ããæ£ç¢ºãªã¿ã€ãã³ã°ã§çºç«ãããå¿ èŠããããŸãã
// PointerDown ã§ã€ã³ã¿ã©ã¯ã·ã§ã³éå§
ExecuteEvents.Execute(trigger, eventData, ExecuteEvents.pointerDownHandler);
// UniTask.Void ã§ã²ãŒã æéããŒã¹ã®é
å»¶åŸã«èªå忢
UniTask.Void(async () => {
await UniTask.WaitUntil(() => Time.timeScale > 0.5f);
var startGameTime = Time.time;
await UniTask.WaitUntil(() => Time.time - startGameTime >= targetDuration);
rigidbody.angularVelocity = Vector3.zero;
ExecuteEvents.Execute(trigger, upData, ExecuteEvents.pointerUpHandler);
});
ãã€ã³ã㯠UniTask.Void ã§ã²ãŒã å
æéããŒã¹ã®é
å»¶åŠçãã¹ã±ãžã¥ãŒã«ããŠãã ããšã§ããbash åŽãã sleep ã§åŸ
ã€ãšå®æéãš TimeScale ã®ãºã¬ã§äžæ£ç¢ºã«ãªããŸãããã²ãŒã å
ã® Time.time ãåºæºã«ããã°çã£ãçµæãå®å®ããŠåçŸã§ããŸãã
Step 5: ã¡ã€ã³ã²ãŒã ã«ãŒãïŒæ¬¡ã»ã¯ã·ã§ã³ã§è©³è¿°ïŒ
ãã¥ãŒããªã¢ã«ã®åã¹ããããèªåæ¶åããããã®E2Eãã¹ãã®æãè€éãªããŒãã§ãããç¶æ ããªããžæ¹åŒããšããç¬èªã®æé©åææ³ãå°å ¥ããŠããŸãã
Step 7-8: ã¯ãªãŒã³ã¢ãã
ãã¹ãçµäºæã¯ããšã©ãŒãã°ã®ç¢ºèª â é²ç»åæ¢ â PlayMode çµäº â ã»ãŒã埩å ãé ã«è¡ããŸãã
# ãšã©ãŒãã°ç¢ºèª
errorCount=$(uloop get-logs --log-type Error 2>/dev/null \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('TotalCount',0))")
# ã¹ã¯ãªãŒã³ã·ã§ããååŸ
screenshotPath=$(uloop screenshot --window-name Game --capture-mode rendering \
--output-directory /tmp 2>/dev/null \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('Screenshots',[{}])[0].get('ImagePath',''))")
# é²ç»åæ¢ïŒPlayMode 忢åã«ïŒïŒ
stop_recording
# PlayMode çµäº
uloop control-play-mode --action Stop
# ã»ãŒã埩å
cp "$DB_DIR/MyGame_backup.db" "$DB_DIR/MyGame.db"
ç¶æ ããªããžæ¹åŒïŒCLIãªãŒããŒãããã®åæž
ã¡ã€ã³ã²ãŒã ã®ãã¥ãŒããªã¢ã«èªååã§æå€§ã®èª²é¡ã«ãªã£ãã®ããCLI åŒã³åºãã®ãªãŒããŒãããã§ããã
課é¡
unity-cli-loop ã1ååŒã³åºãã®ã«çŽ1ã2ç§ããããŸãããã¥ãŒããªã¢ã«ã®åã¹ãããã§çºçããã€ãã³ãïŒãã€ã¢ãã°è¡šç€ºããã¿ã³ã¯ãªãã¯ãã¡ã€ã³ã®ã€ã³ã¿ã©ã¯ã·ã§ã³æäœãèªåæŒåºã®åŸ æ©âŠïŒã1ã€ãã€ç¢ºèªããŠããããããã ãã§æ°åããã£ãŠããŸããŸãã
éåžžæ¹åŒ:
ã·ã§ã« â uloop(1-2ç§) â çµæ â sleep â uloop(1-2ç§) â çµæ â sleep â ...
解決çïŒã²ãŒã å ããŒãªã³ã° + Bridge GameObject
ããã§å°å ¥ããã®ããç¶æ ããªããžæ¹åŒãã§ããCLIïŒå€éšããã»ã¹ïŒãšã²ãŒã å ïŒUnity ã©ã³ã¿ã€ã ïŒãšãã2ã€ã®ç°ãªãäžçãæ©æž¡ãããä»çµã¿ãªã®ã§ãããåŒãã§ããŸãã
æ žãšãªãã¢ã€ãã¢ã¯ãDontDestroyOnLoad 㪠Bridge GameObject ããããã¡ãšããŠäœ¿ããã²ãŒã å
ã§éåæã«ããŒãªã³ã°ããçµæãåãªããžã§ã¯ãã®ååãšããŠæžã蟌ããšãããã®ã§ãã
ãªã static 倿°ã PlayerPrefs ã§ã¯ãªãåãªããžã§ã¯ãã®ååããšãããšãexecute-dynamic-code ã¯å®è¡ã®ãã³ã«ç¬ç«ããã³ã³ããã¹ãã§è©äŸ¡ããããããstatic 倿°ãè·šãã§å
±æã§ããŸããã
äžæ¹ GameObject.Find ã¯ã·ãŒã³äžã®ãªããžã§ã¯ããæšªæçã«æ€çŽ¢ã§ããã®ã§ãæ°žç¶åãã GameObject ã®åæ§é ã§ããã° CLI ã®åŒã³åºãããŸããã§ç¢ºå®ã«èªã¿æžãã§ããŸãã
éåžžæ¹åŒã§ã¯ãç¶æ 確èªã®ãã³ã« CLI ãåŒã³åºãå¿ èŠããããŸãã
ããªããžæ¹åŒã§ã¯ãã²ãŒã å ã§éåæããŒãªã³ã°ãèµ°ããæ¬¡ã® CLI åŒã³åºãæã«ã¯çµæãå³åº§ã«åããŸãã
å®è£
ãŸã Bridge ã®åæåãšçµæã®èªã¿åãã§ãã
// Bridge ããªããã°äœæïŒDontDestroyOnLoad ã§æ°žç¶åïŒ
var bridge = GameObject.Find("__E2E_BRIDGE__");
if (bridge == null) {
bridge = new GameObject("__E2E_BRIDGE__");
Object.DontDestroyOnLoad(bridge);
}
// ååã®ããŒãªã³ã°çµæãããã°èªã¿åã£ãŠè¿ã
if (bridge.transform.childCount > 0) {
var child = bridge.transform.GetChild(0);
var result = child.name; // åãªããžã§ã¯ãå = ç¶æ
Object.Destroy(child.gameObject);
return $"BRIDGE:{result}";
}
ç¶æ
æ€ç¥ãšã¢ã¯ã·ã§ã³å®è¡ã¯ DetectAndAct() 颿°ã«éçŽããŠããŸãã
string DetectAndAct() {
// Editoräžæåæ¢ãã§ãã¯ïŒãšã©ãŒæ€ç¥ïŒ
if (EditorApplication.isPaused) return "STATE:paused";
// ãã¥ãŒããªã¢ã«å®äºå€å®
var autoSpin = GameObject.Find("AutoSpinButton");
if (autoSpin != null && autoSpin.activeInHierarchy) {
var c = autoSpin.GetComponentInParent<Canvas>();
if (c != null && c.sortingOrder >= 11000) return "COMPLETE";
}
// ãã€ã¢ãã°æ€ç¥ + ãã¿ã³ã¯ãªãã¯
string[][] dialogs = {
new[]{"GotQuestReward(Clone)", "CloseButton", "closed_reward"},
new[]{"GotReward(Clone)", "CloseButton", "got_reward_closed"},
new[]{"GetGearDialog(Clone)", "EquipButton", "equip_gear"},
// ... ä»ã®ãã€ã¢ãã°
};
foreach (var db in dialogs) {
var dialog = GameObject.Find(db[0]);
if (dialog != null && dialog.activeInHierarchy) {
foreach (var b in dialog.GetComponentsInChildren<Button>(true)) {
if (b.gameObject.name == db[1] && b.interactable) {
b.onClick.Invoke();
return $"ACTION:{db[2]}";
}
}
}
}
// ... ã¡ã€ã³ãã¿ã³ã®æ€ç¥ãåæ§
return "NO_ACTION";
}
ãããŠãã²ãŒã å ããŒãªã³ã°ã®ã¹ã±ãžã¥ãŒã«ã§ãã
void SchedulePoll(GameObject br) {
UniTask.Void(async () => {
for (int i = 0; i < 20; i++) {
await UniTask.Delay(500); // 500ms à 20å = æå€§10ç§
if (br == null) return;
var state = DetectAndAct(); // ç¶æ
æ€ç¥ïŒå
±é颿°ïŒ
if (state != "NO_ACTION") {
var child = new GameObject(state); // çµæãåãªããžã§ã¯ãåã«æžã蟌ã¿
child.transform.SetParent(br.transform);
return;
}
}
// ã¿ã€ã ã¢ãŠã
if (br != null) {
var child = new GameObject("TIMEOUT");
child.transform.SetParent(br.transform);
}
});
}
bash åŽã®ã«ãŒãã¯ãè¿ãå€ã«å¿ããŠã·ã³ãã«ã«åå²ããã ãã§ãã
while [ $iteration -lt $maxIterations ]; do
iteration=$((iteration + 1))
result=$(uloop_exec "$UNIFIED_CODE")
case "$result" in
COMPLETE|BRIDGE:COMPLETE)
echo "=== TUTORIAL COMPLETE ===" ; break ;;
STATE:paused|BRIDGE:STATE:paused)
echo "=== Error detected ===" ; exit 1 ;;
BRIDGE:*)
stallCount=0 ;; # ã²ãŒã å
ããŒãªã³ã°çµæ â 峿¬¡ãž
MAIN_INTERACTION)
stallCount=0 ; sleep 2 ;; # ã¡ã€ã³ã®ã€ã³ã¿ã©ã¯ã·ã§ã³æäœæžã¿
ACTION:*|MAIN_BTN:*)
stallCount=0 ; sleep 2 ;; # ãã€ã¢ãã°/ãã¿ã³ã¯ãªãã¯æžã¿
NO_ACTION)
stallCount=$((stallCount + 1))
if [ $stallCount -ge 15 ]; then
uloop screenshot ... # åæ»æ€ç¥ â ã¹ã¯ã·ã§1åã ã
fi ;;
esac
done
广
ãã®æé©åã«ãã£ãŠããã¥ãŒããªã¢ã«ã«ãŒãã®å®è¡æéã 240ç§ â 104ç§ïŒ57%åæžïŒ ã«ççž®ãããŸããã
| æ¹åŒ | ä»çµã¿ | æèŠæé |
|---|---|---|
| éåžžæ¹åŒ | æ¯å CLI ã§ç¶æ ç¢ºèª | çŽ240ç§ |
| ããªããžæ¹åŒ | ã²ãŒã å ããŒãªã³ã° + çµæãããã¡ | çŽ104ç§ |
ããªããžæ¹åŒã§ã¯ãCLI åŒã³åºã1åã§ãååã®çµæèªã¿åã + ä»åã®ã¢ã¯ã·ã§ã³å®è¡ + 次åã®ããŒãªã³ã°ã¹ã±ãžã¥ãŒã«ãããŸãšããŠè¡ãããããCLI ã®ã©ãŠã³ãããªããåæ°ãå€§å¹ ã«æžã£ãŠããŸãã
ãªãã104ç§ãšããæ°åã¯äººéãæåã§ãã¥ãŒããªã¢ã«ãæäœããå Žåãšã»ãŒåçã®é床ã§ããCLI ã®ãªãŒããŒããããå·®ãåŒãã°ãã²ãŒã å ã®åŠçèªäœã¯äººéã®æäœãã³ããšå€ãããªãããŒã¹ã§é²ãã§ããããšã«ãªããŸãã
å ±éã©ã€ãã©ãªïŒuloop-helpers.sh
ãã¹ãå
šäœãæ¯ããå
±é颿°ã uloop-helpers.sh ã«ãŸãšããŠããŸãã
| 颿° | çšé |
|---|---|
uloop_exec |
C# ã³ãŒãã execute-dynamic-code ã§å®è¡ããJSON ããçµæãæœåº |
close_remote_config |
éçºçšãã€ã¢ãã°ã Canvas åã§ç¹å®ããŠéãã |
poll_and_click |
ãã¿ã³åºçŸãããŒãªã³ã° â ã¯ãªã㯠|
read_bridge |
Bridge ã®åãªããžã§ã¯ãããçµæãèªã¿åã |
destroy_bridge |
Bridge GameObject ãç Žæ£ |
start_recording |
Unity Recorder ã§ MP4 é²ç»éå§ |
stop_recording |
é²ç»åæ¢ + ãã¡ã€ã«ãã¹åºå |
uloop_execïŒC# å®è¡ã®åºæ¬é¢æ°
ãã¹ãŠã®ã¹ã¯ãªããã§äœ¿ãããæãåºæ¬çãªé¢æ°ã§ãã
uloop_exec() {
local response
response=$(uloop execute-dynamic-code --code "$1" 2>/dev/null) || { echo ""; return; }
echo "$response" | python3 -c \
"import sys,json; print(json.load(sys.stdin).get('Result',''))" 2>/dev/null || echo ""
}
unity-cli-loop ãè¿ã JSON ãã Result ãã£ãŒã«ããæœåºããã ãã®ã·ã³ãã«ãªé¢æ°ã§ãããæ¥ç¶ãšã©ãŒæã«ç©ºæåãè¿ãèšèšã«ããŠããã®ããã€ã³ãã§ãã
ããã«ãããbash åŽã®æ¡ä»¶åå²ã if [ "$result" = "clicked" ] ã®ããã«ã·ã³ãã«ã«æžããŸãã
poll_and_clickïŒããŒãªã³ã°ã¯ãªãã¯
UIèŠçŽ ãéåæã«åºçŸããã²ãŒã ã§ã¯ãããã¿ã³ãèŠã€ãããŸã§åŸ 〠â èŠã€ãã£ããã¯ãªãã¯ããšãããã¿ãŒã³ãé »åºããŸãã
poll_and_click() {
local button_name="$1"
local interval="${2:-1}" # ããŒãªã³ã°ééïŒããã©ã«ã1ç§ïŒ
local max_attempts="${3:-10}" # æå€§è©Šè¡åæ°ïŒããã©ã«ã10åïŒ
for i in $(seq 1 "$max_attempts"); do
local result
result=$(uloop_exec "
var go = UnityEngine.GameObject.Find(\"$button_name\");
if (go == null) return \"not_found\";
go.GetComponent<UnityEngine.UI.Button>().onClick.Invoke();
return \"clicked\";
")
if [ "$result" = "clicked" ]; then return 0; fi
sleep "$interval"
done
return 1
}
ãã¿ã³ã® onClick.Invoke() ãçŽæ¥åŒã¶ããšã§ãããŠã¹ã·ãã¥ã¬ãŒã·ã§ã³ã«ãã座æšèšç®ã®è€éããåé¿ããŠããŸãã
ãã ããã¯ãªãã¯åŸã«ãã€ã¢ãã°ãå³åº§ã«ç Žæ£ãããã±ãŒã¹ã§ã¯ simulate-mouse-ui ã ãšãšã©ãŒã«ãªãããšããããonClick.Invoke() ã®ã»ããå®å
šãšããã®ã¯éçºãéããŠåŠãã ããšã§ããã
ã¢ãŠãããã
E2Eãã¹ãã®çµæã¯ã3ã€ã®åœ¢åŒã§æ®ããŸãã
ã¹ã¯ãªãŒã³ã·ã§ãã
ãã¹ãå®äºæã« Game View ã®ã¹ã¯ãªãŒã³ã·ã§ãããååŸããŸãã
uloop screenshot --window-name Game --capture-mode rendering --output-directory /tmp
å®ã¯åœåãåã¹ãããããšã«ã¹ã¯ãªãŒã³ã·ã§ãããæ®åœ±ããŠç¶æ³ææ¡ã«äœ¿ã£ãŠããŸãããããããããã ãš1åã®ãã¹ãã§å€§éã®ã¹ã¯ãªãŒã³ã·ã§ãããçæãããŠããŸãã確èªã倧å€ãªããã«ãªãŒããŒãããã«ããªã£ãŠããŸããã
æçµçã«ããã¹ãå šäœã®æµãã¯åŸè¿°ããåç»é²ç»ã§èšé²ããæ¹éã«åãæ¿ããããšã§ãã¹ã¯ãªãŒã³ã·ã§ããã®åœ¹å²ã¯ãåæ»æ€ç¥æããšã©ãŒæã®ãã³ãã€ã³ããªèšŒè·¡ãã«çµãããšãã§ããŸããã
ã¡ã€ã³ã«ãŒãäžã«15ã€ãã¬ãŒã·ã§ã³ä»¥äžã¢ã¯ã·ã§ã³ããªãå Žåããåãã¢ã¯ã·ã§ã³ãç¹°ãè¿ãããå Žåã«1æã ãæ®åœ±ããããšããéçšã«èœã¡çããŠããŸãã
åç»é²ç»
ãã¹ãå šäœã®æµãã MP4 åç»ã§èšé²ããŸããUnity Recorder ã䜿ã£ãŠãå©çšèŠçŽåæã®ã¿ã€ãã³ã°ããé²ç»ãéå§ããã¯ãªãŒã³ã¢ããæã«åæ¢ããŸãã
| é ç® | èšå® |
|---|---|
| ãã©ãŒããã | MP4 (H.264) |
| FPS | 30 |
| è§£å床 | 540 à 960ïŒGame View ã®ååïŒ |
| å質 | Medium |
| é³å£° | ãã |
| ãã¡ã€ã«ãµã€ãº | çŽ24MB |
åœåã¯ãã«è§£å床ã§é²ç»ããŠãããã100MBè¿ãã«ãªã£ãŠããŸããããE2Eãã¹ãã®èšŒè·¡ãšããŠã¯åè§£å床ã§ååãšå€æãã24MBãŸã§åæžããŸããã
åç»ã§èšé²ããŠããã°ããã¹ãå®è¡äžã«ç»é¢ã«åŒµãä»ããŠèŠåŒµãå¿ èŠããããŸãããæå確èªã§ãããã¡ãªãäžç¬è¡šç€ºããããã€ã¢ãã°ãèŠèœãšããã䌌ãç»é¢ãèŠééããããšãã£ããã¥ãŒãã³ãšã©ãŒãèµ·ããŸããããã¹ã倱ææã«ã¯ãã©ããŸã§é²ãã§ã©ãã§æ¢ãŸã£ããããåç»ã§å·»ãæ»ããŠç¢ºèªã§ããã®ã§ãåå 調æ»ãå¹ççã§ãã
çµæã¬ããŒã
åã¹ã¯ãªããã®å®è¡ãçµãããšãClaude Code ããã¹ãå šäœã®çµæãããŒãã«åœ¢åŒã®ã¬ããŒãã«ãŸãšããŸããåã¹ãããã®æåŠãã¡ã€ã³ã«ãŒãã®ã€ãã¬ãŒã·ã§ã³åæ°ãšæèŠæéããšã©ãŒãã°ã®ä»¶æ°ãã¹ã¯ãªãŒã³ã·ã§ãããé²ç»ãã¡ã€ã«ã®ãã¹ãäžèЧã§ç¢ºèªã§ããã®ã§ããã¹ããéã£ããã©ãããã²ãšç®ã§ææ¡ã§ããŸãã
## E2E ãã¹ãçµæ: ååèµ·åãã¥ãŒããªã¢ã«å®èµ°
| ãã§ãã¯é
ç® | çµæ | 詳现 |
|---|---|---|
| Boot ã·ãŒã³èµ·å | â
OK | Boot.unity ããèµ·å |
| ã»ãŒãããŒã¿éé¿ | â
OK | ããã¯ã¢ããæžã¿ |
| Opening ã¹ããã | â
OK | SkipButton ã¯ãªã㯠|
| ããã²ãŒã å®äº | â
OK | ã€ã³ã¿ã©ã¯ã·ã§ã³å®è¡ â ã·ãŒã³ã¢ã³ããŒãç¢ºèª |
| ã¡ã€ã³ãã¥ãŒããªã¢ã« | â
OK | 65 iterations / 117ç§ã§å®äº |
| ãã¥ãŒããªã¢ã«å®äº | â
OK | å®äºæŒåºã®è¡šç€ºãç¢ºèª |
| ãšã©ãŒãã° | â
OK | 0 ä»¶ |
| ã»ãŒãããŒã¿åŸ©å
| â
OK | ããã¯ã¢ãããã埩å
|
| ã¹ã¯ãªãŒã³ã·ã§ãã | ååŸæžã¿ | ïŒãã¹ïŒ |
| åç»é²ç» | ååŸæžã¿ | first_launch_tutorial_20260403_110821.mp4 |
ãšã©ãŒãçºçããŠããå Žå㯠uloop get-logs --log-type Error ã§ååŸãããšã©ãŒã¡ãã»ãŒãžãã¬ããŒãã«å«ãŸãããããäœãåå ã§å€±æããã®ããã¬ããŒãäžã§è¿œããŸããã¹ã¯ãªãŒã³ã·ã§ãããåç»ãšåãããŠç¢ºèªããã°ããã¹ãå®è¡åŸã« Unity Editor ãéãçŽããªããŠãç¶æ³ãææ¡ã§ããã®ãå©ç¹ã§ãã
ä»åã¯ãããŸã§éçºç°å¢ã®ããŒã«ã«åäœãåæãšããŠããŸãããããå°ãäœã蟌ããªãããã®ã¬ããŒãã Slack ã«éç¥ããããCI/CD ãã€ãã©ã€ã³ãæ§ç¯ããŠå°çšãã·ã³äžã§ãã¹ãå®è¡ãèªååããããšãã£ãæ¡åŒµãèããããŸããã¬ããŒããããã¹ãããŒã¹ã®ããŒãã«åœ¢åŒãªã®ã§ãéç¥ãéèšãšã®é£æºã¯ããããæ§é ã«ãªã£ãŠããŸãã
èšèšäžã®è£è¶³
äœã蟌ã¿ãããªã
ä»åã¯ãã¹ããã¬ãŒã ã¯ãŒã¯ãèªäœãããããªå€§æãããªããšã¯ããŠããŸãããbash + unity-cli-loop + C# ã®åçå®è¡ã§ååã§ããå®éã.sh ãã¡ã€ã«ã¯1ã€ããã15ã30è¡çšåºŠãå
±é颿°ã7ã€ã ãããã¹ãã·ããªãªã¯ Markdown ã§æžãããŠããŠã人éãèªãã§ãæµããåãããŸãã
ãå°æ¥çã«æ±çšãã¹ãã©ã³ããŒã«è²ãŠãããã¿ãããªéå¿ã¯æšãŠãŠãä»å¿ èŠãªãã¹ããä»åããããšãåªå ããŸããã
äœã蟌ã¿ãé¿ããããã²ãšã€ã®çç±ã¯ãåšèŸºããŒã«ã®é²åãéãããšã§ããunity-cli-loop èªäœã掻çºã«ã¢ããããŒããããŠããŸãããUnityå ¬åŒã®MCP察å¿ãEditorã®æ©èœåŒ·åãé²ãã§ããŸãã
å°æ¥çã«ã¯ãã²ãŒã åãE2Eãã¹ããå°éã«æ±ãå€éšããŒã«ãç»å ŽããŠäžžããšèŒãæ¿ããããšããå±éãååããããŸãããããªã£ããšãã«ãäœã蟌ãã èªåã®ãã¬ãŒã ã¯ãŒã¯ãè¶³æ·ã«ãªããªããããå¿ èŠæå°éã«çããŠããã®ãåŸçã ãšèããŠããŸãã
ãšã©ãŒãã³ããªã³ã°
E2Eãã¹ãã§ãšã©ãŒãç¡èŠããã®ã¯çŠç©ã§ããããšããããã¹ãããããŠå ã«é²ããããããšããã¹ããéã£ãŠãæå³ããªããªããŸãã
æ€ç¥ãšåæ¢
poll_and_click ã®äžéåæ°è¶
éãããªããžæ¹åŒã® maxIterations å°éã§ãšã©ãŒçµäºãããã»ããEditorApplication.isPaused ã§ãšã©ãŒããŒãºãæ€ç¥ãããå³åæ¢ããŸããã¡ã€ã³ã«ãŒãã§ã¯åæ»æ€ç¥ïŒ15ã€ãã¬ãŒã·ã§ã³ä»¥äžå€åãªãïŒãèšããŠãããç¡éã«åãç¶ããããšã¯ãããŸããã
蚌跡ã®èªååé
ãšã©ãŒçºçæã«ã¹ã¯ãªãŒã³ã·ã§ããæ®åœ±ããšã©ãŒãã°ååŸãŸã§èªåŸçã«è¡ããã®ã¯ãunity-cli-loop ãæ¡çšããããããã®åŒ·ã¿ã§ããuloop screenshot ã§ãã®ç¬éã® Game View ãä¿åããuloop get-logs --log-type Error ã§ãšã©ãŒã¡ãã»ãŒãžãååŸããããããã CLI ã®ã³ãã³ããšããŠãã§ã«çšæãããŠãããããã·ã§ã«ã¹ã¯ãªããããæ°è¡ã§åŒã³åºãã ãã§èšŒè·¡ãæããŸããèªåã§ã¹ã¯ãªãŒã³ã·ã§ããæ©èœããã°åéã®ä»çµã¿ãå®è£
ããå¿
èŠããªãã£ãã®ã¯å€§ããã£ãã§ãã
ã¯ãªãŒã³ã¢ããã®ç¢ºå®ãªå®äº
Step 7-8 ã®ã¯ãªãŒã³ã¢ããã§ã¯ set -e ãå€ããŠããŸããé²ç»åæ¢ã PlayMode 忢ã倱æããŠããã»ãŒãããŒã¿ã®åŸ©å
ãŸã§ç¢ºå®ã«å®äºããããšãåªå
ããããã§ãããã¹ãã倱æããŠãéçºç°å¢ãå£ããç¶æ
ã§æŸçœ®ãããªãããšããã®ã¯å®å¿ããŠç¹°ãè¿ãå®è¡ããããã®åææ¡ä»¶ã§ãã
ãŸãšã
ååèµ·åãã¥ãŒããªã¢ã«ã®E2Eãã¹ãã¯ãæãè€éãªã¡ã€ã³ã«ãŒãéšåãåœåã® AI 倿æ¹åŒã§ã¯çŽ240ç§ããã£ãŠããŸãããããã¹ãããžãã¯ãã·ã§ã«ã¹ã¯ãªããã«åºããç¶æ ããªããžæ¹åŒãå°å ¥ããããšã§ çŽ104ç§ãŸã§ççž®ïŒ57%åæžïŒ ã§ããŸãããCLI åŒã³åºãã®ãªãŒããŒããããå€§å¹ ã«åæžãããšã©ãŒ0ä»¶ã§ãã¥ãŒããªã¢ã«å®èµ°ãéæããŠããŸãã
Claude Code ã«ããããŠããã®ã¯ãåã¹ãããã®ã¹ã¯ãªãããé æ¬¡å®è¡ããŠã¬ããŒããæžããã ãããã¹ãããžãã¯ã¯ãã¹ãŠã·ã§ã«ã¹ã¯ãªãããš C# ã«åºããããŠãããAI ã®æšè«ã«äŸåããªãåçŸæ§ã®é«ãèªåãã¹ãã«ãªã£ãŠããŸãã
ä»åŸã¯ã·ããªãªã®è¿œå ïŒã·ã§ããè³Œå ¥ãããŒãã¬ãã£æŒåºãªã©ïŒããCI ãã€ãã©ã€ã³ãžã®çµ±åãèŠéã«å ¥ããŠããŸããunity-cli-loop ã Unity Editor ãå€éšããæäœã§ãããããã§ãbash ã¹ã¯ãªãããæžãè¶³ãã ãã§ãã¹ããæ¡åŒµã§ããã®ã¯å€§ããªåŒ·ã¿ã§ãã
ãAI ã«ãã¹ããä»»ããããšãããšãAI ãç»é¢ãèŠãŠå€æãããããªã€ã¡ãŒãžãæã€ãããããŸããããå®éã«ã¯ããžãã¯ã決å®çãªã³ãŒãã«èœãšã蟌ãã§ AI ã®ä»åšãæå°åããã»ãããéãã»å®ãã»ç¢ºå®ã«åããŸãã
AI ã®åœ¹å²ã¯ã¹ã¯ãªããã®å®è¡æç€ºãšçµæã®ã¬ããŒãã£ã³ã°ã«çµãããã¹ãããžãã¯ãã®ãã®ã¯ã¹ã¯ãªããã«ä»»ãããå°ãªããšãããããŒãåºå®ãããååèµ·åãã¥ãŒããªã¢ã«ã®ãããªãŠãŒã¹ã±ãŒã¹ã§ã¯ããã®èšèšæ¹éãåã£ãŠãããšæããŠããŸãã
ãªãããã®åŸã·ããªãªãå¢ããéçšã§èŠããŠãã課é¡ãšãE2Eãã¹ãåºç€ãã»ãŒãããŒã¿ã¹ãããã·ã§ããæ¹åŒã§äœãçŽãã話ãç¶ç·šãšããŠå ¬éããŠããŸãã
åè
Discussion