Door (Remix)

This is a multi-level survival game where players navigate through 12 increasingly challenging scenarios—from clicking a door handle to avoiding zombies, laser grids, and falling airplane debris. Each level teaches different p5.js techniques like collision detection, mouse/keyboard input, state management, and real-time animation.

🧪 Try This!

Experiment with the code by making these changes:

  1. Skip to Level 3 Basement — Jump directly to the basement by changing the initial gameState to LEVEL_3_BASEMENT—you'll test obstacle collision without grinding through earlier levels.
  2. Double poop spawn rate — Make Level 2 chaotic by spawning poop twice as fast—reduces the interval from 60 frames to 30 frames.
  3. Freeze in 5 seconds instead of unlimited time — Make the Cold Weather level a speed challenge by reducing the temperature drain rate, so body heat vanishes quickly.
  4. Give player 10 lives instead of 3 — Make the game forgiving by increasing starting lives—great for letting new players practice without restarting.
  5. Make Obby gravity twice as strong
  6. Increase door handle click radius for easier access — The door handle becomes larger and easier to click by doubling its collision radius.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates an ambitious multi-level game where players survive escalating dangers: a haunted door, falling poop, basement obstacle courses, freezing weather, guard patrols, laser grids, zombies, store heists, platforming, burning buildings, airplane crashes, and rock hunting. It demonstrates advanced p5.js techniques including game state management with switch statements, frame-based animation, rectangle-circle collision detection, emoji-based graphics, and responsive touch/keyboard controls for mobile and desktop.

The code is organized around a central game state system that switches between START_SCREEN, 12 playable levels, WIN_SCREEN, and GAME_OVER. Each level has its own display function, initialization function, and custom classes (Guard, LaserBeam, Zombie, Cop, Fire, Debris). By reading this sketch, you'll learn how professional game developers structure complex multi-state applications, implement collision systems that power survival games, and create smooth animations using deltaTime for frame-rate independence.

⚙️ How It Works

  1. When the sketch loads, setup() initializes the canvas, creates control buttons (Jump, Left, Right for the Obby level), and calls initializeGame() to set starting variables. The game begins on START_SCREEN.
  2. On the start screen, a click transitions to ANNOUNCEMENT_SCREEN (a teaser screen), and another click starts LEVEL_1_DOOR where players must click a door handle.
  3. Clicking the handle opens the door and begins LEVEL_2_POOP, where the player (following the mouse) must avoid falling poop objects for 10 seconds without losing all 3 lives.
  4. Surviving the poop level advances to LEVEL_3_BASEMENT, where the player must collect 3 'key' items (large food emoji) and reach an exit zone while avoiding 'obstacle' items (small food emoji). The introduction announcement explains the difference.
  5. Each subsequent level introduces new mechanics: LEVEL_4 adds a temperature bar that decreases; LEVEL_5 adds Guard enemies with vision cones; LEVEL_6 adds moving LaserBeams; LEVEL_7 spawns Zombies that chase the player; LEVEL_8 involves stealing an item then evading Cops; LEVEL_9 is a platformer with gravity and jumping using dedicated buttons or keyboard; LEVEL_10 has a crumbling building integrity meter; LEVEL_11 requires dodging falling Debris; LEVEL_12 is finding a tiny rock.
  6. On WIN_SCREEN, clicking continues to the next level (or shows a final victory badge if all 12 levels are complete). GAME_OVER shows when lives reach zero, and the restart button resets the entire game to the start screen.

🎓 Concepts You'll Learn

Game state managementCollision detection (rectangle-circle and rectangle-rectangle)Classes and object-oriented designAnimation with deltaTimeMouse and keyboard input handlingParticle systems and spawningEmoji-based graphicsTouch controls and responsive design

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. It's where you initialize the canvas, create UI elements, and prepare variables. rectMode(CENTER) is especially important in games because it makes collision detection math cleaner—all positions refer to the center of shapes rather than their corners.

