Path 3

โœจ Creator

You're writing the game, not just playing it!

Write new logic from scratch. Understand how every frame works, then build your own features.

Ages 10โ€“11  ยท  4 tasks
1 2 3 4
โœจ Creator tip: These tasks are bigger than the Explorer and Builder ones. Take them one step at a time. If something breaks, undo your last change (Ctrl+Z) and try again โ€” breaking things is how programmers learn!
1

๐Ÿ” Trace the game loop

Before writing new code it helps to understand how the game ticks every frame. Follow this chain through the code โ€” no changes needed, just reading and exploring.

Open this file and find the loop() function
js/game.js
/**
 * The main game loop โ€” called ~60 times per second.
 * Creator Task 1: this is where the chain starts!
 */
function loop(t) {
  requestAnimationFrame(loop); // schedule next frame

  const dt = Math.min(50, t - last) / 1000; // time since last frame (seconds)
  last = t;

  if (state === 'playing') update(dt); // โ† step 1: move everything
  render();                              // โ† step 2: draw everything
}

Now follow the chain. Click into each file and read the function it calls:

  1. requestAnimationFrame(loop) โ€” the browser calls loop() roughly 60 times per second. Each call is one frame.
  2. dt (delta time) โ€” the number of seconds since the last frame (usually about 0.016). Every movement is multiplied by dt so the game runs at the same speed on fast and slow devices.
  3. update(dt) in js/game.js โ€” moves all the game objects. Look for where belly.update(dt, 0.6, planks) is called. Then open js/entities.js and read the update() method โ€” notice how this.vy += gravity makes Belly fall down, and this.y += this.vy moves her.
  4. render() in js/game.js โ€” calls all the Renderer drawing functions. Look for Renderer.drawBelly(...). Then open js/renderer.js and find drawBelly() to see how Belly is actually drawn.
๐Ÿ—บ๏ธ
Draw the chain on paper: requestAnimationFrame โ†’ loop โ†’ update โ†’ render โ†’ drawBelly. You now know how every frame of the game works!
๐Ÿ’ก
You just learned about: The game loop A game loop is the heartbeat of every video game โ€” even big ones like Minecraft. Every frame it does the same three things: check input, update positions, draw everything. Delta time (dt) keeps the game fair on any device.
2

๐Ÿ›’ Add a brand-new obstacle kind

You're going to add a "shopping trolley" obstacle to the game from scratch. This touches three files โ€” size, hitbox, and drawing.

Change 1 of 3 โ€” define the size and hitbox
js/entities.js
Add to Obstacle.SIZES (find the level 1 section)
static SIZES = {
  // Level 1 โ€” playground toys
  'toy-small': { w: 56, h: 56 },
  'dinosaur':  { w: 64, h: 80 },
    'shopping-trolley': { w: 90, h: 68 },  โ† add this!
  // ...
};
Add to OBSTACLE_HITBOX (find the level 1 section at the top)
const OBSTACLE_HITBOX = {
  // Level 1
  'toy-small': [0.10, 0.10, 0.80, 0.80],
    'shopping-trolley': [0.06, 0.15, 0.88, 0.80],  โ† add this!
  // ...
};
Change 2 of 3 โ€” add it to the spawn list
js/game.js
const kindsByLevel = {
  1: ['toy-small', 'toy-large', 'toy-ball',
     'bicycle', 'blocks', 'toy-car', 'dinosaur',
       'shopping-trolley',  โ† add this!
  ],
  // ...
};
Change 3 of 3 โ€” draw it on screen
js/renderer.js
Find the drawObstacle function and add a drawing block. Start with a simple rectangle โ€” you can make it fancy later!
// Creator Task 2: add your new obstacle drawing block here!
if (kind === 'shopping-trolley') {
  // Body of the trolley
  ctx.fillStyle = '#888';
  ctx.fillRect(x + w*0.05, y + h*0.25, w*0.90, h*0.55);
  // Wheels
  ctx.fillStyle = '#444';
  ctx.beginPath();
  ctx.arc(x + w*0.22, y + h*0.88, w*0.10, 0, Math.PI*2);
  ctx.arc(x + w*0.78, y + h*0.88, w*0.10, 0, Math.PI*2);
  ctx.fill();
}
๐ŸŽฎ
Refresh and play level 1 โ€” shopping trolleys will start appearing as obstacles!
๐Ÿ’ก
You just learned about: Classes and data tables A class groups data (the size) and behaviour (the hitbox) together for one kind of thing. The SIZES and OBSTACLE_HITBOX tables separate the data from the logic, which means adding a new kind is just adding one line to each table โ€” no big if/else chain needed.
3

๐Ÿ“ Write a new helper function

The codebase already has a function called aabb() that checks if two rectangles overlap. You're going to write a similar helper โ€” rectContainsPoint() โ€” that checks if a single point is inside a rectangle. Then you'll test it yourself in the browser console.

First, understand aabb() โ€” read this and think about the logic
js/entities.js
/**
 * Checks whether two rectangles overlap.
 * Returns true if they intersect.
 */
