</> Code Your Own Game

Zero to playable ยท no installs ยท no build tools

Learn to code a game with nothing but a text file and a browser.

Ten short lessons take you from your very first <h1> to a finished arcade game you can play, share and keep improving. Nearly every lesson has a live editor โ€” change the code, hit Run, see it happen instantly.

  • 10 lessons
  • 10 live editors
  • 1 real game

Start here: the only setup you need

1

Make a file

Open any text editor โ€” Notepad, TextEdit (set it to plain text), or VS Code if you want colours. Save an empty file as game.html. The .html ending is what matters.

2

Paste the skeleton

Every page starts the same way. Type the block in lesson 1 once by hand โ€” muscle memory beats copy-paste for the first one.

3

Double-click it

Your browser opens the file. That's it โ€” you are running your own program. Edit, save, press F5 to reload. That loop is the whole job.

4

Open the console

Press F12 (or Cmd+Option+I) and click Console. Red text there tells you exactly which line broke. Check it every single time something doesn't work.

Everything on this page runs in the little editors below too, so you can learn first and set up your editor later.

The lessons

Work top to bottom. Each one adds a piece of the game you'll play at the bottom of this page.

01

The page skeleton

HTML is a set of tags that wrap content: <tag>content</tag>. A tag plus its content is an element. Elements nest inside each other like boxes. Here is the shape of every page you will ever write:

  • <!DOCTYPE html> โ€” tells the browser "modern HTML".
  • <head> โ€” invisible setup: title, styles, metadata.
  • <body> โ€” everything people actually see.
  • <script> โ€” your JavaScript, the part that makes things move.

Try it: change the <h1> text, add a second <p>, then press Run.

02

The tags worth memorising

There are about a hundred HTML tags. You need roughly twelve. These are them:

TagWhat it's for
<h1>โ€“<h6>Headings, biggest to smallest. One h1 per page.
<p>A paragraph of text.
<div>A generic box. Your main layout tool.
<span>A generic bit of inline text, e.g. a score number.
<button>Something clickable. Use this, not a clickable div.
<img src="โ€ฆ" alt="โ€ฆ">A picture. alt describes it for screen readers.
<canvas>A blank rectangle you draw pixels on. The game lives here.
<audio>Sound effects and music.
<a href="โ€ฆ">A link.
<ul> / <li>A list โ€” great for high-score tables.
<input>A text box, slider, checkboxโ€ฆ
<style> / <script>CSS and JavaScript, inline in the page.

Tags carry attributes โ€” extra settings inside the opening tag. The two you'll use constantly are id (a unique name so JavaScript can find the element) and class (a label so CSS can style many elements at once).

03

CSS: making it not look like 1996

CSS styles your HTML. A rule is a selector plus declarations:

h1        { color: tomato; }      /* every h1        */
.card     { padding: 16px; }      /* class="card"    */
#score    { font-weight: 700; }   /* id="score"      */

For games you mostly need: background, color, padding, margin, border, border-radius, display:flex for laying things in a row, and position for sticking a score on top of the canvas.

04

JavaScript in five minutes

HTML is the nouns, CSS is the adjectives, JavaScript is the verbs. You need these six ideas and almost nothing else:

IdeaLooks likeMeans
Variablelet x = 10;A named box holding a value. const if it never changes.
Functionfunction jump() { โ€ฆ }A named chunk of steps you can run later.
Conditionif (y > 300) { โ€ฆ }Do this only when that is true.
Loopfor (let i = 0; i < 5; i++)Repeat something a set number of times.
Arraylet enemies = [];A numbered list. enemies.push(e) adds, enemies[0] reads.
Object{ x: 10, y: 20, hp: 3 }A labelled bundle. Every game entity is one of these.

Try it: add a fourth enemy to the array and run it again. Nothing else needs to change โ€” that's why we use arrays.

05

Canvas: your drawing surface

A <canvas> is an empty rectangle. You get a context from it and issue drawing commands. Coordinates start at 0,0 in the top-left, and y grows downwards โ€” the single most common beginner surprise.

  • ctx.fillStyle = '#f00' โ€” set the colour for the next shape.
  • ctx.fillRect(x, y, w, h) โ€” filled rectangle. 90% of your drawing.
  • ctx.clearRect(0, 0, W, H) โ€” wipe the whole canvas.
  • ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI*2); ctx.fill() โ€” a circle.
  • ctx.fillText('Score', x, y) โ€” text, after setting ctx.font.
  • ctx.drawImage(img, x, y) โ€” a sprite.

Try it: move the hero by changing 40, 120. Make the sun bigger by changing the radius 24.

06

The game loop

A game is one function run ~60 times a second. Each pass is a frame, and each frame does three things in order:

  1. Update โ€” move everything, apply gravity, check collisions.
  2. Draw โ€” clear the canvas, redraw everything in its new position.
  3. Repeat โ€” ask the browser for the next frame.

requestAnimationFrame(loop) is how you ask. Never use setInterval for this; requestAnimationFrame syncs to the screen and pauses in background tabs.

Multiply movement by delta time (seconds since the last frame) so the game runs at the same speed on a 60 Hz laptop and a 144 Hz monitor.

07

Input: keyboard, mouse, touch

The trick nobody tells beginners: don't move the player inside the key event. Key events fire irregularly. Instead record which keys are currently held in an object, and read that object in the loop.

Add e.preventDefault() in keydown for arrows and space, or the page will scroll while you play.

08

Collisions, gravity and jumping

Two rectangles overlap when they overlap on both axes. This four-line check ("AABB") covers almost every 2D game:

