3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ralph Loopで シューティングゲームを作った話 - 仕様もAIが書き、実装もAIがする

3
Last updated at Posted at 2026-02-24

はじめに

「AIがコードを書く時代に、わざわざ自分でプログラムを書く意味はあるのか?」

最近、CursorやClaude Codeの進化に触れるたび、この問いについて考えることが増えました。実際、仕様を伝えれば動くコードが返ってくる世界は、もう到来しています。

今回、Claude Codeの対話モードで PRD(要件定義書)を書かせ、さらに「プロンプトモード」をシェルスクリプトでループさせる Ralph Loop という手法で実装まで自動化してみました。人間がやったのは「2D縦スクロールSTGを作りたい」という方向性の提示と、いくつかの選択肢への回答だけ。PRDの執筆も実装も、すべてAIが行い、26ファイル・12コミットのゲームが完成した、という話です。

ただ、先に正直な感想を書いておくと、私個人としては、やはり自分の手でコードを書くほうが楽しいと感じています。AIに任せれば確かに速い。しかし、ロジックを考え、バグと格闘し、動いた瞬間の「あの感覚」は、AIに外注した瞬間に失われます。

それでも、「AIに方向性を伝え、AIが仕様を書き、AIが実装する」という全自動パイプラインの体験は非常に興味深いものでしたので、PRDの生成過程からスクリプトの構造、完成したゲームの中身まで、できる限り詳しく共有したいと思います。

gameplay.gif


目次

  1. Ralph Loopとは何か
  2. プロジェクトの準備(React + Vite)
  3. PRD.mdの生成 —— 成否の9割はここで決まる
  4. ralph.shの全文解説
  5. 実行と経過
  6. 完成したシューティングゲーム
  7. 振り返り —— AIに任せること、人間が握ること

1. Ralph Loopとは何か

Ralph Loopは、Claude Codeのプロンプトモード(claude -p)をシェルスクリプトで繰り返し呼び出す、自律開発パターンです。

仕組みそのものは非常にシンプルです。

  1. 人間がClaude Code(対話モード)に方向性を伝え、PRD(要件定義書)とタスクリストを生成させる
  2. ralph.sh がループでClaude Code(プロンプトモード)を呼び出す
  3. Claudeは PRDと進捗ファイルを読み、未完了タスクを1つだけ実装する
  4. 実装が終わるとスクリプトが自動でgit commitする
  5. 全タスク完了まで繰り返す

つまり、人間の役割は「何を作りたいか」を伝えることだけで、仕様の詳細化も実装もすべてAIが自律的に行うという構造です。人間はPRDの生成を確認したら ./ralph.sh を実行して、あとはコーヒーを飲みながら見ているだけ。

なぜこの仕組みが有効なのか

ポイントは3つあります。

  1. コンテキスト問題の回避
    長い対話セッションではコンテキストウィンドウが溢れがちですが、ループ毎に新しいセッションが立ち上がるため、常にフレッシュな状態で作業が進みます。

  2. 進捗の永続化
    progress.txtとgit commitによって成果が毎回保存されます。途中でCtrl+Cしても、直前のコミットまでの成果は残ります。

  3. タスクの粒度制御
    PRD側でタスクを適切に分割すれば、1ループ = 1タスク = 1コミットという綺麗な履歴になります。後から git diff で各タスクの差分を追いかけるのも容易です。


2. プロジェクトの準備(React + Vite)

まず、Viteでプロジェクトを作成します。今回は最終的にReactは使わないようAIが書き換えてしまいましたが、Viteのテンプレートとして react + TypeScript を使い、そこからゲーム用に変更する流れをとりました。

2.1 プロジェクト作成

npm create vite@latest shooting-game -- --template react-ts
cd shooting-game
npm install

これで以下の構成が生成されます。

shooting-game/
  index.html
  package.json
  tsconfig.json
  tsconfig.app.json
  tsconfig.node.json
  vite.config.ts
  eslint.config.js
  src/
    main.tsx
    App.tsx
    App.css
    index.css
    ...

2.2 ゲーム用のカスタマイズ

今回はPixiJSでキャンバス描画するため、ReactのDOMレンダリングは不要です。PRDのTask 1で以下の変更がClaude(実装AI)によって行われました。

  • PixiJS v8の導入: npm install pixi.js@^8
  • vite.config.ts: Reactプラグインを削除
  • tsconfig.app.json: JSX設定を削除
  • index.html: エントリポイントを /src/main.ts に変更し、ゲーム用CSS(全画面黒背景、キャンバス中央配置)を追加
  • React関連ファイル削除: App.tsx, App.css, main.tsx, index.css

最終的な index.html はこうなります。

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Shooting Game</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
      body { display: flex; justify-content: center; align-items: center; }
      canvas { image-rendering: pixelated; }
    </style>
  </head>
  <body>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

最終的な vite.config.ts はここまでシンプルになります。

import { defineConfig } from 'vite'