function aabb(a, b) {
  return (
    a.x         < b.x + b.w  // a's left edge is left of b's right edge
    && a.x + a.w > b.x        // a's right edge is right of b's left edge
    && a.y         < b.y + b.h  // a's top is above b's bottom
    && a.y + a.h > b.y          // a's bottom is below b's top
  );
}
Now write rectContainsPoint โ€” a point is just a rectangle with zero size!
Add this function just above aabb() in entities.js
/**
 * Checks whether a point lies inside a rectangle.
 * @param {{ x, y, w, h }} rect - The rectangle.
 * @param {number} px - Point x position.
 * @param {number} py - Point y position.
 * @returns {boolean}
 */
function rectContainsPoint(rect, px, py) {
  return (
    px >= rect.x              // point is right of left edge
    && px <= rect.x + rect.w  // point is left of right edge
    && py >= rect.y              // point is below top edge
    && py <= rect.y + rect.h   // point is above bottom edge
  );
}
Add it to the exported object at the bottom of the file
const exported = {
  Belly, Obstacle, Collectible, Plank, aabb,
    rectContainsPoint,  โ† add this!
};
Test it in the browser console (press F12 โ†’ Console tab)
// Type these lines into the console and press Enter each time:
exported.rectContainsPoint({x:10, y:10, w:50, h:50}, 30, 30)
// Should print: true  (point 30,30 is inside the rectangle)

exported.rectContainsPoint({x:10, y:10, w:50, h:50}, 5, 5)
// Should print: false (point 5,5 is outside โ€” top-left of rectangle)
โœ…
If the console prints true then false, your function works perfectly!
๐Ÿ’ก
You just learned about: Writing and testing functions A helper function does one small job and can be reused anywhere. Writing tests in the browser console is how professional developers quickly check if new code works before using it in a bigger system. This technique is called unit testing.
4

๐Ÿ›ก๏ธ Add a shield power-up that blocks one hit

This is the biggest challenge in the whole Academy. You're going to add a shield collectible that protects Belly from one obstacle hit. Take it step by step โ€” each step produces something you can test before moving on.

Plan before you code! The shield needs four pieces: a state variable (does Belly have a shield?), a collectible that gives it to her, a collision check that uses it up, and a visual effect so the player can see it.
Step A โ€” Add the shield state to Belly
js/entities.js
// Inside the Belly constructor, add this line:
this.hasShield = false;
Step B โ€” Create the shield collectible
js/entities.js
// In COLLECTIBLE_RADII, add the shield:
const COLLECTIBLE_RADII = {
  donut: 16,
  // ...
    shield: 18,  โ† add this!
};
js/game.js
// In spawnCollectible, add 'shield' to the kinds array:
const kinds = [
  'donut', 'pizza', 'icecream',
  // ...
    'shield',  โ† add this!
];
Step C โ€” Apply the shield when Belly collects it
js/game.js
Find the collectible collision section (look for "cKind === 'candy-cane'") and add a new check
if (cKind === 'candy-cane') {
  score += CONFIG.CANDY_SCORE;
  candyCaneCount++;
  // ...
} else if (cKind === 'shield') {
  belly.hasShield = true;  // activate the shield!
  score += 25;               // small bonus for finding it
} else {
  score += CONFIG.COLLECT_SCORE;
  belly.collect++;
}
Step D โ€” Block one obstacle hit with the shield
js/game.js
Find the obstacle collision section (look for "belly.lives--") and add the shield check
if (exported.aabb(belly.hitBox(), obstacles[i].hitBox())) {
  if (invincible) { obstacles.splice(i, 1); continue; }
  obstacles.splice(i, 1);
  if (belly.hasShield) {
    belly.hasShield = false;  // shield absorbs the hit!
    continue;                  // skip the lives-lost code below
  }
  belly.lives--;
  lostLifeThisLevel = true;
  // ...
}
Step E โ€” Draw the shield so the player can see it
js/renderer.js
Find the drawBelly function and add this after the main body drawing code
// Draw the shield ring around Belly when active
if (belly.hasShield) {
  ctx.save();
  ctx.strokeStyle = '#00eeff';
  ctx.lineWidth = 4;
  ctx.globalAlpha = 0.75;
  ctx.beginPath();
  ctx.arc(
    belly.x + belly.width  / 2,
    belly.y + belly.height / 2,
    belly.width * 0.65,
    0,
    Math.PI * 2
  );
  ctx.stroke();
  ctx.restore();
}
๐ŸŽฎ
Refresh and play. When you collect the shield, a glowing ring appears around Belly. Run into an obstacle โ€” the ring disappears but Belly survives!
๐Ÿ’ก
You just learned about: Building a game mechanic from scratch Every power-up in every game is built from the same four pieces: a state variable, a way to activate it, a check that uses it, and a visual cue for the player. You can use this exact same pattern to add any power-up you can imagine โ€” double jump, speed boost, magnet for collecting treatsโ€ฆ
๐ŸŽ‰

You're a game developer!

You traced the game loop, added an obstacle, wrote a utility function, and built a complete power-up mechanic. That's real game development. Keep experimenting โ€” and remember you can always Ctrl+Z to undo!

โ† Builder Path โ–ถ Play the Game! ๐Ÿ  All Paths