Canvas 2Dで作ったグリッド爆弾アクション。爆弾で壁を壊して敵を倒すレトロゲーム
旧サイトのデモを開く
実際にゲームを遊べる旧サイトのデモページです。新しいタブで開きます。
デモを開く
昔、迷路状のステージで爆弾を置いて壁を壊し、敵を倒すタイプのゲームに夢中になった記憶がある。グリッド上を歩き、爆弾を置くと数秒後に四方に炎が広がる。壊せる壁を壊して道を開け、敵を巻き込めば得点。出口を探して次のステージへ。シンプルなルールなのに、戦略と反射の両方が求められる。
今回、Canvas 2Dでその手のゲームを一から作った。15×13のタイルマップ、爆弾の連鎖、3種類の敵、パワーアップアイテム、ステージクリア条件。当時の雰囲気を再現しつつ、スマホではタッチのジョイスティックと爆弾ボタンで遊べるようにした。
作ろうと思った理由
レトロなドッジゲーム(midori311)の次に、今度は「グリッド+爆弾+炎」の組み合わせに挑戦したかった。マップがタイルで決まっている分、当たり判定や経路探索の考え方がはっきりしていて、実装の見通しが立てやすい。爆発の炎を四方向に伸ばす処理、壊せる壁を壊したときにアイテムを落とす処理、敵がプレイヤーや爆発に当たったときの処理。一つずつ積み上げていけば形になる感覚があった。
もう一つは、スマホで遊べるようにしたかったことだ。キーボードの矢印とスペースだけだと、外出先では遊べない。そこでタッチ用の仮想ジョイスティックと爆弾ボタンを用意し、Pointer Eventsでキー入力とタッチを同じ入力層にまとめた。
ゲームの基本ルール
プレイヤーは緑色のキャラクター。矢印キーまたはWASDでグリッド上を移動し、スペースでその場に爆弾を置く。爆弾は約2秒後に爆発し、上下左右に炎が広がる。炎の届く範囲は初期値で2マス。固い壁は壊れず、壊せる壁に当たるとそのマスで止まる。壊せる壁を壊すと、一定確率でアイテムが出現する。
敵は3種類。赤いBaronはランダムに方向を変えながら動く。緑のPontanは爆弾を避けつつランダムに隣マスへ移動する。オレンジのOnielはプレイヤーに近い軸の方向へ向かう。敵に触れるとミス。爆発に巻き込むと敵は消え、得点(100〜300点)が入る。
ステージには隠れた出口がある。壊せる壁の下にあり、その壁を壊すと出口が現れる。敵を全滅させたうえで出口に乗るとステージクリア。制限時間は180秒。5ステージクリアでゲームクリアだ。
マップとタイルの設計
マップは2次元配列で、各マスは「空」「固い壁」「壊せる壁」のいずれか。固い壁は外周と、格子状に配置した柱で、絶対に壊れない。壊せる壁はランダムに配置するが、プレイヤー始点から出口まで必ず道がつながるように、後から経路探索(BFS)で確認し、詰まっている場合は壊せる壁を1マスずつ空けて解消している。
タイルは32×32ピクセル。床はチェッカー状に2色を塗り分け、固い壁・壊せる壁は影とハイライトを付けて立体感を出した。マップが変わるときだけタイルを描き直すオフスクリーンキャンバス(mapCache)に一度描いておき、毎フレームはそのキャッシュをコピーする。壊せる壁が壊れたマスだけキャッシュを更新するので、描画コストを抑えられる。
// マップキャッシュの更新(壊せる壁が壊れた1マスだけ)
function renderTileToCache(gx, gy) {
const t = map[gy][gx];
const px = gx * TILE, py = gy * TILE;
mapCacheCtx.fillStyle = (gx + gy) % 2 === 0 ? palette.floor1 : palette.floor2;
mapCacheCtx.fillRect(px, py, TILE, TILE);
if (t === SOLID) { /* 固い壁を描画 */ }
else if (t === SOFT) { /* 壊せる壁を描画 */ }
}
爆弾と炎の処理
爆弾は「置いた時刻」と「導火線の長さ(秒)」を持ち、現在時刻が置いた時刻+導火線を超えたら爆発処理に入る。爆発時は、中心マスから上下左右それぞれに、固い壁に当たるまで、または炎の届くマス数(blastRange)までセルを列挙する。壊せる壁に当たったらそのマスを炎に含めつつ、そこでその方向の伸びは止める。
炎のセルそれぞれについて、(1) 壊せる壁ならマップを空にしてキャッシュ更新・アイテム抽選、(2) そのマスにある爆弾は連鎖で即爆発キューへ、(3) そのマスにいる敵は削除して得点、(4) プレイヤーがそのマスにいればミス、と処理する。連鎖爆発は、新たに爆発した爆弾を順に処理する形で実装した。
炎の見た目は、爆発開始から少し遅延を付けて各セルでフェードアウトするようにし、中心に近いほど早く表示して遠いほど遅く表示するようにした。0.28秒ほどで消える。
敵AIの3パターン
Baronは「現在向いている方向に1マス進む」を基本に、進めないときや一定時間ごとにランダムで方向を変える。Pontanは、上下左右の隣マスのうち「空きマスかつ爆弾がなく、他の敵がいない」マスを列挙し、その中からランダムに選んで0.2秒かけて移動する。Onielはプレイヤーとの相対位置を見て、X方向の差がY方向以上ならX方向に、そうでなければY方向に、1マス分だけ近づくように移動先を決める。
実装で気をつけたのは、敵の移動中は「目標マスへ線形補間」で滑らかに動かし、移動完了時にマス座標にぴったり揃えること。また、敵同士が同じマスに重ならないように、Pontanの隣マス候補から「他の敵がいるマス」を除外している。
アイテムとパワーアップ
壊せる壁を壊したとき、出口マス以外で約35%の確率でアイテムを落とす。種類は「爆弾数+1」「炎の届く範囲+1」「移動速度アップ」の3種。取得すると即座にプレイヤーのパラメータが変わり、最大数は爆弾5個・炎5マス・速度は上限値まで。アイテムはパルスするようなアニメーションで描画し、プレイヤーが同じマスに乗った瞬間に消えて効果が発動する。
入力の統合(キーとタッチ)
キーボードは矢印・WASDで方向、スペースで爆弾。タッチ時は、画面左下の円形ジョイスティックで方向ベクトル(-1〜1)を取得し、右下の爆弾ボタンで爆弾入力とする。キーとジョイスティックのベクトルは加算してから正規化し、デッドゾーン(0.18)未満なら無入力にしている。爆弾は「スペースが押されている」または「爆弾ボタンが押されている」のどちらかで置ける。連打防止のため、前回置いてから0.18秒以内は置けないようにした。
ジョイスティックはPointer EventsのsetPointerCaptureで、指が円から外れても追跡する。これでスマホでも操作が途切れにくい。
音と画面のフィードバック
Web Audio APIのOscillatorNodeで、爆弾設置・爆発・壁破壊・アイテム取得・敵撃破・ステージクリア・ゲームオーバー用の短い効果音を鳴らしている。音声コンテキストはユーザー操作後にresumeする想定で、鳴らなくてもゲームは進行する。
ミス時は画面シェイク(数フレームだけキャンバスをランダムにずらす)とパーティクルを出し、ステージ開始・クリア時は画面を一瞬フラッシュさせる。ポーズはゲームループの時間更新を止め、描画だけ「PAUSED」オーバーレイを出す形にした。
全体の流れ(ゲームループ)
メインループでは、(1) 経過時間の更新と制限時間の減少、(2) 入力に応じた爆弾設置とプレイヤー移動、(3) 導火線が尽きた爆弾の爆発と連鎖・炎リストの更新、(4) 敵の位置更新とプレイヤーとの接触判定、(5) アイテムとの接触判定、(6) 出口が開いていて敵が0かつプレイヤーが出口にいればステージクリア、と処理している。描画はマップキャッシュ→炎→爆弾→アイテム→出口→プレイヤー→敵→パーティクル→フラッシュの順で、PAUSED時はその上に半透明とテキストを描く。
試行錯誤した点
出口の到達可能性
壊せる壁の密度をランダムにしただけだと、出口が壁に囲まれて絶対に到達できないことがあった。そこで、マップ生成後にプレイヤー始点からBFSで「出口の隣マスまで空きでつながっているか」を調べ、つながっていなければ壊せる壁のうち「空きマスと隣接しているもの」を1マス選んで空ける、を繰り返すようにした。これで必ずクリア可能なステージになる。
爆弾の連鎖と同一フレーム
複数の爆弾が同じフレームで爆発し、お互いの炎に含まれる場合、配列から削除しながらforEachすると挙動がおかしくなった。いったん「このフレームで爆発させる爆弾」のリストを作り、そのリストに対して順に爆発処理を呼び、爆発処理内で新たに連鎖した爆弾は別リストに追加してからあとで処理する形にした。
タッチ時のスクロール
ゲームエリアでtouchmoveをpreventDefaultしないと、ページがスクロールして操作しづらかった。ゲーム用のコンテナにtouch-action: none を指定し、touchmoveでpreventDefaultするようにした。
まとめ
- Canvas 2Dで、グリッド状マップに爆弾を置いて炎で壁を壊し敵を倒すレトロ風ゲームを実装した。
- マップはキャッシュ描画で軽くし、爆弾の連鎖・炎の範囲・出口の到達可能性を一貫したルールで処理した。
- 敵は3種類(ランダム移動・爆弾回避+ランダム・プレイヤー追尾)で、それぞれ別の緊張感を与えるようにした。
- キーボードとタッチ(ジョイスティック+爆弾ボタン)の両方に対応し、スマホでも遊べるようにした。
- 記事内のデモでは、ミニゲーム・敵の動き・爆発の概念・入力・フローを確認できる。
さらに深く学ぶには