Sketch 2026-03-01 22:19

This sketch creates a dodge-and-survive game where the player controls a red rectangle using arrow keys or WASD to avoid falling blue objects. The score increases with each dodged object, the game difficulty escalates with faster falling speeds and more frequent spawns, and the game ends on collision.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the player color — The player will instantly become a different color, making it easier to see how fill() works in p5.js
  2. Make objects fall faster initially — Objects will start falling much faster, making the game hard from the beginning instead of gradually ramping up
  3. Spawn objects more frequently — Objects will appear on screen much more often, overwhelming the player faster and demanding quicker reflexes
  4. Make the player bigger — The red rectangle will be much larger, making collisions easier to see but also harder to avoid falling objects
  5. Slow down the difficulty curve — The game will get harder much more gradually after each dodge instead of ramping up quickly
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a playable dodge-and-survive game that teaches game development fundamentals in p5.js. A red rectangle (the player) moves left and right to avoid blue squares falling from the top of the screen. Every successfully dodged object increases the score and makes the game harder by speeding up falling objects and spawning them more frequently. The game ends instantly on collision, displaying the final score and a restart prompt.

The code is organized around three core systems: player movement controlled by keyboard input, a spawning system that creates new falling objects at timed intervals, and a collision detection loop that checks whether objects touch the player. By reading this sketch, you will learn how to manage multiple game objects in arrays, use AABB (axis-aligned bounding box) collision detection, implement progressive difficulty, and structure a game state machine with 'playing' and 'gameOver' modes.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas and initializes the player rectangle at the bottom center with starting speed and position values.
  2. Every frame, draw() clears the background and checks the gameState variable. If 'playing', it handles player movement, spawns falling objects, updates all objects, and checks for collisions.
  3. Player movement is handled by detecting keyIsDown() for arrow keys or WASD; the player's x position is constrained to stay within canvas bounds.
  4. Every 60 frames (initially), a new falling object is added to the fallingObjects array at a random x position above the canvas.
  5. The main loop iterates backward through all falling objects, moving each one down by its speed value, drawing it in blue, and checking AABB collision against the player.
  6. When an object leaves the bottom of the screen, it is removed from the array, the score increases, and objectSpeed and objectSpawnRate change to make the game harder.
  7. If collision is detected, gameState becomes 'gameOver', the game freezes, and displays the final score until the player presses Space to restart via keyPressed().

🎓 Concepts You'll Learn

Game state machineCollision detection (AABB)Array managementKeyboard inputProgressive difficultyObject spawningCanvas boundary constraints

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It prepares the canvas and initializes all variables that the game will use. The player object is a convenient way to bundle related data together.

