Sketch 2026-04-17 17:30

This sketch creates a Geometry Dash-style endless runner game where a player-controlled square jumps over randomly generated obstacles to survive as long as possible. The game features gravity-based physics, collision detection, score tracking, and sound effects that respond to player actions and game events.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the game faster — Obstacles move toward you more quickly, making dodging harder and more exciting
  2. Higher, more floaty jumps — The jump force becomes more negative, launching the player higher and keeping them in the air longer before gravity pulls them down
  3. Reduce gravity for slower falls — Lower gravity makes the player fall more slowly, giving more time to react and creating a floaty feel
  4. Change the player color to red — The player square becomes bright red instead of neon blue, changing the visual feel of the game
  5. Spawn obstacles more frequently — Obstacles appear more often, creating a denser, more challenging stream of hazards
  6. Make obstacles much larger — All obstacles spawn bigger, reducing the available space to dodge and making the game significantly harder
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a playable endless runner game inspired by Geometry Dash, where you control a square that jumps over randomly spawning obstacles (spikes and blocks). What makes this sketch visually and mechanically interesting is that it combines several advanced p5.js techniques: gravity-based physics simulation that makes the jump feel natural, collision detection between different shape types, procedural obstacle generation that keeps the game challenging, and sound effects triggered by player actions using p5.sound.

The code is organized into clear sections: player physics and input handling, obstacle generation and movement, collision detection logic, and game state management. By reading this sketch you will learn how to build a complete interactive game loop, implement gravity-based jumping mechanics, detect collisions between rectangles and triangles, spawn procedural content based on timing, and structure game states (playing vs. game over). This is one of the best examples of turning game design into p5.js code.

⚙️ How It Works

  1. When the sketch starts, setup() initializes the canvas to fill the window, creates the player square on the left side of the screen, and begins the audio context (required for web audio on modern browsers).
  2. Every frame, the draw() loop calls updatePlayer() to apply gravity and check if the player is on the ground, then updateObstacles() to move all obstacles left across the screen and randomly spawn new ones.
  3. When the player taps, clicks, or presses spacebar, the jump() function sets playerVelocityY to a negative value, causing the square to arc upward while gravity pulls it back down.
  4. As obstacles scroll left toward the player, checkCollisions() tests whether the player rectangle overlaps with any obstacle rectangle (for blocks) or triangle (for spikes) and ends the game if they touch.
  5. The score increases every frame, simulating distance traveled, and persists as highScore even after game over.
  6. When a collision occurs, endGame() changes the game state to 'gameOver', halts the draw loop, and plays a low-pitched sound. Tapping or pressing any key calls restartGame() to reset all variables and resume play.

🎓 Concepts You'll Learn

Physics simulation (gravity and velocity)Collision detection (AABB and triangle overlap)Game state managementProcedural content generationInput handling (keyboard, mouse, touch)Sound synthesis with p5.soundAnimation loop and timing

📝 Code Breakdown

preload()

preload() runs before setup() and is where p5.js loads external resources like images, fonts, and sounds. Starting audio generators here ensures they're ready the moment a jump or collision happens.

