TECH · GAME-DEV · CANVAS

Chilla Games 開発記 — AIと一緒にブラウザゲームを3本作った話

チンチラのリンちゃんが主人公のブラウザゲーム3本をAI(Claude)と3日で開発。当たり判定の設計変更、仕様の誤解、確率分布によるゲームバランス設計など、AI協働開発のリアルな体験談。

tech 2026-04-06 39 min read by ちらりん
cover · 1024×1024

はじめに

ちらりんブログに「ゲームコーナー」を作りました。 うちのチンチラ、リンちゃんが主人公のブラウザゲームを3本。

コンセプトは 「2000年代の携帯ゲームにインスピレーションを得た」 ブラウザゲーム。あのシンプルで中毒性のあるゲーム体験を、リンちゃんの世界観で作りました。

  • チラ走 — 横スクロールランナー
  • Chilla Jump — 縦スクロールクライミング
  • Chilla Up — タワークライム(パワーゲージ式ジャンプ)

技術スタックは TypeScript + Canvas 2D API。ゲームエンジンもフレームワークも使わず、外部依存ゼロ。esbuild でバンドルして Hugo のサイトに組み込んでいます。

で、これを 3日で3本リリース しました。AI(Claude Code)との協働開発です。

この記事は、その開発過程で起きた「リアルなハマりポイント」と「AIとの協働で学んだこと」をまとめたものです。AIが万能に見えるこの時代に、AIにできること・できないことが具体例で伝わればいいなと思います。


チラ走 — 当たり判定の沼にハマった話

チラ走のゲーム画面
チラ走 — リンちゃんが障害物を飛び越えて走る

最初に手をつけたのが「チラ走」。横スクロールランナーです。 リンちゃんが走り続けて、穴を飛び越え、障害物を避ける。シンプルなルール。

……のはずだった。

「穴をくり抜く」という設計判断

AIに「地面に穴が空いていて、落ちたらゲームオーバー」と伝えたところ、実装はこうなりました。

地面を一枚のベタ塗りとして描画し、そこから「穴」の範囲を除外する

つまり 「穴をくり抜く」 方式。地面が基本で、穴は「地面がない領域」として表現されています。

一見合理的に見えます。でも、当たり判定のコードを書こうとした瞬間に地獄が始まりました。

修正サイクル v1〜v7、そしてそれ以降

当たり判定のバグが 何度修正しても直らない

  • v1: 壁をすり抜ける
  • v2: 左壁でなぜかゲームオーバーになる
  • v3: 穴の上で着地判定が発生する(空中に立てる)
  • v4: 穴の左端だけすり抜ける
  • v5〜v7: 上記の組み合わせが形を変えて再発

AIは毎回「修正しました」と言って、ピンポイントで条件分岐を追加してくる。でも1つ直すと別の場所が壊れる。モグラ叩きです。

根本原因 — データ構造の設計ミス

7回目あたりで、私はコードを読むのをやめて データ構造を見直しました

問題はコードのバグではなく、そもそもの設計にありました。

「穴をくり抜く」方式では、「穴」という概念がデータに存在しない

地面は「画面幅いっぱいのベタ塗り」として存在していて、穴は「描画しない領域」。でも当たり判定は「何かがある場所」に対して行うもの。存在しないものとの衝突判定を正しくやるのは、コードが複雑になるのが当然です。

発想の転換 — 「穴ではなく地面に注目する」

私が出した結論は 「穴ではなく、地面ブロックを配置する」 でした。

snippet
【Before: 穴くり抜き方式】
地面: ████████████████████████████████
穴:       ↑ここを除外 ↑
判定: 「穴の範囲外かつ地面の高さ」→ 条件が複雑

【After: ブロック配置方式】
ブロック: ████  ████  ████████  ████
判定: 「ブロックの上にいるか」→ 単純な矩形判定

ブロック方式に変えた瞬間、当たり判定は 数行で実装できました。各ブロックは {x, y, width, height} を持っていて、プレイヤーの矩形と重なっているかを調べるだけ。

typescript
// ブロック方式の当たり判定(概念)
const onGround = blocks.some(block =>
  player.x + player.width > block.x &&
  player.x < block.x + block.width &&
  player.y + player.height >= block.y &&
  player.y + player.height <= block.y + TOLERANCE
);

v1〜v7 の修正サイクルで費やした時間より、設計変更後の実装のほうがはるかに短かった。

教訓: 同じ修正が3回失敗したら、コードではなく設計を疑え

AIは 「与えられたアプローチの中で最善の修正」 を出すのが得意です。でも 「アプローチ自体を変える」 という判断は苦手。穴くり抜き方式の中で条件分岐を追加し続けることはできても、「この方式自体が間違っている」とは言ってくれません。

これがこの記事で一番伝えたいことかもしれません。


Chilla Jump — 物理計算とゲームバランス

