AIゲーム制作ラボ

Unityタワーディフェンスの経路計算の作り方:BFSと封鎖判定

Unityの防衛ゲーム「最後の灯台」で、障壁を置くたび敵の進路を変えるC#実装。灯台からのBFS距離場、両入口の封鎖判定、過熱する砲台を、実行できるサンプルと配置比較の結果で説明します。

Libra

公開 2026年9月29日

この記事の要点3項目

灯台からBFSで距離場を作る。 障壁を置いた後、敵は距離が小さくなる隣のマスへ進む。

残り2つの要点を読む
  • 設置前に両入口からの到達性を検査する。 道を完全に塞ぐ配置は資材を使わず拒否する。
  • 実際のCoreと配置探索のC#を配布。 10隻の第1波では砲台2基の合法配置1,223通り中362通りが生存した。
目次6章
検証した環境最終確認 2026年9月29日
  • ゲーム本体: Unity 6000.6.0f1 / WebGL / Codex制作
  • 配布サンプル: Windows / .NET SDK 10.0.401 / 追加NuGetパッケージなし

書いてあるのは、この環境で実際に試した結果です。ツールの仕様や料金は変わるので、重要な判断の前には公式の情報も確認してください。

壁を置くと敵の道が変わる防衛ゲームでは、敵ごとに毎回道を探さなくて済みます。 Codexで作った「最後の灯台」は、灯台から全床への距離を一度計算し、敵がその数字をたどる方式にしました。配置前には同じ盤面で、両方の侵入口から灯台へ届くかも調べます。

ここで作り方を説明するのは、7列×9行の盤面での経路計算と封鎖判定です。完成した画面や音までこのZIPで再現するものではありません。まず実際のゲームを遊ぶと、配置によって水色の進路が曲がる様子を確認できます。

最後の灯台の第1波。砲台2基と障壁1基を配置し、水色の敵の進路が障壁の右側へ折れる
障壁を置いた直後。水色は第1侵入口からの進路、琥珀色は後半に使う第2侵入口からの進路。画面上の線はCoreの距離場から描く。

C#サンプルで配置ごとの結果を確認する

.NET 10 SDKを入れ、経路計算と配置探索のサンプルZIPを展開します。LighthouseSample.csprojのある場所で実行してください。Unityは不要で、追加NuGetパッケージも使いません。

dotnet run --project LighthouseSample.csproj -c Release
配布ZIPを展開して実行した出力から抜粋成功
empty health=0 kills=0 leaks=5 ticks=731
two-cannon wins=362/1223
wall rescues=188

emptyは何も置かず5隻が灯台へ到達して敗北した結果です。362/1223は、初期資材10で合法に置ける砲台2基の組を列挙し、第1波10隻の後に灯台が残った数。順番を入れ替えただけの同じ組は一つと数えています。wall rescues=188は全組ではなく、敗北した組のうち撃退数が多い上位90組に障壁を追加し、敗北から生存に変わった配置数です。成功率と読まないため、母集団を分けました。

63

盤面のマス

7列×9行。岩と施設を含む

362 / 1,223

砲台2基で第1波を生存

合法配置・固定seedの列挙

188

障壁で敗北を生存へ

上位90の敗北配置から追加

灯台からBFSを広げ、敵は小さい数字へ進む

灯台を距離0として、上下左右の通れる床へ1、2、3と広げます。岩礁と建設物は通れません。幅優先探索(BFS)なので、各床を訪れるのは一度。今回の盤面は最大63マスで、接続は上下左右だけです。Defense.csのRebuildDistancesが共有の距離場を作ります。

distance[BeaconX, BeaconY] = 0;
queue[tail++] = BeaconY * Width + BeaconX;
while (head < tail)
{
    int cell = queue[head++], x = cell % Width, y = cell / Width;
    Visit(x + 1, y, distance[x, y] + 1, ref tail);
    Visit(x - 1, y, distance[x, y] + 1, ref tail);
    Visit(x, y + 1, distance[x, y] + 1, ref tail);
    Visit(x, y - 1, distance[x, y] + 1, ref tail);
}

これは実際のメソッドから抜き出した断片です。Visitは盤外・岩礁・建設物・訪問済みの床を除き、距離とキューを更新します。全体をコンパイルするにはZIPのDefense.csを使ってください。

