function setup() {
createCanvas(windowWidth, windowHeight);
// Initialize Matter.js engine and world
engine = Engine.create();
world = engine.world;
world.gravity.y = 1; // Set gravity
// Create the paddle (a static rectangle at the bottom)
const paddleWidth = 150;
const paddleHeight = 20;
paddle = Bodies.rectangle(
width / 2,
height - paddleHeight / 2 - 10,
paddleWidth,
paddleHeight,
{ isStatic: true, label: 'paddle' }
);
World.add(world, paddle);
// Create an invisible ground slightly below the canvas to detect misses
ground = Bodies.rectangle(
width / 2,
height + 50,
width,
100,
{ isStatic: true, label: 'ground' }
);
World.add(world, ground);
// Set up collision events
Events.on(engine, 'collisionStart', collisionHandler);
// Initialize game state
score = 0;
lives = 3;
lastStarSpawnTime = millis();
gameOver = false;
}
Line-by-line explanation (7 lines)
🔧 Subcomponents:
initialization
Physics Engine Creation
engine = Engine.create();
world = engine.world;
world.gravity.y = 1;
Creates the Matter.js physics engine and sets gravity to pull objects downward
initialization
Paddle Body Creation
paddle = Bodies.rectangle(
width / 2,
height - paddleHeight / 2 - 10,
paddleWidth,
paddleHeight,
{ isStatic: true, label: 'paddle' }
);
Creates a static (immovable) rectangular body for the paddle and positions it at the bottom center
initialization
Invisible Ground Creation
ground = Bodies.rectangle(
width / 2,
height + 50,
width,
100,
{ isStatic: true, label: 'ground' }
);
Creates an invisible static body below the canvas to detect missed stars
createCanvas(windowWidth, windowHeight);
- Creates a p5.js canvas that fills the entire browser window
engine = Engine.create();
- Creates a new Matter.js physics engine that will simulate gravity and collisions
world = engine.world;
- Gets a reference to the physics world where all bodies (objects) live
world.gravity.y = 1;
- Sets the downward gravity in the physics world—positive y pulls objects down
{ isStatic: true, label: 'paddle' }
- Makes the paddle static (immovable by physics) and gives it a label so collisions can identify it
Events.on(engine, 'collisionStart', collisionHandler);
- Registers the collisionHandler function to run every time two bodies collide
lastStarSpawnTime = millis();
- Records the current millisecond time so we can track when the next star should spawn
🔬 The constrain() function prevents the paddle from moving off-screen. What happens if you remove the constrain and just set the position directly to mouseX? Try replacing paddleX with mouseX to see the paddle escape the edges.
const paddleX = constrain(mouseX, paddle.width / 2, width - paddle.width / 2);
Matter.Body.setPosition(paddle, { x: paddleX, y: paddle.position.y });
function draw() {
background(40, 42, 54); // Dark background
if (gameOver) {
displayGameOver();
return; // Stop game logic if game over
}
// Update Matter.js engine
Engine.update(engine);
// Move the paddle with the mouse
// Constrain paddle to stay within canvas bounds
const paddleX = constrain(mouseX, paddle.width / 2, width - paddle.width / 2);
Matter.Body.setPosition(paddle, { x: paddleX, y: paddle.position.y });
// Spawn stars
if (millis() - lastStarSpawnTime > starSpawnInterval) {
createStar();
lastStarSpawnTime = millis();
// Gradually increase difficulty by reducing spawn interval
starSpawnInterval = max(200, starSpawnInterval - 10);
}
// Draw paddle
drawBody(paddle, color(255, 204, 0)); // Yellow paddle
// Draw and manage stars
for (let i = stars.length - 1; i >= 0; i--) {
let star = stars[i];
drawBody(star, color(255, 255, 0)); // Yellow stars
// Remove stars that are way off-screen (missed or caught and fell through)
if (star.position.y > height + 100) {
World.remove(world, star);
stars.splice(i, 1);
}
}
// Display score and lives
displayGameInfo();
}
Line-by-line explanation (11 lines)
🔧 Subcomponents:
conditional
Game Over State Check
if (gameOver) {
displayGameOver();
return; // Stop game logic if game over
}
If the game is over, display the game-over screen and skip all game logic
calculation
Physics Engine Update
Engine.update(engine);
Advances the physics simulation one frame, updating all body positions based on gravity and velocities
calculation
Paddle Position Update
const paddleX = constrain(mouseX, paddle.width / 2, width - paddle.width / 2);
Matter.Body.setPosition(paddle, { x: paddleX, y: paddle.position.y });
Moves the paddle to follow the mouse horizontally, keeping it within screen bounds
conditional
Star Spawning with Difficulty Increase
if (millis() - lastStarSpawnTime > starSpawnInterval) {
createStar();
lastStarSpawnTime = millis();
starSpawnInterval = max(200, starSpawnInterval - 10);
Spawns a new star when enough time has passed, then reduces the spawn interval to increase difficulty
for-loop
Star Drawing and Cleanup Loop
for (let i = stars.length - 1; i >= 0; i--) {
let star = stars[i];
drawBody(star, color(255, 255, 0));
if (star.position.y > height + 100) {
World.remove(world, star);
stars.splice(i, 1);
}
}
Draws each star and removes any that have fallen far below the screen to prevent memory buildup
background(40, 42, 54); // Dark background
- Fills the entire canvas with a dark blue-gray color, erasing the previous frame
if (gameOver) {
- Checks if the game state is over
return; // Stop game logic if game over
- Exits the draw function early, skipping all game updates and only showing the game-over screen
Engine.update(engine);
- Tells the Matter.js physics engine to calculate one frame of movement—gravity pulls bodies down, velocities update positions
const paddleX = constrain(mouseX, paddle.width / 2, width - paddle.width / 2);
- Gets the current mouse X position, but constrains it to stay within the paddle's width from both edges
Matter.Body.setPosition(paddle, { x: paddleX, y: paddle.position.y });
- Moves the paddle to the constrained mouse position, keeping its Y position fixed at the bottom
if (millis() - lastStarSpawnTime > starSpawnInterval) {
- Checks if enough milliseconds have passed since the last star spawn
starSpawnInterval = max(200, starSpawnInterval - 10);
- Reduces spawn interval by 10 (stars come faster), but never goes below 200ms so the game doesn't become impossible
for (let i = stars.length - 1; i >= 0; i--) {
- Loops through the stars array backwards (from last to first) so we can safely remove items during iteration
if (star.position.y > height + 100) {
- Checks if the star has fallen far below the canvas—if so, it was either caught or missed and should be cleaned up
stars.splice(i, 1);
- Removes the star from the stars array so it's no longer drawn or checked
🔬 These three lines set the star's size and spawn position. What happens if you change the y value from -starSize to 0 or height / 2? Stars would spawn at different heights instead of always from the top.
const starSize = random(15, 30);
const x = random(starSize, width - starSize);
const y = -starSize;
function createStar() {
const starSize = random(15, 30);
const x = random(starSize, width - starSize);
const y = -starSize; // Start above the canvas
const star = Bodies.circle(x, y, starSize / 2, {
restitution: 0.8, // Make stars bounce slightly
friction: 0.1,
label: 'star' // Label for collision detection
});
World.add(world, star);
stars.push(star);
}
Line-by-line explanation (9 lines)
🔧 Subcomponents:
calculation
Random Star Size
const starSize = random(15, 30);
Generates a random star size between 15 and 30 pixels for visual variety
calculation
Random Star Position
const x = random(starSize, width - starSize);
Places the star at a random horizontal position, offset by its radius to keep it on-screen
initialization
Star Physics Body Creation
const star = Bodies.circle(x, y, starSize / 2, {
restitution: 0.8,
friction: 0.1,
label: 'star'
});
Creates a circular Matter.js body with properties for bouncing and friction
const starSize = random(15, 30);
- Generates a random number between 15 and 30 for the star's diameter—adds variety to the falling objects
const x = random(starSize, width - starSize);
- Generates a random X position, but offset by the star's radius on both sides so the star doesn't spawn half off-screen
const y = -starSize; // Start above the canvas
- Places the star just above the top of the canvas so it falls into view smoothly
const star = Bodies.circle(x, y, starSize / 2, {
- Creates a circular physics body at position (x, y) with radius = starSize/2
restitution: 0.8, // Make stars bounce slightly
- Sets how bouncy the star is—0.8 means it retains 80% of its speed after hitting something (0 = dead stop, 1 = perfect bounce)
friction: 0.1,
- Sets how much friction (air resistance) affects the star—low values mean it's slippery
label: 'star' // Label for collision detection
- Tags this body with a label so the collision handler can identify it
World.add(world, star);
- Adds the star to the physics world so it will be simulated (gravity will pull it, collisions will affect it)
stars.push(star);
- Adds the star to the stars array so draw() can access it and render it
function drawBody(body, fillColor) {
fill(fillColor);
noStroke();
const pos = body.position;
const angle = body.angle;
push();
translate(pos.x, pos.y);
rotate(angle);
// Draw based on body type (only circles and rectangles for this game)
if (body.circleRadius) {
// Circle (star)
ellipse(0, 0, body.circleRadius * 2);
} else {
// Rectangle (paddle, ground)
rectMode(CENTER);
rect(0, 0, body.width, body.height);
}
pop();
}
Line-by-line explanation (12 lines)
🔧 Subcomponents:
conditional
Shape Type Conditional
if (body.circleRadius) {
ellipse(0, 0, body.circleRadius * 2);
} else {
rectMode(CENTER);
rect(0, 0, body.width, body.height);
}
Checks if the body is circular or rectangular and draws the appropriate shape
fill(fillColor);
- Sets the color for the shape being drawn (passed in as a parameter)
noStroke();
- Removes the outline, so shapes are filled solid with no border
const pos = body.position;
- Gets the current position (x, y) of the physics body
const angle = body.angle;
- Gets the current rotation angle of the physics body (in radians)
push();
- Saves the current drawing state (transformation, fill color, etc.) so we can restore it later
translate(pos.x, pos.y);
- Moves the origin (0,0) to the body's position so shapes draw at the correct location
rotate(angle);
- Rotates the drawing context by the body's angle so the shape rotates as the physics body rotates
if (body.circleRadius) {
- Checks if the body has a circleRadius property—if so, it's a circle
ellipse(0, 0, body.circleRadius * 2);
- Draws a circle at the origin with diameter = circleRadius * 2
rectMode(CENTER);
- Sets rect mode to CENTER so the rect is drawn from its center point (0, 0) instead of the top-left
rect(0, 0, body.width, body.height);
- Draws a rectangle at the origin with the body's width and height
pop();
- Restores the drawing state from before the push() call, undoing the translate and rotate
function displayGameOver() {
fill(255);
textSize(64);
textAlign(CENTER, CENTER);
text('GAME OVER', width / 2, height / 2 - 50);
textSize(32);
text(`Final Score: ${score}`, width / 2, height / 2 + 10);
text('Press R to Restart', width / 2, height / 2 + 60);
}
Line-by-line explanation (7 lines)
fill(255);
- Sets text color to white
textSize(64);
- Sets large font size for the 'GAME OVER' title
textAlign(CENTER, CENTER);
- Centers text both horizontally and vertically around its position coordinates
text('GAME OVER', width / 2, height / 2 - 50);
- Draws 'GAME OVER' centered horizontally and positioned above center vertically
textSize(32);
- Reduces font size for the score and restart instruction
text(`Final Score: ${score}`, width / 2, height / 2 + 10);
- Displays the final score centered on screen
text('Press R to Restart', width / 2, height / 2 + 60);
- Displays the restart instruction below the score
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
// Recreate paddle and ground to match new canvas size
World.remove(world, paddle);
World.remove(world, ground);
const paddleWidth = 150;
const paddleHeight = 20;
paddle = Bodies.rectangle(
width / 2,
height - paddleHeight / 2 - 10,
paddleWidth,
paddleHeight,
{ isStatic: true, label: 'paddle' }
);
World.add(world, paddle);
ground = Bodies.rectangle(
width / 2,
height + 50,
width,
100,
{ isStatic: true, label: 'ground' }
);
World.add(world, ground);
}
Line-by-line explanation (7 lines)
resizeCanvas(windowWidth, windowHeight);
- Resizes the p5.js canvas to match the new window dimensions
World.remove(world, paddle);
- Removes the old paddle from the physics world
World.remove(world, ground);
- Removes the old ground from the physics world
paddle = Bodies.rectangle(
- Creates a new paddle at the center of the resized canvas
ground = Bodies.rectangle(
- Creates a new ground that spans the width of the resized canvas
World.add(world, paddle);
- Adds the new paddle to the physics world
World.add(world, ground);
- Adds the new ground to the physics world