function setup() {
createCanvas(windowWidth, windowHeight);
// Initialize Matter.js engine and world
engine = Matter.Engine.create();
world = engine.world;
world.gravity.y = 1; // Standard gravity
// Create the player body
const playerSize = 40;
const playerX = width / 2;
const playerY = height - playerSize; // Start just above the ground
player = Matter.Bodies.circle(playerX, playerY, playerSize / 2, {
restitution: 0.8, // Bounciness for chaotic collisions
friction: 0.1, // Low friction for slippery movement
mass: 1, // Player mass
label: 'player' // Custom label for collision detection
});
Matter.World.add(world, player);
// Create the ground body
ground = Matter.Bodies.rectangle(width / 2, height - 10, width, 20, {
isStatic: true, // Ground doesn't move
friction: 0.5, // Some friction to allow player to move
label: 'ground' // Custom label for collision detection
});
Matter.World.add(world, ground);
// Set up collision event listener
Matter.Events.on(engine, 'collisionStart', handleCollision);
// Initialize lastFrameTime for deltaTime calculation
lastFrameTime = millis();
}
Line-by-line explanation (19 lines)
🔧 Subcomponents:
calculation
Physics Engine Initialization
engine = Matter.Engine.create();
Creates the Matter.js physics engine that will simulate all body interactions
calculation
Gravity Configuration
world.gravity.y = 1; // Standard gravity
Sets the downward acceleration that pulls all bodies toward the ground
calculation
Player Body Creation
player = Matter.Bodies.circle(playerX, playerY, playerSize / 2, {
Creates a circular physics body for the player with bounciness and low friction
calculation
Ground Body Creation
ground = Matter.Bodies.rectangle(width / 2, height - 10, width, 20, {
Creates a static rectangular platform that the player lands on and slides across
calculation
Collision Event Listener
Matter.Events.on(engine, 'collisionStart', handleCollision);
Registers the handleCollision function to run whenever two bodies touch
createCanvas(windowWidth, windowHeight);
- Creates a p5.js canvas that fills the entire browser window, making the game responsive
engine = Matter.Engine.create();
- Creates a Matter.js physics engine that will simulate gravity, forces, and collisions
world = engine.world;
- Stores a reference to the physics world (where all bodies live) for easier access
world.gravity.y = 1;
- Sets gravity to 1 unit downward; higher values pull bodies down faster
const playerSize = 40;
- Defines the player circle's diameter in pixels
const playerX = width / 2;
- Places the player horizontally in the center of the canvas
const playerY = height - playerSize;
- Positions the player near the bottom, just above the ground
player = Matter.Bodies.circle(playerX, playerY, playerSize / 2, {
- Creates a circular physics body with the specified position and radius (diameter / 2)
restitution: 0.8,
- Sets bounciness to 0.8; the player bounces back 80% of its impact energy (makes collisions chaotic)
friction: 0.1,
- Low friction value makes the player slippery—it slides easily across surfaces
mass: 1,
- Assigns the player a mass of 1 unit; heavier bodies are harder to push
label: 'player'
- Adds a string label so collision detection can identify this body as the player
Matter.World.add(world, player);
- Adds the player body to the physics world so it experiences gravity and collisions
ground = Matter.Bodies.rectangle(width / 2, height - 10, width, 20, {
- Creates a large rectangular body for the ground, positioned near the bottom of the canvas
isStatic: true,
- Marks the ground as static (immovable); it won't fall or be pushed by other bodies
friction: 0.5,
- Moderate friction on the ground allows the player to slide but provides traction
Matter.World.add(world, ground);
- Adds the ground body to the physics world
Matter.Events.on(engine, 'collisionStart', handleCollision);
- Registers handleCollision to run each time two bodies first collide, enabling game-over detection
lastFrameTime = millis();
- Records the current time in milliseconds to calculate deltaTime (time since last frame) in draw()
🔬 This code counts down the timer and ends the game at zero. What happens if you change the second line to `timer -= deltaTime / 2000` instead? How much slower does the countdown become?
// Update game timer
timer -= deltaTime / 1000;
if (timer <= 0) {
gameState = 'gameOver';
timer = 0;
}
🔬 This loop removes objects when they fall 50 pixels below the screen. What happens if you change `height + 50` to `height + 200`? Objects will stay longer—does your score go up faster?
for (let i = fallingObjects.length - 1; i >= 0; i--) {
let obj = fallingObjects[i];
renderBody(obj);
// Remove objects that go off-screen to prevent performance issues
if (obj.position.y > height + 50) {
function draw() {
background(220);
if (gameState === 'playing') {
// Calculate deltaTime for consistent physics and timer updates
let currentTime = millis();
let deltaTime = currentTime - lastFrameTime;
lastFrameTime = currentTime;
// Update game timer
timer -= deltaTime / 1000;
if (timer <= 0) {
gameState = 'gameOver';
timer = 0;
}
// Update Matter.js engine with deltaTime
Matter.Engine.update(engine, deltaTime);
// Spawn falling objects at intervals
timeElapsedSinceLastObject += deltaTime;
if (timeElapsedSinceLastObject >= OBJECT_SPAWN_INTERVAL) {
spawnFallingObject();
timeElapsedSinceLastObject = 0;
}
// Render all physics bodies
renderBody(player);
renderBody(ground);
for (let i = fallingObjects.length - 1; i >= 0; i--) {
let obj = fallingObjects[i];
renderBody(obj);
// Remove objects that go off-screen to prevent performance issues
if (obj.position.y > height + 50) {
Matter.World.remove(world, obj);
fallingObjects.splice(i, 1);
score += 10; // Reward for dodging an object
}
}
// Handle player movement based on keyboard or touch input
handlePlayerMovement();
// Display score and timer
textSize(24);
fill(0);
noStroke();
text(`Score: ${score}`, 20, 40);
text(`Time: ${ceil(timer)}s`, 20, 80);
} else if (gameState === 'gameOver') {
// Display Game Over screen
textSize(48);
fill(255, 0, 0);
textAlign(CENTER, CENTER);
text('Game Over!', width / 2, height / 2 - 50);
textSize(32);
fill(0);
text(`Final Score: ${score}`, width / 2, height / 2 + 10);
textSize(24);
text('Press Space to Restart', width / 2, height / 2 + 80);
}
}
Line-by-line explanation (35 lines)
🔧 Subcomponents:
calculation
Delta Time Calculation
let deltaTime = currentTime - lastFrameTime;
Measures milliseconds since the last frame to keep physics and timers consistent regardless of frame rate
calculation
Timer Countdown
timer -= deltaTime / 1000;
Decrements the timer by seconds (deltaTime / 1000 converts milliseconds to seconds)
conditional
Win Condition Check
if (timer <= 0) {
gameState = 'gameOver';
timer = 0;
}
Ends the game when the 60-second timer expires, triggering the Game Over state
conditional
Object Spawn Timer
if (timeElapsedSinceLastObject >= OBJECT_SPAWN_INTERVAL) {
spawnFallingObject();
timeElapsedSinceLastObject = 0;
}
Spawns a new falling object every 1000 milliseconds and resets the spawn counter
for-loop
Falling Objects Render & Cleanup Loop
for (let i = fallingObjects.length - 1; i >= 0; i--) {
Loops backwards through all falling objects to render and remove ones that exit the screen
conditional
Game Over Screen Rendering
} else if (gameState === 'gameOver') {
Displays the Game Over message and final score when the game ends
background(220);
- Clears the canvas with a light gray color (220) each frame, preventing trails and making the animation clean
if (gameState === 'playing') {
- Checks if the game is in 'playing' mode; if so, updates and renders; otherwise shows Game Over screen
let currentTime = millis();
- Gets the current time in milliseconds since the sketch started
let deltaTime = currentTime - lastFrameTime;
- Calculates how many milliseconds passed since the last frame—used to keep physics and timers frame-rate-independent
lastFrameTime = currentTime;
- Updates lastFrameTime so the next frame can calculate the new deltaTime
timer -= deltaTime / 1000;
- Subtracts seconds from the timer; dividing by 1000 converts milliseconds to seconds
if (timer <= 0) {
- If the timer runs out, the game ends
gameState = 'gameOver';
- Changes the game state to trigger the Game Over screen on the next frame
Matter.Engine.update(engine, deltaTime);
- Updates the Matter.js physics simulation; deltaTime ensures consistent physics regardless of frame rate
timeElapsedSinceLastObject += deltaTime;
- Adds the time elapsed this frame to the spawn timer counter
if (timeElapsedSinceLastObject >= OBJECT_SPAWN_INTERVAL) {
- When enough time has passed (1000 milliseconds), spawn a new falling object
spawnFallingObject();
- Calls spawnFallingObject() to create a new random rectangle above the canvas
timeElapsedSinceLastObject = 0;
- Resets the spawn timer to zero so the next object spawns after another 1000 milliseconds
renderBody(player);
- Draws the player circle at its current physics position and rotation
renderBody(ground);
- Draws the ground rectangle at its position (which doesn't change since it's static)
for (let i = fallingObjects.length - 1; i >= 0; i--) {
- Loops backwards through the falling objects array; backward iteration allows safe removal during the loop
let obj = fallingObjects[i];
- Gets the current falling object
renderBody(obj);
- Draws the falling object at its current position and rotation
if (obj.position.y > height + 50) {
- Checks if the object has fallen below the bottom of the canvas (with 50-pixel buffer)
Matter.World.remove(world, obj);
- Removes the object from the physics simulation so it no longer affects other bodies
fallingObjects.splice(i, 1);
- Removes the object from the fallingObjects array
score += 10;
- Awards 10 points to the player for successfully dodging the object
handlePlayerMovement();
- Calls handlePlayerMovement() to apply keyboard/touch input as physics forces on the player
textSize(24);
- Sets the font size for the score and timer text
fill(0);
- Sets text color to black
noStroke();
- Removes the stroke (outline) around text
text(`Score: ${score}`, 20, 40);
- Displays the current score at position (20, 40) in the top-left corner
text(`Time: ${ceil(timer)}s`, 20, 80);
- Displays the remaining time rounded up to the nearest whole second at (20, 80)
} else if (gameState === 'gameOver') {
- If the game state is 'gameOver', display the Game Over screen instead
textSize(48);
- Sets a large font size for the 'Game Over!' title
fill(255, 0, 0);
- Sets text color to red for the Game Over message
textAlign(CENTER, CENTER);
- Centers text horizontally and vertically on its position
text('Game Over!', width / 2, height / 2 - 50);
- Displays 'Game Over!' centered in the middle of the canvas
text(`Final Score: ${score}`, width / 2, height / 2 + 10);
- Displays the final score below the Game Over message
text('Press Space to Restart', width / 2, height / 2 + 80);
- Instructs the player to press Space to restart the game
🔬 This code caps the player's horizontal speed at 5 pixels per frame. What happens if you increase maxHorizontalVelocity to 10? Will the player zoom across the screen faster? Try it!
// Cap horizontal velocity to prevent infinite acceleration and keep player on screen
const maxHorizontalVelocity = 5;
Matter.Body.setVelocity(player, {
x: constrain(player.velocity.x, -maxHorizontalVelocity, maxHorizontalVelocity),
y: player.velocity.y
});
function handlePlayerMovement() {
const moveForce = 0.005; // Force for horizontal movement
const jumpForce = 0.02; // Force for jumping (exaggerated for chaos)
// Keyboard controls
if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) { // LEFT or 'A'
Matter.Body.applyForce(player, player.position, { x: -moveForce, y: 0 });
}
if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) { // RIGHT or 'D'
Matter.Body.applyForce(player, player.position, { x: moveForce, y: 0 });
}
// Jump (Up arrow or W) - Looser check for "exaggerated jumps"
// Allows some mid-air boosts if player velocity isn't too high upwards
if (keyIsDown(UP_ARROW) || keyIsDown(87)) { // UP or 'W'
if (player.position.y > height - 60 || player.velocity.y > -2) {
Matter.Body.applyForce(player, player.position, { x: 0, y: -jumpForce });
}
}
// Cap horizontal velocity to prevent infinite acceleration and keep player on screen
const maxHorizontalVelocity = 5;
Matter.Body.setVelocity(player, {
x: constrain(player.velocity.x, -maxHorizontalVelocity, maxHorizontalVelocity),
y: player.velocity.y
});
// Keep player within horizontal bounds
let playerRadius = player.circleRadius;
if (player.position.x - playerRadius < 0) {
Matter.Body.setPosition(player, { x: playerRadius, y: player.position.y });
Matter.Body.setVelocity(player, { x: 0, y: player.velocity.y });
} else if (player.position.x + playerRadius > width) {
Matter.Body.setPosition(player, { x: width - playerRadius, y: player.position.y });
Matter.Body.setVelocity(player, { x: 0, y: player.velocity.y });
}
// Touch controls (basic horizontal movement)
if (touches.length > 0) {
let touchX = touches[0].x;
if (touchX < width / 2) {
Matter.Body.applyForce(player, player.position, { x: -moveForce * 1.5, y: 0 });
} else {
Matter.Body.applyForce(player, player.position, { x: moveForce * 1.5, y: 0 });
}
}
}
Line-by-line explanation (26 lines)
🔧 Subcomponents:
conditional
Left Movement Input
if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) { // LEFT or 'A'
Checks if the player pressed left arrow or 'A' key and applies leftward force to the player
conditional
Right Movement Input
if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) { // RIGHT or 'D'
Checks if the player pressed right arrow or 'D' key and applies rightward force to the player
conditional
Jump Input with Ground Check
if (player.position.y > height - 60 || player.velocity.y > -2) {
Allows jumping only when the player is near the ground or not moving upward too fast, preventing infinite air jumps
conditional
Horizontal Velocity Limiter
x: constrain(player.velocity.x, -maxHorizontalVelocity, maxHorizontalVelocity),
Prevents the player from accelerating infinitely; caps horizontal speed at ±5 pixels per frame
conditional
Left Edge Boundary Check
if (player.position.x - playerRadius < 0) {
Detects when the player's left edge crosses the left edge of the canvas and pushes them back
conditional
Right Edge Boundary Check
} else if (player.position.x + playerRadius > width) {
Detects when the player's right edge crosses the right edge of the canvas and pushes them back
conditional
Touch Input Handler
if (touches.length > 0) {
Enables mobile/tablet play by moving the player left or right based on where the user touches the screen
const moveForce = 0.005; // Force for horizontal movement
- Defines the strength of the force applied to move the player left or right when you press the arrow keys or WASD
const jumpForce = 0.02; // Force for jumping (exaggerated for chaos)
- Defines the upward force applied when you press the jump key; higher values produce bigger jumps
if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) { // LEFT or 'A'
- Checks if the left arrow key or the 'A' key is currently held down (65 is the ASCII code for 'A')
Matter.Body.applyForce(player, player.position, { x: -moveForce, y: 0 });
- Applies a leftward force (negative x) to the player's body; the force is applied at the player's center position
if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) { // RIGHT or 'D'
- Checks if the right arrow key or the 'D' key is currently held down (68 is the ASCII code for 'D')
Matter.Body.applyForce(player, player.position, { x: moveForce, y: 0 });
- Applies a rightward force (positive x) to the player's body
if (keyIsDown(UP_ARROW) || keyIsDown(87)) { // UP or 'W'
- Checks if the up arrow key or the 'W' key is held down (87 is the ASCII code for 'W')
if (player.position.y > height - 60 || player.velocity.y > -2) {
- Only allows jumping if the player is within 60 pixels of the bottom (near ground) OR not moving upward too fast—prevents infinite jumps mid-air
Matter.Body.applyForce(player, player.position, { x: 0, y: -jumpForce });
- Applies an upward force (negative y, since y increases downward) to launch the player into the air
const maxHorizontalVelocity = 5;
- Sets a speed limit so the player can't accelerate infinitely left or right
Matter.Body.setVelocity(player, {
- Sets the player's velocity directly, overriding accumulated forces; allows clamping the horizontal speed
x: constrain(player.velocity.x, -maxHorizontalVelocity, maxHorizontalVelocity),
- Clamps the horizontal velocity between -5 and +5, preventing runaway acceleration
y: player.velocity.y
- Keeps the vertical velocity unchanged so vertical physics (gravity, jumping) work naturally
let playerRadius = player.circleRadius;
- Stores the player's radius (half the diameter) for boundary checking
if (player.position.x - playerRadius < 0) {
- Checks if the player's left edge has gone past the left side of the canvas
Matter.Body.setPosition(player, { x: playerRadius, y: player.position.y });
- Teleports the player back so their left edge is exactly at x = 0
Matter.Body.setVelocity(player, { x: 0, y: player.velocity.y });
- Stops the player's leftward motion so they don't immediately push back out of bounds again
} else if (player.position.x + playerRadius > width) {
- Checks if the player's right edge has gone past the right side of the canvas
Matter.Body.setPosition(player, { x: width - playerRadius, y: player.position.y });
- Teleports the player back so their right edge is exactly at x = width
Matter.Body.setVelocity(player, { x: 0, y: player.velocity.y });
- Stops the player's rightward motion
if (touches.length > 0) {
- Checks if there are any active touch points (mouse clicks or finger touches) on the screen
let touchX = touches[0].x;
- Gets the x-coordinate of the first touch point
if (touchX < width / 2) {
- If the touch is on the left half of the screen, apply leftward force
Matter.Body.applyForce(player, player.position, { x: -moveForce * 1.5, y: 0 });
- Applies leftward force 1.5 times stronger than keyboard input for easier mobile control
} else {
- If the touch is on the right half of the screen, apply rightward force
Matter.Body.applyForce(player, player.position, { x: moveForce * 1.5, y: 0 });
- Applies rightward force 1.5 times stronger than keyboard input
🔬 These four lines set up the falling object's random size and position. What happens if you change `const objectY = -objectHeight / 2` to `const objectY = 0`? Will objects spawn at the top edge of the canvas instead of above it?
const objectWidth = random(20, 50);
const objectHeight = random(20, 50);
const objectX = random(objectWidth / 2, width - objectWidth / 2);
const objectY = -objectHeight / 2; // Start above canvas
function spawnFallingObject() {
const objectWidth = random(20, 50);
const objectHeight = random(20, 50);
const objectX = random(objectWidth / 2, width - objectWidth / 2);
const objectY = -objectHeight / 2; // Start above canvas
let obj = Matter.Bodies.rectangle(objectX, objectY, objectWidth, objectHeight, {
restitution: 0.5, // Bouncy
friction: 0.1, // Slippery
mass: 0.5, // Lighter than player
label: 'fallingObject' // Custom label
});
Matter.World.add(world, obj);
fallingObjects.push(obj);
}
Line-by-line explanation (11 lines)
🔧 Subcomponents:
calculation
Random Object Width
const objectWidth = random(20, 50);
Creates variation by randomizing each falling object's width between 20 and 50 pixels
calculation
Random Object Height
const objectHeight = random(20, 50);
Randomizes the falling object's height to make each one unique
calculation
Horizontal Spawn Position
const objectX = random(objectWidth / 2, width - objectWidth / 2);
Places the object randomly across the width of the canvas, but keeps it on-screen (doesn't let it spawn half off-screen)
calculation
Physics Body Creation
let obj = Matter.Bodies.rectangle(objectX, objectY, objectWidth, objectHeight, {
Creates a rectangular physics body with the random dimensions and physical properties
calculation
Add to Physics World
Matter.World.add(world, obj);
Adds the new body to the physics simulation so it falls due to gravity and can collide
calculation
Add to Tracking Array
fallingObjects.push(obj);
Adds the body to the fallingObjects array so draw() can track and render it
const objectWidth = random(20, 50);
- Generates a random width between 20 and 50 pixels; different widths make objects look varied and unpredictable
const objectHeight = random(20, 50);
- Generates a random height between 20 and 50 pixels
const objectX = random(objectWidth / 2, width - objectWidth / 2);
- Picks a random horizontal position, but constrains it so the object stays fully on-screen (offset by its half-width)
const objectY = -objectHeight / 2; // Start above canvas
- Places the object above the canvas (negative y-coordinate) so it falls into view
let obj = Matter.Bodies.rectangle(objectX, objectY, objectWidth, objectHeight, {
- Creates a rectangular physics body at position (objectX, objectY) with the random dimensions
restitution: 0.5, // Bouncy
- Sets bounciness to 0.5; the object bounces back 50% of its impact energy when it hits the ground or player
friction: 0.1, // Slippery
- Low friction makes objects slide easily when they hit other bodies
mass: 0.5, // Lighter than player
- Sets the object's mass to 0.5 (half the player's mass), making them easier to push around on impact
label: 'fallingObject' // Custom label
- Adds a label so collision detection can identify this as a falling object (vs. the player or ground)
Matter.World.add(world, obj);
- Adds the physics body to the world so it experiences gravity and can collide with other bodies
fallingObjects.push(obj);
- Adds the object to the fallingObjects array so the draw() function can render it and check if it's off-screen
🔬 These four lines set up the drawing transformation. What happens if you comment out the `rotate(body.angle);` line (add // before it)? Objects will stop spinning visually—do they still rotate in the physics?
push();
translate(body.position.x, body.position.y);
rotate(body.angle);
noStroke();
// Function to render a Matter.js body on the p5.js canvas
function renderBody(body) {
push();
translate(body.position.x, body.position.y);
rotate(body.angle);
noStroke();
if (body.label === 'player') {
fill(0, 150, 200); // Blue for player
ellipse(0, 0, body.circleRadius * 2);
} else if (body.label === 'ground') {
fill(100); // Gray for ground
rectMode(CENTER);
rect(0, 0, body.bounds.max.x - body.bounds.min.x, body.bounds.max.y - body.bounds.min.y);
} else if (body.label === 'fallingObject') {
fill(255, 0, 0); // Red for falling objects
rectMode(CENTER);
rect(0, 0, body.bounds.max.x - body.bounds.min.x, body.bounds.max.y - body.bounds.min.y);
}
pop();
}
Line-by-line explanation (16 lines)
🔧 Subcomponents:
calculation
Transform Stack Management
push();
Saves the current transformation state so local transforms don't affect subsequent drawings
calculation
Position Translation
translate(body.position.x, body.position.y);
Moves the drawing origin to the body's physics position
calculation
Rotation Application
rotate(body.angle);
Rotates the drawing to match the body's angular orientation from the physics engine
conditional
Player Rendering
if (body.label === 'player') {
Draws a blue circle if the body is the player
conditional
Ground Rendering
} else if (body.label === 'ground') {
Draws a gray rectangle if the body is the ground
conditional
Falling Object Rendering
} else if (body.label === 'fallingObject') {
Draws a red rectangle if the body is a falling object
push();
- Saves the current p5.js transformation state (position, rotation, fill color, etc.) so local changes don't affect other drawings
translate(body.position.x, body.position.y);
- Moves the drawing origin to the body's current physics position—all subsequent draws happen from this point
rotate(body.angle);
- Rotates the drawing context by the body's current angle in radians; makes shapes rotate as they tumble in physics
noStroke();
- Disables shape outlines so bodies are drawn as solid fills only
if (body.label === 'player') {
- Checks if this body is labeled 'player'
fill(0, 150, 200); // Blue for player
- Sets the fill color to blue (RGB: 0, 150, 200)
ellipse(0, 0, body.circleRadius * 2);
- Draws a circle centered at (0, 0) with diameter = 2 × circleRadius; the local coordinates (0, 0) become the body's position due to translate()
} else if (body.label === 'ground') {
- Checks if this body is labeled 'ground'
fill(100); // Gray for ground
- Sets the fill color to gray (RGB: 100, 100, 100)
rectMode(CENTER);
- Changes rectangle drawing mode so the rectangle is centered at (0, 0) instead of having the corner there
rect(0, 0, body.bounds.max.x - body.bounds.min.x, body.bounds.max.y - body.bounds.min.y);
- Draws a rectangle centered at (0, 0) with width and height calculated from the body's bounding box (the space it occupies)
} else if (body.label === 'fallingObject') {
- Checks if this body is labeled 'fallingObject'
fill(255, 0, 0); // Red for falling objects
- Sets the fill color to red (RGB: 255, 0, 0)
rectMode(CENTER);
- Centers rectangles at (0, 0)
rect(0, 0, body.bounds.max.x - body.bounds.min.x, body.bounds.max.y - body.bounds.min.y);
- Draws a red rectangle at the falling object's position and rotation
pop();
- Restores the saved transformation state, so the next body renders with fresh position/rotation/color
function restartGame() {
gameState = 'playing';
score = 0;
timer = 60;
lastFrameTime = millis();
timeElapsedSinceLastObject = 0;
// Remove all falling objects from the Matter.js world and the array
for (let obj of fallingObjects) {
Matter.World.remove(world, obj);
}
fallingObjects = [];
// Reset player position, velocity, and angular velocity
Matter.Body.setPosition(player, { x: width / 2, y: height - player.circleRadius });
Matter.Body.setVelocity(player, { x: 0, y: 0 });
Matter.Body.setAngularVelocity(player, 0);
}
Line-by-line explanation (11 lines)
🔧 Subcomponents:
calculation
Game State Reset
gameState = 'playing';
Changes the game state back to 'playing' to resume the game loop
calculation
Score Reset
score = 0;
Sets the score back to 0 for a fresh start
calculation
Timer Reset
timer = 60;
Resets the game duration back to 60 seconds
for-loop
Falling Objects Cleanup Loop
for (let obj of fallingObjects) {
Removes all remaining falling objects from the physics world and the tracking array
calculation
Player State Reset
Matter.Body.setPosition(player, { x: width / 2, y: height - player.circleRadius });
Moves the player back to the starting position at the bottom center of the canvas
gameState = 'playing';
- Changes the game state from 'gameOver' back to 'playing' so the game loop resumes
score = 0;
- Resets the score to 0 for a fresh game
timer = 60;
- Resets the timer back to 60 seconds
lastFrameTime = millis();
- Records the current time so the next draw() call calculates deltaTime correctly
timeElapsedSinceLastObject = 0;
- Resets the spawn timer so the first new object spawns after 1000 milliseconds
for (let obj of fallingObjects) {
- Loops through all remaining falling objects (uses for...of syntax)
Matter.World.remove(world, obj);
- Removes each falling object from the physics world
fallingObjects = [];
- Clears the fallingObjects array by setting it to an empty array
Matter.Body.setPosition(player, { x: width / 2, y: height - player.circleRadius });
- Teleports the player back to the center-bottom of the canvas
Matter.Body.setVelocity(player, { x: 0, y: 0 });
- Stops the player by setting both horizontal and vertical velocity to zero
Matter.Body.setAngularVelocity(player, 0);
- Stops the player from spinning by setting angular velocity to zero
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
// Update ground position and size for responsiveness
Matter.Body.setPosition(ground, { x: width / 2, y: height - 10 });
// Scaling a static body in Matter.js can be tricky. For a simple rectangle like ground,
// we can roughly scale and reset properties. For complex shapes, recreating is better.
Matter.Body.scale(ground, width / (ground.bounds.max.x - ground.bounds.min.x), 1);
ground.bounds.max.x = width / 2 + ground.width / 2;
ground.bounds.min.x = width / 2 - ground.width / 2;
ground.area = ground.width * ground.height;
ground.inertia = Matter.Body.nextId(); // Reset inertia for static bodies after scaling
ground.mass = ground.area * 0.001; // Recalculate mass based on new area
}
Line-by-line explanation (8 lines)
🔧 Subcomponents:
calculation
Canvas Resize
resizeCanvas(windowWidth, windowHeight);
Adjusts the p5.js canvas to match the new window size
calculation
Ground Repositioning
Matter.Body.setPosition(ground, { x: width / 2, y: height - 10 });
Moves the ground to stay centered horizontally and near the bottom of the resized canvas
calculation
Ground Scaling
Matter.Body.scale(ground, width / (ground.bounds.max.x - ground.bounds.min.x), 1);
Stretches the ground horizontally to match the new canvas width
resizeCanvas(windowWidth, windowHeight);
- Adjusts the p5.js canvas to the new window dimensions whenever the browser window is resized
Matter.Body.setPosition(ground, { x: width / 2, y: height - 10 });
- Moves the ground to the horizontal center and near the bottom of the new canvas
Matter.Body.scale(ground, width / (ground.bounds.max.x - ground.bounds.min.x), 1);
- Scales the ground horizontally to span the new canvas width (vertical scale stays at 1, no vertical stretching)
ground.bounds.max.x = width / 2 + ground.width / 2;
- Manually updates the right edge of the ground's bounding box after scaling
ground.bounds.min.x = width / 2 - ground.width / 2;
- Manually updates the left edge of the ground's bounding box after scaling
ground.area = ground.width * ground.height;
- Recalculates the ground's area based on its new dimensions
ground.inertia = Matter.Body.nextId();
- Resets the ground's rotational inertia after scaling (a workaround for Matter.js static body updates)
ground.mass = ground.area * 0.001;
- Recalculates the ground's mass based on its new area