function hits(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;
}

Gravity is just a constant added to vertical speed every frame. Jumping is setting that speed to a negative number once, while standing on the ground.

09

Sprites, sound and game states

Sprites. Load an image once, draw it every frame. Wait for onload before drawing, or you get nothing.

const hero = new Image();
hero.src = 'hero.png';
// in draw():  ctx.drawImage(hero, p.x, p.y, p.w, p.h);

For an animation strip, draw one slice of the sheet: ctx.drawImage(sheet, frame*32, 0, 32, 32, p.x, p.y, 32, 32) and advance frame every few hundredths of a second.

Sound. Browsers block audio until the player interacts, so start sound on the first click or keypress.

const coinSfx = new Audio('coin.wav');
function play(a) { a.currentTime = 0; a.play().catch(() => {}); }

States. A game is never only "playing". Keep one variable and branch on it โ€” this is what stops your code turning into spaghetti.

10

Saving progress & tidy structure

localStorage keeps small values on the player's machine, forever, with no server. Values are always strings, so wrap numbers with Number() and objects with JSON.stringify / JSON.parse.

Once a file passes ~300 lines, split it: index.html for structure, style.css for looks, game.js for logic. Link them with <link rel="stylesheet" href="style.css"> and <script src="game.js"></script> placed just before </body>.

A structure that scales to a real game:

const game = {
  state: 'menu',
  player: { โ€ฆ },
  enemies: [],
  score: 0,
};

function update(dt) { /* move + collide, branch on game.state */ }
function draw()      { /* clear + render, branch on game.state */ }
function reset()     { /* put game back to a fresh run        */ }
function loop(now)   { update(dt); draw(); requestAnimationFrame(loop); }

The finished game

Everything from the ten lessons, assembled. Arrow keys or A/D to move, Space to jump, or tap the two buttons on a phone. Collect coins, dodge the red blocks, beat your high score.

Making changes & updating your game

Shipping version 1 is the easy half. Here's the workflow that keeps version 12 from collapsing.

Change one thing, reload, look

Edit a single value, save, press F5. If it broke, you know exactly what did it. Changing five things at once and then debugging is how evenings disappear.

Put the knobs at the top

Keep tunable numbers together as named constants โ€” GRAVITY, JUMP, SPAWN_RATE, PLAYER_SPEED. Balancing then means editing a tidy list, not hunting through logic.

Data, not code

Describe levels and enemy types as arrays of objects. Adding content becomes adding a line of data: { type:'bat', hp:2, speed:120 }.

Keep a working copy

Before a risky change, duplicate the file as game-v3.html. Better: learn git init, git add ., git commit -m "added bats" โ€” then every version is recoverable.

Hard-refresh after deploying

Browsers cache aggressively. If an update "didn't apply", press Ctrl/Cmd+Shift+R before you start debugging code that's already correct.

Debug with the console

console.log('player', p.x, p.y) inside the loop prints every frame. Red errors name the file and line. debugger; freezes execution so you can inspect everything.

The five bugs that get everyone

SymptomCauseFix
Blank canvasScript ran before the canvas existedPut <script> just before </body>, or use defer
Everything smears into trailsYou never cleared the canvasctx.clearRect(0,0,W,H) at the top of draw()
Player teleports / sprintsrequestAnimationFrame called twice, so two loops runStart the loop exactly once
Speed differs per computerMovement not multiplied by delta timex += speed * dt
Blurry or stretched canvasSized with CSS instead of attributesSet width/height attributes; use CSS only to scale

Cheat sheet

Page skeleton

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport"
        content="width=device-width,
                 initial-scale=1">
  <title>My Game</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <canvas id="game" width="640"
          height="360"></canvas>
  <script src="game.js"></script>
</body>
</html>

Canvas commands

const ctx = canvas.getContext('2d');
ctx.fillStyle   = '#3ddc97';
ctx.strokeStyle = '#fff';
ctx.lineWidth   = 2;
ctx.fillRect(x, y, w, h);
ctx.strokeRect(x, y, w, h);
ctx.clearRect(0, 0, W, H);
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
ctx.font = 'bold 20px system-ui';
ctx.textAlign = 'center';
ctx.fillText('Score', x, y);
ctx.drawImage(img, x, y, w, h);
ctx.save(); ctx.translate(x, y);
ctx.rotate(angle); ctx.restore();

Maths you'll reuse

Math.random()            // 0โ€“1
Math.floor(Math.random()*n)
Math.max(lo, Math.min(hi, v)) // clamp
Math.hypot(bx-ax, by-ay)      // distance
Math.sin(t), Math.cos(t)      // waves
Math.PI * 2                   // full turn
Math.round(v), Math.abs(v)

Events

addEventListener('keydown', fn);
addEventListener('keyup', fn);
el.addEventListener('click', fn);
el.addEventListener('pointerdown', fn);
addEventListener('resize', fn);
// e.key  'ArrowLeft' 'a' ' '
// e.code 'Space' 'KeyA' 'ArrowUp'

Finding & changing elements

const el = document.getElementById('hud');
const b  = document.querySelector('.btn');
el.textContent = 'Score: ' + score;
el.classList.add('hidden');
el.classList.toggle('paused');
el.style.display = 'none';

Where to go next

Your next three evenings

  1. Copy the lesson 8 editor into a real game.html file and get it running from your own desktop.
  2. Add one feature of your own: a second coin type, a double jump, a timer, a pause key.
  3. Give it a title screen and a high score, then send the file to one person. Watching someone else play teaches you more than another tutorial.
Built with KickStartMVP ♥