AIとゼロから作る脱出ゲーム開発日記 第1話:ChatGPT×Claude Codeで「初号機」を作って公開してみた

ノートPCで脱出ゲームを開発している様子を描いたイラスト。画面には部屋の一室と鍵のアイコンが表示されている。 開発日記

この連載は、企画やシナリオの相談にChatGPT、コードレビューにGeminiのPro拡張モード、そして実装にClaude Codeと、複数のAIを役割分担で相棒にしながら、ブラウザで遊べる脱出ゲームをゼロから作り、いずれはテンプレートとして販売するところまでを目指す「作りながら全部公開していく」開発日記です。

完成品をドヤ顔で発表する記事ではありません。むしろ逆で、「AIに勧められたけど実は存在しなかったもの」「一度作ったのに設計をひっくり返したもの」「バグに気づかず放置していたもの」など、泥臭い試行錯誤をそのまま記録していきます。

第1話の今回は、「土台探し」でいきなりつまずいたところから始まって、1部屋だけの小さな脱出ゲーム(初号機)が動くようになるまでの話です。

第1章:AIが勧めてきたテンプレートは、実は存在しなかった

最初に考えたのは「ゼロから全部書くのは大変だから、土台になるオープンソースのテンプレートを探そう」ということでした。まずCopilotに相談したところ、いくつかそれらしい名前のGitHubリポジトリを紹介してくれました。

ところが、実際に1件ずつ存在確認をしてみると、紹介された「脱出ゲーム特化のテンプレートリポジトリ」はほとんどが実在しないものでした。土台となるゲームエンジン自体(PhaserやPixiJSなど)は確かに存在するのですが、それを使って作られた再利用可能な脱出ゲームのテンプレートは、探しても星0件の学習用サンプルくらいしか見つからなかったのです。

AIは自信満々に「おすすめです」と紹介してくるので、そのまま信じて時間を溶かしてしまうところでした。この一件で「Copilotにこの手の調べ物をさせるのはもうやめよう」と判断し、以降はネット検索を得意とするChatGPTに切り替えることに。この経験から、この連載では「AIの提案は必ず自分の目で裏取りする」というルールも徹底することにしました。

結果として、「土台にできる既存リポジトリは無い」という事実がはっきりしたので、方針を転換。外部ライブラリに一切頼らない、素のJavaScriptとHTML5だけで自作することに決めました。ライブラリの使い方をAIが覚え違えるリスクをゼロにできますし、何より「ZIPを解凍してダブルクリックすればすぐ動く」という、将来テンプレートとして販売するときに一番大事な条件を満たしやすくなるからです。

開発環境には、自分のPCのスペックに左右されないGitHub Codespacesを採用しました。クラウド上の開発環境なので、ブラウザさえあればどこからでも同じ環境で作業を続けられます。

第2章:「シナリオ」「見た目」「進行管理」を分けて作る

土台が無いなら設計から自分で決めるしかありません。採用したのは、次の3つの役割に分けて作る「データ駆動」という考え方です。

  • config.js:どの部屋にどんなアイテムがあるか、といった「シナリオそのもの」を書くファイル
  • engine.js:それを実際に画面に描画する「エンジン」の部分
  • game.js:アイテムを拾った・扉が開いたといった「進行状況」を管理する部分

こうしておくと、将来テンプレートとして販売するときに「シナリオファイルを書き換えるだけで別のゲームが作れる」状態に近づけます。

この土台の上に、部屋を移動する仕組み、アイテムを拾ってインベントリに入れる仕組み、2つのアイテムを組み合わせて別のアイテムにする「合成」の仕組み(棒と布を組み合わせて松明を作る、など)、画面を拡大して細部を見る「ズーム」、番号を入力して鍵を開ける「パスコード」といった、脱出ゲームの基本パーツを一通り実装しました。

地味に苦労したのがBGMと効果音です。フリー音源サイトを調べてみると、多くのサイトが「完成した作品に組み込んで配布するのはOKだけど、音声ファイルそのものを素材集として再配布するのはNG」という利用規約になっていました。テンプレートとして販売することを考えると、これは相性が良くありません。そこで、外部の音声ファイルを一切同梱せず、ブラウザのWeb Audio APIでドアの解錠音や効果音、BGMをその場で合成して鳴らす方式を採用しました。音声ファイル無しでも、ダウンロードした直後から音が鳴る状態にできています。

第3章:Canvasで作り始めたら、早々に壁にぶつかった

最初は画面の描画に<canvas>要素を使っていました。ところが「鍵が扉に吸い込まれて開く」ようなちょっとした演出をやりたいと思った瞬間、Canvas方式の弱点にぶつかります。クリックした場所がどのオブジェクトに当たっているかの判定を自分で計算しなければならず、アニメーションもコマ送りで自前実装する必要があるのです。

そこで方針転換。Canvasを使うのをやめて、画面上のすべてのオブジェクトを普通のHTML要素(div)として配置するDOM方式に全面移行しました。ブラウザ標準のクリック判定がそのまま使えるようになり、CSSの@keyframesアニメーションもそのまま使えるようになります。

ちなみに、コードレビュー役のGemini(Pro拡張モード)から「Canvasの上ではパスコード入力のような画面が作りにくいのでは」という指摘も一度ありましたが、これは実は事実誤認でした。パスコード入力は最初からCanvasに重ねる形のポップアップとして実装していたので、Canvas方式でも問題なく動いていたのです。AIレビュワーの指摘も鵜呑みにせず、実際のコードを確認してから対応するというのは、この連載を通じての基本姿勢にしたいと思っています。