function preload() {
  // Load sound effects
  // Using p5.Noise for simple sounds, but you could load .wav or .mp3 files
  jumpSound = new p5.Noise('white'); // 'white', 'pink', 'brown'
  jumpSound.amp(0); // Start silent
  jumpSound.start();

  gameOverSound = new p5.Noise('brown');
  gameOverSound.amp(0); // Start silent
  gameOverSound.start();
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Jump sound creation jumpSound = new p5.Noise('white');

Creates a white noise oscillator for the jump sound effect

calculation Game over sound creation gameOverSound = new p5.Noise('brown');

Creates a brown noise oscillator for the game over sound effect

jumpSound = new p5.Noise('white');
Creates a white noise generator—this is the raw sound object we'll trigger when jumping. 'white' noise contains all frequencies at equal volume
jumpSound.amp(0);
Sets the initial amplitude (volume) to 0 so the sound is silent until we explicitly play it
jumpSound.start();
Starts the noise generator running in the background; it's silent but ready to become audible instantly when we need it
gameOverSound = new p5.Noise('brown');
Creates a brown noise generator for the game over sound—'brown' noise has more bass frequencies than white noise
gameOverSound.amp(0);
Keeps the game over sound silent until the collision happens
gameOverSound.start();
Starts the game over sound generator running in the background

setup()

setup() runs once when the sketch starts. Use it to initialize canvas properties, load resources, and set starting variable values. Everything you configure here affects the entire game.

function setup() {
  createCanvas(windowWidth, windowHeight);
  colorMode(HSB, 360, 100, 100); // Use HSB for vibrant colors
  noStroke(); // No outlines on shapes
  textSize(24);
  textAlign(CENTER, CENTER);

  // Initialize player position
  playerX = width / 4;
  playerY = height - playerSize - 50; // 50 pixels above the bottom ground line

  // Start audio on user interaction
  userStartAudio(); // IMPORTANT for web audio
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Canvas setup createCanvas(windowWidth, windowHeight);

Creates a canvas that fills the entire browser window

calculation Player initialization playerX = width / 4; playerY = height - playerSize - 50;

Positions the player on the left side of the screen, 50 pixels above the ground line

createCanvas(windowWidth, windowHeight);
Creates a canvas as wide and tall as the entire browser window, making the game fullscreen
colorMode(HSB, 360, 100, 100);
Switches from RGB to HSB (Hue, Saturation, Brightness) color mode for more vibrant colors—this makes neon colors easier to define
noStroke();
Removes outlines from all shapes drawn after this, giving the game a cleaner look
textSize(24);
Sets the font size for all text displays (score, game over message) to 24 pixels
textAlign(CENTER, CENTER);
Aligns all text to be centered both horizontally and vertically around its (x, y) coordinate
playerX = width / 4;
Positions the player 1/4 of the way across the screen horizontally, giving obstacles room to spawn on the right
playerY = height - playerSize - 50;
Places the player on the ground: the ground line is 50 pixels from the bottom, so the player's center is at (height - 50 - playerSize/2)
userStartAudio();
Initializes the Web Audio API context, which is required by modern browsers before playing any sound. Without this, p5.sound won't work

draw()

draw() is the heart of every p5.js sketch—it runs 60 times per second and contains all the animation and game logic. The structure here is typical: check game state, update positions and logic, then draw everything on screen.

🔬 This conditional structure chooses what to draw based on gameState. What if you add a third state called 'paused'? Can you add another else-if branch that displays 'PAUSED' text on screen?

  if (gameState === 'playing') {
    updatePlayer();
    updateObstacles();
    checkCollisions();
    displayScore();
  } else if (gameState === 'gameOver') {
    displayGameOver();
  }
function draw() {
  background(0, 0, 0); // Black background

  if (gameState === 'playing') {
    updatePlayer();
    updateObstacles();
    checkCollisions();
    displayScore();
  } else if (gameState === 'gameOver') {
    displayGameOver();
  }

  drawPlayer();
  drawObstacles();
  drawGround();
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

conditional Playing game logic if (gameState === 'playing') { updatePlayer(); updateObstacles(); checkCollisions(); displayScore(); }

When the game is active, update physics, spawn obstacles, check for collisions, and show the score

conditional Game over display } else if (gameState === 'gameOver') { displayGameOver(); }

When the game has ended, display the game over screen and score instead of updating

background(0, 0, 0);
Clears the canvas to black every frame, erasing the previous frame's drawing and creating the animation
if (gameState === 'playing') {
Checks if the game is currently active; if yes, run all game logic
updatePlayer();
Applies gravity, updates the player's position, checks if they're on the ground, and increases score
updateObstacles();
Moves all obstacles leftward across the screen and spawns new obstacles on a random schedule
checkCollisions();
Tests whether the player rectangle overlaps any obstacle; if yes, ends the game
displayScore();
Draws the current score and high score on the screen
} else if (gameState === 'gameOver') {
If the game has ended, skip all game logic and instead display the game over screen
drawPlayer();
Always draws the player on screen, whether the game is playing or over
drawObstacles();
Always draws all obstacles on screen
drawGround();
Always draws the ground line at the bottom of the screen

updatePlayer()

This function demonstrates classic gravity-based physics: velocity increases every frame (acceleration), and position increases by velocity (motion). The ground collision check is crucial—without it, the player would fall infinitely. Note that playerVelocityY is only reset on the ground, not in mid-air, allowing the player to accelerate downward while jumping.

🔬 This code checks the player's bottom edge. The expression playerY + playerSize / 2 is the bottom edge position. What if you changed >= to > instead? Will the player behavior change? Why or why not?

  // Prevent player from falling through the ground
  if (playerY + playerSize / 2 >= height - 50) {
    playerY = height - playerSize / 2 - 50;
    playerVelocityY = 0;
    isOnGround = true;
  } else {
    isOnGround = false;
  }
function updatePlayer() {
  // Apply gravity
  playerVelocityY += gravity;
  playerY += playerVelocityY;

  // Prevent player from falling through the ground
  if (playerY + playerSize / 2 >= height - 50) {
    playerY = height - playerSize / 2 - 50;
    playerVelocityY = 0;
    isOnGround = true;
  } else {
    isOnGround = false;
  }

  // Score increases as time passes (simulating distance)
  score += 0.5;
  if (score > highScore) {
    highScore = score;
  }
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

calculation Gravity and velocity update playerVelocityY += gravity; playerY += playerVelocityY;

Accelerates the player downward due to gravity, then moves them by their current velocity

conditional Ground collision detection if (playerY + playerSize / 2 >= height - 50) { playerY = height - playerSize / 2 - 50; playerVelocityY = 0; isOnGround = true; } else { isOnGround = false; }

Stops the player from sinking below the ground and marks when they can jump again

calculation Score increment score += 0.5; if (score > highScore) { highScore = score; }

Increases the score every frame to simulate distance traveled and tracks the highest score ever achieved

playerVelocityY += gravity;
Every frame, add the gravity value to playerVelocityY, making downward velocity increase over time—this is what causes objects to accelerate when falling
playerY += playerVelocityY;
Move the player downward by their current velocity; the further down they fall, the faster they move
if (playerY + playerSize / 2 >= height - 50) {
Check if the player's bottom edge (their center Y plus half their size) has reached or passed the ground line (50 pixels from the bottom)
playerY = height - playerSize / 2 - 50;
Snap the player to the exact ground position so they don't sink through it
playerVelocityY = 0;
Stop the player's downward motion by resetting velocity to zero—this prevents them from accumulating downward speed while standing
isOnGround = true;
Mark that the player is touching the ground, which allows them to jump on the next input
} else { isOnGround = false; }
If the player is above the ground, mark that they are not on the ground and cannot jump again until they land
score += 0.5;
Increase the score by 0.5 every frame—since the sketch runs at 60 FPS, this adds 30 points per second
if (score > highScore) {
After updating the score, check if it has beaten the previous record
highScore = score;
If the current score is higher, update highScore so it persists through game overs

drawPlayer()

This is a simple drawing function that uses the current player position variables to render a visual representation. It's called every frame in draw(), so the player moves across the screen because playerX and playerY are updated by physics calculations.

function drawPlayer() {
  fill(180, 100, 100); // Neon blue
  // Draw the player as a square
  rectMode(CENTER);
  rect(playerX, playerY, playerSize, playerSize, 5); // Rounded corners
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Player color fill(180, 100, 100);

Sets the fill color to neon blue (in HSB mode)

calculation Player shape rect(playerX, playerY, playerSize, playerSize, 5);

Draws a square with slightly rounded corners at the player's current position

fill(180, 100, 100);
Sets the fill color: in HSB mode, 180 is cyan-blue hue, 100 is full saturation (vivid), and 100 is full brightness
rectMode(CENTER);
Changes how rect() interprets coordinates; CENTER means the x,y values are the center of the rectangle, not the top-left corner
rect(playerX, playerY, playerSize, playerSize, 5);
Draws a square centered at (playerX, playerY) with width and height both equal to playerSize (40 pixels), and 5-pixel rounded corners for a softer look

jump()

The jump() function shows how to combine game logic (checking if jumping is allowed) with sound effects (p5.sound). The isOnGround guard prevents double-jumping—a classic game mechanic. The sound uses setTimeout to schedule the fade-out, demonstrating how Web Audio events can be timed independently of the draw loop.

🔬 This code controls the jump sound's volume and duration. What happens if you change the 100 millisecond timeout to 500 milliseconds? Will the jump sound last longer or shorter?

    jumpSound.amp(0.3, 0.05); // Fade in to 0.3 amplitude
    jumpSound.freq(440); // A4 note
    setTimeout(() => jumpSound.amp(0, 0.5), 100); // Fade out after 100ms
function jump() {
  if (isOnGround) {
    playerVelocityY = jumpForce;
    isOnGround = false;

    // Play jump sound
    jumpSound.amp(0.3, 0.05); // Fade in to 0.3 amplitude
    jumpSound.freq(440); // A4 note
    setTimeout(() => jumpSound.amp(0, 0.5), 100); // Fade out after 100ms
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Ground check if (isOnGround) {

Only allow jumping when the player is touching the ground

calculation Apply jump velocity playerVelocityY = jumpForce;

Set upward velocity to launch the player into the air

calculation Jump sound effect jumpSound.amp(0.3, 0.05); jumpSound.freq(440); setTimeout(() => jumpSound.amp(0, 0.5), 100);

Triggers the jump sound with a fade-in, sets the pitch, and fades it out after 100 milliseconds

if (isOnGround) {
Check whether the player is currently touching the ground; if not, don't allow a jump (prevents double-jumping)
playerVelocityY = jumpForce;
Set the vertical velocity to jumpForce (which is -12, a negative value). Negative velocity moves upward because Y increases downward in p5.js
isOnGround = false;
Mark that the player is no longer on the ground, preventing them from jumping again until they land
jumpSound.amp(0.3, 0.05);
Fade the jump sound in to volume 0.3 over 0.05 seconds (50 milliseconds), making it instantly audible
jumpSound.freq(440);
Set the sound frequency to 440 Hz, which is the musical note A4—this defines the pitch of the jump sound
setTimeout(() => jumpSound.amp(0, 0.5), 100);
After 100 milliseconds, fade the sound out to silence over 0.5 seconds. This prevents the jump sound from playing indefinitely

Obstacle(x, y, w, h, type)

This constructor function demonstrates object-oriented programming in JavaScript. Each Obstacle instance stores its own position and size, and has its own methods (move and display) that act on that data. This pattern scales well—you can create hundreds of obstacles, each one independently updated and drawn.

🔬 This triangle has three vertices: two at the bottom and one at the top, creating an upward-pointing spike. What if you swapped the first and third vertices? Would the spike point down instead of up?

      triangle(this.x, this.y + this.h, // Bottom-left
               this.x + this.w / 2, this.y, // Top-middle
               this.x + this.w, this.y + this.h); // Bottom-right
function Obstacle(x, y, w, h, type) {
  this.x = x;
  this.y = y;
  this.w = w;
  this.h = h;
  this.type = type; // 'spike' or 'block'

  this.move = function() {
    this.x -= obstacleSpeed;
  };

  this.display = function() {
    fill(60, 100, 100); // Neon green
    if (this.type === 'block') {
      rectMode(CORNER);
      rect(this.x, this.y, this.w, this.h, 5); // Rounded corners
    } else if (this.type === 'spike') {
      // Draw a triangle for a spike
      triangle(this.x, this.y + this.h, // Bottom-left
               this.x + this.w / 2, this.y, // Top-middle
               this.x + this.w, this.y + this.h); // Bottom-right
    }
  };
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

calculation Property initialization this.x = x; this.y = y; this.w = w; this.h = h; this.type = type;

Stores the obstacle's position, size, and type as object properties

calculation Move method this.move = function() { this.x -= obstacleSpeed; };

Decreases the obstacle's x position to scroll it leftward toward the player

conditional Block drawing if (this.type === 'block') { rectMode(CORNER); rect(this.x, this.y, this.w, this.h, 5); }

Draws a rounded rectangle when the obstacle is a block

conditional Spike drawing } else if (this.type === 'spike') { triangle(this.x, this.y + this.h, this.x + this.w / 2, this.y, this.x + this.w, this.y + this.h); }

Draws a triangle pointing upward when the obstacle is a spike

function Obstacle(x, y, w, h, type) {
This is a constructor function that creates a new obstacle object with position (x, y), size (w, h), and type ('spike' or 'block')
this.x = x;
Stores the x coordinate as a property of this obstacle, allowing it to change position over time
this.y = y;
Stores the y coordinate (which stays fixed; obstacles don't move vertically)
this.w = w;
Stores the width of the obstacle in pixels
this.h = h;
Stores the height of the obstacle in pixels
this.type = type;
Stores either 'spike' or 'block' to determine how this obstacle is drawn and collided with
this.move = function() {
Defines a method that will be called to move the obstacle each frame
this.x -= obstacleSpeed;
Decreases the x position by the global obstacleSpeed, scrolling it leftward
this.display = function() {
Defines a method that draws this obstacle to the canvas
fill(60, 100, 100);
Sets the fill color to neon green (HSB: hue 60, full saturation and brightness)
if (this.type === 'block') {
If this obstacle is a block, draw it as a rectangle
rectMode(CORNER);
Switch to CORNER mode so the rectangle is positioned at its top-left corner, not its center
rect(this.x, this.y, this.w, this.h, 5);
Draw a rectangle with width this.w and height this.h at position (x, y), with 5-pixel rounded corners
} else if (this.type === 'spike') {
If this obstacle is a spike, draw it as a triangle
triangle(this.x, this.y + this.h, this.x + this.w / 2, this.y, this.x + this.w, this.y + this.h);
Draw a triangle with three vertices: bottom-left, top-center, and bottom-right, creating a spike pointing upward

generateObstacle()

This function demonstrates procedural generation—creating content randomly within defined parameters. By varying obstacle size and timing, the game stays fresh without pre-designed levels. The random() function with ranges ensures obstacles are always challenging but never impossible.

function generateObstacle() {
  let type = random(['spike', 'block']);
  let obstacleWidth, obstacleHeight, obstacleY;

  if (type === 'spike') {
    obstacleWidth = random(20, 40);
    obstacleHeight = random(30, 60);
    obstacleY = height - 50 - obstacleHeight; // Spikes sit on the ground line
  } else { // type === 'block'
    obstacleWidth = random(50, 100);
    obstacleHeight = random(40, 100);
    obstacleY = height - 50 - obstacleHeight; // Blocks sit on the ground line
  }

  let newObstacle = new Obstacle(width, obstacleY, obstacleWidth, obstacleHeight, type);
  obstacles.push(newObstacle);

  // Schedule the next obstacle generation
  nextObstacleTime = frameCount + random(40, 120); // Random frames until next obstacle
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

calculation Obstacle type selection let type = random(['spike', 'block']);

Randomly picks whether the next obstacle is a spike or a block

conditional Spike generation if (type === 'spike') { obstacleWidth = random(20, 40); obstacleHeight = random(30, 60); obstacleY = height - 50 - obstacleHeight; }

Generates random sizes for spikes (smaller and more variable than blocks)

conditional Block generation } else { obstacleWidth = random(50, 100); obstacleHeight = random(40, 100); obstacleY = height - 50 - obstacleHeight; }

Generates random sizes for blocks (larger than spikes)

calculation Obstacle instantiation let newObstacle = new Obstacle(width, obstacleY, obstacleWidth, obstacleHeight, type); obstacles.push(newObstacle);

Creates the new obstacle at the right edge of the screen and adds it to the obstacles array

calculation Next spawn time nextObstacleTime = frameCount + random(40, 120);

Schedules when the next obstacle should spawn (40 to 120 frames in the future)

let type = random(['spike', 'block']);
Randomly selects either 'spike' or 'block' with equal probability, determining what kind of obstacle to create
let obstacleWidth, obstacleHeight, obstacleY;
Declares variables to store the width, height, and y position of the upcoming obstacle
if (type === 'spike') {
If the randomly selected type is 'spike', configure it with spike-specific properties
obstacleWidth = random(20, 40);
Spikes are narrow, so width varies between 20 and 40 pixels
obstacleHeight = random(30, 60);
Spike height varies between 30 and 60 pixels, creating spikes of varying danger
obstacleY = height - 50 - obstacleHeight;
Position the spike so its bottom edge sits on the ground line (50 pixels from the canvas bottom)
} else {
If the type is not 'spike', it must be 'block', so configure it accordingly
obstacleWidth = random(50, 100);
Blocks are wider, varying between 50 and 100 pixels
obstacleHeight = random(40, 100);
Blocks are taller, varying between 40 and 100 pixels
let newObstacle = new Obstacle(width, obstacleY, obstacleWidth, obstacleHeight, type);
Creates a new Obstacle object positioned at the right edge of the screen (x = width) with the randomly generated properties
obstacles.push(newObstacle);
Adds the new obstacle to the obstacles array so updateObstacles() will move and display it
nextObstacleTime = frameCount + random(40, 120);
Schedules the next obstacle to spawn between 40 and 120 frames from now, creating random intervals between obstacles

updateObstacles()

This function shows how to manage a dynamic array of game objects. Looping backwards through the array while removing items is a common pattern because forward loops get confused when the array length changes mid-iteration. The memory management (removing off-screen obstacles) is essential for games with long play sessions.

🔬 This loop moves obstacles and removes them when they leave the screen. What if you removed the splice() line? Obstacles would still move off-screen, but what would happen to performance and memory over time?

  // Move and display obstacles
  for (let i = obstacles.length - 1; i >= 0; i--) {
    obstacles[i].move();
    // Remove obstacles that are off-screen
    if (obstacles[i].x + obstacles[i].w < 0) {
      obstacles.splice(i, 1);
    }
  }
function updateObstacles() {
  // Generate new obstacles
  if (frameCount >= nextObstacleTime) {
    generateObstacle();
  }

  // Move and display obstacles
  for (let i = obstacles.length - 1; i >= 0; i--) {
    obstacles[i].move();
    // Remove obstacles that are off-screen
    if (obstacles[i].x + obstacles[i].w < 0) {
      obstacles.splice(i, 1);
    }
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Obstacle spawn timing if (frameCount >= nextObstacleTime) { generateObstacle(); }

Generates a new obstacle when the scheduled frame count is reached

for-loop Update all obstacles for (let i = obstacles.length - 1; i >= 0; i--) { obstacles[i].move(); if (obstacles[i].x + obstacles[i].w < 0) { obstacles.splice(i, 1); } }

Loops through all obstacles backwards, moving each one and removing those that have scrolled off-screen

if (frameCount >= nextObstacleTime) {
Check if we've reached the frame when the next obstacle should spawn
generateObstacle();
If so, call generateObstacle() to create and add a new obstacle to the array
for (let i = obstacles.length - 1; i >= 0; i--) {
Loop through the obstacles array backwards (from end to start). Looping backwards is important because we're removing items with splice()
obstacles[i].move();
Call the move() method on each obstacle, which decreases its x position by obstacleSpeed
if (obstacles[i].x + obstacles[i].w < 0) {
Check if the obstacle's right edge (x + width) has moved past the left edge of the screen (x < 0), meaning it's completely off-screen
obstacles.splice(i, 1);
Remove the off-screen obstacle from the array using splice(), preventing memory from being wasted on invisible objects

drawObstacles()

This simple function shows the power of object-oriented design. Because each Obstacle knows how to draw itself (via the display() method), drawObstacles() can be extremely concise. The actual drawing logic stays encapsulated in the Obstacle class.

function drawObstacles() {
  for (let obstacle of obstacles) {
    obstacle.display();
  }
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

for-loop Display all obstacles for (let obstacle of obstacles) { obstacle.display(); }

Loops through every obstacle and calls its display() method to draw it

for (let obstacle of obstacles) {
Use a for-of loop to iterate through each obstacle in the obstacles array
obstacle.display();
Call the display() method on each obstacle, which draws it as either a rectangle (block) or triangle (spike)

checkCollisions()

Collision detection is one of the most important skills in game programming. This function uses AABB (Axis-Aligned Bounding Box) collision for rectangles—comparing the edges of two squares. The spike collision is simplified, treating the triangle as though it were a thin rectangle. Real triangle-circle collision is more complex, but this approximation works well for fast-paced games.

🔬 This four-part condition checks if the rectangles overlap. What if you changed the first && to || instead? Would collisions be harder or easier to trigger?

      if (
        playerX + playerSize / 2 > obstacle.x &&
        playerX - playerSize / 2 < obstacle.x + obstacle.w &&
        playerY + playerSize / 2 > obstacle.y &&
        playerY - playerSize / 2 < obstacle.y + obstacle.h
      ) {
function checkCollisions() {
  for (let obstacle of obstacles) {
    if (obstacle.type === 'block') {
      // Simple rect-rect collision for blocks
      if (
        playerX + playerSize / 2 > obstacle.x &&
        playerX - playerSize / 2 < obstacle.x + obstacle.w &&
        playerY + playerSize / 2 > obstacle.y &&
        playerY - playerSize / 2 < obstacle.y + obstacle.h
      ) {
        endGame();
        return;
      }
    } else if (obstacle.type === 'spike') {
      // Simplified rect-triangle collision for spikes
      // Check if player's bottom touches the spike's top point, or player's sides overlap
      let spikeTipX = obstacle.x + obstacle.w / 2;
      let spikeTipY = obstacle.y;

      if (
        playerX + playerSize / 2 > obstacle.x &&
        playerX - playerSize / 2 < obstacle.x + obstacle.w &&
        playerY + playerSize / 2 > spikeTipY // Player's bottom is below spike tip
      ) {
        endGame();
        return;
      }
    }
  }
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

conditional Block collision detection if (obstacle.type === 'block') { if ( playerX + playerSize / 2 > obstacle.x && playerX - playerSize / 2 < obstacle.x + obstacle.w && playerY + playerSize / 2 > obstacle.y && playerY - playerSize / 2 < obstacle.y + obstacle.h ) { endGame(); return; } }

Tests rectangle-to-rectangle collision (AABB) for blocks using four edge comparisons

conditional Spike collision detection } else if (obstacle.type === 'spike') { let spikeTipX = obstacle.x + obstacle.w / 2; let spikeTipY = obstacle.y; if ( playerX + playerSize / 2 > obstacle.x && playerX - playerSize / 2 < obstacle.x + obstacle.w && playerY + playerSize / 2 > spikeTipY ) { endGame(); return; } }

Tests simplified triangle collision for spikes by checking overlap with the spike's width and top point

for (let obstacle of obstacles) {
Loop through every obstacle to check if the player has hit any of them
if (obstacle.type === 'block') {
If this obstacle is a block (a rectangle), use rectangle-to-rectangle collision detection
playerX + playerSize / 2 > obstacle.x &&
Check if the player's right edge is past the block's left edge
playerX - playerSize / 2 < obstacle.x + obstacle.w &&
Check if the player's left edge is before the block's right edge
playerY + playerSize / 2 > obstacle.y &&
Check if the player's bottom is below the block's top
playerY - playerSize / 2 < obstacle.y + obstacle.h
Check if the player's top is above the block's bottom. If all four conditions are true, the rectangles overlap
endGame();
Call endGame() to trigger the game over state
return;
Exit the function immediately so we don't check more obstacles (once we've hit something, the game is over)
} else if (obstacle.type === 'spike') {
If this obstacle is a spike (a triangle), use simplified spike collision detection
let spikeTipX = obstacle.x + obstacle.w / 2;
Calculate the x position of the spike's tip (the center of its base)
let spikeTipY = obstacle.y;
The spike's tip y position is its top (obstacle.y)
if ( playerX + playerSize / 2 > obstacle.x && playerX - playerSize / 2 < obstacle.x + obstacle.w && playerY + playerSize / 2 > spikeTipY ) {
Check if the player's horizontal bounds overlap the spike's horizontal bounds AND the player's bottom is below the spike's top. This approximates a triangle collision by treating the spike as a thin rectangle

endGame()

endGame() demonstrates how state changes drive game behavior. By changing gameState and calling noLoop(), we completely alter what the sketch does. The sound effect provides immediate feedback, making the game feel responsive and polished.

function endGame() {
  gameState = 'gameOver';
  noLoop(); // Stop the draw loop

  // Play game over sound
  gameOverSound.amp(0.4, 0.05); // Fade in to 0.4 amplitude
  gameOverSound.freq(220); // A3 note
  setTimeout(() => gameOverSound.amp(0, 0.5), 200); // Fade out after 200ms
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Game state change gameState = 'gameOver';

Switches the game state so draw() stops updating and only displays the game over screen

calculation Stop the draw loop noLoop();

Stops the continuous animation loop, freezing the game

calculation Game over sound gameOverSound.amp(0.4, 0.05); gameOverSound.freq(220); setTimeout(() => gameOverSound.amp(0, 0.5), 200);

Plays a low-pitched sound effect when the game ends

gameState = 'gameOver';
Changes the game state variable so that draw() will enter the 'gameOver' branch and display the game over screen
noLoop();
Calls the p5.js noLoop() function to stop the draw loop, halting all animation and updates
gameOverSound.amp(0.4, 0.05);
Fade in the game over sound to volume 0.4 over 50 milliseconds
gameOverSound.freq(220);
Set the sound frequency to 220 Hz (A3, an octave below the jump sound at 440 Hz), giving it a lower, more serious tone
setTimeout(() => gameOverSound.amp(0, 0.5), 200);
After 200 milliseconds, fade the sound out to silence over 0.5 seconds, preventing it from playing indefinitely

restartGame()

restartGame() shows how to reset all game variables to a clean state. It's crucial to reset everything—if you forget to clear obstacles or reset velocity, bugs emerge. The symmetry between endGame() and restartGame() makes the game cycle clear: playing → gameOver → playing again.

function restartGame() {
  gameState = 'playing';
  score = 0;
  playerY = height - playerSize - 50;
  playerVelocityY = 0;
  isOnGround = true;
  obstacles = [];
  nextObstacleTime = 0;
  loop(); // Resume the draw loop
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Reset game state gameState = 'playing'; score = 0;

Changes the game state back to 'playing' and resets the score to 0

calculation Reset player position and velocity playerY = height - playerSize - 50; playerVelocityY = 0; isOnGround = true;

Returns the player to the starting position with no velocity, ready to jump again

calculation Clear obstacles obstacles = []; nextObstacleTime = 0;

Removes all obstacles from the previous game and resets the spawn timer

calculation Resume draw loop loop();

Restarts the continuous animation and update loop

gameState = 'playing';
Set the game state back to 'playing' so draw() will execute game logic instead of the game over screen
score = 0;
Reset the current score to 0; note that highScore is NOT reset, so it persists across games
playerY = height - playerSize - 50;
Return the player to their starting vertical position on the ground
playerVelocityY = 0;
Reset vertical velocity to zero so the player isn't falling when the new game starts
isOnGround = true;
Mark the player as being on the ground so they can immediately jump
obstacles = [];
Clear the obstacles array, removing all obstacles from the previous game
nextObstacleTime = 0;
Reset the spawn timer so the first obstacle in the new game spawns on frame 0 or shortly after
loop();
Call loop() to resume the draw() loop, which was halted by noLoop() in endGame()

displayScore()

This simple function uses template literals (backticks and ${}) to embed variable values into text strings. The floor() function ensures the score displays as a whole number rather than a decimal with many places. This text is only displayed when gameState is 'playing', not when gameState is 'gameOver'.

function displayScore() {
  fill(0, 0, 100); // White text
  text(`Score: ${floor(score)}`, width - 100, 50);
  text(`High Score: ${floor(highScore)}`, width - 100, 80);
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Text color fill(0, 0, 100);

Sets text color to white (in HSB mode)

calculation Current score display text(`Score: ${floor(score)}`, width - 100, 50);

Displays the current score in the upper-right corner

calculation High score display text(`High Score: ${floor(highScore)}`, width - 100, 80);

Displays the high score below the current score

fill(0, 0, 100);
Set the text color to white (HSB: hue 0, saturation 0, brightness 100)
text(`Score: ${floor(score)}`, width - 100, 50);
Draw the text 'Score: X' at position (width - 100, 50). floor() rounds the score down to an integer so it displays cleanly without decimals. Template literals (`...`) let us embed variables in strings with ${}
text(`High Score: ${floor(highScore)}`, width - 100, 80);
Draw the high score text 50 pixels below the current score

displayGameOver()

This function is only called when gameState equals 'gameOver', providing a complete game over screen with score information and restart instructions. The layout uses height/2 as a reference point and offsets from it, creating a visually centered design regardless of screen size.

function displayGameOver() {
  fill(0, 0, 100); // White text
  text('GAME OVER', width / 2, height / 2 - 40);
  text(`Final Score: ${floor(score)}`, width / 2, height / 2);
  text(`High Score: ${floor(highScore)}`, width / 2, height / 2 + 40);
  text('Tap or press any key to restart', width / 2, height / 2 + 100);
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Game over message text('GAME OVER', width / 2, height / 2 - 40);

Displays 'GAME OVER' at the top of the game over screen, centered

calculation Final score display text(`Final Score: ${floor(score)}`, width / 2, height / 2);

Shows the score from the game that just ended

calculation High score persistence text(`High Score: ${floor(highScore)}`, width / 2, height / 2 + 40);

Displays the all-time high score, which persists across games

calculation Restart instruction text('Tap or press any key to restart', width / 2, height / 2 + 100);

Prompts the player how to restart the game

fill(0, 0, 100);
Set the text color to white
text('GAME OVER', width / 2, height / 2 - 40);
Display 'GAME OVER' centered horizontally and 40 pixels above the vertical center
text(`Final Score: ${floor(score)}`, width / 2, height / 2);
Display the final score at the exact vertical center of the screen
text(`High Score: ${floor(highScore)}`, width / 2, height / 2 + 40);
Display the high score 40 pixels below the center
text('Tap or press any key to restart', width / 2, height / 2 + 100);
Display restart instructions 100 pixels below the center

drawGround()

drawGround() is called every frame to draw the platform on which obstacles sit and where the player lands. It's always drawn on top of the background, creating a visual ground line. The 50-pixel height matches the offset used in collision detection and player positioning.

function drawGround() {
  fill(0, 0, 20); // Dark grey ground
  rectMode(CORNER);
  rect(0, height - 50, width, 50); // The ground line
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Ground fill color fill(0, 0, 20);

Sets the ground to a dark grey color

calculation Ground rectangle rect(0, height - 50, width, 50);

Draws a 50-pixel tall rectangle along the bottom of the screen

fill(0, 0, 20);
Set the fill color to dark grey (HSB: brightness 20, very dim)
rectMode(CORNER);
Switch to CORNER mode so the rectangle's position (0, height-50) refers to its top-left corner
rect(0, height - 50, width, 50);
Draw a rectangle from (0, height-50) with width equal to the full canvas width and height 50 pixels, creating the ground line at the bottom

keyPressed()

keyPressed() is a p5.js callback function that fires whenever any key is pressed. Using gameState to branch logic lets the same keys do different things depending on the game phase. The `key` variable is built-in to p5.js and automatically contains the character that was pressed.

🔬 This code checks gameState and responds with different actions. What if you removed the else-if branch? During game over, could you still jump, or would nothing happen?

  if (gameState === 'playing') {
    if (key === ' ' || key === 'ArrowUp') { // Spacebar or Up Arrow
      jump();
    }
  } else if (gameState === 'gameOver') {
    restartGame();
  }
function keyPressed() {
  if (gameState === 'playing') {
    if (key === ' ' || key === 'ArrowUp') { // Spacebar or Up Arrow
      jump();
    }
  } else if (gameState === 'gameOver') {
    restartGame();
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Jump on spacebar or up arrow if (gameState === 'playing') { if (key === ' ' || key === 'ArrowUp') { jump(); } }

When the game is active and spacebar or up arrow is pressed, call jump()

conditional Restart on any key during game over } else if (gameState === 'gameOver') { restartGame(); }

When the game is over, any key press triggers a restart

function keyPressed() {
This is a p5.js built-in function that is called automatically every time a key is pressed
if (gameState === 'playing') {
If the game is currently active, check which key was pressed
if (key === ' ' || key === 'ArrowUp') {
The variable `key` contains the character of the pressed key. Check if it's a spacebar (' ') or up arrow ('ArrowUp')
jump();
If either jump key was pressed, call the jump() function
} else if (gameState === 'gameOver') {
If the game is over, any key press will restart the game
restartGame();
Call restartGame() to reset all variables and resume play

mousePressed()

mousePressed() makes the game playable on touch devices and with the mouse. By using the same gameState checking as keyPressed(), we ensure consistent behavior across input methods. Returning false is a p5.js convention to prevent unwanted browser interactions.

function mousePressed() {
  if (gameState === 'playing') {
    jump();
  } else if (gameState === 'gameOver') {
    restartGame();
  }
  // Prevent default browser behavior (e.g., right-click context menu)
  return false;
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Jump on click during play if (gameState === 'playing') { jump(); }

During gameplay, any mouse click triggers a jump

conditional Restart on click during game over } else if (gameState === 'gameOver') { restartGame(); }

During game over, any mouse click restarts the game

calculation Block context menu return false;

Prevents the browser's default right-click menu from appearing

function mousePressed() {
This is a p5.js built-in function called automatically when any mouse button is pressed
if (gameState === 'playing') {
If the game is currently active, respond to the mouse press
jump();
Trigger a jump—this makes the game playable by clicking/tapping on touch devices
} else if (gameState === 'gameOver') {
If the game has ended, respond to mouse press by restarting
restartGame();
Restart the game
return false;
Returning false prevents p5.js from triggering any browser default behavior (like context menus or text selection), keeping the focus on the game

touchStarted()

touchStarted() is the touch equivalent of mousePressed(). On mobile devices, this is how players interact with the game. Returning false is crucial to prevent the browser from interpreting the touch as a scroll or pinch-zoom gesture.

function touchStarted() {
  if (gameState === 'playing') {
    jump();
  } else if (gameState === 'gameOver') {
    restartGame();
  }
  // Prevent default browser behavior (e.g., scrolling, zooming)
  return false;
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Jump on touch during play if (gameState === 'playing') { jump(); }

During gameplay, any touch triggers a jump

conditional Restart on touch during game over } else if (gameState === 'gameOver') { restartGame(); }

During game over, any touch restarts the game

calculation Block default touch behavior return false;

Prevents scrolling and zooming that would normally happen on touch devices

function touchStarted() {
This is a p5.js built-in function called when a finger first touches the screen
if (gameState === 'playing') {
If the game is active, respond to the touch
jump();
Trigger a jump on any touch, making the game fully playable on mobile
} else if (gameState === 'gameOver') {
If game over, respond to touch by restarting
restartGame();
Restart the game
return false;
Returning false prevents the browser from scrolling or zooming, keeping the game at full-screen attention

windowResized()

windowResized() allows the game to adapt when the player resizes their browser window. Without it, the canvas would stay at the original size, causing layout issues. This function demonstrates responsive design in p5.js sketches.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Re-center player if canvas changes size
  playerY = height - playerSize - 50;
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Resize canvas to window resizeCanvas(windowWidth, windowHeight);

Adjusts the canvas to match the new window dimensions

calculation Reposition player playerY = height - playerSize - 50;

Adjusts the player's ground position to match the new canvas height

function windowResized() {
This is a p5.js built-in function called automatically whenever the browser window is resized
resizeCanvas(windowWidth, windowHeight);
p5.js's resizeCanvas() updates the canvas to the new window dimensions, stretching or shrinking it as needed
playerY = height - playerSize - 50;
Recalculate the player's ground position using the new height value, ensuring they stay on the ground after resize

📦 Key Variables

playerX number

Stores the horizontal position of the player on the canvas; never changes because the player stays on the left side

let playerX = width / 4;
playerY number

Stores the vertical position of the player on the canvas; changes due to gravity and jumping

let playerY = height - playerSize - 50;
playerSize number

The width and height of the player square in pixels; set to 40 by default

let playerSize = 40;
playerVelocityY number

The vertical speed of the player; positive means falling down, negative means moving up after a jump

let playerVelocityY = 0;
gravity number

How much playerVelocityY increases each frame, simulating gravitational pull downward

let gravity = 0.6;
jumpForce number

The initial upward velocity set when the player jumps; must be negative since Y increases downward

let jumpForce = -12;
isOnGround boolean

Tracks whether the player is currently touching the ground; controls whether jumping is allowed

let isOnGround = true;
obstacles array

A dynamic array containing all currently active Obstacle objects; obstacles are added and removed as they spawn and leave the screen

let obstacles = [];
obstacleSpeed number

How many pixels obstacles move leftward each frame; higher values make the game faster and harder

let obstacleSpeed = 5;
obstacleSpacing number

The minimum space between obstacles (not currently used in the core logic, but could be implemented)

let obstacleSpacing = 200;
nextObstacleTime number

The frameCount value at which the next obstacle should spawn; set to a random future frame each time an obstacle is created

let nextObstacleTime = 0;
gameState string

Tracks the current phase of the game: either 'playing' or 'gameOver'; controls which logic runs in draw()

let gameState = 'playing';
score number

The current game's score, increasing by 0.5 each frame to simulate distance traveled; resets on restart

let score = 0;
highScore number

The best score ever achieved; persists across game restarts and game overs

let highScore = 0;
jumpSound object (p5.Noise)

A p5.sound noise oscillator that plays when the player jumps; created in preload() and triggered in jump()

let jumpSound = new p5.Noise('white');
gameOverSound object (p5.Noise)

A p5.sound noise oscillator that plays when the player collides with an obstacle; created in preload() and triggered in endGame()

let gameOverSound = new p5.Noise('brown');

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG checkCollisions() spike collision detection

The spike collision check is overly simplified and treats spikes as thin rectangles instead of actual triangles. A player jumping over the side of a spike might register a false collision

💡 Implement proper triangle-circle or triangle-rectangle collision detection using point-in-triangle algorithms, or define a collision radius around the spike tip

PERFORMANCE draw()

drawObstacles() is called every frame even during 'gameOver' state when no obstacles should be moving or updating. This wastes CPU cycles

💡 Move drawObstacles() and drawGround() into the 'if (gameState === 'playing')' block, or only draw frozen obstacles during game over for visual effect

STYLE Obstacle constructor

The Obstacle function uses prototype-style methods (this.move, this.display) instead of ES6 class syntax, making it less consistent with modern JavaScript patterns

💡 Refactor to use ES6 class syntax: class Obstacle { constructor() {...} move() {...} display() {...} }

FEATURE Game state management

There is no 'paused' state, so players cannot temporarily pause the game to take a break

💡 Add a 'paused' game state and listen for a 'p' key or pause button that toggles between 'playing' and 'paused', displaying a pause screen

BUG updatePlayer() score tracking

The score increases by 0.5 every frame even when the player is on the ground and stationary, which doesn't realistically reward movement or survival distance

💡 Track distance traveled instead by calculating movement (playerX is fixed, but you could track frame count or actual time elapsed), or weight score by how high the player jumps

FEATURE Game difficulty progression

Obstacle speed and spacing never change, so the game difficulty stays constant throughout the entire game. After 30 seconds, most players know the rhythm

💡 Increase obstacleSpeed and decrease spawn intervals as score increases, creating a progressive difficulty curve that keeps late-game play engaging

🔄 Code Flow

Code flow showing preload, setup, draw, updateplayer, drawplayer, jump, obstacle, generateobstacle, updateobstacles, drawobstacles, checkcollisions, endgame, restartgame, displayscore, displaygameover, drawground, keypressed, mousepressed, touchstarted, windowresized

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

graph TD start[Start] --> setup[setup] setup --> preload[preload] preload --> canvas-init[canvas-init] preload --> jumpsound-init[jumpsound-init] preload --> gameoversound-init[gameoversound-init] setup --> player-init[player-init] setup --> draw[draw loop] click setup href "#fn-setup" click preload href "#fn-preload" click canvas-init href "#sub-canvas-init" click jumpsound-init href "#sub-jumpsound-init" click gameoversound-init href "#sub-gameoversound-init" click player-init href "#sub-player-init" draw --> playing-state[playing-state] playing-state --> updateplayer[updateplayer] updateplayer --> gravity-apply[gravity-apply] updateplayer --> ground-collision[ground-collision] playing-state --> generateobstacle[generateobstacle] generateobstacle --> type-selection[type-selection] generateobstacle --> spike-properties[spike-properties] generateobstacle --> block-properties[block-properties] generateobstacle --> obstacle-creation[obstacle-creation] generateobstacle --> next-spawn-schedule[next-spawn-schedule] playing-state --> checkcollisions[checkcollisions] checkcollisions --> block-collision[block-collision] checkcollisions --> spike-collision[spike-collision] playing-state --> drawplayer[drawplayer] drawplayer --> player-fill[player-fill] drawplayer --> player-rect[player-rect] playing-state --> drawobstacles[drawobstacles] drawobstacles --> obstacle-iteration[obstacle-iteration] obstacle-iteration --> obstacle-display-block[obstacle-display-block] obstacle-iteration --> obstacle-display-spike[obstacle-display-spike] playing-state --> displayscore[displayscore] displayscore --> score-update[score-update] draw --> drawground[drawground] drawground --> ground-color[ground-color] drawground --> ground-rect[ground-rect] draw --> keypressed[keypressed] keypressed --> playing-input[playing-input] keypressed --> gameover-input[gameover-input] draw --> mousepressed[mousepressed] mousepressed --> mouse-jump[mouse-jump] mousepressed --> mouse-restart[mouse-restart] draw --> touchstarted[touchstarted] touchstarted --> touch-jump[touch-jump] touchstarted --> touch-restart[touch-restart] draw --> windowresized[windowresized] windowresized --> canvas-resize[canvas-resize] windowresized --> player-reposition[player-reposition] click draw href "#fn-draw" click playing-state href "#sub-playing-state" click updateplayer href "#fn-updateplayer" click generateobstacle href "#fn-generateobstacle" click checkcollisions href "#fn-checkcollisions" click drawplayer href "#fn-drawplayer" click drawobstacles href "#fn-drawobstacles" click displayscore href "#fn-displayscore" click drawground href "#fn-drawground" click keypressed href "#fn-keypressed" click mousepressed href "#fn-mousepressed" click touchstarted href "#fn-touchstarted" click windowresized href "#fn-windowresized" playing-state --> gameover-state[gameover-state] gameover-state --> endgame[endgame] endgame --> state-change[state-change] endgame --> loop-halt[loop-halt] endgame --> gameover-sound[gameover-sound] endgame --> displaygameover[displaygameover] displaygameover --> gameover-text[gameover-text] displaygameover --> final-score-text[final-score-text] displaygameover --> highscore-persist-text[highscore-persist-text] displaygameover --> restart-prompt[restart-prompt] click gameover-state href "#sub-gameover-state" click endgame href "#fn-endgame" click state-change href "#sub-state-change" click loop-halt href "#sub-loop-halt" click gameover-sound href "#sub-gameover-sound" click displaygameover href "#fn-displaygameover" click gameover-text href "#sub-gameover-text" click final-score-text href "#sub-final-score-text" click highscore-persist-text href "#sub-highscore-persist-text" click restart-prompt href "#sub-restart-prompt" restartgame[restartgame] --> reset-state[reset-state] reset-state --> reset-player[reset-player] reset-state --> reset-obstacles[reset-obstacles] reset-state --> resume-loop[resume-loop] click restartgame href "#fn-restartgame" click reset-state href "#sub-reset-state" click reset-player href "#sub-reset-player" click reset-obstacles href "#sub-reset-obstacles" click resume-loop href "#sub-resume-loop"

❓ Frequently Asked Questions

What visual elements can users expect to see in the p5.js sketch titled 'Sketch 2026-04-17 17:30'?

This sketch features a player character that jumps and avoids obstacles against a vibrant, colorful background, with a black canvas for contrast.

How can users interact with the 'Sketch 2026-04-17 17:30' coding project?

Users can interact with the sketch by pressing a key to make the player jump, aiming to avoid obstacles and achieve a high score.

What creative coding concepts does 'Sketch 2026-04-17 17:30' illustrate?

This sketch demonstrates concepts such as gravity simulation, collision detection, and sound integration to enhance the gaming experience.

Preview

Sketch 2026-04-17 17:30 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch 2026-04-17 17:30 - Code flow showing preload, setup, draw, updateplayer, drawplayer, jump, obstacle, generateobstacle, updateobstacles, drawobstacles, checkcollisions, endgame, restartgame, displayscore, displaygameover, drawground, keypressed, mousepressed, touchstarted, windowresized
Code Flow Diagram