マガジン

タワーディフェンスの作り方:経路探索と敵の波

遊びをつくる

執筆にClaudeを使用しています。

道をつなぐ線、丸い駒、幾何学のタイルからなるパズルの模型。
記事のテーマをもとに制作したAI生成イメージ。実際のゲーム画面ではありません。

タワーディフェンスを作ろうとしたとき、最初に壁にぶつかるのは「敵をどう動かすか」という問題です。砲台の配置やUI以上に、経路探索と敵の波の設計がゲームの手触りを決めます。この2点をおろそかにすると、敵が壁をすり抜けたり、難易度がばらついて遊びが成立しなくなったりします。 この記事では、ブラウザで動くタワーディフェンスを作る前提で、経路探索アルゴリズムの選び方と実装の落とし穴、そして敵の波(ウェーブ)データをどう設計すれば調整しやすくなるかを順に説明します。すでに「砲台を置いて弾を当てる」部分は動いているけれど経路がうまく機能しない、あるいはウェーブバランスが崩壊する、という段階の人に特に役立ちます。

この記事の目次
  1. 経路探索のアルゴリズムを選ぶ
  2. BFSをグリッドに実装する
  3. 敵の波データを設計する
  4. ウェーブ間の「猶予」を設計する
  5. 経路と波をつなぐデバッグの方法
  6. 次の行動
  7. 参考資料

経路探索のアルゴリズムを選ぶ

タワーディフェンスの経路探索には主に3つの選択肢があります。

固定パス方式は、スタート地点からゴールまでの経路をあらかじめ座標列として定義しておく方法です。敵はそのウェイポイントを順に目指して動きます。プレイヤーが経路を変更できないゲームならこれで十分で、実装コストが圧倒的に低く、挙動も予測しやすいです。

BFS(幅優先探索)は、グリッドマップ上でスタートから全セルへの最短距離を計算します。タワーを置くたびに再計算することで、動的に経路が変わるゲームに対応できます。計算量はマップサイズに比例し、たとえば20×20のグリッドなら1回の計算は数ミリ秒以内で収まることがほとんどです。タワーディフェンスの定番はこのBFSです。

**A\*(エースター)**は、ゴールまでの推定コストを使って探索を効率化します。広いマップや複雑な地形での速度優位がありますが、タワーディフェンス程度のグリッドサイズではBFSとの差がほぼ感じられません。実装の複雑さを考えると、最初はBFSで始めるほうが失敗が少ないです。

固定パスかBFSかの判断基準

  • 経路が1本で変化しない、推奨:固定パス
  • タワーを置くと経路が変わる、推奨:BFS
  • マップが40×40以上かつ敵が多い、推奨:A\* を検討

BFSをグリッドに実装する

BFSの基本的な流れは、ゴールセルをキューに入れ、隣接する通行可能なセルへ距離を広げていく逆方向探索です。全セルにゴールへの距離(コスト)を記録しておき、敵はいる場所から「コストが低い隣接セル」へ毎フレーム移動します。これをフローフィールドと呼ぶこともあります。

// セルのコストマップを作る(擬似コード)
function buildCostMap(grid, goalCell) {
  const cost = new Map();
  const queue = [goalCell];
  cost.set(goalCell, 0);
  while (queue.length > 0) {
    const current = queue.shift();
    for (const neighbor of getPassableNeighbors(current, grid)) {
      if (!cost.has(neighbor)) {
        cost.set(neighbor, cost.get(current) + 1);
        queue.push(neighbor);
      }
    }
  }
  return cost;
}

実装で詰まりやすい点が3つあります。

1. タワー設置時の経路封鎖チェック。プレイヤーがタワーを置いたとき、ゴールまでの経路が完全に塞がれる可能性があります。BFSを実行してゴールに到達できなかったセルが存在する場合は、設置を拒否する必要があります。この判定をしないと敵が動けなくなります。

2. 斜め移動の扱い。隣接セルを4方向にするか8方向にするかで経路の見た目が変わります。4方向はマス目らしさが出て実装が単純です。8方向は自然な動きになりますが、斜め移動で壁の角をすり抜ける問題が起きやすく、コーナー判定を別途加える必要があります。最初は4方向から始めることをお勧めします。

