この記事の対象と、書いている範囲
Codex に Unity のゲームを作らせたい人に向けた、制作と検証の記録です。設計の判断・つまずき・確かめ方を残しています。 吸収反撃のルールは、下の「C#サンプルを動かす」で試せます。完成した画面全体の写経教材ではありません。環境づくりから公開までの順番はAIでUnityゲームを作る流れにまとめています。
Claude Codeに4本のゲームを作ってもらったあと、5本目をCodexに頼みました。できたのが、敵弾を吸収して撃ち返す縦シューティング、リフレアです。
依頼どおりに動くこと、画面で状況が分かること、遊びたくなることは、別の確認でした。この記事では、吸収反撃のルール、自動操作の失敗、画面の不具合を分け、どの問題に何を変えたかを残します。題材の公開は終了しましたが、実測・画像・C#サンプルは引き続き参照できます。ここにある検証結果だけで、面白さや完成品の品質を保証するものではありません。


最初に渡したのは「同じジャンルで、別ゲームを」という依頼
依頼の中心は、この一文でした。
別ゲームでいいからCodexが考えるシューティングゲームを作って欲しい
参考としてムレウチのページを渡し、成果物を比べたいと伝えました。吸収やボスを作るようには指定していません。そこはCodexが考えています。
一方、開発環境、テストの回し方、公開用スクリプト、制作ログを残す約束は、先にClaude Codeと整えていたものです。「白紙からCodexだけで全部用意した」わけではありません。
引き継いだもの
今回Codexが作ったもの
同じ仕様書で作ったブロック崩しの比較とは条件が違います。今回は、題材を任せたときに何を作るかを見る試みです。後から作る側は、それまでの失敗と道具も利用できます。
シューティングの仕様を、実装とテストへ分ける
依頼文だけでは、いつ完成なのか決まりません。今回は、Codexが仕様書を先に作り、遊びのルールと確認方法を対にして進めました。元の仕様書から、次の制作にも使える部分を抜き出すとこうなります。
| 作るもの | 実装する条件 | 確かめ方 |
|---|---|---|
| 操作 | PCはWASD・矢印で移動、Spaceで吸収。通常射撃は自動 | 開始後に移動・吸収・停止・再開を実際に操作する |
| 吸収反撃 | 0.65秒だけ敵弾を吸収し、発動から4秒は再使用できない | 吸収成功、空振り、押し続け、敵本体への接触を別々に試す |
| 終了条件 | 60秒でボス、90秒で時間切れ。ライフは3 | ボス出現・撃破・ライフ切れ・時間切れをそれぞれ検査する |
| 見た目 | 機体の輪郭、雲と施設の速度差、吸収リング、撃破の反応 | 通常戦とボス戦を撮り、HUDで隠れていないか確認する |
| 公開 | WebGLで開始から再挑戦まで動く | テストの件数に加え、公開URLで通しプレイする |
ルール本体は Core/Battle.cs、自動操縦は Core/Pilot.cs に分けました。Runtime/Flight.cs が入力とUI、Runtime/FlightArt.cs が絵を担当します。v1.3では、画面の入力を固定ステップへ渡す処理をCore/FlightClock.csへ切り出しました。ルールがUnityの画面に依存しないので、弾を置いて1回進めるだけの検査ができます。
- 1
最小の戦闘を作る
移動・射撃・被弾・吸収を実装し、ルールのテストで確かめる。 - 2
画面と進行を合わせる
背景・機体・反応を付け、編隊からボス戦まで進める。 - 3
入力と表示を確かめる
キーとタッチの入力、HUD、ボスの位置をブラウザで見る。 - 4
ビルドして公開する
容量と起動時間を記録し、公開URLでも最後まで遊ばせる。
環境をまだ用意していなければCodexからUnityを操作する準備、実行コマンドはUnity CLIのテスト・ビルド手順、配信設定はWebGLゲームの自前公開に分けてあります。
敵弾を「避けるもの」と「取りに行くもの」にした
リフレアの通常射撃は自動です。プレイヤーが考えるのは移動と、吸収を使うタイミング。敵弾が来たら避けるだけでなく、近づいて取り込む選択があります。
手順 01
吸収を押す
スペースキーか画面下の「吸収」
手順 02
0.65秒だけ取る
リングに触れた敵弾を吸収
手順 03
貫通弾で返す
吸収終了時に最大12発
手順 04
再使用を待つ
発動から4秒後に使える
ここには小さな損得があります。早く押すと、弾が届く前に吸収が終わる。待ちすぎると被弾する。使った直後には次の弾を吸収できません。敵本体への接触は防げないので、吸収中ならどこへでも突っ込める、というルールにもしていません。
実装の数値は次のとおりです。記事を読んだ人が、遊びのどの部分を調整したのか追えるように残します。
| 項目 | 実装した値 | 遊びへの影響 |
|---|---|---|
| 吸収の有効時間 | 0.65秒 | 押すタイミングを選ぶ |
| 再使用 | 発動から4秒 | 連打で守り続けることはできない |
| 反撃 | 吸収数+2発、上限12発 | 多く取ると返す弾が増える |
| 反撃1発のダメージ | 4(通常弾は1) | ボスへの攻撃にも使える |
| ライフと被弾後の無敵 | 3、1.2秒 | 一度の重なりで全部失わない |
| ボス | 60秒に出現、90秒で時間切れ | 後半は倒し切る必要がある |
反撃には、同じ敵へ当たり続けて毎フレーム4ダメージを入れないための管理もあります。貫通弾ごとに「すでに当たった敵の枠」を記録し、次の敵へ進ませる作りです。2体を貫通して倒せることをテストしています。今回は敵の配列が64枠なので、ヒット済みの印は64ビットで持たせています。
20秒ごとに敵を増やすだけだった区間を作り直した
公開後に通常戦の3区間を調べると、v1.5では直進・左右移動・狙撃の3種類が最初から出ていました。20秒と40秒で主に変わるのは敵の出現間隔です。そこでv1.6は、区間ごとに読むものを変えました。
| ゲーム内の時間 | 追加した判断 | 画面で伝える方法 |
|---|---|---|
| 0〜20秒 | 編隊を見て射撃位置を取る | 直進と左右移動の敵だけを出す |
| 20〜40秒 | 狙われた場所から離れるか、吸収して返すか | 約0.67秒の照準線と着弾予定の輪 |
| 40〜60秒 | 左右から交差する弾道をどう抜けるか | 約0.83秒の予告、二方向の敵と3発の幅ある射撃 |
最初の案は、予告を付けた代わりに弾が減り、適応する自動操縦の勝利が旧版35/100から98/100へ増えました。区間名を変えただけでは、遊びの作り込みにならないと分かったため、その案は採用していません。予告から着弾までの余裕を残しながら、弾の幅と第3区間の再攻撃を調整しました。
同じゲーム本体のC#を使い、seed1〜100を各方針で再実行した最終候補は、周囲を見て動き吸収時を選ぶ自動操縦が77/100勝、同じ動きで吸収しない場合は10/100勝、使えるたび即吸収する場合は33/100勝でした。中央に留まる・8秒周期で往復する方針は各0/100勝。旧版の対応する3方針は35/100、20/100、21/100です。適応操縦の総勝率は上がったので、全体を単純に「難しくした」とは言えません。予告を読んで動くことと、吸収の時期で結果が変わるかを比較した数字です。人の初見クリア率はまだ測っていません。
C#サンプルを動かす:吸収から反撃まで
説明だけでは挙動を追いにくかったので、v1.6公開版のルール本体と自動操縦を取り出しました。v1.3で追加した入力の固定ステップ処理も同梱しています。Unityの画面を起動せずに、吸収、3区間の攻撃、ボス撃破、入力頻度と移動距離を確かめる小さなコンソール用サンプルです。
必要なのは .NET 10 SDK。今回の実行確認はWindows、SDK 10.0.401です。ZIPを展開し、ReflearSample.csproj があるフォルダで実行します。Unityに付属する環境だけで、このコマンドが使えるとは限りません。
dotnet --list-sdks
dotnet run --project ReflearSample.csproj
dotnet が見つからない場合は.NET SDKの導入が必要です。プロジェクトが見つからない場合は、ZIPを展開したフォルダへ移動できているか確認します。サンプルには追加のNuGetパッケージはありません。
中の Program.cs は、たとえば次の順で吸収を試しています。これは同梱した Battle.cs を使う抜粋で、これだけを空のUnityシーンへ貼るコードではありません。
var game = new Reflear.Core.Battle();
game.Start();
// 自機と同じ場所に、速度0の敵弾を1発置く。
Reflear.Core.Battle.Spawn(game.Bullets, 0, -5, 0, 0, 1, 0);
game.Tick(new Reflear.Core.Command { X = 0, Y = -5, Absorb = true });
// 期待値: Lives=3、Score=25、Stored=1
for (int i = 0; i < 40; i++)
game.Tick(new Reflear.Core.Command { X = 0, Y = -5 });
// 期待値: Shield=0、Bursts=1、Cooldownはまだ0より大きい
Tick 1回が1/60秒です。Command.X/Y は移動速度ではなく、移動先の座標。サンプルは実時間を待たずにルールを進めます。1秒に何回描画できるかを測るコードではありません。
PASS: same seed and inputs produce the same snapshot PASS: standard mission: won score=19025 lives=2 absorbed=37 14 checks passed. No graphics or browser performance measured.
吸収の得点、反撃、再使用の制限、空振り、接触、3区間の攻撃、入力の再現性、標準配置の通しプレイに加え、30・60・120・144Hzの入力頻度での移動を検査します。計14項目で、失敗したら例外で終了します。数値を変えて失敗した場合は、狙った変更なのか、別のルールまで壊したのかを確認します。
このサンプルに含まれないもの
画面・音・UI・入力デバイスの読取は含めていません。Unityへ持ち込む場合は Battle.cs・Pilot.cs・FlightClock.cs を使い、入力座標の変換と描画をつなぎます。固定ステップはFlightClock.Advanceに任せます。Program.cs はコンソール用です。今回の14項目はサンプルの検査で、v1.6のUnityテスト38件とは別に数えています。
素材を増やす前に、輪郭と奥行きを作った
見た目は、画像素材を集めて組み合わせたものではありません。機体も雲もコードでテクスチャを生成しています。ただし、四角や丸をそのまま並べているわけでもありません。
自機には細い胴体、左右の翼、尾の部分があります。ピクセルを塗る際に上側を明るくし、輪郭にも明るい縁を付けています。その上に白い操縦席と青緑のエンジン光を重ねています。
輪郭
何なのかを伝える
機体の翼・尾・敵の違う形
光
反応を伝える
エンジン・弾・吸収・撃破
速度差
奥行きを伝える
遅い雲と速い施設
背景の雲はゆっくり、施設の影は速く流れます。背景そのものに明るさの差があり、その前を自機が進む。敵を倒したときは破片が広がり、被弾時には自機と画面が反応します。効果音もコードで生成しています。
「素材なし」は見た目を省く条件にはしませんでした。輪郭、陰影、光、背景の速度差を別々に用意したことが、今回の画面の具体的な構成です。どの要素が私の印象に何割効いたかまでは測っていません。
自動で遊ばせたら、最初は24秒で負けた
ゲームのルールはUnityの描画から分離し、1回の更新を1/60秒に固定しています。自動操縦もプレイヤーと同じ移動・吸収の入力を渡します。無敵にしたり、敵を消したりしてボスまで進めているわけではありません。
ところが、最初の自動操縦は途中で負けました。敵配置を決める乱数の種を3種類に変えても、全部ボス登場前に終了しています。
| 敵配置の種 | 初版の生存時間 | 結果 |
|---|---|---|
| 1 | 47.90秒 | 敗北 |
| 19(標準) | 23.93秒 | 敗北 |
| 987 | 47.27秒 | 敗北 |
少し先の一点だけ見ても、弾を避け切れなかった
- 症状
- 標準配置では23.93秒で敗北。ボスまで検証できない。
- 原因
- 初版の自動操縦は、0.25秒先の危険だけを評価し、移動も左右に限っていた。
- 対処
- 0.15・0.30・0.45・0.60秒先を評価し、上下移動も候補に追加。複数の時点で危険が少ない方向を選ぶようにした。
この変更後、標準配置では87.23秒でボス撃破まで進みました。ここで直したのは自動操縦の判断です。人が遊ぶゲームの難しさを下げて通した記録ではありません。
ただ、自動操縦は内部の座標を正確に読めます。ボスを倒せたからといって、初めて遊ぶ人が簡単にクリアできるとは言えません。使い道は、毎回同じ条件で最後まで進め、途中の破綻や変更による差を見つけることです。
数字が通っても、ボスが隠れていた
ブラウザの65秒時点の画面を確認すると、ボスが上部の情報表示、HUDに隠れていました。ルールのテストでは勝てる。けれど、肝心の敵が全部見えていない。
ボスの位置を下げたら、クリア時間まで変わった
- 症状
- ボス上部がHUDに重なって見えない。
- 原因
- ボスの中心をゲーム内のY座標5に置いていた。描画サイズとHUDの占有範囲を、通し画面で確認できていなかった。
- 対処
- 中心をY=3.2へ下げた。自動テストだけで済ませず、PC幅とスマホ幅で最後まで再検証した。
| 標準配置・同じ自動操縦 | 修正前 | 修正後 |
|---|---|---|
| ボス撃破まで | 87.23秒 | 79.73秒 |
| 残りライフ | 1 | 2 |
| スコア | 22,525点 | 23,550点 |
| 吸収した敵弾 | 37発 | 38発 |
位置を下げると、自機の弾も敵の弾も到達するまでの時間が変わります。見た目を直すつもりでも、戦闘の条件が変わっていました。このため、位置変更後にもう一度通しで遊ばせています。画面の修正でも、結果は測り直す必要がありました。
公開後に、予告の嘘と状態の読みにくさを直した
最初の公開ページには遊び方と数字を載せましたが、制作過程の説明が足りませんでした。私は「薄くね? 内容」と伝え、記事を書く前に改善点も見直すよう依頼しました。
この見直しで見つかったのが、ボスの照準線です。狙撃弾は2回に1回なのに、予告は毎回出ていました。実際には来ない攻撃を避けさせる表示です。
The first volley has only fan bullets Expected: 0 But was: 0.650000036f
修正は、予告を出す条件に「次の攻撃に狙撃を含む」を足すこと。テストも、最初の扇弾のみの回には予告が出ず、次の狙撃回には予告が出て3本の弾が発射される、というところまで確認します。
| v1.1で変えた箇所 | 変更後 |
|---|---|
| 照準線 | 狙撃する回だけ予告する |
| ボス耐久 | HPの数字に加えてゲージを表示。後半は色も変わる |
| 吸収ボタン | 吸収中と充電中で色を変え、残り時間をゲージで示す |
敵の数や吸収の性能は、この改善では変えていません。必要な情報を信頼できる表示にする修正です。