function setup() {
  createCanvas(windowWidth, windowHeight);
  
  // Initialize the player object
  player = {
    x: width / 2,
    y: height - 50,
    width: 60,
    height: 20,
    speed: 5
  };
  
  resetGame(); // Set initial game state
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Canvas Sizing createCanvas(windowWidth, windowHeight);

Creates a canvas that fills the entire browser window dynamically

calculation Player Object Initialization player = {

Creates an object that stores all player properties (position, size, speed) in one place

createCanvas(windowWidth, windowHeight);
Creates a p5.js canvas sized to the full width and height of the browser window, making the game responsive
player = {
Starts creating a JavaScript object to store all player data (position, dimensions, speed) together
x: width / 2,
Sets the player's starting x position to the horizontal center of the canvas
y: height - 50,
Places the player 50 pixels from the bottom of the canvas where it should stay
width: 60,
The player rectangle is 60 pixels wide—used for drawing and collision detection
height: 20,
The player rectangle is 20 pixels tall—a narrow horizontal bar that fits the dodge-game aesthetic
speed: 5
The player moves 5 pixels per frame when a movement key is held down
resetGame();
Calls resetGame() to initialize score, objectSpeed, objectSpawnRate, and clear any previous game data

resetGame()

resetGame() is called from setup() and also from keyPressed() when the player presses Space after losing. It centralizes all the 'reset to start' logic in one place, making the code easier to maintain and change.

function resetGame() {
  score = 0;
  objectSpeed = 3;
  objectSpawnRate = 60;
  fallingObjects = []; // Clear all falling objects
  gameState = 'playing'; // Set game state to playing
}
Line-by-line explanation (5 lines)
score = 0;
Resets the score counter to 0 so the player starts fresh
objectSpeed = 3;
Resets object falling speed to the initial difficulty—the first game will always be the same speed
objectSpawnRate = 60;
Resets the spawn interval to 60 frames, so new objects appear at the starting frequency
fallingObjects = [];
Clears the array of all falling objects so the screen is empty at the start of a new game
gameState = 'playing';
Sets the game state to 'playing' so draw() will run the game logic instead of showing the game-over screen

draw()

draw() is the heart of the game—it runs 60 times per second and handles player input, spawning, physics updates, collision detection, and rendering. Notice how it uses a single gameState variable to branch into completely different behavior (playing vs. gameOver). This pattern is called a state machine and is powerful for games with multiple modes.

🔬 These two blocks let you move left and right. What happens if you change -= to += in the first block? What would the player do then?

    if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) { // LEFT_ARROW or 'A' key
      player.x -= player.speed;
    }
    if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) { // RIGHT_ARROW or 'D' key
      player.x += player.speed;
    }

🔬 These four lines run every time you dodge an object. What happens if you remove the line that increases objectSpeed so the objects never get faster?

      // Remove objects that go off-screen and update score
      if (obj.y > height + obj.height / 2) {
        fallingObjects.splice(i, 1); // Remove object from array
        score++; // Increase score
        objectSpeed += 0.05; // Slightly increase object speed for difficulty
        objectSpawnRate = max(30, objectSpawnRate - 0.5); // Slightly decrease spawn rate (more objects)
function draw() {
  background(220); // Clear the background each frame

  if (gameState === 'playing') {
    // --- Player Movement ---
    if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) { // LEFT_ARROW or 'A' key
      player.x -= player.speed;
    }
    if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) { // RIGHT_ARROW or 'D' key
      player.x += player.speed;
    }

    // Keep player within canvas boundaries
    player.x = constrain(player.x, player.width / 2, width - player.width / 2);

    // --- Spawn New Objects ---
    // Spawn a new object every 'objectSpawnRate' frames
    if (frameCount % objectSpawnRate === 0) {
      fallingObjects.push({
        x: random(player.width / 2, width - player.width / 2), // Random x position
        y: -10, // Start just above the canvas
        width: 30,
        height: 30,
        speed: objectSpeed
      });
    }

    // --- Update, Draw, and Check Collisions for Objects ---
    // Loop through falling objects from end to beginning to safely remove them
    for (let i = fallingObjects.length - 1; i >= 0; i--) {
      let obj = fallingObjects[i];
      obj.y += obj.speed; // Move object down

      // Draw object
      fill(0, 100, 200); // Blue color
      noStroke();
      rectMode(CENTER); // Draw rectangle from its center
      rect(obj.x, obj.y, obj.width, obj.height);

      // Check collision with player (simple AABB collision detection)
      // This checks if the bounding boxes of the player and object overlap
      if (
        player.x - player.width / 2 < obj.x + obj.width / 2 && // Player's left edge is left of object's right edge
        player.x + player.width / 2 > obj.x - obj.width / 2 && // Player's right edge is right of object's left edge
        player.y - player.height / 2 < obj.y + obj.height / 2 && // Player's top edge is above object's bottom edge
        player.y + player.height / 2 > obj.y - obj.height / 2    // Player's bottom edge is below object's top edge
      ) {
        // Collision detected
        gameState = 'gameOver';
      }

      // Remove objects that go off-screen and update score
      if (obj.y > height + obj.height / 2) {
        fallingObjects.splice(i, 1); // Remove object from array
        score++; // Increase score
        objectSpeed += 0.05; // Slightly increase object speed for difficulty
        objectSpawnRate = max(30, objectSpawnRate - 0.5); // Slightly decrease spawn rate (more objects)
      }
    }

    // --- Draw Player ---
    fill(255, 0, 0); // Red color
    rectMode(CENTER);
    rect(player.x, player.y, player.width, player.height);

    // --- Display Score ---
    fill(0); // Black color
    textSize(24);
    textAlign(LEFT, TOP);
    text('Score: ' + score, 10, 10);

  } else if (gameState === 'gameOver') {
    // --- Game Over Screen ---
    fill(0);
    textSize(48);
    textAlign(CENTER, CENTER);
    text('Game Over!', width / 2, height / 2 - 50);
    textSize(32);
    text('Final Score: ' + score, width / 2, height / 2);
    textSize(24);
    text('Press Space to Restart', width / 2, height / 2 + 50);
  }
}
Line-by-line explanation (29 lines)

🔧 Subcomponents:

