Ball catcher

This sketch creates an interactive catch game where colorful items fall from the top of the screen and the player controls a paddle with their mouse to catch them. Each caught item increases the score by 10 points, demonstrating spawning, collision detection, and responsive game mechanics.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up the game — Lower the spawn rate so items appear more frequently and faster—increase difficulty and excitement
  2. Make items bigger and easier to catch — Larger items are easier to see and catch—makes the game feel more forgiving
  3. Reward high scores with bigger point values — Catching an item feels more satisfying when you earn more points per catch
  4. Change paddle color to red — Use a different fill() color to make your paddle stand out visually
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a fully playable catch game where falling circles rain from above and you control a paddle with your mouse to intercept them. It's visually engaging because the items are randomly colored and fall at different speeds, creating unpredictable gameplay. The code teaches you four essential game-building techniques: spawning objects in a loop, updating their positions every frame, detecting collisions between objects, and managing a dynamic array by removing items when they're caught or off-screen.

The sketch is organized into three functions: setup() initializes the canvas and player paddle, draw() runs 60 times per second to spawn items, update their positions, check collisions, and render everything, and windowResized() keeps the game full-screen when the browser window changes size. By studying this code you will learn the pattern every game uses to manage multiple moving objects and respond to player input.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-screen canvas and defines the player paddle as an object with position (x, y), width, and height properties positioned near the bottom center
  2. Every frame, draw() sets a dark background and then spawns a new falling item every 60 frames (once per second) by pushing a new object into the items array with random position, size, speed, and color
  3. The code loops backward through the items array, updating each item's y position by its speed to make it fall downward, then drawing it as a colored ellipse
  4. For each item, a multi-part if-statement checks collision by testing if the item's bottom edge has passed the paddle's top AND the item's x position overlaps the paddle's left and right edges—if true, the score increases and the item is removed from the array
  5. Items that fall below the screen are also removed from the array to prevent memory waste, then the player paddle is drawn at the current mouseX position and constrained to stay within the canvas boundaries
  6. Finally, the score is displayed in the top-left corner, and windowResized() ensures the canvas stretches to fill the browser window whenever it is resized

🎓 Concepts You'll Learn

Array managementCollision detectionSpawning objectsObject propertiesMouse interactionGame loopsConditional logicCanvas resizing

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. Use it to initialize your canvas, create objects, and set up the starting state of your game.