3. 再計算のタイミング。タワーが置かれるたびにBFSを実行するのは正しいですが、毎フレーム呼ぶと重くなります。「設置イベント発生時だけ再計算し、敵はコストマップを参照して移動する」という構造にすると効率的です。

敵の波データを設計する

ウェーブの設計とは、「どのタイミングで、何の敵が、何体、どの間隔で出てくるか」を定義することです。この設計が甘いと序盤が簡単すぎるか、突然詰みが訪れるかのどちらかになります。

ウェーブデータは配列で持つのが調整しやすい構造です。

const waves = [
  { wave: 1, enemies: [{ type: "normal", count: 5, interval: 1.5 }] },
  { wave: 2, enemies: [{ type: "normal", count: 8, interval: 1.2 },
                       { type: "fast",   count: 2, interval: 2.0 }] },
  { wave: 3, enemies: [{ type: "normal", count: 10, interval: 1.0 },
                       { type: "tank",   count: 1, interval: 0.0 }] }
];

ここで重要なのが「脅威値」の概念です。各敵の強さを数値化し、1ウェーブ全体の合計脅威値がどれくらいかを管理すると難易度の一貫性が保ちやすくなります。たとえば通常敵を1、高速敵を1.5、タンクを4と設定しておき、ウェーブごとに合計脅威値を一定の割合で増やしていく設計です。「気がついたら後半ウェーブが理不尽」という事態を防ぐために有効です。

敵の種類を増やすときの判断基準

敵の種類を追加するタイミングは、新たな「プレイヤーへの問いかけ」が生まれるときです。単なるHP倍増や速度増加は種類が増えたように見えても戦術的な変化を生みません。以下のような軸で差別化すると、各ウェーブに明確な意味が生まれます。

  • 移動速度が速い敵:砲台の射程・連射速度の再配置を促す
  • 装甲が高い敵:単体高火力タワーの価値を高める
  • 複数に分裂する敵:範囲攻撃タワーの役割を作る
  • 飛行する敵:地上タワーが効かないルートを発生させる

種類を増やしすぎると管理コストが増えるだけでなく、プレイヤーが戦略を立てにくくなります。最初の完成版は3〜4種類で十分です。

ウェーブ間の「猶予」を設計する

ウェーブとウェーブの間にある準備時間は、単なる休憩ではありません。プレイヤーが資源を使ってタワーを強化・増設できる機会であり、この時間の長さと資源の量がゲームのリズムを作ります。

猶予時間が短すぎると、プレイヤーは常に焦りを感じて戦略を楽しめません。長すぎると間延びします。目安として、ウェーブが長くなるほど次の猶予も少し長くする比例設計が自然です。また、「いますぐ開始」ボタンを設けてプレイヤーが猶予を短縮できるようにするとテンポが改善します。この機能は実装コストが低いわりに体験への影響が大きいです。

資源の設計については、1ウェーブで得られる資源量と、それで買えるタワーの数・強化回数のバランスを先に決めてから難易度調整に入ることをお勧めします。逆順に作ると調整のたびに全体が崩れます。

経路と波をつなぐデバッグの方法

完成前に動作を確認する手段として、デバッグ表示を実装に組み込んでおくと後で大幅に時間が節約できます。

コストマップは各セルに数値を重ねて表示するだけで、経路が正しく計算されているかが一目でわかります。Canvas APIならfillText()でセルに数字を描くだけです。敵の実際の移動経路をラインで描画する機能も加えると、「特定のウェーブで敵が変な動きをする」問題の原因特定が早くなります。

ウェーブのデバッグには、任意のウェーブから開始できるスキップ機能が有効です。ウェーブ20の問題を確認するために19ウェーブ待つのはバランス調整のコストを極端に上げます。開発中だけ使えるstartWave(20)のような関数を用意しておくだけで調整効率が変わります。

次の行動

経路探索はBFSをグリッドに実装してコストマップを作るところから始め、タワー設置時の経路封鎖チェックを最初に組み込んでおきましょう。ウェーブデータはJSONや配列として外部に切り出し、脅威値の合計をウェーブごとに管理すると難易度の調整が素直になります。完成したゲームはHTMLファイルまたはZIPファイルでアソバースに投稿して公開できます。

参考資料

  • Red Blob Games「Introduction to A\*」(redblobgames.com)
  • Red Blob Games「Breadth First Search」(redblobgames.com)
  • MDN Web Docs「Canvas API」