First Prototype

game design — today we build

$ make game

REMEMBER THIS?

Today we build it

ACTION FEEDBACK REWARD REPEAT

Pick your tool

find all tutorials at tutorials.hackenshaw.com

Why Scratch?

blocksno syntax errors — all your attention goes to the logic
instantclick the green flag, see it run
made for gamessprites, sounds, collisions built in
freeruns in the browser, nothing to install

Almost all game developers started with Scratch.

Why Godot?

a real engineused for released, commercial games
free, open sourceno licence, no fees, no account
smallone download, no installer, runs on any laptop
GDScriptreads like Python, made for games
everywhereexport to Windows, Mac, Linux, web, phones

The engine takes care of the boring parts.

Why the web?

everywhereevery phone and laptop has a browser
nothing hiddenno engine — every line is yours to read
two toolsa text editor and a browser, that's all
share itsend a link, it just runs

You have full control over everything.

Scratch

The Scratch editor with four areas outlined: 1 blocks, 2 scripts, 3 stage, 4 sprites.

Godot

The Godot editor with five areas outlined: 1 scene tree, 2 the 2D view, 3 the inspector, 4 files, 5 run.

VS Code

VS Code with the catch folder open: 1 the files, index.html, style.css and game.js; 2 game.js in the editor.

plus a browser: save, reload, play

Same concepts, different implementation

ScratchGodotWeb
loopforeverfunc _process(delta)requestAnimationFrame(loop)
conditionif <touching Bowl?>area_enteredif (caught)
variablescorevar score := 0let score = 0
LIVE DEMO

The loop, one line at a time

bowl.xapple.y
scoreloops
LIVE DEMO

Let's build it

  1. Three files
  2. Some style
  3. Draw the player
  4. The game loop
  5. Again, and again
  6. Take control
  7. Catch it
The finished web game: a paddle, a falling apple, score 3.

Every step, written out: tutorials.hackenshaw.com/html-css-js

LIVE DEMO · STEP 1 / 7

Three files

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Catch!</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>Catch!</h1>
  <canvas id="game" width="480" height="480"></canvas>
  <script src="game.js"></script>
</body>
</html>

step 1: a heading, and an invisible canvas

LIVE DEMO · STEP 2 / 7

Some style

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: #111;
  color: white;
  font-family: monospace;
}

canvas {
  background: #1d2b53;
  border-radius: 12px;
}

step 2: a playing field

LIVE DEMO · STEP 3 / 7

Draw the player

// Find the canvas on the page, and get the "pen" we draw with.
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");

// The player: where it is and how big it is.
const player = { x: 200, y: 440, width: 80, height: 16 };

// Draw the player once, as an orange rectangle.
ctx.fillStyle = "#ffa300";
ctx.fillRect(player.x, player.y, player.width, player.height);

step 3: drawn once, never moves

LIVE DEMO · STEP 4 / 7

The game loop

const apple = { x: 240, y: 0, radius: 12, speed: 3 };

function update() {
  apple.y += apple.speed;
}

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // ...the player and the apple, as before
}

function loop() {
  update();
  draw();
  requestAnimationFrame(loop);
}

loop();

step 4: it falls… and keeps falling

Clear the canvas

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // ...then paint everything
}
function draw() {
  // no clearRect
  // ...then paint everything
}
LIVE DEMO · STEP 5 / 7

Again, and again

function resetApple() {
  apple.x = apple.radius + Math.random() * (canvas.width - apple.radius * 2);
  apple.y = -apple.radius;
}

// in update():
if (apple.y - apple.radius > canvas.height) {
  resetApple();
}

step 5: a new apple each time

LIVE DEMO · STEP 6 / 7

Take control

const keys = {};
document.addEventListener("keydown", (event) => { keys[event.key] = true; });
document.addEventListener("keyup", (event) => { keys[event.key] = false; });

// in update():
if (keys["ArrowLeft"]) player.x -= player.speed;
if (keys["ArrowRight"]) player.x += player.speed;
player.x = Math.max(0, Math.min(canvas.width - player.width, player.x));

step 6: click it, then ← →

Two ways to move

// on every key event
document.addEventListener("keydown", (event) => {
  if (event.key === "ArrowLeft") player.x -= 10;
});
// every frame, in update()
if (keys["ArrowLeft"]) {
  player.x -= player.speed;
}

click it, then hold ← or → · or push the stick

Frame-rate independent speed

// per frame: faster computer, faster game
apple.y += 3;
// per second: the same on every computer
// timestamp is in milliseconds; dt = 0.0167 at 60 fps
const dt = (timestamp - last) / 1000;
apple.y += 180 * dt;   // 180 pixels per second
LIVE DEMO · STEP 7 / 7

Catch it

let score = 0;

// in update():
const caught =
  apple.y + apple.radius >= player.y &&
  apple.y - apple.radius <= player.y + player.height &&
  apple.x >= player.x &&
  apple.x <= player.x + player.width;

if (caught) {
  score += 1;
  resetApple();
}

// in draw():
ctx.fillText("Score: " + score, 12, 30);

step 7: click it, then catch some

LIVE DEMO

Caught? Four tests

const caught =
1apple.y + apple.radius >= player.y &&
2apple.y - apple.radius <= player.y + player.height &&
3apple.x >= player.x &&
4apple.x <= player.x + player.width;
caught = false

drag the apple anywhere · click the stage, then ← → move the paddle

LIVE DEMO

Fixed: count the radius

const caught =
1apple.y + apple.radius >= player.y &&
2apple.y - apple.radius <= player.y + player.height &&
3apple.x + apple.radius >= player.x &&
4apple.x - apple.radius <= player.x + player.width;
caught = false

drag the apple anywhere · click the stage, then ← → move the paddle

LIVE DEMO

Exact: the nearest point

// clamp(v, lo, hi): v, kept between lo and hi
1const nx = clamp(apple.x, player.x, player.x + player.width);
2const ny = clamp(apple.y, player.y, player.y + player.height);
3const dx = apple.x - nx, dy = apple.y - ny;
4const caught = dx * dx + dy * dy <= apple.radius ** 2;
caught = false box test: false
box4 comparisonseasy to readwrong at the corners
exact5 comparisons, 3 multipliesright everywheremore to read

drag the apple onto a corner of the zone · click the stage, then ← → move the paddle

ACTIVITY

Your turn

Build the catching game. Pick a tool:

  • Scratch — never programmed before
  • Godot — want a real engine
  • Web — interested in web development

$ sleep 1200

20:00

20 min

scratch · godot · web

Debugging

webF12 → Console: the error, in red, with a line number
godotthe Output panel, same idea — and a warning triangle on the node
scratchis it inside the forever? is it on the right sprite?
everywherenames and capitals must match exactly

Test one thing at a time.

Works? Next feature. Doesn't? Fix it before you add more.

Prototype first

Prototype

the core mechanic works

is it fun?

quick to change

ugly is fine

Polish

graphics, sound, music

animation, juice

menus, UI

later

Ugly is okay. Boring isn't.

You built a game

  • Loops, conditions, variables — in any tool
  • Movement, collision, score
  • There's always more than one way

homework: two new features · or the same game in a second tool

$ next team workflows & git

QR code to playground.hackenshaw.com/join

playground.hackenshaw.com/join