Chilla Jumpのゲーム画面
Chilla Jump — 自動ジャンプで上を目指す

2本目は「Chilla Jump」。縦スクロールクライミングです。 自動ジャンプ + 左右移動のシンプルな操作で、ひたすら上を目指す。

このゲームでハマったのは 物理計算とスプライトの問題 でした。

物理的に到達不可能な足場

実装初期、テストプレイで「どう見ても届かない足場」が出てきました。

原因を調べたら、数式が合っていなかった。

snippet
JUMP_VELOCITY = -720 px/s(初速)
GRAVITY = 1800 px/s²

最大到達高度 = v² / (2 * g)
            = 720² / (2 * 1800)
            = 518400 / 3600
            = 144 px

一方、足場の最大ギャップ(maxGap)は 180px に設定されていました。

物理的に144pxしか飛べないのに、180px先に足場を置いたら 絶対に届かない。ゲームとして成立しません。

これは単純な計算ミスですが、AIは maxGapJUMP_VELOCITY を別々のモジュールで設定していたため、両者の整合性チェックが抜けていました。人間がプレイして「届かない」と気づかなければ、そのままリリースしていたかもしれません。

スプライトのアスペクト比問題

もう1つ厄介だったのが スプライト画像のサイズ

リンちゃんのスプライト run-up1.png のサイズは 85 x 247px。極端な縦長です。

これをそのまま使うと、キャラクターの表示高さが足場ギャップより大きくなり、見た目上は足場に触れているのに当たり判定が発生しない、あるいはその逆が起きます。

解決策として、スプライトの種別(走り・ジャンプ上昇・ジャンプ下降)ごとに 表示サイズを個別に計算 する方式にしました。元画像のアスペクト比を維持しつつ、ゲーム内での当たり判定サイズは統一する。見た目とロジックの分離です。

4タイプの足場と難易度テーブル

Chilla Jump には4タイプの足場があります。

足場タイプ挙動登場スコア
通常乗ると跳ねる最初から
壊れる一度乗ると消える500点〜
動く左右に移動1000点〜
バネ通常の2倍の高さまで飛べる1500点〜

スコアに応じて出現確率を変える 難易度テーブル を設計しました。この部分はAIに「スコア帯ごとの出現確率を配列で定義して」と明確に指示すれば、きちんと実装してくれます。

AIへの指示が「明確な仕様」になっているかどうかで、出力品質が大きく変わる好例です。


Chilla Up — AIの仕様誤解と確率分布設計

Chilla Upのゲーム画面
Chilla Up — パワーゲージでジャンプ力を調整

3本目は「Chilla Up」。パワーゲージ式ジャンプのタワークライムです。

このゲームの開発では 仕様の伝え方 について大きな学びがありました。

仕様誤解事件 — 元ゲームの操作体系を取り違えた

AIに「自動ジャンプ + 左右移動のタワークライムを作って」と指示したところ、出てきたのは 意図と全く違う操作体系のゲーム でした。

ん? と思ってよく見ると、私の指示が悪かった。

意図していた操作体系:

  • 左右移動 → 自動(キャラが勝手に往復する)
  • ジャンプ → 手動(パワーゲージでタイミングと強さを決める)

私が伝えたこと:

  • 「自動ジャンプ + 左右移動」

AIは素直に「自動でジャンプして、左右は手動で操作する」と解釈しました。しかし意図していたのは 自動と手動が逆 の操作体系だったのです。

ここで学んだのは、ゲームの仕様を伝えるときは 「何を操作するか」「何が自動か」 を明確に分けて書くこと。

snippet
【曖昧な指示】
「自動ジャンプ + 左右移動」

【明確な指示】
- 操作: ジャンプ(パワーゲージ式、長押しで強く飛ぶ)
- 自動: 左右移動(壁に当たると反転して往復する)

たった数行の違いですが、出てくる実装は全く別物になります。

足場配置の縦密集問題

Chilla Up では足場をランダムに配置するのですが、初期実装では 足場が縦に積み上がる 問題がありました。

横位置がランダムなので、たまたま同じX座標付近に連続して配置されると、ジャンプしなくても上に進めてしまう。ゲームとして成立しません。

最初の対策は「直前の足場と横位置が重ならないようにする」というもの。AIが提案してくれた方法です。

でもこれだと 3段おきに同じ位置に戻る パターンが発生しました。

snippet
足場1: 左
足場2: 右(足場1と重ならない → OK)
足場3: 左(足場2と重ならない → OK、でも足場1と同じ位置)
足場4: 右(足場3と重ならない → OK、でも足場2と同じ位置)
→ 結果: 左右交互に並ぶだけ。ジグザグに登れてしまう

多段階確率分布制御

私が考えた解決策は 多段階の確率分布制御 でした。