function setup() {
  createCanvas(windowWidth, windowHeight);
  rectMode(CENTER);
  textAlign(CENTER, CENTER);
  textSize(24);
  pixelDensity(1);

  restartButton = createButton('Restart');
  restartButton.position(width / 2 - restartButton.width / 2, height / 2 + 100);
  restartButton.mousePressed(restartGame);
  restartButton.style('font-size', '24px');
  restartButton.style('padding', '10px 20px');
  restartButton.hide();
  
  jumpButton = createButton('Jump');
  jumpButton.size(120, 60);
  const handleJump = () => {
    if (gameState === LEVEL_9_OBBY_CHALLENGE && !isJumping) {
      playerYVelocity = -jumpStrength;
      isJumping = true;
    } else if (gameState === LEVEL_9_OBBY_CHALLENGE) {
    }
  };
  jumpButton.mousePressed(handleJump);
  jumpButton.touchStarted(handleJump);
  jumpButton.hide();

  leftButton = createButton('Left');
  leftButton.size(120, 60);
  leftButton.mousePressed(() => playerDirectionX = -1);
  leftButton.mouseReleased(() => playerDirectionX = 0);
  leftButton.touchStarted(() => playerDirectionX = -1);
  leftButton.touchEnded(() => playerDirectionX = 0);
  leftButton.hide();

  rightButton = createButton('Right');
  rightButton.size(120, 60);
  rightButton.mousePressed(() => playerDirectionX = 1);
  rightButton.mouseReleased(() => playerDirectionX = 0);
  rightButton.touchStarted(() => playerDirectionX = 1);
  rightButton.touchEnded(() => playerDirectionX = 0);
  rightButton.hide();

  jumpButton.elt.addEventListener('contextmenu', (event) => event.preventDefault());
  jumpButton.elt.addEventListener('touchstart', (event) => event.preventDefault());

  leftButton.elt.addEventListener('contextmenu', (event) => event.preventDefault());
  leftButton.elt.addEventListener('touchstart', (event) => event.preventDefault());

  rightButton.elt.addEventListener('contextmenu', (event) => event.preventDefault());
  rightButton.elt.addEventListener('touchstart', (event) => event.preventDefault());
  
  initializeGame();

  hideObbyButtons();
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

initialization Canvas Configuration createCanvas(windowWidth, windowHeight); rectMode(CENTER); textAlign(CENTER, CENTER);

Creates a full-window canvas, sets rectangles to draw from their center point, and centers all text—critical for consistent positioning throughout the game

initialization Button Creation and Styling restartButton = createButton('Restart'); restartButton.position(width / 2 - restartButton.width / 2, height / 2 + 100); restartButton.mousePressed(restartGame); restartButton.style('font-size', '24px'); restartButton.style('padding', '10px 20px'); restartButton.hide();

Creates and styles the restart button, positions it at the center-bottom of the screen, and hides it initially until the game ends

initialization Obby Control Buttons jumpButton = createButton('Jump'); jumpButton.size(120, 60); const handleJump = () => { if (gameState === LEVEL_9_OBBY_CHALLENGE && !isJumping) { playerYVelocity = -jumpStrength; isJumping = true; } }; jumpButton.mousePressed(handleJump); jumpButton.touchStarted(handleJump);

Creates Jump, Left, and Right buttons for the platforming level, with unified handlers for both mouse and touch input to support mobile devices

createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the game responsive to different screen sizes
rectMode(CENTER);
Changes rectangle drawing so that (x, y) is the center point rather than the top-left corner—simplifies collision detection and positioning
textAlign(CENTER, CENTER);
Centers all text both horizontally and vertically around the (x, y) coordinates used in text() calls
pixelDensity(1);
Renders at the device's native pixel density for better performance on touch devices by avoiding unnecessary high-resolution rendering
restartButton = createButton('Restart');
Creates an HTML button element labeled 'Restart' and stores it in the global restartButton variable
restartButton.mousePressed(restartGame);
Attaches the restartGame function to the button—when clicked, restartGame() executes
jumpButton.touchStarted(handleJump);
Registers the handleJump function to fire on touch screen events (not just mouse clicks) so the button works on mobile
jumpButton.elt.addEventListener('contextmenu', (event) => event.preventDefault());
Prevents the browser's right-click context menu from appearing on the button, improving the mobile experience
initializeGame();
Calls the function that sets up all game variables and positions to their starting state

draw()

draw() is called 60 times per second by p5.js. The switch statement is a clean way to organize multi-state applications: instead of nested if-else chains, you branch to exactly one case based on gameState. Every game—from simple arcade games to massive multiplayer worlds—uses this pattern. The background() call erases the previous frame's drawings, creating the illusion of smooth animation.

function draw() {
  background(220);

  switch (gameState) {
    case START_SCREEN:
      displayStartScreen();
      break;
    case ANNOUNCEMENT_SCREEN:
      displayAnnouncementScreen();
      break;
    case LEVEL_1_DOOR:
      displayLevel1Door();
      break;
    case LEVEL_2_POOP:
      displayLevel2Poop();
      break;
    case LEVEL_3_BASEMENT:
      displayLevel3Basement();
      break;
    case LEVEL_4_COLD_WEATHER:
      displayLevel4ColdWeather();
      break;
    case LEVEL_5_GUARDED_GALLERY:
      displayLevel5GuardedGallery();
      break;
    case LEVEL_6_LASER_GRID:
      displayLevel6LaserGrid();
      break;
    case LEVEL_7_ZOMBIE_SURVIVAL:
      displayLevel7ZombieSurvival();
      break;
    case LEVEL_8_STORE_HEIST:
      displayLevel8StoreHeist();
      break;
    case LEVEL_9_OBBY_CHALLENGE:
      displayLevel9ObbyChallenge();
      break;
    case LEVEL_10_BURNING_BUILDING:
      displayLevel10BurningBuilding();
      break;
    case LEVEL_11_AIRPLANE_CRASH:
      displayLevel11AirplaneCrash();
      break;
    case LEVEL_12_TINY_ROCK:
      displayLevel12TinyRock();
      break;
    case GAME_OVER:
      displayGameOver();
      break;
    case WIN_SCREEN:
      displayWinScreen();
      break;
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

switch-case Game State Switch switch (gameState) { case START_SCREEN: displayStartScreen(); break; case LEVEL_1_DOOR: displayLevel1Door(); break; // ... more cases

Routes the draw loop to the correct display function based on the current gameState—the heart of the entire game's control flow

calculation Background Clear background(220);

Clears the canvas with a light gray color every frame, erasing the previous frame's drawings to create animation

background(220);
Clears the entire canvas with light gray (RGB value 220) at the start of each frame, preparing it for fresh drawing
switch (gameState) {
Begins a switch statement that checks the current gameState variable and branches to the appropriate case
case START_SCREEN: displayStartScreen(); break;
If gameState equals START_SCREEN, call displayStartScreen() to draw the title and instructions, then break out of the switch
case LEVEL_1_DOOR: displayLevel1Door(); break;
When gameState is LEVEL_1_DOOR, call displayLevel1Door() to draw the door and handle. Each level has its own case and display function

checkCollision()

checkCollision() is the mathematical heart of the entire game. Axis-Aligned Bounding Box (AABB) collision is fast and simple: it checks if two rectangular areas overlap by testing their edges. Rectangle-circle collision is trickier: it finds the closest point on the rectangle to the circle's center, then checks if that distance is within the radius. This function is called dozens of times per frame, so efficiency matters. Professional games use spatial partitioning (like quadtrees) to avoid checking every pair, but for a game this size, brute-force AABB is plenty fast.

function checkCollision(x1, y1, w1, h1, x2, y2, w2, h2, type1, type2) {
  function checkRectRectCollision(rx1, ry1, rw1, rh1, rx2, ry2, rw2, rh2) {
    let rect1Left = rx1 - rw1 / 2;
    let rect1Right = rx1 + rw1 / 2;
    let rect1Top = ry1 - rh1 / 2;
    let rect1Bottom = ry1 + rh1 / 2;

    let rect2Left = rx2 - rw2 / 2;
    let rect2Right = rx2 + rw2 / 2;
    let rect2Top = ry2 - rh2 / 2;
    let rect2Bottom = ry2 + rh2 / 2;

    if (rect1Right < rect2Left || rect1Left > rect2Right || rect1Bottom < rect2Top || rect1Top > rect2Bottom) {
      return false;
    }
    return true;
  }

  function checkRectCircleCollision(x, y, w, h, cx, cy, r) {
    let testX = cx;
    let testY = cy;

    let rectLeft = x - w / 2;
    let rectRight = x + w / 2;
    let rectTop = y - h / 2;
    let rectBottom = y + h / 2;

    if (cx < rectLeft) {
      testX = rectLeft;
    } else if (cx > rectRight) {
      testX = rectRight;
    }

    if (cy < rectTop) {
      testY = rectTop;
    } else if (cy > rectBottom) {
      testY = rectBottom;
    }

    let distX = cx - testX;
    let distY = cy - testY;
    let distance = sqrt((distX * distX) + (distY * distY));

    return distance <= r;
  }

  if (type1 === "rect" && type2 === "circle") {
    return checkRectCircleCollision(x1, y1, w1, h1, x2, y2, w2 / 2);
  }
  if (type1 === "rect" && type2 === "rect") {
    return checkRectRectCollision(x1, y1, w1, h1, x2, y2, w2, h2);
  }
  return false;
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

function Rectangle-Rectangle AABB Collision function checkRectRectCollision(rx1, ry1, rw1, rh1, rx2, ry2, rw2, rh2) { let rect1Left = rx1 - rw1 / 2; let rect1Right = rx1 + rw1 / 2; // ... calculate bounding edges if (rect1Right < rect2Left || rect1Left > rect2Right || rect1Bottom < rect2Top || rect1Top > rect2Bottom) { return false; } return true; }

Uses Axis-Aligned Bounding Box (AABB) logic to detect if two rectangles overlap by checking if any edge of rectangle 1 lies outside rectangle 2

function Rectangle-Circle Collision function checkRectCircleCollision(x, y, w, h, cx, cy, r) { // Find closest point on rect to circle center let testX = cx, testY = cy; // ... clamp testX/testY to rect bounds let distance = sqrt((distX * distX) + (distY * distY)); return distance <= r; }

Finds the closest point on the rectangle to the circle's center, then checks if that distance is within the circle's radius

conditional Collision Type Dispatch if (type1 === "rect" && type2 === "circle") { return checkRectCircleCollision(x1, y1, w1, h1, x2, y2, w2 / 2); } if (type1 === "rect" && type2 === "rect") { return checkRectRectCollision(x1, y1, w1, h1, x2, y2, w2, h2); }

Routes to the correct collision detection algorithm based on the types of the two objects being tested

let rect1Left = rx1 - rw1 / 2;
Calculates the left edge of rectangle 1 by subtracting half the width from the center x-coordinate (because rectMode(CENTER))
if (rect1Right < rect2Left || rect1Left > rect2Right || rect1Bottom < rect2Top || rect1Top > rect2Bottom) {
Returns false (no collision) if any side of rect1 lies completely outside rect2. If none of these conditions are true, the rectangles overlap.
if (cx < rectLeft) { testX = rectLeft;
Clamps the test point's x-coordinate to the rectangle's left edge if the circle center is to the left of the rectangle
let distance = sqrt((distX * distX) + (distY * distY));
Calculates the Euclidean distance between the circle's center and the closest point on the rectangle using the Pythagorean theorem
if (type1 === "rect" && type2 === "circle") {
Checks if we're testing a rectangle against a circle, then calls the specialized rectangle-circle collision function

decreaseLife()

decreaseLife() is called whenever the player collides with a hazard (poop, zombies, cops, etc.). Notice the Obby level exemption: platformers traditionally don't penalize crashes with a life loss; instead, the player respawns on the same level. This function demonstrates conditional game logic—different rules apply to different levels. It's a small example of how complex games have special cases and exceptions to the general rules.

function decreaseLife() {
  if (gameState === LEVEL_9_OBBY_CHALLENGE) {
    return;
  }
  lives--;
  if (lives <= 0) {
    gameState = GAME_OVER;
    restartButton.show();
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Obby Level Exemption if (gameState === LEVEL_9_OBBY_CHALLENGE) { return; }

Prevents the Obby platforming level from draining lives when the player crashes—the level just restarts instead

calculation Decrement Lives lives--;

Reduces the lives count by 1 whenever the player collides with a hazard

conditional Game Over Check if (lives <= 0) { gameState = GAME_OVER; restartButton.show(); }

Triggers game over state and shows the restart button when lives reach zero

if (gameState === LEVEL_9_OBBY_CHALLENGE) {
Checks if the current level is the Obby platformer challenge
return;
Exits the function immediately without decrementing lives, so Obby obstacles don't cost lives
lives--;
Decrements the global lives variable by 1 each time this function is called (e.g., when a hazard is touched)
if (lives <= 0) {
Checks if lives have been reduced to zero or below
gameState = GAME_OVER;
Changes the game state to GAME_OVER, which will trigger displayGameOver() on the next draw() call
restartButton.show();
Makes the restart button visible so the player can click it to start over

spawnPoop()

spawnPoop() generates new poop objects on demand, ensuring they always enter from off-screen. This 'spawning from edges' pattern is used in classic arcade games like Asteroids and Space Invaders. By randomizing both position and velocity, each poop feels unpredictable. The key insight: velocity (dx, dy) controls direction and speed, while position (x, y) controls where. By spawning off-screen with velocity pointing inward, objects smoothly slide onto the visible canvas.

🔬 Each edge has different velocity rules. Notice the right edge uses 'random(-poopMaxSpeed, -poopMinSpeed)'—always negative, so poop moves LEFT into the screen. What happens if you change the right edge to use positive speeds instead: 'random(poopMinSpeed, poopMaxSpeed)'? Will the poop escape off the right side?

  if (edge === 0) { // Top
    x = random(width);
    y = -size;
    dx = random(-poopMaxSpeed, poopMaxSpeed);
    dy = random(poopMinSpeed, poopMaxSpeed);
  } else if (edge === 1) { // Right
    x = width + size;
    y = random(height);
    dx = random(-poopMaxSpeed, -poopMinSpeed);
    dy = random(-poopMaxSpeed, poopMaxSpeed);
function spawnPoop() {
  let size = random(poopMinSize, poopMaxSize);
  let x, y, dx, dy;

  let edge = floor(random(4));

  if (edge === 0) { // Top
    x = random(width);
    y = -size;
    dx = random(-poopMaxSpeed, poopMaxSpeed);
    dy = random(poopMinSpeed, poopMaxSpeed);
  } else if (edge === 1) { // Right
    x = width + size;
    y = random(height);
    dx = random(-poopMaxSpeed, -poopMinSpeed);
    dy = random(-poopMaxSpeed, poopMaxSpeed);
  } else if (edge === 2) { // Bottom
    x = random(width);
    y = height + size;
    dx = random(-poopMaxSpeed, poopMaxSpeed);
    dy = random(-poopMaxSpeed, -poopMinSpeed);
  } else { // Left
    x = -size;
    y = random(height);
    dx = random(poopMinSpeed, poopMaxSpeed);
    dy = random(-poopMaxSpeed, poopMaxSpeed);
  }

  poop.push({ x: x, y: y, dx: dx, dy: dy, size: size });
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

calculation Random Size Generation let size = random(poopMinSize, poopMaxSize);

Generates a random diameter for the poop object between min and max values, adding visual variety

calculation Random Edge Selection let edge = floor(random(4));

Picks a random edge (0=top, 1=right, 2=bottom, 3=left) for the poop to spawn from

switch-case Edge-Specific Spawn Logic if (edge === 0) { // Top x = random(width); y = -size; dx = random(-poopMaxSpeed, poopMaxSpeed); dy = random(poopMinSpeed, poopMaxSpeed); } else if (edge === 1) { // Right // ... position from right edge, velocity toward left

For each edge, positions the poop off-screen and gives it a velocity vector pointing into the canvas

calculation Push to Poop Array poop.push({ x: x, y: y, dx: dx, dy: dy, size: size });

Adds the newly created poop object (with all its properties) to the global poop array for management in draw()

let size = random(poopMinSize, poopMaxSize);
Picks a random size between the configured minimum and maximum, so poop objects vary in diameter
let edge = floor(random(4));
Generates a random number 0–3.99, then floors it to get a whole number 0, 1, 2, or 3 representing one of four screen edges
if (edge === 0) { // Top
If the chosen edge is the top (edge 0), execute this branch to spawn poop above the screen
y = -size;
Positions the poop above the visible canvas (negative y) so it enters from the top as it moves downward
dx = random(-poopMaxSpeed, poopMaxSpeed);
Assigns a random horizontal velocity (can be negative for leftward or positive for rightward motion)
dy = random(poopMinSpeed, poopMaxSpeed);
Assigns a positive vertical velocity so poop always moves downward when spawning from the top
poop.push({ x: x, y: y, dx: dx, dy: dy, size: size });
Creates an object with x, y, dx, dy, and size properties and adds it to the poop array for the draw loop to manage

displayLevel2Poop()

displayLevel2Poop() is the first level the player encounters. It teaches three core game concepts: (1) Making the player follow input (mouse), (2) Updating and rendering multiple objects in a loop, and (3) Detecting collisions and managing a timer. Notice the backward loop: removing items from an array during iteration is tricky if you loop forward—splicing shifts indices, causing items to be skipped. Looping backward avoids this problem. This level is designed to ease players into the game's mechanics before introducing more complex challenges in later levels.

function displayLevel2Poop() {
  playerX = mouseX;
  playerY = mouseY;

  background(200, 230, 255);

  fill(255);
  noStroke();
  rect(playerX, playerY, playerSize, playerSize);

  for (let i = poop.length - 1; i >= 0; i--) {
    let p = poop[i];
    p.x += p.dx;
    p.y += p.dy;

    if (p.x < -p.size || p.x > width + p.size || p.y < -p.size || p.y > height + p.size) {
      poop.splice(i, 1);
    }

    if (checkCollision(playerX, playerY, playerSize, playerSize, p.x, p.y, p.size, p.size, "rect", "circle")) {
      decreaseLife();
      poop.splice(i, 1);
      return;
    }

    fill(139, 69, 19);
    noStroke();
    ellipse(p.x, p.y, p.size);
  }

  if (poop.length === 0 && poopTimer < poopLevelDuration) {
    spawnPoop();
  } else if (frameCount % 60 === 0 && poop.length < poopCount) {
    spawnPoop();
  }

  poopTimer += deltaTime;
  let remainingTime = max(0, poopLevelDuration - poopTimer);
  fill(0);
  textSize(20);
  noStroke();
  text(`Survive: ${nf(remainingTime / 1000, 1, 1)}s`, width / 2, 30);

  fill(0);
  textSize(20);
  noStroke();
  text(`Lives: ${lives}`, width - 60, 30);

  if (poopTimer >= poopLevelDuration) {
    gameState = LEVEL_3_BASEMENT;
    initializeBasementLevel();
  }
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

calculation Player Position Update playerX = mouseX; playerY = mouseY;

Makes the player follow the mouse cursor in real-time, providing direct input control

for-loop Poop Update and Collision Loop for (let i = poop.length - 1; i >= 0; i--) { let p = poop[i]; p.x += p.dx; p.y += p.dy; // ... collision checks and removal

Iterates backward through the poop array, updating positions, checking collisions, and removing off-screen objects

conditional Off-Screen Poop Removal if (p.x < -p.size || p.x > width + p.size || p.y < -p.size || p.y > height + p.size) { poop.splice(i, 1); }

Deletes poop objects that have traveled far off-screen, freeing memory

conditional Poop Spawn Timing if (poop.length === 0 && poopTimer < poopLevelDuration) { spawnPoop(); } else if (frameCount % 60 === 0 && poop.length < poopCount) { spawnPoop(); }

Spawns new poop: immediately if the array is empty, or every 60 frames if below the max count

calculation Timer and Display poopTimer += deltaTime; let remainingTime = max(0, poopLevelDuration - poopTimer); fill(0); textSize(20); text(`Survive: ${nf(remainingTime / 1000, 1, 1)}s`, width / 2, 30);

Accumulates deltaTime to track elapsed time, calculates remaining time, and displays it as a countdown

conditional Level Completion Check if (poopTimer >= poopLevelDuration) { gameState = LEVEL_3_BASEMENT; initializeBasementLevel(); }

Advances to Level 3 when the survival timer expires

playerX = mouseX;
Updates the player's x position to match the mouse cursor every frame, creating direct mouse-following control
p.x += p.dx;
Adds the poop object's horizontal velocity to its x position, moving it one step this frame
if (p.x < -p.size || p.x > width + p.size || p.y < -p.size || p.y > height + p.size) {
Checks if the poop has moved far enough off-screen that it's no longer visible and can be safely removed
poop.splice(i, 1);
Removes the poop object at index i from the poop array, reducing the array length by 1
if (checkCollision(playerX, playerY, playerSize, playerSize, p.x, p.y, p.size, p.size, "rect", "circle")) {
Calls checkCollision() to test if the player rectangle overlaps the poop circle
decreaseLife();
If a collision is detected, loses one life and optionally triggers game over
poopTimer += deltaTime;
Adds the milliseconds elapsed since the last frame to the total timer, accumulating the level's elapsed time
let remainingTime = max(0, poopLevelDuration - poopTimer);
Calculates how many milliseconds remain before winning by subtracting elapsed time from the target duration
text(`Survive: ${nf(remainingTime / 1000, 1, 1)}s`, width / 2, 30);
Divides remainingTime by 1000 to convert from milliseconds to seconds, formats it with nf() to one decimal place, and displays it

displayLevel9ObbyChallenge()

displayLevel9ObbyChallenge() is a complete platformer implementation. It uses Newtonian physics (velocity + gravity = acceleration), world-space scrolling, and precise collision-based landing detection. The key sophistication: applying worldOffsetX to convert between world coordinates (elem.x) and screen coordinates (elemXOnScreen). Professional platformers like Super Mario use the same physics model. The 'obbyDifficulty' variable lets you swap entire difficulty presets (easy/normal/hard) by changing gravity, jump power, and obstacle layout—a great example of data-driven game design.

function displayLevel9ObbyChallenge() {
  background(100, 150, 200);

  playerY += playerYVelocity;
  playerYVelocity += gravity;

  if (playerY - playerSize / 2 > height) {
    initializeLevel9ObbyChallenge();
    return;
  }

  worldOffsetX -= playerDirectionX * playerForwardSpeed;

  let onPlatform = false;

  for (let elem of obbyElements) {
    if (elem.type === 'platform') {
      let elemXOnScreen = elem.x + worldOffsetX;
      if (checkCollision(playerX, playerY, playerSize, playerSize, elemXOnScreen, elem.y, elem.w, elem.h, "rect", "rect")) {
        if (playerYVelocity > 0 && playerY + playerSize / 2 > elem.y - elem.h / 2) {
          playerYVelocity = 0;
          playerY = elem.y - elem.h / 2 - playerSize / 2;
          isJumping = false;
          onPlatform = true;
        }
      }
    }
  }

  for (let elem of obbyElements) {
    if (elem.type === 'obstacle') {
      let elemXOnScreen = elem.x + worldOffsetX;

      if (elem.direction) {
        if (elem.direction === 'vertical') {
          elem.y += elem.speed;
          if (elem.y <= elem.min || elem.y >= elem.max) elem.speed *= -1;
        } else if (elem.direction === 'horizontal') {
          elem.x += elem.speed;
          if (elem.x <= elem.min || elem.x >= elem.max) elem.speed *= -1;
        }
      }

      if (checkCollision(playerX, playerY, playerSize, playerSize, elemXOnScreen, elem.y, elem.w, elem.h, "rect", "rect")) {
        initializeLevel9ObbyChallenge();
        return;
      }
    }
  }

  const obbyGroundY = height * 0.8;
  if (!onPlatform && playerY + playerSize / 2 >= obbyGroundY && playerYVelocity > 0) {
    playerYVelocity = 0;
    playerY = obbyGroundY - playerSize / 2;
    isJumping = false;
  }

  for (let elem of obbyElements) {
    let elemXOnScreen = elem.x + worldOffsetX;
    fill(150, 100, 50);
    noStroke();
    rect(elemXOnScreen, elem.y, elem.w, elem.h);
  }

  let obbyExitXOnScreen = obbyExitX + worldOffsetX;
  fill(0, 255, 0, 100);
  noStroke();
  rect(obbyExitXOnScreen, obbyExitY, obbyExitWidth, obbyExitHeight);
  fill(0, 255, 0);
  textSize(16);
  noStroke();
  text("EXIT", obbyExitXOnScreen, obbyExitY);

  if (checkCollision(playerX, playerY, playerSize, playerSize, obbyExitXOnScreen, obbyExitY, obbyExitWidth, obbyExitHeight, "rect", "rect")) {
    levelJustWon = LEVEL_9_OBBY_CHALLENGE;
    gameState = WIN_SCREEN;
    restartButton.show();
    return;
  }

  fill(255);
  noStroke();
  rect(playerX, playerY, playerSize, playerSize);

  fill(0);
  textSize(20);
  noStroke();
  text(`Lives: ${lives}`, width - 60, 30);

  fill(0);
  textSize(24);
  noStroke();
  text("Use Left/Right buttons or keys to move, Jump button/Spacebar to jump! (No lives lost)", width / 2, height - 50);

  if (millis() - obbyIntroAnnouncementStartTime < obbyIntroAnnouncementDuration) {
    fill(0, 0, 0, 180);
    noStroke();
    rect(width / 2, height / 2, width * 0.8, height * 0.4, 20);

    fill(255);
    textSize(32);
    noStroke();
    text("ATTENTION!", width / 2, height / 2 - 50);
    textSize(24);
    text("Small platforms are DANGEROUS!", width / 2, height / 2);
    text("Big, long stick-like platforms are SAFE!", width / 2, height / 2 + 30);
  }
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Gravity and Vertical Velocity playerY += playerYVelocity; playerYVelocity += gravity;

Applies acceleration due to gravity: each frame, velocity increases by gravity, and position increases by velocity, creating realistic falling motion

conditional Player Fall Detection if (playerY - playerSize / 2 > height) { initializeLevel9ObbyChallenge(); return; }

If the player falls below the visible canvas, restart the level instead of losing a life

calculation World Scrolling worldOffsetX -= playerDirectionX * playerForwardSpeed;

Shifts the entire obby world horizontally based on player input, creating a scrolling camera effect

for-loop Platform Landing Detection for (let elem of obbyElements) { if (elem.type === 'platform') { let elemXOnScreen = elem.x + worldOffsetX; if (checkCollision(...)) { if (playerYVelocity > 0 && playerY + playerSize / 2 > elem.y - elem.h / 2) { playerYVelocity = 0; playerY = elem.y - elem.h / 2 - playerSize / 2; isJumping = false; onPlatform = true; } } } }

Detects when the player lands on a platform and snaps them to its top surface, resetting jump ability

for-loop Obstacle Collision and Movement for (let elem of obbyElements) { if (elem.type === 'obstacle') { let elemXOnScreen = elem.x + worldOffsetX; if (elem.direction) { if (elem.direction === 'vertical') { elem.y += elem.speed; if (elem.y <= elem.min || elem.y >= elem.max) elem.speed *= -1; } } if (checkCollision(...)) { initializeLevel9ObbyChallenge(); return; } } }

Updates moving obstacles (bounces them between min/max), detects player-obstacle collisions, and restarts the level on hit

conditional Ground Landing const obbyGroundY = height * 0.8; if (!onPlatform && playerY + playerSize / 2 >= obbyGroundY && playerYVelocity > 0) { playerYVelocity = 0; playerY = obbyGroundY - playerSize / 2; isJumping = false; }

Snaps the player to the ground level if they're not on a platform and falling

conditional Exit Zone Collision if (checkCollision(playerX, playerY, playerSize, playerSize, obbyExitXOnScreen, obbyExitY, obbyExitWidth, obbyExitHeight, "rect", "rect")) { levelJustWon = LEVEL_9_OBBY_CHALLENGE; gameState = WIN_SCREEN; restartButton.show(); return; }

Advances to the win screen when the player reaches the exit

playerY += playerYVelocity;
Updates the player's y position by adding their vertical velocity each frame—if velocity is positive (downward), y increases
playerYVelocity += gravity;
Increases velocity by the gravity constant each frame, accelerating the player downward (simulating gravitational pull)
if (playerY - playerSize / 2 > height) {
Checks if the player's bottom edge has fallen below the bottom of the canvas (height)
worldOffsetX -= playerDirectionX * playerForwardSpeed;
Adjusts the world offset based on player input: moving right (playerDirectionX=1) shifts the world left (negative), creating a scrolling effect
let elemXOnScreen = elem.x + worldOffsetX;
Adds the world offset to the element's world x-coordinate to get its on-screen x-position for rendering and collision
if (playerYVelocity > 0 && playerY + playerSize / 2 > elem.y - elem.h / 2) {
Only snaps the player to the platform if they're falling (velocity > 0) and their bottom edge is below the platform's top edge
playerY = elem.y - elem.h / 2 - playerSize / 2;
Snaps the player's center y to a position where their bottom edge sits on the platform's top edge
if (elem.direction === 'vertical') { elem.y += elem.speed; if (elem.y <= elem.min || elem.y >= elem.max) elem.speed *= -1;
Updates the obstacle's position and reverses its speed when it reaches the min or max boundary, creating back-and-forth oscillation

mouseClicked()

mouseClicked() is an event handler fired whenever the player clicks the canvas. Using event handlers for state changes is cleaner than checking mouse state inside draw(). Notice how the logic mirrors the game's narrative: each click progresses the player forward through the adventure. The win-screen transitions use levelJustWon to track which level was just beaten, then branch to the next level accordingly. This is a common pattern in narrative games: each click advances the story. The `!restartButton.elt.offsetParent` check is a clever way to detect if a button is visible—offsetParent is null for hidden elements in the DOM.

function mouseClicked() {
  if (gameState === START_SCREEN) {
    gameState = ANNOUNCEMENT_SCREEN;
  } else if (gameState === ANNOUNCEMENT_SCREEN) {
    gameState = LEVEL_1_DOOR;
  } else if (gameState === LEVEL_1_DOOR) {
    if (dist(mouseX, mouseY, handleX, handleY) < handleSize / 2) {
      gameState = LEVEL_2_POOP;
      initializePoopLevel();
    }
  } else if (gameState === WIN_SCREEN) {
    if (!restartButton.elt.offsetParent) {
      if (levelJustWon === LEVEL_3_BASEMENT) {
        gameState = LEVEL_4_COLD_WEATHER;
        initializeLevel4();
      } else if (levelJustWon === LEVEL_4_COLD_WEATHER) {
        gameState = LEVEL_5_GUARDED_GALLERY;
        initializeLevel5();
      } else if (levelJustWon === LEVEL_5_GUARDED_GALLERY) {
        gameState = LEVEL_6_LASER_GRID;
        initializeLevel6();
      } else if (levelJustWon === LEVEL_6_LASER_GRID) {
        gameState = LEVEL_7_ZOMBIE_SURVIVAL;
        initializeLevel7();
      } else if (levelJustWon === LEVEL_7_ZOMBIE_SURVIVAL) {
        gameState = LEVEL_8_STORE_HEIST;
        initializeLevel8StoreHeist();
      } else if (levelJustWon === LEVEL_8_STORE_HEIST) {
        gameState = LEVEL_9_OBBY_CHALLENGE;
        initializeLevel9ObbyChallenge();
      } else if (levelJustWon === LEVEL_9_OBBY_CHALLENGE) {
        gameState = LEVEL_10_BURNING_BUILDING;
        initializeLevel10BurningBuilding();
      } else if (levelJustWon === LEVEL_10_BURNING_BUILDING) {
        gameState = LEVEL_11_AIRPLANE_CRASH;
        initializeLevel11AirplaneCrash();
      } else if (levelJustWon === LEVEL_11_AIRPLANE_CRASH) {
        gameState = LEVEL_12_TINY_ROCK;
        initializeLevel12TinyRock();
      } else {
        restartGame();
      }
    }
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Start Screen Transition if (gameState === START_SCREEN) { gameState = ANNOUNCEMENT_SCREEN; }

Clicking on the start screen advances to the announcement screen

conditional Announcement to Level 1 else if (gameState === ANNOUNCEMENT_SCREEN) { gameState = LEVEL_1_DOOR; }

Clicking the announcement screen starts the game at Level 1

conditional Door Handle Detection else if (gameState === LEVEL_1_DOOR) { if (dist(mouseX, mouseY, handleX, handleY) < handleSize / 2) { gameState = LEVEL_2_POOP; initializePoopLevel(); } }

Checks if the click is within the door handle radius; if so, opens the door and starts Level 2

conditional Win Screen Level Progression else if (gameState === WIN_SCREEN) { if (!restartButton.elt.offsetParent) { if (levelJustWon === LEVEL_3_BASEMENT) { gameState = LEVEL_4_COLD_WEATHER; initializeLevel4(); } // ... more level transitions

On the win screen, checks which level was just completed and transitions to the next level accordingly

if (gameState === START_SCREEN) {
Checks if the current game state is the start screen
gameState = ANNOUNCEMENT_SCREEN;
Changes the game state to the announcement screen, so draw() will call displayAnnouncementScreen() on the next frame
if (dist(mouseX, mouseY, handleX, handleY) < handleSize / 2) {
Calculates the distance from the click position to the door handle center and checks if it's within the handle's radius
if (!restartButton.elt.offsetParent) {
Checks if the restart button is hidden (offsetParent is null when hidden)—if true, it's a level transition, not a final victory
if (levelJustWon === LEVEL_3_BASEMENT) {
Checks which level was just completed by comparing levelJustWon to each level constant
gameState = LEVEL_4_COLD_WEATHER; initializeLevel4();
Sets the game state to the next level and calls its initialization function to prepare variables and objects

initializeLevel9ObbyChallenge()

initializeLevel9ObbyChallenge() demonstrates data-driven game design: instead of hard-coding level parameters, they're stored in the obbySettings object. Changing obbyDifficulty to 'normal' or 'hard' instantly swaps physics and level layout. Converting relative coordinates (0.0–1.0) to absolute pixels makes the level responsive to different screen sizes. The exit position is computed relative to the last element, so if you add or remove elements, the exit adjusts automatically. This approach scales to much larger games: professional studios manage thousands of levels this way.

function initializeLevel9ObbyChallenge() {
  playerX = width * 0.2;
  playerY = height * 0.8;
  playerYVelocity = 0;
  isJumping = false;
  playerDirectionX = 0;
  obbyIntroAnnouncementStartTime = millis();

  showObbyButtons();

  let buttonY = height - 160;

  leftButton.position(width * 0.05, buttonY);
  rightButton.position(width * 0.05 + leftButton.width + 20, buttonY);
  jumpButton.position(width * 0.95 - jumpButton.width, buttonY);

  obbyElements = [];

  const settings = obbySettings[obbyDifficulty];

  playerForwardSpeed = settings.playerForwardSpeed;
  gravity = settings.gravity;
  jumpStrength = settings.jumpStrength;

  playerY = height * 0.8 - playerSize / 2;

  for (let elem of settings.elements) {
    let newElem = {
      type: elem.type,
      x: elem.x * width,
      y: elem.y * height,
      w: elem.w,
      h: elem.h
    };
    if (elem.direction) {
      newElem.speed = elem.speed;
      newElem.direction = elem.direction;
      newElem.min = elem.min * height;
      newElem.max = elem.max * height;
    }
    obbyElements.push(newElem);
  }

  obbyExitWidth = min(width, height) * 0.1;
  obbyExitHeight = height * 0.8;
  let lastElemX = obbyElements.length > 0 ? obbyElements[obbyElements.length - 1].x : width * 0.2;
  obbyExitX = lastElemX + width * 0.3;
  obbyExitY = height / 2;
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

calculation Player Initial State playerX = width * 0.2; playerY = height * 0.8; playerYVelocity = 0; isJumping = false; playerDirectionX = 0;

Positions the player on the left side at ground level and resets all movement variables

calculation Button Positioning showObbyButtons(); let buttonY = height - 160; leftButton.position(width * 0.05, buttonY); rightButton.position(width * 0.05 + leftButton.width + 20, buttonY); jumpButton.position(width * 0.95 - jumpButton.width, buttonY);

Shows the Obby control buttons and positions them at the bottom of the screen, responsive to canvas size

calculation Load Difficulty Settings const settings = obbySettings[obbyDifficulty]; playerForwardSpeed = settings.playerForwardSpeed; gravity = settings.gravity; jumpStrength = settings.jumpStrength;

Retrieves the difficulty preset (easy/normal/hard) and applies its physics parameters (speed, gravity, jump strength)

for-loop Spawn Obby Elements for (let elem of settings.elements) { let newElem = { type: elem.type, x: elem.x * width, y: elem.y * height, w: elem.w, h: elem.h }; if (elem.direction) { newElem.speed = elem.speed; newElem.direction = elem.direction; newElem.min = elem.min * height; newElem.max = elem.max * height; } obbyElements.push(newElem); }

Converts relative position coordinates (as percentages) to absolute screen coordinates, then adds each platform and obstacle to the obbyElements array

playerX = width * 0.2;
Positions the player 20% from the left edge of the canvas
playerY = height * 0.8;
Positions the player 80% down from the top, near the bottom at the ground level
playerYVelocity = 0;
Resets vertical velocity to zero so the player doesn't start with momentum
obbyIntroAnnouncementStartTime = millis();
Records the current time in milliseconds so displayLevel9ObbyChallenge() can display the intro announcement for exactly 5 seconds
showObbyButtons();
Calls the helper function that makes the Jump, Left, and Right buttons visible on the canvas
let buttonY = height - 160;
Calculates a y position 160 pixels from the bottom of the canvas for responsive button placement
const settings = obbySettings[obbyDifficulty];
Retrieves the difficulty configuration object (easy, normal, or hard) based on the global obbyDifficulty variable
x: elem.x * width,
Converts the relative x position (0.0–1.0) to an absolute pixel position by multiplying by canvas width
if (elem.direction) { newElem.speed = elem.speed; newElem.direction = elem.direction;
If the element is a moving obstacle, copy its speed and direction properties for animation in displayLevel9ObbyChallenge()
let lastElemX = obbyElements.length > 0 ? obbyElements[obbyElements.length - 1].x : width * 0.2;
Finds the x position of the last platform/obstacle, or defaults to 20% if the array is empty
obbyExitX = lastElemX + width * 0.3;
Places the exit 30% to the right of the last obstacle, creating a clear goal at the end of the course

📦 Key Variables

gameState number

Stores the current state of the game (START_SCREEN, LEVEL_1_DOOR, LEVEL_2_POOP, ..., WIN_SCREEN, GAME_OVER). The draw() switch statement routes to the correct display function based on this value.

let gameState = START_SCREEN;
lives number

Tracks how many lives the player has remaining. Decreases when hazards are touched; game ends when it reaches zero.

let lives = 3;
levelJustWon number

Stores which level was just completed so the WIN_SCREEN knows which level to transition to next.

let levelJustWon = START_SCREEN;
playerX number

Horizontal position of the player character on the canvas, used for collision detection and rendering.

let playerX = width / 2;
playerY number

Vertical position of the player character on the canvas, updated by input or physics depending on the level.

let playerY = height / 2;
poop array

Array of poop objects in Level 2, each with x, y, dx, dy (velocities), and size properties. Updated and rendered every frame.

let poop = [];
poopTimer number

Elapsed time in milliseconds since Level 2 started. Compared to poopLevelDuration to determine when the player wins.

let poopTimer = 0;
obstacles array

Array of food emoji objects (keys and obstacles) in Level 3 basement. Each has x, y, w, h, emoji, and type ('key' or 'obstacle').

let obstacles = [];
collectedKeys array

Array of collected key emoji strings in Level 3. Length is compared to keysNeeded to determine if the exit unlocks.

let collectedKeys = [];
bodyTemperature number

Player's body temperature in Level 4 (Cold Weather). Decreases over time; reaching 0 causes game over.

let bodyTemperature = 100;
guards array

Array of Guard objects in Level 5 (Guarded Gallery). Each has patrol path, vision cone, and detection logic.

let guards = [];
laserBeams array

Array of LaserBeam objects in Level 6. Each has position, size, direction, and speed for oscillating hazards.

let laserBeams = [];
zombies array

Array of Zombie objects in Level 7. Each has x, y, size, and speed; they chase the player via atan2 targeting.

let zombies = [];
cops array

Array of Cop objects in Level 8 (Store Heist). Spawn after the donut is stolen and chase the player.

let cops = [];
playerYVelocity number

Vertical velocity of the player in the Obby platformer (Level 9). Increases due to gravity; reset on jump.

let playerYVelocity = 0;
gravity number

Gravitational acceleration applied to playerYVelocity each frame in Level 9. Varies by difficulty setting.

let gravity = 0.5;
jumpStrength number

Magnitude of upward velocity when the player jumps in Level 9. Varies by difficulty setting.

let jumpStrength = 10;
isJumping boolean

Tracks whether the player is currently in a jump (true = airborne, false = on ground). Prevents double-jumping.

let isJumping = false;
playerDirectionX number

Horizontal input direction in Level 9: -1 for left, 1 for right, 0 for idle. Set by button/key events.

let playerDirectionX = 0;
worldOffsetX number

Camera scroll offset in Level 9 (Obby). Adjusted by playerDirectionX to create a scrolling platformer effect.

let worldOffsetX = 0;
obbyElements array

Array of platforms and obstacles in Level 9. Each element has type, x, y, w, h; moving obstacles also have speed, direction, min, max.

let obbyElements = [];
airplaneDebris array

Array of Debris objects in Level 11 (Airplane Crash). Each has x, y, size, speed, and emoji; falls downward.

let airplaneDebris = [];
fires array

Array of Fire objects in Level 10 (Burning Building). Static hazards that hurt the player on contact.

let fires = [];
buildingIntegrity number

Structural integrity of the building in Level 10. Decreases over time; reaching 0 causes game over.

let buildingIntegrity = 100;
restartButton object

p5.js HTML button element for restarting the game. Shown when the game ends; clicking calls restartGame().

let restartButton;
jumpButton object

p5.js HTML button element for jumping in Level 9. Responds to both mouse click and touch input.

let jumpButton;
leftButton object

p5.js HTML button element for moving left in Level 9. Sets playerDirectionX to -1 on press, 0 on release.

let leftButton;
rightButton object

p5.js HTML button element for moving right in Level 9. Sets playerDirectionX to 1 on press, 0 on release.

let rightButton;

🔧 Potential Improvements (8)

Here are some ways this code could be enhanced:

BUG displayLevel2Poop() poop update loop

Iterating backward through poop array is correct, but if checkCollision returns true and the function returns early, subsequent poop objects aren't updated or drawn that frame, causing visual glitches.

💡 Separate collision detection from rendering: first update and check all collisions, then draw all remaining poop. Or, don't return early; instead, use a break statement or set a collision flag.

PERFORMANCE displayLevel9ObbyChallenge() collision detection

The function loops through obbyElements three times per frame (once for platforms, once for obstacles, once for drawing)—O(3n) complexity. With many elements, this becomes slow.

💡 Combine the platform and obstacle loops into a single pass, storing results to avoid redundant distance calculations. Or, use spatial partitioning (divide the canvas into a grid) to only check nearby elements.

PERFORMANCE displayLevel7ZombieSurvival() and displayLevel8StoreHeist()

No distance culling—zombies and cops off-screen continue to compute atan2 and update positions, wasting CPU cycles.

💡 Before updating, check if the entity is within a reasonable distance (e.g., 2× screen width/height from the player) and skip updates for distant entities.

STYLE draw() switch statement

12 case statements for 12 levels create repetitive code (case X: displayLevelX(); break;). This is maintainable but verbose.

💡 Use a map or object: `const levelDisplayFunctions = { [LEVEL_1_DOOR]: displayLevel1Door, ... }; levelDisplayFunctions[gameState]?.();` This reduces boilerplate and makes adding levels easier.

BUG initializeGame() and level initialization functions

Variables like obstacleMinSize, poopMinSize, etc., are defined globally but initialized in initializeGame(). If a level's initialization function is called before initializeGame(), these will be undefined.

💡 Initialize all variables with default values at the top of the sketch (e.g., let obstacleMinSize = 0;) so they're never undefined. Or, ensure initializeGame() always runs first.

FEATURE Overall game structure

No save/load system: if the player closes the browser mid-game, all progress is lost. Badge is saved to localStorage but not the current level.

💡 Store gameState and levelJustWon to localStorage when transitioning between levels, then restore them on page reload. Example: localStorage.setItem('currentLevel', gameState);

BUG displayAnnouncementScreen()

The announcement screen has no visual feedback that it's clickable. Players may not realize they need to click again.

💡 Add text like 'Click to continue' at the bottom, or fade the text in/out to draw attention. Use a hover cursor change: canvas { cursor: pointer; } in CSS.

PERFORMANCE Emoji rendering in displayLevel3Basement() and other levels

textSize() is called for every emoji every frame. This is inefficient because the size rarely changes within a level.

💡 Call textSize() once during initialization, not in the render loop. If size changes are needed, cache the value and only call textSize() when it changes.

🔄 Code Flow

Code flow showing setup, draw, checkcollision, decreaselife, spawmpoop, displayLevel2Poop, displayLevel9ObbyChallenge, mouseclicked, initializeLevel9ObbychAllenge

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> canvas-setup[Canvas Configuration] setup --> button-creation[Button Creation and Styling] setup --> obby-buttons[Obby Control Buttons] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-setup href "#sub-canvas-setup" click button-creation href "#sub-button-creation" click obby-buttons href "#sub-obby-buttons" draw --> state-switch[Game State Switch] draw --> background-clear[Background Clear] click draw href "#fn-draw" click state-switch href "#sub-state-switch" click background-clear href "#sub-background-clear" state-switch -->|gameState| displayLevel2Poop[displayLevel2Poop] state-switch -->|gameState| displayLevel9ObbyChallenge[displayLevel9ObbyChallenge] displayLevel2Poop --> player-update[Player Position Update] displayLevel2Poop --> poop-update-loop[Poop Update and Collision Loop] displayLevel2Poop --> timer-management[Timer and Display] displayLevel2Poop --> level-transition[Level Completion Check] click displayLevel2Poop href "#fn-displayLevel2Poop" click player-update href "#sub-player-update" click poop-update-loop href "#sub-poop-update-loop" click timer-management href "#sub-timer-management" click level-transition href "#sub-level-transition" poop-update-loop --> off-screen-removal[Off-Screen Poop Removal] poop-update-loop --> collision-dispatch[Collision Type Dispatch] click off-screen-removal href "#sub-off-screen-removal" click collision-dispatch href "#sub-collision-dispatch" collision-dispatch -->|type| rect-rect-helper[Rectangle-Rectangle AABB Collision] collision-dispatch -->|type| rect-circle-helper[Rectangle-Circle Collision] click rect-rect-helper href "#sub-rect-rect-helper" click rect-circle-helper href "#sub-rect-circle-helper" displayLevel9ObbyChallenge --> gravity-physics[Gravity and Vertical Velocity] displayLevel9ObbyChallenge --> platform-collision[Platform Landing Detection] displayLevel9ObbyChallenge --> obstacle-collision[Obstacle Collision and Movement] displayLevel9ObbyChallenge --> ground-collision[Ground Landing] displayLevel9ObbyChallenge --> exit-check[Exit Zone Collision] click displayLevel9ObbyChallenge href "#fn-displayLevel9ObbyChallenge" click gravity-physics href "#sub-gravity-physics" click platform-collision href "#sub-platform-collision" click obstacle-collision href "#sub-obstacle-collision" click ground-collision href "#sub-ground-collision" click exit-check href "#sub-exit-check" mouseclicked[mouseClicked] --> door-handle-click[Door Handle Detection] click mouseclicked href "#fn-mouseclicked" click door-handle-click href "#sub-door-handle-click" decreaselife[decreaseLife] --> life-decrement[Decrement Lives] decreaselife --> obby-exemption[Obby Level Exemption] decreaselife --> game-over-check[Game Over Check] click decreaselife href "#fn-decreaselife" click life-decrement href "#sub-life-decrement" click obby-exemption href "#sub-obby-exemption" click game-over-check href "#sub-game-over-check" spawmpoop[spawnPoop] --> random-size[Random Size Generation] spawmpoop --> edge-selection[Random Edge Selection] spawmpoop --> spawn-logic[Edge-Specific Spawn Logic] spawmpoop --> push-object[Push to Poop Array] click spawmpoop href "#fn-spawnPoop" click random-size href "#sub-random-size" click edge-selection href "#sub-edge-selection" click spawn-logic href "#sub-spawn-logic" click push-object href "#sub-push-object" initializeLevel9ObbyChallenge[initializeLevel9ObbyChallenge] --> difficulty-loading[Load Difficulty Settings] initializeLevel9ObbyChallenge --> element-spawning[Spawn Obby Elements] click initializeLevel9ObbyChallenge href "#fn-initializeLevel9ObbychAllenge" click difficulty-loading href "#sub-difficulty-loading" click element-spawning href "#sub-element-spawning"

❓ Frequently Asked Questions

What visual elements can users expect to see in the Door (Remix) sketch?

The sketch features a series of creatively designed levels, including doors, obstacles, and various thematic environments that change as users progress through the game.

How do users interact with the Door (Remix) sketch during gameplay?

Players navigate through different levels by avoiding obstacles and completing challenges, using keyboard or mouse controls to interact with the game elements.

What coding concepts does the Door (Remix) sketch showcase?

This sketch demonstrates game state management, dynamic object spawning, and the use of local storage to track player progress and achievements.

Preview

Door (Remix) - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Door (Remix) - Code flow showing setup, draw, checkcollision, decreaselife, spawmpoop, displayLevel2Poop, displayLevel9ObbyChallenge, mouseclicked, initializeLevel9ObbychAllenge
Code Flow Diagram