export default defineConfig({})

3. PRD.mdの生成 —— 成否の9割はここで決まる

Ralph Loopにおいて、PRDの品質がプロジェクトの成否をほぼ決定するということは、強調してもしすぎることはありません。

今回はこのPRD自体もClaude Codeの対話モードで生成しました。人間がやったのは、「2Dシューティングゲームの要件定義をして」と伝え、AIからの質問(ゲームタイプは?スコープは?描画エンジンは?グラフィックは?)に対して「縦スクロール」「ミニマム」「PixiJS」「図形描画」と選択肢を選んだだけです。それだけの入力から、AIが数値レベルまで詳細化されたPRDを一気に生成してくれました。

ここに、FDE(前線配置エンジニア)の記事で書いた話と通じるものがあります。AIが得意なのは「Howの実行」だけでなく、「Whatの詳細化」もです。 人間が握るべきは、むしろ「Why(なぜ作るのか)」と大まかな方向性の判断だと、今回の体験で改めて感じました。

3.1 PRDの構成

AIが生成したPRDは以下の6セクションで構成されていました。

セクション 内容
1. プロジェクト概要 名前、ジャンル、技術スタック
2. ゲーム仕様 画面構成、シーン遷移、自機・敵・弾・アイテム・ボス・ステージの詳細仕様
3. 技術設計 アーキテクチャ方針、ファイル構成、依存関係
4. 非機能要件 パフォーマンス、ブラウザ対応
5. スコープ外 今回やらないことの明示
6. 実装タスク チェックリスト形式のタスク一覧

3.2 仕様は「すべて数値」で書く

ここが最も重要なポイントです。AIが生成したPRDでは、「速い」「大きい」ではなく、5 px/frame64x64px と数値で定義されていました。色はカラーコードで、時間はフレーム数で、サイズはピクセルで。人間が「ミニマムスコープ」と伝えただけで、AIがここまで具体化してくれたのは正直驚きました。

例えば自機の仕様は、このように生成されています。

| 項目 | 値 |
|---|---|
| 形状 | 三角形(上向き)16x20px / シアン(0x00ffff) |
| 通常速度 | 5 px/frame |
| 低速(集中)モード | 2 px/frame(Shift押下時) |
| 当たり判定 | 4x4px(中心、視覚より小さい) |
| 残機 | 3 |
| 被弾時無敵 | 2秒(120フレーム) |
| 発射レート | 4フレームに1発(毎秒15発) |

PRDを書くAIと、実装するAIは別セッションです。PRD側で数値が明確に定義されていれば、実装側のAIが「意図を解釈する」余地がなくなります。解釈の余地がないということは、仕様からのブレが生まれないということです。

3.3 ファイル構成もAIが設計

どのファイルに何を書くかも、PRD生成時にAIが明確に定義してくれました。 これは非常に重要です。

src/
  main.ts                  # エントリポイント
  game/
    Game.ts                # トップレベルゲームコントローラ
    SceneManager.ts        # シーン遷移管理
    scenes/
      Scene.ts             # シーン基底インターフェース
      TitleScene.ts
      GameScene.ts
      GameOverScene.ts
    entities/
      Entity.ts            # エンティティ基底クラス
      Player.ts
      Enemy.ts
      Bullet.ts
      EnemyBullet.ts
      PowerUp.ts
      Boss.ts
    systems/
      KeyboardManager.ts
      CollisionSystem.ts
      ObjectPool.ts
      BulletPatterns.ts
      StageManager.ts
      ParticleSystem.ts
    data/
      stage1.ts            # ステージ1ウェーブ定義
      enemyConfig.ts
    ui/
      HUD.ts
    utils/
      math.ts
      constants.ts
      graphics.ts

ファイル構成がPRDに定義されていないと何が起きるか。実装AIはループ毎に異なる設計判断を下し、整合性が崩れるリスクがあります。PRD生成時にアーキテクチャまで確定させておくことが、Ralph Loopの安定動作に直結します。

3.4 タスクの分割

タスクも依存関係の順に並べてもらい、各タスクの説明に「何を作るか」「どのファイルを作成/変更するか」「具体的なメソッドやプロパティ」まで記述させました。

- [ ] Task 5: 自機・自機弾・オブジェクトプール
  - `ObjectPool.ts` を作成: ジェネリック `ObjectPool<T extends Entity>``get(): T`(非activeを返すか新規作成)、`getActive(): T[]``releaseAll(): void`
  - `Bullet.ts` を作成: Entity 継承。`vx`, `vy` で移動、画面外で active=false
  - `Player.ts` を作成: 8方向移動(通常5px/f, Shift時2px/f)、
    Zキーで4フレーム毎にBullet発射、パワーレベル0-2でショット数変化

1タスク = 1コミットの粒度を意識し、各タスクが「ビルド可能な状態」で完了するようにしています。これは、途中で問題が起きた際にロールバックしやすくするための保険でもあります。


4. ralph.shの全文解説