snippet
n+1段目との重なり率: 最大20%
n+2段目との重なり率: 最大35%
n+3段目との重なり率: 最大50%
n+4段目との重なり率: 最大65%

直近の足場ほど重なりを厳しく制限し、離れた足場とは緩やかに許容する。これで 単純なジグザグパターンが崩れ、プレイヤーはちゃんとジャンプの方向を考える必要が出てきます。

AIにこの方式を伝えたら実装自体はスムーズでした。でも この設計自体を思いつくのはAIではなかった

「足場が重ならないようにして」という指示には応えられる。でも「何段先まで、どの程度の重なりを許容するか」という ゲームの面白さに直結する設計 は、実際にプレイして「これは面白くない」と感じた人間のフィードバックから生まれたものです。


共通のハマりポイント

3本のゲームに共通して遭遇した、地味だけど時間を食う問題たちです。

Canvas と DOM の相互作用

Hugo のテンプレートで生成される HTML と、ゲーム側の JavaScript が想定するコンテナIDが一致しない。game-containergameContainer のような微妙な不一致で、Canvas が表示されない。

さらに、Canvas の上に DOM のボタン(リスタートボタンなど)を重ねると、タッチイベントが Canvas に伝播してしまい、ボタンを押したつもりがゲーム内の操作として処理される問題も起きました。event.stopPropagation() で解決しますが、Canvas とDOM を混在させる設計では常に意識が必要です。

esbuild の outfile 名と HTML の参照先不一致

esbuild でバンドルした JS ファイルの出力名と、HTML 側の <script src="..."> が一致していない。ゲームごとに chira-run.jschilla-jump.jschilla-up.js と命名しているので、どこかでタイポすると無言で動かなくなります。

ブラウザキャッシュ問題

ゲームの修正をデプロイしても、ブラウザのキャッシュで古いJSが使われ続ける。「バグが直ってない」と思って何度もコードを確認した結果、単にキャッシュだった――という、古典的だけど毎回やる失敗。


AIとゲーム開発 — 得意なこと・苦手なこと

3本のゲームを開発して見えてきた、AIとの協働における得手不得手をまとめます。

AIが得意だったこと

  • Canvas 2D API の描画コードfillRectdrawImageclearRect の組み合わせをスラスラ書く
  • requestAnimationFrame のゲームループ — deltaTime の計算、FPS制御など、定型パターンの実装が速い
  • キーボード/タッチの入力ハンドリング — イベントリスナーの登録、モバイル対応のタッチ判定
  • esbuild の設定 — TypeScript のバンドル設定、watch モードの構成
  • 足場生成のアルゴリズム — 仕様が明確であれば、複雑なロジックもきちんと実装できる

要するに 「何を作るか」が明確に定義されていれば、実装は速くて正確

AIが苦手だったこと

  • データ構造の根本的な設計変更 — 穴くり抜き方式の中での修正は得意だが、「この方式自体をやめよう」とは言えない
  • ゲームバランスの調整 — 数値の計算はできても、「これが面白いか」の判断は人間にしかできない
  • 仕様の曖昧さの解消 — 「自動ジャンプ」に複数の解釈がある場合、どちらが正しいか聞き返すことはあっても、文脈から推測する精度は高くない
  • 多段階の確率分布設計 — 「重ならないように」は対応できるが、「何段先まで、どの確率で」という段階的な設計は人間の発想から生まれた

協働のコツ

この3本の開発を通じて、AIとのゲーム開発で意識すべきことが見えてきました。

  1. 3回同じ修正が失敗したら設計を見直す — AIはアプローチ内の最適化は得意だが、アプローチの転換は人間の判断
  2. 仕様は「操作」と「自動」を明確に区別する — 曖昧な指示は曖昧な実装になる
  3. ゲームバランスは必ず自分でプレイして確認する — 数値の整合性は計算で検証できるが、「面白さ」はプレイしないとわからない
  4. AIの実装力 x 人間の設計判断 = 最速の開発 — どちらか片方では成立しない

まとめ

3日で3本のブラウザゲームを公開できたのは、間違いなくAIとの協働があったからです。 Canvas の描画コード、ゲームループ、入力処理、足場生成アルゴリズム――これらを1人で全部書いていたら、3日では絶対に終わりません。

でも、AIだけでは「面白いゲーム」にはならない

データ構造の選択を誤れば、いくらコードを修正しても当たり判定は正しく動かない。物理パラメータとレベルデザインの整合性は、人間がプレイして初めて検証できる。足場配置の確率分布は、「遊んでみてつまらない」というフィードバックから設計が始まる。

AIは最高の実装パートナーだけど、設計者は人間

この役割分担がうまくハマったとき、個人開発のスピードは劇的に上がります。

ゲームは下のリンクから遊べます。リンちゃんと一緒に走って、跳んで、登ってみてください。

Chilla Games: https://chillablog.chillarin39.com/games/

· · ·

コメント