Entities & Drawing
Data first — objects that know how to paint themselves
Chapter goal
Stop hard-coding rectangles. Represent the player and platforms as data objects, then write draw functions that read that data.
How to do it
- 1
Model the world as data
A player is { x, y, w, h, color }. Platforms are an array of similar shapes. The engine state is just JavaScript objects.
- 2
One draw path per type
drawEntity(ctx, e) keeps rendering out of game logic. Later you can swap rectangles for sprites without touching physics.
- 3
Order matters
Draw background → platforms → player → HUD. Later layers cover earlier ones.
Live lab
Solution code updates with sliders. Tinker is your sandbox. Play runs whichever document you launch.
Live settings
Every slider injects into the engine constants for this chapter.
The chapter engine script is prewritten below. Type it line by line — each line explains why it belongs there. Matching a line advances automatically; Enter re-checks if you are stuck.
1(function () {2 "use strict";3 // Single boot — if this document is re-parsed, abandon prior work.4 if (window.__engineBooted) return;5 window.__engineBooted = true;6 let alive = true;7 window.addEventListener("pagehide", function () { alive = false; }, { once: true });8 const raf = window.requestAnimationFrame.bind(window);9 window.requestAnimationFrame = function (cb) {10 return raf(function (ts) {11 if (!alive) return;12 try { cb(ts); } catch (err) { console.error(err); alive = false; }13 });14 };1516const canvas = document.getElementById('c');17const ctx = canvas.getContext('2d');18const W = canvas.width, H = canvas.height;1920const PLAYER_W = 28;21const PLAYER_H = 36;22const PLATFORM_H = 14;23const SPACING = 100;24const HUE = 168;2526const player = {27 x: 80, y: H - 80 - PLAYER_H,28 w: PLAYER_W, h: PLAYER_H,29 color: `hsl(${HUE} 80% 55%)`30};3132const platforms = [33 { x: 0, y: H - 28, w: W, h: 28, color: '#1a2535' },34 { x: 140, y: H - 28 - SPACING * 0.7, w: 110, h: PLATFORM_H, color: '#243246' },35 { x: 320, y: H - 28 - SPACING, w: 90, h: PLATFORM_H, color: '#243246' },36 { x: 480, y: H - 28 - SPACING * 0.55, w: 120, h: PLATFORM_H, color: '#243246' },37];3839function drawEntity(e, radius = 6) {40 ctx.fillStyle = e.color;41 roundRect(ctx, e.x, e.y, e.w, e.h, radius);42 ctx.fill();43}4445function roundRect(ctx, x, y, w, h, r) {46 const rr = Math.min(r, w/2, h/2);47 ctx.beginPath();48 ctx.moveTo(x+rr, y);49 ctx.arcTo(x+w, y, x+w, y+h, rr);50 ctx.arcTo(x+w, y+h, x, y+h, rr);51 ctx.arcTo(x, y+h, x, y, rr);52 ctx.arcTo(x, y, x+w, y, rr);53 ctx.closePath();54}5556function draw() {57 ctx.fillStyle = '#0b1018';58 ctx.fillRect(0, 0, W, H);5960 // parallax bands61 for (let i = 0; i < 4; i++) {62 ctx.fillStyle = `rgba(94,234,212,${0.02 + i * 0.01})`;63 ctx.fillRect(0, 40 + i * 50, W, 24);64 }6566 for (const p of platforms) drawEntity(p, 4);6768 // player glow69 ctx.fillStyle = 'rgba(94,234,212,0.15)';70 ctx.beginPath();71 ctx.ellipse(player.x + player.w/2, player.y + player.h, player.w * 0.7, 6, 0, 0, Math.PI*2);72 ctx.fill();73 drawEntity(player, 8);7475 // face76 ctx.fillStyle = '#042f2e';77 ctx.fillRect(player.x + player.w * 0.25, player.y + player.h * 0.3, 4, 4);78 ctx.fillRect(player.x + player.w * 0.65, player.y + player.h * 0.3, 4, 4);7980 document.getElementById('hud').textContent =81 'Chapter 3 · entities: 1 player + ' + platforms.length + ' platforms';82}8384draw();8586})();
Static composition — reshape entities with the sliders. · Sliders update the play window after a short pause.