conditional Keyboard-Controlled Movement if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) {

Detects when left arrow or 'A' is held and moves the player left

calculation Boundary Constraint player.x = constrain(player.x, player.width / 2, width - player.width / 2);

Keeps the player from moving off the left or right edge of the canvas

conditional Timed Object Spawning if (frameCount % objectSpawnRate === 0) {

Spawns a new falling object every objectSpawnRate frames

for-loop Object Update and Collision Check for (let i = fallingObjects.length - 1; i >= 0; i--) {

Iterates through all falling objects backward to update them, draw them, check collisions, and safely remove them

conditional AABB Collision Detection if ( player.x - player.width / 2 < obj.x + obj.width / 2 &&

Compares the bounding boxes of the player and object to detect overlaps

conditional Off-Screen Object Removal if (obj.y > height + obj.height / 2) {

Removes objects that have fallen off the bottom, increments score, and increases difficulty

background(220);
Clears the entire canvas to a light gray each frame, erasing the previous frame's drawings to create fresh animation
if (gameState === 'playing') {
Checks if the game is still running; if 'playing', executes all game logic; if 'gameOver', shows the end screen instead
if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) {
Checks if either the left arrow key OR the 'A' key is currently held down (65 is the key code for 'A')
player.x -= player.speed;
Subtracts the player's speed from their x position, moving them left by 5 pixels per frame
if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) {
Checks if either the right arrow key OR the 'D' key is held down (68 is the key code for 'D')
player.x += player.speed;
Adds the player's speed to their x position, moving them right by 5 pixels per frame
player.x = constrain(player.x, player.width / 2, width - player.width / 2);
Uses constrain() to ensure the player's x position stays within the canvas—prevents them from escaping off the edges
if (frameCount % objectSpawnRate === 0) {
The modulo operator (%) checks if frameCount is evenly divisible by objectSpawnRate; this triggers every N frames to spawn new objects
fallingObjects.push({
Creates a new falling object and adds it to the fallingObjects array using push()
x: random(player.width / 2, width - player.width / 2),
Generates a random x position between the canvas edges, guaranteeing new objects spawn within the playable area
y: -10,
Starts the new object 10 pixels above the top of the canvas so it appears to fall down onto the screen
for (let i = fallingObjects.length - 1; i >= 0; i--) {
Loops backward through the array (from last object to first) so that when objects are removed with splice(), the indices don't get confused
obj.y += obj.speed;
Moves each object down by adding its speed value to its y position each frame, creating the falling motion
fill(0, 100, 200);
Sets the fill color to blue (RGB: 0 red, 100 green, 200 blue) for all subsequent rect() calls
rectMode(CENTER);
Tells p5.js that the x,y coordinates passed to rect() should represent the CENTER of the rectangle, not its top-left corner
rect(obj.x, obj.y, obj.width, obj.height);
Draws a blue rectangle at the object's position with its width and height
player.x - player.width / 2 < obj.x + obj.width / 2 &&
First collision check: the player's left edge must be to the LEFT of the object's right edge
player.x + player.width / 2 > obj.x - obj.width / 2 &&
Second check: the player's right edge must be to the RIGHT of the object's left edge
player.y - player.height / 2 < obj.y + obj.height / 2 &&
Third check: the player's top edge must be ABOVE the object's bottom edge
player.y + player.height / 2 > obj.y - obj.height / 2
Fourth check: the player's bottom edge must be BELOW the object's top edge—all four conditions must be true for a collision
gameState = 'gameOver';
Changes the game state to 'gameOver' when collision is detected, which stops the game logic and shows the end screen on the next frame
if (obj.y > height + obj.height / 2) {
Checks if an object has fallen completely off the bottom of the canvas (y position is beyond the canvas height)
fallingObjects.splice(i, 1);
Removes the object from the array starting at index i and removing 1 element, cleaning up memory
score++;
Increments the score by 1 each time the player successfully dodges an object
objectSpeed += 0.05;
Increases the falling speed very slightly (0.05 pixels per frame) each time an object is dodged, making the game progressively harder
objectSpawnRate = max(30, objectSpawnRate - 0.5);
Decreases spawn rate by 0.5 (objects appear more frequently), but uses max(30, ...) to never let it go below 30 to prevent spawning too fast
fill(255, 0, 0);
Sets fill color to red (pure red) for drawing the player
text('Score: ' + score, 10, 10);
Draws the current score in the top-left corner of the canvas, updating every frame
text('Game Over!', width / 2, height / 2 - 50);
When the game ends, displays 'Game Over!' centered horizontally, slightly above the middle of the screen

keyPressed()

keyPressed() is a p5.js callback function that runs automatically whenever ANY key is pressed. By checking the gameState and key variables, we can respond differently to player input based on the current game mode. This is cleaner than tracking key presses every frame in draw().

function keyPressed() {
  // If game is over and Spacebar is pressed, restart the game
  if (gameState === 'gameOver' && key === ' ') {
    resetGame();
  }
}
Line-by-line explanation (2 lines)
if (gameState === 'gameOver' && key === ' ') {
Checks TWO conditions: the game must be over AND the key pressed must be the spacebar (represented as ' ')
resetGame();
Calls resetGame() to clear the score, reset speeds, empty the falling objects array, and set gameState back to 'playing'

windowResized()

windowResized() is a p5.js callback that automatically triggers whenever the browser window is resized. Without this function, the canvas would not adapt and the game would break. This makes the sketch responsive and playable on any screen size or when the window is dragged to resize.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Re-center player if window is resized
  player.x = width / 2;
  player.y = height - 50;
}
Line-by-line explanation (3 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the new browser window width and height when the window is resized
player.x = width / 2;
Re-centers the player horizontally to the new canvas center after the resize
player.y = height - 50;
Resets the player's vertical position to maintain its distance from the bottom in the new canvas size

📦 Key Variables

player object

Stores all player data including position (x, y), dimensions (width, height), and speed for movement

let player = { x: 200, y: 350, width: 60, height: 20, speed: 5 };
fallingObjects array

Holds all currently active falling objects on screen; each object has x, y, width, height, and speed properties

let fallingObjects = [];
score number

Tracks how many objects the player has successfully dodged; increments by 1 each time an object leaves the bottom

let score = 0;
gameState string

Tracks whether the game is 'playing' or 'gameOver'; controls which logic branch executes in draw()

let gameState = 'playing';
objectSpeed number

The current falling speed of all objects; starts at 3 and increases by 0.05 after each successful dodge

let objectSpeed = 3;
objectSpawnRate number

How many frames must pass before a new object spawns; lower values = more frequent spawns; starts at 60 and decreases with difficulty

let objectSpawnRate = 60;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG draw() collision detection

AABB collision is overly generous because player and objects use CENTER mode—hitboxes can be imprecise at edges

💡 Use consistent rectMode for all rectangles or add visual debugging with stroke() to verify hitbox alignment matches player expectations

PERFORMANCE draw() spawning

If the player is skilled and reaches high scores, fallingObjects array can grow very large with old-but-lingering objects

💡 Add a safety cleanup: after 10 seconds off-screen, force-remove any object that shouldn't still exist to prevent memory creep

STYLE draw()

Magic numbers like -10 (spawn y), -50 (player y), and 25 (object size) are scattered throughout without explanation

💡 Define these as named constants at the top (e.g., const SPAWN_Y = -10, PLAYER_START_Y = height - 50) to make code more readable and tweakable

FEATURE global/game design

Game difficulty is tied only to score, not to time—a skilled player can reach very high speeds and spawn rates indefinitely

💡 Add a difficulty cap (e.g., max objectSpeed = 12, min objectSpawnRate = 15) or introduce a time-based hard limit so late-game becomes a plateau instead of infinitely harder

FEATURE keyPressed()

Only spacebar restarts; players may miss the instruction or expect Enter to work

💡 Allow multiple keys to restart: if (gameState === 'gameOver' && (key === ' ' || key === 'Enter'))

🔄 Code Flow

Code flow showing setup, resetgame, draw, keypressed, windowresized

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

graph TD start[Start] --> setup[setup] setup --> resetgame[resetGame] resetgame --> playerinit[player-init] playerinit --> draw[draw loop] draw --> canvascreation[canvas-creation] draw --> playermovement[player-movement] draw --> playerboundary[player-boundary] draw --> spawnlogic[spawn-logic] draw --> collisionloop[collision-loop] collisionloop --> aabbcollision[aabb-collision] collisionloop --> offscreenremoval[offscreen-removal] click setup href "#fn-setup" click resetgame href "#fn-resetgame" click draw href "#fn-draw" click playerinit href "#sub-player-init" click canvascreation href "#sub-canvas-creation" click playermovement href "#sub-player-movement" click playerboundary href "#sub-player-boundary" click spawnlogic href "#sub-spawn-logic" click collisionloop href "#sub-collision-loop" click aabbcollision href "#sub-aabb-collision" click offscreenremoval href "#sub-offscreen-removal" playermovement -->|left arrow or 'A' pressed| playerboundary playerboundary -->|within bounds| spawnlogic spawnlogic -->|every objectSpawnRate frames| collisionloop collisionloop -->|iterate through falling objects| aabbcollision aabbcollision -->|detect overlaps| offscreenremoval offscreenremoval -->|remove off-screen objects| draw draw --> keypressed[keyPressed] keypressed -->|check gameState| draw draw --> windowresized[windowResized] windowresized -->|resize canvas| draw

❓ Frequently Asked Questions

What visual experience does the p5.js sketch create?

The sketch generates a dynamic game environment where the player controls a movable object at the bottom of the screen, while various objects fall from above, creating a visually engaging challenge.

How can users interact with this creative coding sketch?

Users can interact by using the left and right arrow keys or 'A' and 'D' keys to move the player object and avoid the falling objects.

What creative coding techniques are demonstrated in this sketch?

This sketch showcases object spawning, collision detection, and user input handling, which are essential techniques in game development using p5.js.

Preview

Sketch 2026-03-01 22:19 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch 2026-03-01 22:19 - Code flow showing setup, resetgame, draw, keypressed, windowresized
Code Flow Diagram