Ralph Loopの心臓部である ralph.sh の全文を見ていきます。

#!/bin/bash
trap "echo -e '\n🛑 中断されました。'; exit 1" INT

MAX_ITERATIONS=15
echo "🚀 Ralph Loop 起動..."

for ((i=1; i<=MAX_ITERATIONS; i++)); do
  echo "========================================="
  echo " 🔄 ループ $i 回目"
  echo "========================================="

  PROMPT=$(cat << 'EOF'
あなたはRalph Loop内で動く自律型AIです。
1. PRD.mdとprogress.txtを確認。
2. 未完了タスクを上から1つだけ実装。
3. 実装後、progress.txtに完了報告を追記し、PRD.mdのチェックを[x]にする。
4. 全タスク完了なら'.loop_status'を'PROJECT_COMPLETED'に書き換える。
人間への質問は不要。1タスクで即終了せよ。
EOF
)

  # macOS用の記述です。Linux環境では script コマンドの引数が異なるため注意してください
  script -q /dev/null claude -p "$PROMPT" --dangerously-skip-permissions

  # Git自動コミット
  if [ -n "$(git status --porcelain)" ]; then
      MSG=$(tail -n 1 progress.txt | sed 's/^- //')
      git add .
      git commit -m "Ralph Loop $i: ${MSG:-Auto Commit}"
      echo "✅ コミット完了"
  fi

  # 終了判定
  if grep -q "PROJECT_COMPLETED" .loop_status; then
    echo "🎉 プロジェクト完了!"
    break
  fi

  echo "⌛ 5秒後に次タスクへ..."
  sleep 5
done

一つずつ見ていきます。

trapによるグレースフル停止

trap "echo -e '\n🛑 中断されました。'; exit 1" INT

Ctrl+Cで中断した場合にメッセージを表示して終了します。途中で止めても、直前のコミットまでの成果は安全に残ります。

ループ上限

MAX_ITERATIONS=15

無限ループを避けるための安全弁です。タスク数 + 余裕分で設定します。今回は12タスクなので15としました。

プロンプト

PROMPT=$(cat << 'EOF'
あなたはRalph Loop内で動く自律型AIです。
1. PRD.mdとprogress.txtを確認。
2. 未完了タスクを上から1つだけ実装。
3. 実装後、progress.txtに完了報告を追記し、PRD.mdのチェックを[x]にする。
4. 全タスク完了なら'.loop_status'を'PROJECT_COMPLETED'に書き換える。
人間への質問は不要。1タスクで即終了せよ。
EOF
)

このプロンプト設計にはいくつかの意図があります。

「1つだけ実装」と明示する。 これがないと、AIが複数タスクを一気にやろうとして、コンテキストが溢れて失敗するリスクがあります。

進捗ファイルの更新を指示する。 progress.txtPRD.md のチェックボックスを更新させることで、次のループが正しく次のタスクを認識できます。

終了条件を明示する。 .loop_status ファイルによる終了フラグです。これがないとループが永遠に止まりません。

「人間への質問は不要」と断言する。 プロンプトモードでは質問しても答える人がいません。自分で判断して進めるよう明確に指示する必要があります。

Claude Codeの実行

script -q /dev/null claude -p "$PROMPT" --dangerously-skip-permissions

ここには3つの要素があります。

  • claude -p: プロンプトモード。対話なしで単一プロンプトを実行して終了します
  • --dangerously-skip-permissions: ファイル書き込みやコマンド実行の確認をスキップします。自律実行には必須ですが、名前の通り危険です。 信頼できるサンドボックス環境でのみ使用してください
  • script -q /dev/null: claude -p は端末でないと出力がバッファリングされるため、script コマンドで擬似端末を割り当ててリアルタイム表示を実現しています

Git自動コミット

if [ -n "$(git status --porcelain)" ]; then
    MSG=$(tail -n 1 progress.txt | sed 's/^- //')
    git add .
    git commit -m "Ralph Loop $i: ${MSG:-Auto Commit}"
fi

変更があれば progress.txt の最終行をコミットメッセージに使って自動コミットします。これにより、1タスク = 1コミットの綺麗なgit履歴が自然に出来上がります。

終了判定

if grep -q "PROJECT_COMPLETED" .loop_status; then
  echo "🎉 プロジェクト完了!"
  break
fi

Claudeが全タスク完了時に .loop_statusPROJECT_COMPLETED と書き込み、それを検出してループを終了させます。


5. 実行と経過

5.1 実行方法

chmod +x ralph.sh
./ralph.sh

これだけです。あとは見ているだけ。

5.2 各ループの実行結果

git logがそのまま進捗レポートになります。