あわせて、最後の被弾とボスへの攻撃が同じ更新で重なる条件も検査しました。これは修正前から正しく敗北になっていました。「バグを直した件数」に混ぜず、今後その挙動を守るテストとして残しています。
PCで重く感じたので、fpsと操作応答を分けて測った
公開後、私は「PCで少し重い」と伝えました。まずChromeで通しプレイを測りましたが、継続的な処理落ちは再現できませんでした。HUDの更新を減らす試作も、ゲーム全体の高速化を示す結果にはなっていません。
一方、コードでは入力とゲームの更新より先に、機体の描画位置を決めていました。これをLateUpdateへ移し、同じフレームの入力後の座標を描くようにしました。採用前に、キーイベントの受信から、WebGL上の自機の画素が動くまでを比較しています。
- ① v1.133.10ms
フレーム数の中央値:2
- ② 改善版16.35ms
フレーム数の中央値:1
- ③ 改善版16.60ms
フレーム数の中央値:1
- ④ v1.132.25ms
フレーム数の中央値:2
各16入力の中央値。番号は測定順。短いほど、この測定条件での描画応答が早い。
測定はWindowsのChrome、描画領域506×900、DPR 1。旧版→改善版→改善版→旧版の順で新しいブラウザコンテキストを開き、開始直後に左右の入力を交互に送りました。青緑の自機の画素の重心が、入力した方向に2px以上動いた時点を検出します。旧版の32入力は全て2フレーム、改善版は31入力が1フレーム、1入力が2フレームでした。
何の遅延を測った数字か
ブラウザ内のキーイベント受信から、描画済み画素の読取までです。キーボードの機械的な遅延やモニターへの表示時間は含みません。画素の読取自体にも負荷がかかります。操作に対する描画の遅れが減った根拠として採用し、「ゲーム全体が2倍速い」「重さを完全に解消した」とは扱っていません。
この変更とHUDの不要な文字列更新の削減をv1.2に採用しました。同じコードでEditMode 22件・PlayMode 3件が合格しています。この時点では、私が感じた引っかかりとの対応は未確認。平均fpsだけでは、操作応答の問題を見落とすと分かりました。
120Hzの入力で移動が半分になる不具合も見つかった
追加調査では、キーボード移動にも問題が見つかりました。画面が更新されるたびに移動量を計算し、その後にゲーム内部を1/60秒ずつ進めていました。内部を進めない画面フレームでは、その回の移動量が捨てられていたのです。
旧版の計算を切り出して、0.5秒間ずっと右キーを押す条件を再現しました。入力を120Hzで渡すと、ゲーム内時間は0.5秒進んでいるのに、移動は本来の半分。一定の60Hzだけを確認しても見つからない問題でした。
- 旧計算1.75
ゲーム内の距離。設定速度からの期待値は3.50。
- 修正後3.50
各1/60秒の更新で、方向×1/60×速度7を加算。
入力頻度を制御したC#の再現テスト。120Hzの実機モニターでプレイした測定ではありません。
修正では、画面から受け取る情報を「移動方向」と「ポインターの目標座標」に分け、FlightClockがゲーム内部の更新ごとに移動量を加算します。短いフレームで吸収ボタンを押しても次の更新まで保持し、再挑戦ではその保留を消します。
30・60・120・144Hz、不規則な間隔、長いフレームをテストに追加しました。ポインター操作と自動操縦の結果も含め、v1.3はEditMode 31件・PlayMode 3件が合格。実ブラウザでもキー保持を6回測り、移動量は期待値の約98〜103%でした。PC・スマホ幅ともボス撃破と再挑戦、キー・タッチ操作を確認して公開しています。上の配布サンプルにも、4種類の入力頻度を検査するコードを追加しました。
ただし、通常の60HzのChromeでは旧版も期待する移動量の約98〜103%でした。すべてのPCで移動速度が倍になる修正ではありません。負荷調査でも通常条件の引っかかりは実行ごとに変動しており、今回見つけた不具合だけで、私が感じた重さを全部説明できたとは考えていません。
WebGLのボタンは「押せるか」を画面でも伝える
v1.4では出撃・吸収・停止・音のボタンが似た色で、PCのWebGL画面にマウスを重ねても手形になりませんでした。吸収の再充填中もボタンに見えます。内部のルールは押下を無視しますが、プレイヤーにはその状態が伝わりません。
v1.5では、主操作と補助操作の色を分け、ホバー・押下・キーボードの焦点に輪郭を付けました。再充填中や一時停止中は操作を無効表示にします。Unity公式のUSSプロパティの説明では、cursorはランタイムUIで機能しないとされています。そのためUI Toolkitのポインター進入・退出を受け、WebGL側のcanvasのカーソルを切り替えています。
ここでもUnityのテストだけでは足りませんでした。最初のビルドはクリックと手形が動いたものの、押している間の色はホバー時から変わらないことが、ブラウザ画像の同じ画素を比較して分かりました。押下表示はInput Systemのボタン保持状態に合わせて更新し、離す場所によるクリック成立はUI ToolkitのButtonに任せました。PC幅と375px幅で、ボタンの外で離すと出撃しないこと、再充填中は手形にならないこと、一時停止で時間が進まないことを確認しています。幅375pxの検査はChromeのエミュレーションで、スマホ実機の操作感は未評価です。
どこまで確かめたか(確認の手順)と、まだ分からないこと
初版の公開環境では、次の値を記録しました。ロード時間は3回測り、中央値を使っています。ダウンロード完了から起動までの時間と、ページを開いてからの総時間は分けました。
| 初版v1の実測(2026-09-19) | 結果 |
|---|---|
| Buildの4ファイル合計 | 6,988,252バイト |
| WebGLビルド時間 | 163.715秒 |
| 受信完了から起動までの中央値 | 439.6ms |
| ページ遷移から起動までの中央値 | 1,269.8ms |
| 10秒間のfps・3回の中央値 | 60.07 |
測定環境はWindowsのChrome、画面960×720、描画領域383×682、DPR 1です。約60fpsはこの環境の値で、低性能なスマホでの余裕を示す数字ではありません。最新ビルドの容量と検証記録はゲーム紹介ページにも掲載しています。
v1.1の自動テストはEditMode 22件、PlayMode 2件が合格しました。予告の検査を追加した直後はEditModeが21/22件で、修正して22/22件になった、という順序です。既存のテストが全部通っていたことだけでは、この問題は見つかりませんでした。
スマホ幅の確認はChromeのタッチエミュレーションです。実機での指の隠れ方、発熱、長く遊んだときの疲れは未評価。難易度も、初見の人を複数集めたテストはしていません。自動操縦のクリア記録と、人が感じる遊びやすさは分けて扱います。
私は今回の出来を見て驚きました。でも、次に引き継ぐなら「Codexの方がすごい」という一行より、何を選ばせるゲームにするか、何を画面で伝えるか、最後までどう確かめるかを残したい。リフレアでは、吸収する瞬間、遠景と機体の描き分け、ボス撃破までの通し検証が、その具体例になりました。