How to Make a Tower Defense Game — Build a Browser Game with JavaScript

Play this game → Full source code →

What You'll Learn in This Tutorial

Tower defense is a strategy game where enemies march along a set path and you stop them with towers you place on the map. You'll build the system that moves enemies along a route, the AI that lets towers automatically attack enemies in range, and the wave manager that makes enemies stronger over time. It's packed with fun game programming techniques.

Set Up the HTML and Canvas

Tower defense uses a canvas to draw the map, towers, enemies, and bullets in real time. Use requestAnimationFrame to run a game loop that updates and redraws everything every frame.

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();  // Update the game state
  draw();    // Draw the screen
  if (gameRunning) {
    animFrameId = requestAnimationFrame(gameLoop);
  }
}

We calculate the width and height of each cell from the number of columns and rows in the map. The gameLoop function runs every frame, updating and drawing over and over.

Build the Map and Enemy Path

Define the map with a 2D array

The map is defined as a 2D array. A 0 is empty ground (where you can place towers), a 1 is the road (where enemies walk), a 2 is the start, and a 3 is the goal. The route enemies follow is defined ahead of time as an array of coordinates.

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]
];

// The route enemies follow (a list of coordinates)
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},
  // ... and so on
  {r:8,c:2}  // the goal
];

Enemies move by following the points in the PATH array one at a time. Calculate the direction from the current position to the next point so they move smoothly.

Moving the enemies

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
  };
}

// Move each enemy inside the update loop
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;
}

We calculate the distance and direction to the next point, then move the enemy a little closer based on its speed. Once it gets close enough to the point, we switch to the next one.

Add Tower AI and Shooting

Towers attack automatically when an enemy comes into range. If there are several enemies, target the one that has traveled farthest along the path (the one closest to the goal). Each tower type has a different range, damage, and fire rate.

var TOWER_TYPES = {
  normal: { cost: 20, range: 2.2, damage: 1,
            fireRate: 50, color: '#3498DB', name: 'Normal' },
  fast:   { cost: 35, range: 1.8, damage: 0.6,
            fireRate: 18, color: '#2ECC71', name: 'Fast' },
  strong: { cost: 50, range: 2.5, damage: 3.5,
            fireRate: 90, color: '#E74C3C', name: 'Strong' }
};

// Tower attack logic (runs every frame)
for (var t = 0; t < towers.length; t++) {
  var tower = towers[t];
  tower.cooldown--;

  // Find the enemy in range that is farthest along the path
  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
    });
  }
}

The cooldown controls the time between attacks. An enemy with a higher pathIndex is closer to the goal, so it gets attacked first. Bullets chase their target every frame and deal damage when they hit.

Manage Enemy Waves

Enemies appear in groups called "waves." As the waves go on, the enemies get more numerous and tougher, making the game harder. Sending out a boss enemy every 5 waves adds even more strategy.

function startWave() {
  waveInProgress = true;
  enemiesToSpawn = ENEMIES_PER_WAVE_BASE + Math.floor(wave * 1.5);
  spawnTimer = 0;
}

// Check for wave completion inside the update loop
if (waveInProgress && enemiesToSpawn === 0 && enemies.length === 0) {
  waveInProgress = false;
  wave++;
  waveDelay = 90; // Wait a little before the next wave
}

// Spawning a boss enemy
var isBoss = (wave % 5 === 0) && enemiesToSpawn === 1;
if (isBoss) {
  hp *= 5;     // 5 times the HP
  speed *= 0.6; // Moves slower
}

When you defeat every enemy in the current wave, there's a short break before the next wave begins. The wave * 1.5 formula gradually increases the number of enemies, and the boss every 5 waves keeps the game exciting.

Wrap-Up — Next Steps

In this tutorial you learned how to define a map and path, make enemies follow a route, write tower AI attack logic, and manage waves. To take it further, try adding tower upgrades, new maps, splash damage (area attacks), slow effects (making enemies move slower), or even a map editor.

Frequently Asked Questions

Q: Can I build this as a programming beginner?

A: Yes, absolutely. This tutorial walks you through the code step by step, so even if it's your first time, you can finish it by following along in order.

Q: How long does it take?

A: You can build the basics in about 30 minutes to an hour. Polishing the visuals or adding features makes it even more fun to expand.