壁を置くと敵の道が変わる防衛ゲームでは、敵ごとに毎回道を探さなくて済みます。 Codexで作った「最後の灯台」は、灯台から全床への距離を一度計算し、敵がその数字をたどる方式にしました。配置前には同じ盤面で、両方の侵入口から灯台へ届くかも調べます。
ここで作り方を説明するのは、7列×9行の盤面での経路計算と封鎖判定です。完成した画面や音までこのZIPで再現するものではありません。まず実際のゲームを遊ぶと、配置によって水色の進路が曲がる様子を確認できます。

C#サンプルで配置ごとの結果を確認する
.NET 10 SDKを入れ、経路計算と配置探索のサンプルZIPを展開します。LighthouseSample.csprojのある場所で実行してください。Unityは不要で、追加NuGetパッケージも使いません。
dotnet run --project LighthouseSample.csproj -c Release
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を使ってください。
敵は進むたびに現在位置の上下左右を見て、距離が最小の床を選びます。同じ距離なら「上、左、右、下」の順に決めました。この固定順がないと、同じ配置でも見た目の進路とテストの結果が揺れます。画面の進路線も敵と同じ距離場・同じ順で引き、表示と実際の移動を一致させています。

封鎖する壁は資材を使う前に拒否する
プレイヤーには二つの侵入口があります。候補マスを一時的に通行不可として灯台から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のブラウザにタッチ幅を再現したものです。機械的に勝てる配置があっても、進路の読みやすさや試行錯誤の気持ちよさを保証しません。次の改良では、その手触りを実際の操作で確かめます。