774d02a Ralph Loop 12: 全12タスクの実装が完了
2d32da2 Task 11: GameScene統合 — GameScene.ts, Game.ts
15ca125 Task 10: ボス・パーティクル — Boss.ts, ParticleSystem.ts
d6660ac Task 9: ステージ管理 — stage1.ts, StageManager.ts
e332e9a Task 8: HUD — スコア・残機・パワー表示UI
d9c6408 Task 7: 衝突判定・アイテム — PowerUp.ts, CollisionSystem.ts
af2b213 Task 6: 敵システム — enemyConfig.ts, EnemyBullet.ts, BulletPatterns.ts, Enemy.ts
4a25d7c Task 5: 自機・自機弾・オブジェクトプール — ObjectPool.ts, Bullet.ts, Player.ts
753c3fc Task 4: ゲームコントローラ・タイトル画面 — Game.ts, TitleScene.ts
615eb4b Task 3: 入力・基盤システム — KeyboardManager.ts, Entity.ts, Scene.ts, SceneManager.ts
bbcf4f8 Ralph Loop 2: Task 3(リトライ)
e1b31be feat: initialize shooting game project with PixiJS and Vite

12タスクが各ループで1つずつ消化され、全12コミット(+ 初期化1コミット)で完成しました。Task 3で1回リトライが入りましたが、それ以外はすべて一発で成功しています。

5.3 ループ実行ログの詳細

実際のターミナル出力から、各ループでAIが何をしたかを抜粋します。ループが進むにつれて、AIの報告がだんだん構造化されていくのが面白いところです。

% ./ralph.sh
🚀 Ralph Loop 起動...
=========================================
🔄 ループ 1 回目
=========================================
**Task 1 完了。** 実施内容:

- `pixi.js@^8` インストール
- `vite.config.ts` — React plugin削除
- `tsconfig.app.json` — JSX設定削除
- `index.html` — エントリポイントを `/src/main.ts` に変更、ゲーム用CSS追加
- React関連ファイル削除 (`App.tsx`, `App.css`, `main.tsx`, `index.css`, `assets/`)
- `src/main.ts` — PixiJS Application初期化
- `src/game/Game.ts` — スタブ
- `src/game/utils/constants.ts` — 全ゲーム定数
- ディレクトリ構造 `src/game/{scenes,entities,systems,data,ui,utils}/` 作成
- **tsc + vite build 成功確認済み**

次回: Task 2 (コアユーティリティ — math.ts, graphics.ts)
fatal: not a git repository (or any of the parent directories): .git
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 2 回目
=========================================
**Task 2 完了。** 以下を実装しました:

- **`math.ts`** — `clamp`, `angleBetween`, `dist`, `distSq`, `degToRad`, `radToDeg`, `lerp`, `normalizeAngle`
- **`graphics.ts`** — 全エンティティ(Player, Drone, Tank, Spinner, Boss, PlayerBullet, EnemyBullet, PowerUp)の図形描画ファクトリ関数
- **`constants.ts`** — 敵HP/速度/スコア/攻撃間隔/ボス行動パラメータを追加

`tsc` & `vite build` 成功確認済み。次のタスクは **Task 3: 入力・基盤システム** です。
[main bbcf4f8] Ralph Loop 2: Task 3: 入力・基盤システム — KeyboardManager.ts, Entity.ts, Scene.ts, SceneManager.ts
5 files changed, 177 insertions(+), 2 deletions(-)
create mode 100644 src/game/utils/graphics.ts
create mode 100644 src/game/utils/math.ts
✅ コミット完了
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 3 回目
=========================================
**Task 3 完了。** 実装内容:

| ファイル | 内容 |
|---|---|
| `src/game/systems/KeyboardManager.ts` | keydown/keyup管理、`isDown()`(長押し判定)と`isPressed()`(単発判定)、フレーム末の`update()`でpressed状態クリア |
| `src/game/entities/Entity.ts` | 抽象基底クラス。Containerラッパー、x/yアクセサ、AABB半サイズ衝突判定`collidesWith()` |
| `src/game/scenes/Scene.ts` | Sceneインターフェース(container, enter, update, exit) |
| `src/game/SceneManager.ts` | `switchTo()`でシーン切替、`update()`で現シーン更新委譲 |

tsc & vite build 成功確認済み。コミット `615eb4b`。
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 4 回目
=========================================
**Task 4 完了。** 実装内容:

- **`src/game/Game.ts`**: `Application`, `SceneManager`, `KeyboardManager` を保持。`start()` で `app.ticker.add()` にゲームループ(シーン更新→キーボード状態クリア)を登録し、`TitleScene` を初期シーンとして設定
- **`src/game/scenes/TitleScene.ts`**: "SHOOTING GAME"(白太字36px)+ "Press SPACE to Start"(灰色20px)を画面中央に配置。`update()` で SPACE キーの `isPressed` を検知しコールバックで遷移通知
- **`src/main.ts`**: 変更不要(既存のGame生成→start()呼び出しがそのまま動作)
- `tsc --noEmit` & `vite build` ゼロエラー確認済み
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 5 回目
=========================================
**Task 5 完了: 自機・自機弾・オブジェクトプール**

実装した3ファイル:

