Game Development
Interactive games built with HTML5 Canvas and JavaScript
Space Invaders
How to Play
Move with ←/→ (or A/D), shoot with Space. Press Enter to start or restart.
Coding Concepts Used
- Variables & constants for game state (score, lives, level, speeds).
- Functions for drawing, updating, spawning, and collision checks.
- Objects & ES6 classes (
Player,Invader,Bullet) to encapsulate data + behavior. - Arrays for managing bullets and enemies, including filtering and mapping.
- Event listeners for keyboard input; a keys map for pressed/released state.
- Game loop using
requestAnimationFramewith delta time for smooth motion. - Collision detection with axis-aligned bounding boxes (AABB).
- State machine-style flags (running, paused, gameOver, level).
- Math for movement, grid stepping, descent, and speed ramp by level.
- Canvas 2D API for rendering shapes, text, and a starfield background.
- Timing via simple cooldowns to limit fire rate and invader step rate.
- Immutability patterns (e.g., filtering arrays) to remove off-screen or hit entities.
- Encapsulation & composition to keep code modular and readable.
Space Invaders Coding Concepts — Student Worksheet (line‑by‑line explained)
Game Summary
You will explore a simple Space Invaders game coded in HTML, CSS, and JavaScript. The aim is to link programming concepts to actual examples in the game.
Every code line below has a plain‑English comment at the end of the line.
1. Variables & Constants
Definition: Store data that can change (variables) or stay the same (constants).
Taken from the game:
const W = 560, H = 640; // W and H are the canvas width and height
const PLAYER_SPEED = 320; // how fast the player moves (pixels per second)
const BULLET_SPEED = 520; // how fast bullets move (pixels per second)
const INVADER_STEP_X = 18; // how far invaders move sideways each step
const INVADER_STEP_Y = 24; // how far invaders drop down on a bounce
const INVADER_BASE_RATE = 0.65; // time between invader steps at level 1 (in seconds)
const FIRE_COOLDOWN = 220; // time between player shots (in milliseconds)
const state = { // one object holding the whole game state
running: false, // is the game currently running?
gameOver: false, // has the player lost?
level: 1, // current level number
score: 0, // player score
invaders: [], // list of enemy invaders
bullets: [], // list of all bullets (player and enemy)
invaderDir: 1, // direction invaders move: 1 right, -1 left
invaderTimer: 0, // countdown until the next invader step
invaderRate: INVADER_BASE_RATE, // current step rate (can get faster)
stars: Array.from({length: 120}, () => ({ // make 120 random stars for the background
x: Math.random()*W, // random x position on screen
y: Math.random()*H, // random y position on screen
s: Math.random()*2+0.5 // random star size
}))
};
2. Functions
Definition: Reusable blocks of code that perform a specific task.
Taken from the game (level setup):
function resetLevel(level = 1) { // set up a level (default to level 1)
state.level = level; // store the level number
state.invaders = []; // clear any old invaders
const cols = 10, rows = 5; // how many invaders across and down
const startX = 40, startY = 80, spacingX = 46, spacingY = 36; // grid layout numbers
for (let r = 0; r < rows; r++) { // loop over each row
for (let c = 0; c < cols; c++) { // loop over each column
state.invaders.push( // add an invader to the list
new Invader(startX + c*spacingX, // x position based on column
startY + r*spacingY, // y position based on row
r) // store the row index (used for colour/score)
);
}
}
state.invaderDir = 1; // start moving to the right
state.invaderRate = Math.max(0.20, // never go faster than 0.20s per step
INVADER_BASE_RATE * (1 - (level-1)*0.12) // higher levels step faster
);
state.invaderTimer = state.invaderRate; // reset the step timer
state.bullets = []; // clear any bullets
player.x = W/2 - player.w/2; // centre the player horizontally
player.y = H - 70; // set the player near the bottom
player.cooldown = 0; // allow immediate shooting
player.invincibleTimer = 0; // remove any temporary invincibility
}
3. Objects & ES6 Classes
Definition: Structures for grouping data and related functions.
Taken from the game (Player class):
class Player extends Entity { // Player is a type of Entity (inherits size/position)
constructor(){
super(W/2-18, H-70, 36, 18); // call Entity with starting x,y and width,height
this.cooldown = 0; // time until the player can shoot again (ms)
this.lives = 3; // number of lives
this.invincibleTimer = 0; // short time after hit where player can't be hit
}
update(dt){ // update player each frame (dt = time since last frame in seconds)
const left = keys.get('arrowleft') || keys.get('a'); // is left key held?
const right = keys.get('arrowright') || keys.get('d'); // is right key held?
let dx = (right?1:0) - (left?1:0); // convert keys to direction: -1 left, 0 still, 1 right
this.x = clamp(this.x + dx * PLAYER_SPEED * dt, 8, W - this.w - 8); // move and keep inside screen
this.cooldown = Math.max(0, this.cooldown - dt*1000); // count down the shoot cooldown
this.invincibleTimer = Math.max(0, this.invincibleTimer - dt*1000); // count down invincibility
}
canFire(){ return this.cooldown <= 0; } // player can shoot if cooldown finished
fire(){ // create a new bullet from the player
this.cooldown = FIRE_COOLDOWN; // reset the cooldown
return new Bullet(this.x + this.w/2 - 2, // bullet x starts at centre of the ship
this.y - 10, // bullet y starts just above the ship
-1); // -1 means bullet travels upwards
}
}
4. Arrays
Definition: Lists that hold multiple values.
Taken from the game (managing entities):
state.invaders.push( // add a new invader to the list
new Invader(startX + c*spacingX, // x position for this column
startY + r*spacingY, // y position for this row
r) // store the row index
);
state.bullets = state.bullets.filter(b => // replace bullets with a filtered list
!b.offscreen() // keep only bullets still on screen
); // (removes bullets that went off the top/bottom)
5. Event Listeners
Definition: Code that reacts when something happens (like a key press).
Taken from the game (keyboard handling):
const keys = new Map(); // a map to remember which keys are pressed
addEventListener('keydown', (e) => { // when any key is pressed down
if (["ArrowLeft","ArrowRight"," ","Space","Enter","a","d","A","D"].includes(e.key)) // if it's a game key
e.preventDefault(); // stop the page from scrolling etc.
keys.set(e.key.toLowerCase(), true); // remember this key is now pressed
});
addEventListener('keyup', (e) => // when any key is released
keys.set(e.key.toLowerCase(), false) // remember this key is no longer pressed
);
addEventListener('keydown', (e) => { // when a key is pressed
if (e.key === 'Enter'){ // if the key was Enter
if (!state.running) { restartGame(); } // start (or restart) the game if it's not running
}
});
6. Game Loop
Definition: A loop that keeps updating and drawing the game every frame.
Taken from the game:
let last = performance.now(); // remember the time of the previous frame
function tick(){ // one frame of the game
const t = performance.now(); // current time (ms)
const dt = Math.min(0.033, (t - last) / 1000); // seconds since last frame, capped for stability
last = t; // update last time to now
drawStarfield(dt); // draw the moving star background
if (state.running){ // only update gameplay if running
player.update(dt); // move the player based on keys
const shoot = keys.get(' ') || keys.get('space'); // is spacebar held?
const bulletCount = state.bullets.filter(b=>b.dir<0).length; // how many player bullets exist
if (shoot && player.canFire() && bulletCount < MAX_PLAYER_BULLETS){ // allowed to fire now?
state.bullets.push(player.fire()); // create and store a new bullet
}
updateInvaders(dt); // move enemies and maybe shoot
state.bullets.forEach(b=>b.update(dt)); // move all bullets
// collisions & drawing happen elsewhere (see sections 7 and 10) // info comment
} else {
overlay.classList.remove('hidden'); // show the start/game over message
}
requestAnimationFrame(tick); // ask the browser to call tick() again next frame
}
7. Collision Detection
Definition: Checking if two objects overlap.
Taken from the game:
class Entity { // base class for anything with a box shape
constructor(x, y, w, h) { this.x=x; this.y=y; this.w=w; this.h=h; } // store position and size
get left(){return this.x} // left side x value
get right(){return this.x+this.w} // right side x value
get top(){return this.y} // top y value
get bottom(){return this.y+this.h} // bottom y value
intersects(o){ // do two boxes overlap?
return !(this.right < o.left // no overlap if this is completely left of other
|| this.left > o.right // or completely right of other
|| this.bottom < o.top // or completely above other
|| this.top > o.bottom); // or completely below other
}
}
for (const b of state.bullets){ // check each bullet
if (b.dir < 0){ // only player bullets (they move up)
for (const inv of state.invaders){ // check against each invader
if (inv.alive && b.intersects(inv)){ // if invader is alive and bullet hits it
inv.alive = false; // mark invader as destroyed
b.y = -99; // move bullet off-screen so it will be removed
state.score += inv.value + state.level*2; // increase the score for this hit
}
}
}
}
8. State Machine‑style Flags
Definition: Variables that control which part of the game is running.
Taken from the game:
function restartGame(){ // start a new run
state.running = true; // the game is now running
state.gameOver = false; // clear game over flag
state.score = 0; // reset score
player.lives = 3; // give the player 3 lives
resetLevel(1); // build level 1 enemies
overlay.classList.add('hidden'); // hide the overlay message
}
if (!state.running){ // if the game is not running
overlay.querySelector('h2').textContent = // set the big title text
state.gameOver ? 'Game Over' : 'Defend Earth!'; // show different text if lost or not
overlay.querySelector('.badge').textContent = // set the small badge text
state.gameOver ? 'Press Enter to Restart' : 'Press Enter to Play'; // what the player should do
overlay.classList.remove('hidden'); // make sure overlay is visible
}
9. Math
Definition: Using calculations for movement, positioning, and scaling.
Taken from the game:
const clamp = (v, min, max) => Math.min(max, Math.max(min, v)); // keep a value between min and max
this.x = clamp( // set the player's x position to a safe value
this.x + dx * PLAYER_SPEED * dt, // new position based on speed, direction, and time
8, // don't go beyond 8 pixels from the left edge
W - this.w - 8 // don't go beyond 8 pixels from the right edge
); // end of clamp
10. Canvas 2D API
Definition: Commands for drawing shapes, text, and images.
Taken from the game:
g.fillStyle = '#9be564'; // choose a green colour for the ship
g.fillRect(this.x, this.y, this.w, 6); // draw the bottom part of the ship
g.fillRect(this.x+6, this.y-6, this.w-12, 6); // draw the middle part (smaller)
g.fillRect(this.x+12, this.y-12, this.w-24, 6); // draw the top part (even smaller)
g.fillStyle = '#e6f1ff'; // choose a light colour for text
g.font = '14px ui-monospace, Menlo, monospace'; // choose the text font and size
g.fillText(`Score: ${state.score}`, 12, 20); // draw the score text at the top-left
11. Timing
Definition: Controlling when events happen (cooldowns, step timers).
Taken from the game:
player.cooldown = Math.max(0, player.cooldown - dt*1000); // tick down the shoot cooldown (ms)
if (shoot && player.canFire()){ // if space is pressed and cooldown finished
state.bullets.push(player.fire()); // create a new bullet and add it to the list
}
state.invaderTimer -= dt; // subtract time from the invader step timer (s)
if (state.invaderTimer <= 0){ // if it reached zero or less
state.invaderTimer = state.invaderRate; // reset the timer to the current rate
// here we move invaders sideways, and drop down if they hit the edge // info comment
}
12. Immutability Patterns
Definition: Creating new arrays instead of changing them directly.
Taken from the game:
state.bullets = state.bullets.filter(b => // replace bullets with a filtered version
!b.offscreen() // only keep bullets that are still visible
); // result: removed off‑screen bullets
const live = state.invaders.filter(v => v.alive); // make a list of invaders that are still alive
13. Encapsulation & Composition
Definition: Keeping related code together and building bigger features from smaller ones.
Taken from the game:
class Bullet extends Entity { // Bullet is also an Entity (has a box)
constructor(x,y,dir){ // build a bullet at x,y with a direction
super(x,y,4,12); // bullet has a width of 4 and height of 12
this.dir = dir; // direction: -1 up, +1 down
}
update(dt){ // move the bullet each frame
this.y += this.dir * BULLET_SPEED * dt; // change its y based on direction and speed
}
}
fire(){ // a method inside Player that creates bullets
this.cooldown = FIRE_COOLDOWN; // set shoot delay before next shot
return new Bullet( // return a brand new Bullet object
this.x + this.w/2 - 2, // start at the centre of the ship (x)
this.y - 10, // start just above the ship (y)
-1 // this bullet will travel upwards
);
}
Your Turn — Challenge Task
Open the game code. For each concept above, find another example and rewrite it with a simple end‑of‑line comment.
Extra Credit
Suggest one improvement to the game (e.g., sound effects, more levels, new enemy types) and list which coding concepts you'd use to build it (e.g., arrays for multiple sounds, timing for cooldowns, classes for new enemies).