JavaScript Game Examples

Small patterns that explain how playable browser games actually work.

Code patterns

Most browser games are built from a few honest systems repeated carefully.

A game can look mysterious from the outside, but the browser version usually comes down to a loop, input, state, collision, drawing and a few rules that decide victory or failure. Supagames has many small games, so the same mistakes appear again and again: a variable is undefined, the player spawns off a platform, the spacebar scrolls the page, a timer is too fast, an enemy looks dangerous but never deals damage, or a visible object does not match the collision data.

This page collects practical JavaScript patterns from those fixes. It is not a replacement for the full JavaScript game development course. It is a field guide: what to watch for when turning a playable idea into a stable browser page.

1. Keep update and draw separate

The most reliable canvas games separate game state from rendering. Update changes numbers: position, velocity, timers, health, score and flags. Draw reads those numbers and paints the frame. Mixing the two makes bugs harder to understand because drawing a sprite might accidentally change the game.

function update(dt) {
  player.vy += gravity * dt;
  player.x += player.vx * dt;
  player.y += player.vy * dt;
  spawnTimer -= dt;
  if (spawnTimer <= 0) spawnObstacle();
}

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawBackground();
  drawPlayer(player);
  obstacles.forEach(drawObstacle);
}

This pattern appears in arcade games, platformers and shooters. It also makes testing easier because you can run update logic without caring about visual style. When a game "does nothing," the first debugging question is whether the loop is calling update, draw or both.

2. Route input only when the game owns the key

Keyboard bugs are common in browser games because the page itself also uses keys. Space can scroll the page. Arrow keys can move the viewport. Textareas need spaces. If a global key handler blocks every keydown event, comment forms and submission forms become unusable. A game should prevent default browser behavior only for keys it actually owns, and only when the event target is not an input field.

function isTypingTarget(target) {
  return target && (
    target.tagName === "INPUT" ||
    target.tagName === "TEXTAREA" ||
    target.isContentEditable
  );
}

document.addEventListener("keydown", function(event) {
  if (isTypingTarget(event.target)) return;
  if (["Space", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.code)) {
    event.preventDefault();
    keys[event.code] = true;
  }
});

This is the same principle behind mobile buttons and joysticks: controls should support the game without hiding it or breaking the rest of the page.

3. Make collision data match what the player sees

One of the worst game bugs is standing in mid-air or falling through a visible platform. It usually happens when the camera scrolls but the collision coordinates stay in a different coordinate system. The fix is not glamorous: store world coordinates, convert to screen coordinates only while drawing, and use the same world positions for collision.

function worldToScreen(rect, cameraY) {
  return {
    x: rect.x,
    y: rect.y - cameraY,
    w: rect.w,
    h: rect.h
  };
}

function overlaps(a, b) {
  return a.x < b.x + b.w &&
    a.x + a.w > b.x &&
    a.y < b.y + b.h &&
    a.y + a.h > b.y;
}

Use the collision function on world rectangles. Use worldToScreen only for drawing. This small discipline prevents many platformer and runner problems.

4. Design the first five seconds

A game can technically load and still fail the player. If enemies instantly collide, orders appear too fast, gravity kills the player before they can react or a snake starts by driving into another snake, the problem is pacing. The first five seconds should introduce the core action safely. Later, the game can increase pressure.

Useful beginner settings include a spawn delay, a safe starting platform, slower first waves, visible countdowns and one objective at a time. Time management games especially need generous easy modes. "Hard" can be stressful, but "easy" should let a real person understand the interface before the first failure.

const difficulty = {
  easy: { orderTime: 42, spawnEvery: 8, mistakesAllowed: 4 },
  normal: { orderTime: 30, spawnEvery: 6, mistakesAllowed: 3 },
  hard: { orderTime: 22, spawnEvery: 4.5, mistakesAllowed: 2 }
};

The important part is not the exact numbers. It is the gap between them. If easy and hard feel identical, the selector is only decorative.

5. Use explicit objectives

When Supagames improved dungeon and action games, the biggest change was not only fixing console errors. It was adding reasons to play: named enemies, exits, floors, item goals, visible damage and objectives that fit the theme. A vampire game should ask you to stake vampires or break coffins. A robot bunker should involve drones, terminals or power cores. A generic "move and shoot" loop can be a prototype, but a published game needs identity.

const objectives = [
  { text: "Collect three silver keys", done: () => inventory.silverKeys >= 3 },
  { text: "Defeat the gate demon", done: () => !enemies.some(e => e.id === "gate-demon") },
  { text: "Step onto the exit sigil", done: () => overlaps(player, exit) }
];

This also helps testing. If the game has explicit objectives, a smoke test can check that an exit exists, enemies can damage the player and progress requires the intended action.

6. Test boring things automatically

Automated checks do not prove that a game is fun, but they catch many embarrassing failures. Supagames uses syntax checks and small scripts for issues like missing pages, broken search rendering, missing sitemap entries, invalid canonical URLs and specific game regressions. A simple Node script can inspect HTML before you push.

const html = fs.readFileSync("index.html", "utf8");
assert.ok(html.includes('href="learn/index.html"'));
assert.ok(html.includes('class="home-feature-strip"'));
assert.ok(!html.includes("raw-email@example.com"));

The best tests are not abstract. They come from real bugs: a search result rendered HTML entity code instead of an icon, a comment form could not type spaces, a game had a title but no playable objective, or a sitemap exposed duplicate index.html URLs. Every bug is a chance to add one small guardrail.

Where to go next

If you want a full path, start with the canvas loop lesson, then Flying Bird, mobile controls, collision detection and difficulty pacing. If you already have a game, read the review standards and submission rules before publishing. Good browser game code is not magic; it is careful state, fair timing and clear feedback.