- **`ObjectPool.ts`** — ジェネリックプール `ObjectPool<T extends Entity>`。factory関数で事前確保、`get()`で非activeエンティティを再利用or新規作成。オプションのparent Containerで自動addChild管理
- **`Bullet.ts`** — 自機弾。`vx`/`vy`で毎フレーム移動、画面外で自動非active化。`reset(x,y,vx,vy)`でプール再利用
- **`Player.ts`** — 自機。矢印キー8方向移動(斜め正規化済み)、Shift低速、Zキー連射(4f間隔)。パワーレベル0→単発、1→2連±5°、2→3連±10°。残機3、被弾で120f無敵(alpha点滅)&パワーリセット

tsc & vite build エラーゼロ確認済み。コミット `4a25d7c`。
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 6 回目
=========================================
**Task 6: 敵システム 完了**

実装内容:

- **`src/game/data/enemyConfig.ts`** — `EnemyType` 型と `ENEMY_CONFIGS` Record で Drone/Tank/Spinner の HP・速度・サイズ・スコア・攻撃間隔を定義
- **`src/game/entities/EnemyBullet.ts`** — Entity 継承、vx/vy 移動、画面外で自動非active化、`reset()` でプール再利用
- **`src/game/systems/BulletPatterns.ts`** — `fireAimed()` 自機狙い弾、`fireSpread()` 扇状弾(3way等)、`fireRing()` 全方位弾(8way/16way等)
- **`src/game/entities/Enemy.ts`** — 3種の移動パターン実装:
 - **Drone**: サイン波下降(振幅40px)、60f毎に自機狙い弾
 - **Tank**: 直進→y=150で120f停止→退場、停止中90f毎に3way弾
 - **Spinner**: 円形移動(半径60px)+緩やかな下降+回転演出、120f毎に8way全方位弾

`tsc --noEmit` & `npm run build` ともにエラーゼロ確認済み。
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 7 回目
=========================================
**Task 7: 衝突判定・アイテム — 完了**

作成したファイル:
- **`src/game/entities/PowerUp.ts`** — Entity継承。緑ダイヤ12x12px、2px/fで落下、画面外で自動非active化、`reset(x,y)`でプール再利用
- **`src/game/systems/CollisionSystem.ts`** — 4種のAABB衝突判定:
 1. 自機弾 vs 敵 → 弾消滅、ダメージ、撃破時30%でPowerUpドロップ&スコア加算
 2. 敵弾 vs 自機 → 無敵中スキップ、被弾処理
 3. 敵本体 vs 自機 → 同上
 4. PowerUp vs 自機 → パワーレベル+1

`tsc --noEmit` & `vite build` ともにエラーゼロ確認済み。
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 8 回目
=========================================
**Task 8: HUD 完了**

`src/game/ui/HUD.ts` を作成しました:
- **SCORE** — 右上に右寄せ表示 (`anchor(1,0)`)
- **LIFE** — 左上に表示
- **POW** — LIFEの下に表示
- `updateScore()` / `updateLives()` / `updatePower()` で動的更新
- フォント: monospace 14px 白

tsc & vite build 成功確認済み。次のタスクは **Task 9: ステージ管理**。
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 9 回目
=========================================
Task 9 完了。

**実装内容:**

- **`src/game/data/stage1.ts`** — `WaveDefinition`型と全18ウェーブ定義。PRDセクション2.9のタイムライン通り:
 - Drone 3波 (0-900f) → Tank+Drone (900-1800f) → Spinner左右 (1800-3000f) → 混成 (3000-4200f) → Drone大群+Spinner (4200-5400f) → Tank編隊+Spinner (5400-6600f) → 小休止 (6600-7200f) → ボス (7200f)

- **`src/game/systems/StageManager.ts`** — フレームカウンタでウェーブ配列を走査。`ActiveWave`管理で`spawnInterval`毎にEnemy生成(spawnX〜spawnXEnd間で均等配置)。`onBossSpawn`コールバック、`notifyBossDefeated()`、`isStageCleared()`公開

- tsc & vite build エラーゼロ確認済み
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 10 回目
=========================================
**Task 10: ボス・パーティクル — 完了**

実装内容:

- **`src/game/entities/Boss.ts`**: Entity継承のボスエンティティ
 - HP=100, 64x64px六角形(hitbox半サイズ32x32)
 - entering状態で上→y=80まで降下
 - 左右揺動: `x = centerX + sin(frame * swaySpeed) * 100`
 - Phase 1 (HP>50): 60f毎に自機狙い5way弾
 - Phase 2 (HP≤50): 追加で90f毎に16way全方位弾 + 揺動速度2倍
 - `takeDamage()` で撃破時に `onDefeat` コールバック

- **`src/game/systems/ParticleSystem.ts`**: パーティクル演出
 - `emit(x, y, count, color)` で4x4矩形パーティクルをランダム放射
 - 毎フレーム alpha 減少、消滅時に自動クリーンアップ
 - `clear()` / `destroy()` でリソース管理

