タワーディフェンスは、決められた経路を進む敵を、設置したタワーで迎え撃つ戦略ゲームです。マップ上の経路に沿って敵を移動させる仕組み、タワーが射程内の敵を自動で攻撃するAI、ウェーブごとに強くなる敵の管理など、ゲームプログラミングの面白いテクニックがたくさん登場します。
タワーディフェンスはCanvasを使ってマップ、タワー、敵、弾をリアルタイムに描画します。requestAnimationFrameでゲームループを回し、毎フレーム更新と描画を行いましょう。
var canvas = document.getElementById('game-canvas');
var ctx = canvas.getContext('2d');
var canvasW, canvasH, cellW, cellH;
function resizeCanvas() {
var wrapper = canvas.parentElement;
var rect = wrapper.getBoundingClientRect();
var dpr = window.devicePixelRatio || 1;
canvasW = rect.width;
canvasH = rect.height;
cellW = canvasW / MAP_COLS;
cellH = canvasH / MAP_ROWS;
canvas.width = canvasW * dpr;
canvas.height = canvasH * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
function gameLoop() {
update(); // ゲームの状態を更新
draw(); // 画面を描画
if (gameRunning) {
animFrameId = requestAnimationFrame(gameLoop);
}
}
マップの列数・行数からセルの幅と高さを計算します。gameLoop関数が毎フレーム呼ばれて、更新と描画を繰り返します。
マップは2次元配列で定義します。0が空地(タワーを置ける場所)、1が道(敵が通る場所)、2がスタート、3がゴールです。敵が通る経路は、座標の配列として事前に定義しておきます。
var MAP_COLS = 12;
var MAP_ROWS = 9;
var MAP = [
[0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0],
[0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
// 敵の通る経路(座標のリスト)
var PATH = [
{r:0,c:2}, {r:1,c:2}, {r:2,c:2}, {r:3,c:2},
{r:3,c:3}, {r:3,c:4}, {r:3,c:5}, {r:2,c:5},
// ... 続く
{r:8,c:2} // ゴール
];
敵はPATH配列のポイントを1つずつたどって移動します。現在の位置と次のポイントの方向を計算して、滑らかに動かしましょう。
function createEnemy() {
var hp = ENEMY_BASE_HP + wave * 1.5;
var speed = ENEMY_BASE_SPEED + wave * 0.001;
var start = cellCenter(PATH[0].r, PATH[0].c);
return {
x: start.x, y: start.y,
pathIndex: 0, hp: hp, maxHp: hp,
speed: speed, baseSpeed: speed
};
}
// 更新ループ内で敵を移動
var target = cellCenter(PATH[e.pathIndex + 1].r, PATH[e.pathIndex + 1].c);
var dx = target.x - e.x;
var dy = target.y - e.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < 2) {
e.pathIndex++;
} else {
e.x += (dx / d) * e.speed * cellW;
e.y += (dy / d) * e.speed * cellW;
}
次のポイントまでの距離と方向を計算し、速度に応じて少しずつ近づけます。ポイントに十分近づいたら次のポイントに切り替えます。
タワーは射程内に敵が入ると自動で攻撃します。複数の敵がいる場合は、経路を最も進んでいる敵(ゴールに近い敵)を優先的に狙いましょう。タワーの種類ごとに射程、ダメージ、攻撃速度が異なります。
var TOWER_TYPES = {
normal: { cost: 20, range: 2.2, damage: 1,
fireRate: 50, color: '#3498DB', name: 'ノーマル' },
fast: { cost: 35, range: 1.8, damage: 0.6,
fireRate: 18, color: '#2ECC71', name: 'はやい' },
strong: { cost: 50, range: 2.5, damage: 3.5,
fireRate: 90, color: '#E74C3C', name: 'つよい' }
};
// タワーの攻撃処理(毎フレーム実行)
for (var t = 0; t < towers.length; t++) {
var tower = towers[t];
tower.cooldown--;
// 射程内で最も進んだ敵を探す
var bestTarget = null;
var bestProgress = -1;
for (var ei = 0; ei < enemies.length; ei++) {
var en = enemies[ei];
var d = dist(tower.x, tower.y, en.x, en.y);
if (d <= tower.range && en.pathIndex > bestProgress) {
bestProgress = en.pathIndex;
bestTarget = en;
}
}
if (bestTarget && tower.cooldown <= 0) {
tower.cooldown = tower.fireRate;
bullets.push({
x: tower.x, y: tower.y,
target: bestTarget,
damage: tower.damage,
speed: 5
});
}
}
cooldownで攻撃の間隔を管理します。pathIndexが大きい敵ほどゴールに近いので優先して攻撃します。弾は毎フレーム敵を追いかけ、当たったらダメージを与えます。
敵は「ウェーブ」(波)ごとにまとまって出現します。ウェーブが進むにつれて敵の数とHPが増え、難しくなっていきます。5ウェーブごとにボス敵を出すと、より戦略性が増します。
function startWave() {
waveInProgress = true;
enemiesToSpawn = ENEMIES_PER_WAVE_BASE + Math.floor(wave * 1.5);
spawnTimer = 0;
}
// 更新ループ内でウェーブ完了をチェック
if (waveInProgress && enemiesToSpawn === 0 && enemies.length === 0) {
waveInProgress = false;
wave++;
waveDelay = 90; // 次のウェーブまで少し待つ
}
// ボス敵の生成
var isBoss = (wave % 5 === 0) && enemiesToSpawn === 1;
if (isBoss) {
hp *= 5; // HPが5倍
speed *= 0.6; // 移動が遅い
}
現在のウェーブの全敵を倒すと、少しの休憩を挟んで次のウェーブが始まります。wave * 1.5で敵の数を段階的に増やし、5ウェーブごとのボスがゲームにメリハリをつけます。
このチュートリアルでは、マップと経路の定義、敵の経路追従、タワーAIの攻撃ロジック、ウェーブ管理の仕組みを学びました。さらに発展させるなら、タワーのアップグレード機能、新しいマップの追加、スプラッシュダメージ(範囲攻撃)、スロウ効果(敵の減速)、マップエディタなどに挑戦してみましょう。
A: はい、大丈夫です。このチュートリアルではステップごとにコードを書いていくので、初めての方でも順番に進めれば完成できます。わからないところがあれば、ひなテックの教室で質問もできますよ。
A: 基本部分は約30分〜1時間で作れます。見た目をこだわったり機能を追加すると、さらに楽しく発展させられます。