function setup() {
  createCanvas(windowWidth, windowHeight);
  player = { x: width / 2, y: height - 50, w: 80, h: 20 };
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

calculation Full-screen canvas createCanvas(windowWidth, windowHeight);

Creates a canvas that fills the entire browser window at any size

calculation Player paddle object player = { x: width / 2, y: height - 50, w: 80, h: 20 };

Defines the paddle as an object with position, width, and height so it can be moved and drawn

createCanvas(windowWidth, windowHeight);
Creates a canvas that matches the full browser window size using windowWidth and windowHeight variables
player = { x: width / 2, y: height - 50, w: 80, h: 20 };
Creates a player object with x and y position (centered horizontally, 50 pixels from bottom), width w of 80 pixels, and height h of 20 pixels

draw()

draw() runs 60 times per second. This is where all the game logic lives: spawning, updating, collision detection, drawing, and scoring. Understanding how to organize a draw() function is the key to every interactive p5.js sketch.

🔬 This loop walks backward through the items array and moves each one downward. What happens if you change item.y += item.speed to item.y += item.speed * 2? How does doubling the speed change the game feel?

  for (let i = items.length - 1; i >= 0; i--) {
    let item = items[i];
    item.y += item.speed;

🔬 Items get random speeds between 2 and 5. What happens if you change this to random(1, 8) so some items fall much slower and others much faster? Does it make the game more chaotic and interesting?

      speed: random(2, 5),
      color: color(random(255), random(255), random(255))
function draw() {
  background(30, 30, 40);
  
  // Spawn items
  if (frameCount % 60 === 0) {
    items.push({
      x: random(20, width - 20),
      y: 0,
      size: random(15, 30),
      speed: random(2, 5),
      color: color(random(255), random(255), random(255))
    });
  }
  
  // Update & draw items
  for (let i = items.length - 1; i >= 0; i--) {
    let item = items[i];
    item.y += item.speed;
    
    fill(item.color);
    noStroke();
    ellipse(item.x, item.y, item.size);
    
    // Check collision with player
    if (item.y + item.size/2 > player.y && 
        item.x > player.x && 
        item.x < player.x + player.w) {
      score += 10;
      items.splice(i, 1);
    }
    
    // Remove if off screen
    if (item.y > height) {
      items.splice(i, 1);
    }
  }
  
  // Draw player
  player.x = mouseX - player.w / 2;
  player.x = constrain(player.x, 0, width - player.w);
  fill(100, 200, 255);
  rect(player.x, player.y, player.w, player.h, 5);
  
  // Score
  fill(255);
  textSize(24);
  textAlign(LEFT, TOP);
  text('Score: ' + score, 20, 20);
}
Line-by-line explanation (18 lines)

🔧 Subcomponents:

conditional Item spawning condition if (frameCount % 60 === 0) {

Spawns a new item every 60 frames instead of every single frame to pace the game

for-loop Item update and draw loop for (let i = items.length - 1; i >= 0; i--) {

Loops backward through all items to safely remove them without skipping any

conditional Collision detection if (item.y + item.size/2 > player.y && item.x > player.x && item.x < player.x + player.w) {

Tests if an item's bottom edge overlaps with the paddle's top edge AND item's x is within paddle's width

conditional Off-screen item removal if (item.y > height) {

Removes items that fall below the screen to free memory

calculation Mouse-following paddle player.x = mouseX - player.w / 2;

Centers the paddle on the mouse cursor by subtracting half the paddle's width

calculation Paddle boundary constraint player.x = constrain(player.x, 0, width - player.w);

Prevents the paddle from moving off the left or right edges of the screen

background(30, 30, 40);
Fills the canvas with a dark blue-gray color to erase everything from the previous frame
if (frameCount % 60 === 0) {
The modulo operator (%) checks if frameCount is divisible by 60—true every 60 frames (about once per second at 60 FPS)
items.push({
Creates a new object and adds it to the end of the items array using push()
x: random(20, width - 20),
Sets a random x position between 20 and (width - 20) pixels, keeping spawned items slightly inset from the edges
color: color(random(255), random(255), random(255))
Creates a random RGB color where each red, green, and blue value is between 0 and 255
for (let i = items.length - 1; i >= 0; i--) {
Loops backward from the last item (i = items.length - 1) down to the first item (i = 0), decrementing i each iteration
item.y += item.speed;
Updates the item's y position downward by adding its speed value, making it fall each frame
ellipse(item.x, item.y, item.size);
Draws a colored circle at the item's current x and y position with a diameter equal to its size
if (item.y + item.size/2 > player.y &&
The first part of the collision check: tests if the item's bottom edge (center y plus half size) has fallen below the paddle's top edge
item.x > player.x &&
Second part: tests if the item's x position is to the right of the paddle's left edge
item.x < player.x + player.w) {
Third part: tests if the item's x position is to the left of the paddle's right edge—if all three are true, there's a collision
items.splice(i, 1);
Removes the item from the array by deleting 1 element starting at index i
if (item.y > height) {
Checks if the item has fallen below the bottom of the screen
player.x = mouseX - player.w / 2;
Centers the paddle on the mouse cursor by subtracting half the paddle's width from the mouse's x position
player.x = constrain(player.x, 0, width - player.w);
Clamps the paddle's x position between 0 (left edge) and (width - player.w) (right edge), preventing it from sliding off screen
fill(100, 200, 255);
Sets the fill color to a bright blue before drawing the paddle
rect(player.x, player.y, player.w, player.h, 5);
Draws a rounded rectangle (the paddle) at the player's position with width, height, and corner radius of 5 pixels
text('Score: ' + score, 20, 20);
Displays the current score as text in the top-left corner by concatenating the string 'Score: ' with the score number

windowResized()

windowResized() is a special p5.js function that runs automatically whenever the browser window is resized. Without it, the canvas would stay its original size and not fill the window. This function is essential for making responsive full-screen games.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
Automatically resizes the canvas to match the new browser window dimensions whenever the window is resized

📦 Key Variables

player object

Stores the paddle's position (x, y), width (w), and height (h) so it can be moved with the mouse and drawn

let player = { x: 200, y: 550, w: 80, h: 20 };
items array

Holds all currently falling items—each item is an object with x, y, size, speed, and color properties

let items = [];
score number

Tracks the total points earned by catching items—increases by 10 each time a collision is detected

let score = 0;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

BUG draw() collision detection

The collision check only tests if the item's CENTER x is within the paddle range, not the full item circle. A wide item might visually overlap the paddle but not register as caught.

💡 Improve collision by checking if the item's left edge (x - size/2) OR right edge (x + size/2) overlaps the paddle's bounds, not just the center point.

PERFORMANCE draw() item loop

Looping backward through items is correct for safe removal, but creating new color objects every frame (in the spawn condition) is wasteful. Color creation happens 60 times per second.

💡 Pre-define an array of nice colors outside draw() and pick randomly from it using color(random(colorArray)) instead of generating RGB values every spawn.

STYLE Variables and structure

Magic numbers (60 for spawn rate, 10 for points, 5 for item speed range) are scattered throughout the code, making it hard to tune difficulty without understanding the whole sketch.

💡 Define constants at the top like const SPAWN_RATE = 60; const POINTS_PER_CATCH = 10; const MIN_SPEED = 2; so you can adjust difficulty from one place.

FEATURE draw() scoring

Score only increases; there's no penalty or game-over condition if items reach the bottom. Players could afk and infinite score.

💡 Track missed items or add a lives/health system: subtract points or end the game if items pass the paddle without being caught.

🔄 Code Flow

Code flow showing setup, draw, windowresized

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

graph TD start[Start] --> setup[setup] setup --> canvas-creation[canvas-creation] setup --> player-init[player-init] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-creation href "#sub-canvas-creation" click player-init href "#sub-player-init" draw --> spawn-loop[spawn-loop] draw --> item-loop[item-loop] draw --> collision-check[collision-check] draw --> offscreen-removal[offscreen-removal] draw --> player-control[player-control] draw --> boundary-constraint[boundary-constraint] click draw href "#fn-draw" click spawn-loop href "#sub-spawn-loop" click item-loop href "#sub-item-loop" click collision-check href "#sub-collision-check" click offscreen-removal href "#sub-offscreen-removal" click player-control href "#sub-player-control" click boundary-constraint href "#sub-boundary-constraint" spawn-loop -->|Every 60 frames| draw item-loop -->|Loop backward through items| draw collision-check -->|Check for overlap| draw offscreen-removal -->|Remove items below screen| draw player-control -->|Center paddle on mouse| draw boundary-constraint -->|Restrict paddle movement| draw draw --> windowresized[windowresized] click windowresized href "#fn-windowresized"

❓ Frequently Asked Questions

What visual elements are present in the Ball Catcher sketch?

The sketch features a player paddle that moves horizontally at the bottom of the canvas and colorful falling items that the player must catch.

How can users interact with the Ball Catcher sketch?

Users can control the player paddle's position using their mouse, aiming to catch the falling items to increase their score.

What creative coding concepts does the Ball Catcher sketch illustrate?

The sketch demonstrates basic game mechanics such as collision detection, item spawning, and dynamic scoring within a visually engaging environment.

Preview

Ball catcher - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Ball catcher - Code flow showing setup, draw, windowresized
Code Flow Diagram