`tsc` & `vite build` エラーゼロ確認済み。コミット完了。
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 11 回目
=========================================
**Task 11: GameScene統合 — 完了**

実装内容:

- **`src/game/scenes/GameScene.ts`** (新規作成)
 - `enter()`: Player, 全ObjectPool (Bullet:100, EnemyBullet:300, Enemy:30, PowerUp:10), Boss, CollisionSystem, StageManager, ParticleSystem, HUD を生成・container追加
 - `update(dt)`: Player → StageManager → Enemies → Boss → Bullets → EnemyBullets → PowerUps → CollisionSystem → Boss衝突判定 → ParticleSystem → HUD更新 の順で実行
 - Boss衝突判定は敵プールとは独立して処理(自機弾vsBoss / Boss本体vs自機)
 - ボス撃破時: 白+赤パーティクル爆発 & 敵弾全消去
 - 残機0 → 120フレーム遅延後 `onGameOver(score)` コールバック
 - ステージクリア → 120フレーム遅延後 `onStageClear(score)` コールバック

- **`src/game/Game.ts`** (更新)
 - `startGame()` でGameSceneを生成し、SceneManagerに登録
 - onGameOver/onStageClear は暫定的に `showTitle()` に接続(Task 12でGameOverScene接続予定)

- tsc & vite build 成功確認済み
grep: .loop_status: No such file or directory
⌛ 5秒後に次タスクへ...
=========================================
🔄 ループ 12 回目
=========================================
**Task 12 完了 — 全12タスク実装完了、PROJECT_COMPLETED。**

実装内容:

- **`GameOverScene.ts`** — `isClear` フラグに応じて "STAGE CLEAR!"(緑) / "GAME OVER"(赤) + スコア表示 + "Press SPACE to Return"。SPACEキーでタイトルへ遷移
- **`Game.ts`** — フルシーンループ接続: `Title → [SPACE] → Game → [死亡] → GameOver("GAME OVER") → [SPACE] → Title` / `Game → [ボス撃破] → GameOver("STAGE CLEAR!") → [SPACE] → Title`
- **`npm run build`** エラーゼロ確認済み
[main 774d02a] Ralph Loop 12: 全12タスクの実装が完了。シーン遷移フルループ: TitleScene → [SPACE] → GameScene → [死亡] → GameOverScene("GAME OVER") → [SPACE] → TitleScene / GameScene → [ボス撃破] → GameOverScene("STAGE CLEAR!") → [SPACE] → TitleScene
5 files changed, 95 insertions(+), 5 deletions(-)
create mode 100644 .loop_status
create mode 100644 src/game/scenes/GameOverScene.ts
✅ コミット完了
🎉 プロジェクト完了!
  • ループ1(Task 1: プロジェクトセットアップ)では、PixiJSのインストール、React関連ファイルの削除、index.htmlvite.config.ts の書き換え、ディレクトリ構造の作成まで一気にやってくれました。最後に「tsc + vite build 成功確認済み」と報告しています。ただし、この時点ではまだ git init されていなかったため、git commitが fatal: not a git repository で失敗しています。ここは人間側の準備不足でした。

  • ループ2(Task 2: コアユーティリティ)では、math.tsclamp, angleBetween, dist 等の数学関数を、graphics.ts に全エンティティの図形描画ファクトリ関数を実装。さらに constants.ts に敵のHP・速度・スコア・攻撃間隔まで追加定義しています。PRDに数値を書いておいたことで、AIが迷わず定数化してくれたのが印象的でした。

  • ループ3(Task 3: 入力・基盤システム)は、KeyboardManager、Entity基底クラス、Sceneインターフェース、SceneManagerの4ファイルを作成。AIの報告では表形式で各ファイルの実装内容を整理してくれました。KeyboardManagerには isDown()(長押し判定)と isPressed()(単発判定)の2つのメソッドが実装されており、PRDでは指定していなかった「単発判定」まで自主的に追加しています。この判断は正しく、後のTitleSceneでSPACEキーの単発検知に使われることになります。

  • ループ4(Task 4: タイトル画面)では、Game.tsのticker登録とTitleScene.tsの画面レイアウトを実装。main.tsは「変更不要(既存のGame生成→start()呼び出しがそのまま動作)」と報告しており、AIが既存コードとの整合性を正しく判断できていることがわかります。

  • ループ5(Task 5: 自機・自機弾)では、ObjectPool、Bullet、Playerの3ファイルを作成。報告の中で「斜め正規化済み」とあるのが目を引きます。斜め移動時に速度が√2倍にならないよう正規化している、という意味です。PRDでは明示的に指示していませんでしたが、ゲーム開発の常識として自主的に実装してくれました。

  • ループ6(Task 6: 敵システム)が最もファイル数が多いタスクでした。enemyConfig、EnemyBullet、BulletPatterns、Enemyの4ファイルを作成し、3種の敵(Drone: サイン波、Tank: 停止→3way弾、Spinner: 円形移動+8way弾)の移動・攻撃パターンを実装しています。

  • ループ7〜8(Task 7-8: 衝突判定・HUD)は比較的軽量なタスク。CollisionSystemの4種AABB判定(自機弾vs敵、敵弾vs自機、敵本体vs自機、PowerUpvs自機)と、HUDのスコア・残機・パワー表示がそれぞれ1ループで完了しました。

  • ループ9(Task 9: ステージ管理)では、PRDのタイムライン(セクション2.9)に従い全18ウェーブの定義を生成しています。フレーム換算(0:15 = 900f、0:30 = 1800f...)も正確で、ボス出現の7200fもPRD通りでした。

  • ループ10(Task 10: ボス・パーティクル)で、Boss.tsとParticleSystem.tsを実装。ボスのPhase 1(5way弾)→ Phase 2(16way弾追加、揺動速度2倍)の挙動や、撃破時のパーティクル演出まで、PRDの仕様がそのままコードになっています。

  • ループ11(Task 11: GameScene統合)が最大の山場です。これまで個別に作ってきた全システム(Player、5つのObjectPool、Boss、CollisionSystem、StageManager、ParticleSystem、HUD)をGameScene.tsに統合しました。update()メソッドの実行順序や、Boss衝突判定を敵プールとは独立して処理する設計判断など、かなり複雑なオーケストレーションを1ループで完遂しています。

  • ループ12(Task 12: 最終結合)で、GameOverScene.tsの作成とGame.tsのフルシーン接続を行い、「PROJECT_COMPLETED」が書き込まれてループ終了。ターミナルに「プロジェクト完了!」と表示された瞬間は、正直なところ少し感動しました。