DOM方式に移行して実装を進める中で、2つの落とし穴も見つかりました。

1つ目は、アニメーションが終わったタイミングをanimationendイベントだけで検知していたこと。ブラウザのタブが非アクティブな状態だとこのイベントが発火せず、ゲームがそこで止まってしまうことに気づきました。対策として、setTimeoutで「もし一定時間経ってもイベントが来なかったら強制的に完了扱いにする」というフォールバックを仕込みました。

2つ目は、画面サイズが変わったときにゲーム画面を追従させる仕組みをResizeObserverだけに頼っていたこと。環境によっては発火しないケースがあることがわかり、こちらも念のため軽いポーリング処理(0.4秒ごとにチェック)を保険として組み合わせました。

「イベントが発火する前提の設計は、壊れたときに気づきにくい」というのがこの週の一番の教訓でした。以降のアニメーションはすべて「イベント待ち+タイムアウトの保険」をセットで実装するルールにしています。

第4章:「結局、鍵を探すだけ」と言われて気づいたこと

DOM移行が一段落したところで、いったん動くものを見返したところ、「金庫のギミックを足しても、結局『鍵を見つけてドアを開けるだけ』は変わっていない。棒+布を組み合わせて作った松明も、どこにも使われていない飾りになっている」ということに気が付きました。

アイテムの合成という仕組みは用意したものの、それを使わないとクリアできないような場面がどこにも無かったのです。

そこでシナリオを作り直しました。「松明を作る→松明で引き出しの中を照らしてメモを見つける→メモに書かれた番号で金庫を開ける→中の鍵を手に入れる→扉を開ける」という、一本につながった依存関係に組み替えたのです。これでようやく、合成した松明が実際に意味を持つようになりました。

ただ、これを場当たり的に実装すると、エンジン側のif/elseがどんどん連なって収拾がつかなくなります。そこで、「このタイプのオブジェクトをクリックしたときはこの処理」という対応をregisterInteraction(type, handler)という形で登録できるプラグイン方式に書き直しました。あわせて、「特定のフラグが立つまで画面上に一切描画しない」隠しオブジェクト(requiresFlag)と、「調べるとフラグだけ立てる」最小限のギミック(examineタイプ、たとえば絵画を調べると裏からメモが出てくる、のような発見演出)を追加。これで「松明で照らすと初めて存在に気づく」ような仕掛けが、config.jsの設定だけで作れるようになりました。

ついでに、アイテムを拾ったときの効果音再生やUI更新といった「副作用」の処理を、進行管理のロジック本体から切り離すために、依存ゼロの小さなイベントバス(pub/sub の仕組み)も自作しています。

このタイミングで、GSAPやHowler.jsといった定番ライブラリの導入も検討しました。ただ、GSAPが解決してくれるはずの「アニメーションのタイミングのズレ」は前章のsetTimeoutフォールバックで、Howler.jsが解決してくれる「自動再生ブロック」の問題も自前の音声処理で、それぞれ既に対応済みだったため、両方とも見送りました。「軽量・依存ゼロ・ダブルクリックで動く」というテンプレートとしての価値を優先した形です。

第5章:部屋が「部屋」に見えるまで

パズルの中身ができたところで、見た目の作り込みに戻りました。背景がずっとグラデーションだけで、部屋というより抽象的な空間に見えるという指摘があり、調べてみるとCanvasからDOMに移行したときに「床を塗りつぶす処理」自体を落としてしまっていたことが原因だとわかりました。専用の床レイヤーと、壁と床の境目にあたる巾木(木目のトリム)を追加し、観葉植物や窓、時計といった小物も足して、部屋らしい見た目に近づけました。ついでに、扉の下端が床の境界より約21px浮いていた配置ミスも見つかり、修正しています。

🎮 今回作った脱出ゲーム

まだ1部屋だけの小さなゲームですが、松明を作って引き出しを照らし、見つけたメモの番号で金庫を開け、鍵を手に入れて扉を開ける——というひとつながりの謎解きが遊べます。ぜひ実際に触ってみてください。

💬 読者へのヒアリング

今回はPCでの動作を中心に作りましたが、実はまだスマホでの動作確認ができていません。もしスマホやタブレットで遊んでみて、ボタンが押しにくかった・画面がはみ出していた・タップしても反応しないところがあった、などに気づいたら、ぜひコメントで教えてください。次回の検証記事の参考にさせてもらいます。

まとめ&次回予告

土台になるテンプレートを探すところから始まり、存在しないものを追いかけたり、Canvasで作り始めてから作り直したりと、初回から遠回りの多い開発になりました。それでも、なんとか1部屋だけの脱出ゲームが動くところまでたどり着けました。

とはいえ、正直に言うと今の状態にはまだ抜けがあります。触ったら即ゲームオーバーになるような「危険な仕掛け」はまだ一つも無いですし、そもそもスマホでちゃんと遊べるのかどうかも、この時点ではまったく確認できていません。

次回は、このゲームに危険な仕掛けを追加しつつ、実際にスマホ(iPhone・Android)で開いて遊んでみた検証編をお届けします。PCでは気づかなかった問題が、いくつも出てくることになります。


次回:第2話「触ったら感電GAME OVER? 危険な仕掛けとスマホ実機検証編」

コメント

タイトルとURLをコピーしました