敵は進むたびに現在位置の上下左右を見て、距離が最小の床を選びます。同じ距離なら「上、左、右、下」の順に決めました。この固定順がないと、同じ配置でも見た目の進路とテストの結果が揺れます。画面の進路線も敵と同じ距離場・同じ順で引き、表示と実際の移動を一致させています。

小艇と装甲船が進路線上を進み、砲台が攻撃する最後の灯台の戦闘画面
戦闘中の実画面。敵の位置は固定1/60秒で更新し、描画だけセル間を補間する。赤い小艇と琥珀色の装甲船を区別した。

封鎖する壁は資材を使う前に拒否する

プレイヤーには二つの侵入口があります。候補マスを一時的に通行不可として灯台からBFSを行い、どちらか一方でも到達できなくなれば拒否します。岩礁、侵入口、灯台、使用済みの床、資材不足も別々の理由として返します。拒否した操作は盤面も資材も変えません。画面のホバー表示はこのCheckPlacementを使うので、押せそうな色と実際の判定が食い違わないようにしました。

public bool TryPlace(int x, int y, Building building, out PlacementError error)
{
    error = CheckPlacement(x, y, building);
    if (error != PlacementError.None) return false;
    buildings[x, y] = building;
    RebuildDistances();
    Material -= Cost(building);
    return true;
}

合法な設置では、候補の到達性を確認するBFSと、設置後の距離場を確定するBFSを実行します。敵の数だけBFSをやり直すわけではありません。戦闘中の建設を禁止しているため、進行中の敵が新しい壁の中へ取り残されるケースも生じません。戦闘中に建てられるゲームへ拡張するなら、そのケースの処理を別途設計する必要があります。

第1波だけ勝てる調整では、全編が成立しなかった

症状
原因
対処

最初は12隻の第1波で、砲台2基の合法配置1,223組のうち14組しか生存しませんでした。10隻へ直した時点では第1波の生存が362組に増えましたが、2波へ砲台を足しても生存例がありませんでした。原因の一つは、波の切り替わりで過熱した砲台が冷えないことでした。

次波の開始時に砲台の熱をリセットし、各波を10・10・12隻、波間の資材+5・灯台耐久+1へ調整。すると2波まで生き残る砲台追加が1,651列、3波まで守り切る列が19,322列見つかりました。障壁を使って第1波の敗北を生存へ変えた代表配置からも、追加する砲台の位置によって251列が全3波に勝てます。これらは配置列の数であり、初見の人が勝てる割合ではありません。

何を検査し、何をまだ測っていないか

UnityのEditMode 13件では封鎖、資材、固定入力での再現、波間の冷却と3波の勝利を確認しました。PlayMode 1件では素材の読込、実際の配置、距離場の変化、進行と停止を確認。ローカルWebGLでもPCと375px幅で配置から3波の勝利まで操作しました。公開ビルドのサイズと起動時間はゲーム紹介ページに現行値を載せます。

人の初見難度とスマホ実機の処理速度は未評価です。 ここでの375px検査はPCのブラウザにタッチ幅を再現したものです。機械的に勝てる配置があっても、進路の読みやすさや試行錯誤の気持ちよさを保証しません。次の改良では、その手触りを実際の操作で確かめます。

よくある質問

よくある質問

壁で敵の道を完全に塞げますか?

塞げません。配置候補を障害物とみなして灯台からBFSを行い、二つの侵入口のどちらかに届かない場合は設置と資材消費を拒否します。

敵が動くたびBFSを実行しますか?

実行しません。建設時に共有距離場を作り直し、敵は隣接マスの距離を読むだけです。現在の作品は戦闘中の建設を許可していません。

1,223通り中362通りなら初見でも約30%の確率で勝てますか?

そうは言えません。数字は固定盤面・固定seedで機械的に列挙した合法な砲台2基の配置結果です。選び方に偏りがある人の初見難度は測っていません。

配布ZIPだけで完成ゲームを作れますか?

再現できるのは経路、設置、敵、砲台の熱と配置探索です。Unityの画面、画像、音、入力、WebGL設定は含みません。

出典

出典

  1. 01.NET 10.0 のダウンロードMicrosoft2026-09-29にWindows向けSDK 10.0.401の配布とSDKの用途を確認。サンプルの実行に必要。

この記事は役に立ちましたか?

関連記事