5.4 生成されたファイル群

最終的に26個のTypeScriptファイルが生成されました。

src/main.ts
src/game/Game.ts
src/game/SceneManager.ts
src/game/scenes/Scene.ts
src/game/scenes/TitleScene.ts
src/game/scenes/GameScene.ts
src/game/scenes/GameOverScene.ts
src/game/entities/Entity.ts
src/game/entities/Player.ts
src/game/entities/Enemy.ts
src/game/entities/Bullet.ts
src/game/entities/EnemyBullet.ts
src/game/entities/PowerUp.ts
src/game/entities/Boss.ts
src/game/systems/KeyboardManager.ts
src/game/systems/CollisionSystem.ts
src/game/systems/ObjectPool.ts
src/game/systems/BulletPatterns.ts
src/game/systems/StageManager.ts
src/game/systems/ParticleSystem.ts
src/game/data/stage1.ts
src/game/data/enemyConfig.ts
src/game/ui/HUD.ts
src/game/utils/math.ts
src/game/utils/constants.ts
src/game/utils/graphics.ts

6. 完成したシューティングゲーム

6.1 ゲーム概要

  • ジャンル: 2D縦スクロールシューティング
  • 技術スタック: TypeScript + Vite + PixiJS v8
  • 画面サイズ: 480 x 720px
  • 操作: 矢印キー(移動)、Z(ショット)、Shift(低速移動)

6.2 シーン遷移

TitleScene → [SPACE] → GameScene → [死亡] → GameOverScene("GAME OVER") → [SPACE] → TitleScene
                                  → [ボス撃破] → GameOverScene("STAGE CLEAR!") → [SPACE] → TitleScene

3つのシーン(タイトル、ゲーム、ゲームオーバー)を SceneManager が管理し、完全なゲームループを実現しています。

6.3 ゲームの要素

自機

シアン色の三角形。通常速度5px/frame、Shift押下で2px/frameの低速モードに切り替わります。パワーレベル0〜2でショットが単発→2連→3連に変化し、残機3、被弾時120フレームの無敵時間(点滅演出)があります。

敵キャラクター3種

タイプ 特徴
Drone(赤い円) HP1、サイン波で下降、自機狙い弾
Tank(オレンジ矩形) HP5、停止して3way弾
Spinner(黄色三角) HP3、円形移動、8way全方位弾

ボス "Guardian"

HP100の大型六角形です。Phase 1では5way弾、Phase 2(HP半分以下)では16way弾が追加され、移動速度も倍増します。撃破時にはパーティクル爆発演出が入ります。

システム

  • オブジェクトプール: 弾丸や敵の生成/破棄によるGC負荷を回避
  • AABB衝突判定: 4種の判定(自機弾vs敵、敵弾vs自機、敵本体vs自機、パワーアップvs自機)
  • ステージ管理: フレームベースのウェーブ定義で約2分30秒のステージ構成

6.4 アーキテクチャ

main.ts
  └─ Game(トップレベルコントローラ)
       ├─ SceneManager(シーン遷移)
       ├─ KeyboardManager(入力管理)
       └─ Scenes
            ├─ TitleScene
            ├─ GameScene
            │    ├─ Player
            │    ├─ ObjectPool<Enemy>
            │    ├─ ObjectPool<Bullet>
            │    ├─ ObjectPool<EnemyBullet>
            │    ├─ ObjectPool<PowerUp>
            │    ├─ Boss
            │    ├─ CollisionSystem
            │    ├─ StageManager
            │    ├─ ParticleSystem
            │    └─ HUD
            └─ GameOverScene

ECS(Entity Component System)ではなく、シンプルなクラス継承ベースのアーキテクチャを採用しました。プロトタイプ規模であれば、これで十分だと判断しています。

6.5 起動方法

npm run dev

ブラウザで http://localhost:5173 を開き、SPACEでゲーム開始です。


7. 振り返り —— AIに任せること、人間が握ること

うまくいったこと

PRDに数値が徹底的に書かれていた。 色コード、ピクセル数、フレーム数、確率。PRD生成AIが曖昧さを排除してくれたことで、実装AIが仕様通りのコードを生成できました。

ファイル構成がPRDで確定していた。 実装AIが勝手にファイルを増やしたり構成を変えたりする問題を防げました。

1タスク = 1コミット。 途中で問題が発生しても git diff で差分を確認しやすく、ロールバックも容易でした。

progress.txt による進捗管理。 ループ間で状態が正しく引き継がれ、タスクの重複実行や飛ばしが起きませんでした。

注意すべきこと

--dangerously-skip-permissions は本当に危険です。 AIがファイルを自由に読み書き・コマンド実行できるため、信頼できるサンドボックス環境で使うべきです。間違っても本番環境で走らせてはいけません。

タスクの粒度が大きすぎると失敗します。 1タスクが大きいと、1セッション内でコンテキストが溢れる可能性があります。目安として、1タスクで作成・変更するファイルは3〜4個以内に収めるのが安全だと感じました。

ビルド確認を各タスクに含める。 各タスクの末尾に「tsc && vite build 成功確認」を暗黙の完了条件としたことで、壊れた状態でコミットされることを防げました。

Ralph Loopに向いているプロジェクト

  • 仕様が明確で、事前にタスク分割できるもの
  • 依存関係が一方向(後のタスクが前のタスクに依存する直線的な構造)
  • 1タスクあたりの変更量が適度(ファイル数3〜4個、コード量200〜500行程度)

逆に、要件が曖昧で探索的な開発や、複雑な依存関係を持つリファクタリングには向きません。そういったケースでは対話型セッションのほうが適しています。

「コードを書く楽しさ」について

最後に、少し個人的な話をさせてください。

今回、PRDの生成からRalph Loopによる実装まで、26ファイルのゲームがほぼ人間の介入ゼロで完成しました。これは技術的には素晴らしい成果ですし、方向性さえ伝えれば誰でも同じことができるという再現性の高さも大きな魅力です。

しかし、正直に言えば、完成した時の達成感は、自分の手で書いた時とは全く異なるものでした。

自分でコードを書くと、バグに苦しみ、設計を何度もやり直し、深夜に「なぜ動かないんだ」と頭を抱える瞬間があります。しかし、その苦しみを乗り越えた先にある「動いた!」という感覚は、何物にも代えがたいものです。AIに任せた場合、その「苦闘のプロセス」がまるごとスキップされます。効率的ではあるけれど、楽しいかと聞かれると、私の答えはNoです。

これは以前の記事でも書きましたが、AIが当たり前になった時代だからこそ、「あえて自分で書く」という選択が持つ意味は、むしろ大きくなっていると感じています。業務では効率を追求してAIを使いながら、趣味の開発では自分の手で書く。この使い分けが、エンジニアとしての「目利き力」を維持する上で重要になるのではないでしょうか。


まとめ

Ralph Loopは、「人間が方向性を示し、AIが仕様を書き、AIが実装する」というパイプラインを自動化する仕組みです。鍵はPRDの品質にあり、今回はそのPRD自体もAIに生成させました。人間がやったのは「2D縦スクロールSTG、PixiJS、ミニマムスコープ、図形描画」という方向性の提示だけです。

今回のシューティングゲームは26ファイル、12タスク、12コミットで完成しました。PRD生成から実装完了まで、人間の介入は方向性の選択のみでした。

ただ、だからといって「もうエンジニアは何もしなくていい」とは思いません。AIは「How」の実行だけでなく「What」の詳細化にも極めて優秀ですが、「なぜ作るか(Why)」の判断と、生成されたPRDの良し悪しを見極める力は、依然として人間に求められます。 そして、その「目利き力」を持つためには、自分でコードを書いた経験が不可欠です。

コードを書くことの価値は、成果物だけにあるのではない。書くプロセスそのものが、エンジニアとしての判断力を鍛えている。

AIが劇的に進化する今だからこそ、この事実を忘れずにいたいと思っています。

プロジェクトファイルはこちらにおいておきます。PRD.md、ralph.sh、生成されたソースコード一式を含みます。興味のある方は覗いてみてください。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?