Bouncing Ball Catcher with p5play

Bouncing Ball Catcher

In this tutorial, we'll create a simple game where balls fall due to gravity, and the player moves a paddle to bounce them back up.

We'll use p5play, a physics engine for p5.js, which makes it easier to handle movement, collisions, and gravity.


Step 1: Setting Up p5play

What’s new?

  • In p5.js, we create a canvas(width, height).

  • In p5play, we use new Canvas("1:1"), which automatically scales the canvas to a 1:1 ratio.

Code:

function setup() {
  new Canvas("1:1"); // Auto-resizing canvas
  world.gravity.y = 1; // Enable gravity
}

Why is this useful? With p5play, you don’t have to manually update positions—gravity and physics handle it for you!


🏓 Step 2: Creating a Paddle

What’s new?

  • Instead of a rect(), we create a paddle sprite using new Sprite().

  • The KINEMATIC collider makes it immovable by physics, but we can still move it with code.

Code:

let paddle;

function setup() {
  new Canvas("1:1");
  world.gravity.y = 1;

  // Create paddle
  paddle = new Sprite();
  paddle.width = 50;
  paddle.height = 50;
  paddle.collider = KINEMATIC; // Allows manual movement, not affected by gravity
}

Why is this useful? In plain p5.js, you’d have to manually handle movement, but p5play handles collisions automatically.


Step 3: Adding Balls

What’s new?

  • Instead of using an array of objects, we create physics-enabled balls using new Sprite().

  • We set speed and direction instead of manually updating x and y.

Code

let balls = [];
let nBalls = 5;

function addBall() {
  let ball = new Sprite(random(50, width - 50), 50, 30, 30);
  ball.shapeColor = color(200, 50, 100);
  ball.speed = 2;
  ball.direction = random(-200, -340); // Random initial velocity
  ball.restitution = 0; // Bounciness (0 = no bounce, 1 = full bounce)

  balls.push(ball);
}

function setup() {
  new Canvas("1:1");
  world.gravity.y = 1;

  paddle = new Sprite();
  paddle.width = 50;
  paddle.height = 50;
  paddle.collider = KINEMATIC;

  for (let i = 0; i < nBalls; i++) {
    addBall();
  }
}

Why is this useful? Instead of manually updating positions, p5play handles physics like gravity and movement automatically.


🎮 Step 4: Adding Player Controls

What’s new?

  • Instead of checking keyIsDown(), we use p5play’s built-in kb.pressing().

  • We update the paddle’s velocity (paddle.vel.x), instead of setting paddle.x directly.

Code:

function draw() {
  clear();

  if (kb.pressing("left")) paddle.vel.x = -5;
  else if (kb.pressing("right")) paddle.vel.x = 5;
  else paddle.vel.x = 0;

  // Keep paddle inside the screen
  paddle.position.x = constrain(paddle.position.x, paddle.width / 2, width - paddle.width / 2);
}

Why is this useful?

  • Instead of manually setting position, velocity-based movement feels smoother.

  • kb.pressing() is cleaner than keyIsDown().


🔄 Step 5: Handling Collisions

What’s new?

  • Instead of writing a manual collision check, we use paddle.collides(ball).

  • When the paddle hits the ball, we reverse the ball’s velocity to bounce it.

Code:

for (let i = balls.length - 1; i >= 0; i--) {
  let ball = balls[i];

  if (paddle.collides(ball)) {
    ball.vel.y = -3; // Bounce upwards
  }
}

Why is this useful?

  • In p5.js, we’d have to manually calculate distance for collisions.

  • p5play automatically detects when objects touch!


🎯 Step 6: Adding Score & Removing Balls

What’s new?

  • We introduce a score that increases when catching balls.

  • If a ball falls off the screen, we remove it and decrease the score.

Code:

let score = 0;

function draw() {
  clear();

  if (kb.pressing("left")) paddle.vel.x = -5;
  else if (kb.pressing("right")) paddle.vel.x = 5;
  else paddle.vel.x = 0;

  paddle.position.x = constrain(paddle.position.x, paddle.width / 2, width - paddle.width / 2);

  for (let i = balls.length - 1; i >= 0; i--) {
    let ball = balls[i];

    if (paddle.collides(ball)) {
      ball.vel.y = -3; // Bounce
      score++;
    }

    if (ball.position.y > height) {
      balls.splice(i, 1); // Remove ball
      score--;
    }
  }

  // Display score
  fill(0);
  textSize(24);
  text(`Score: ${score}`, 10, 30);
}

Why is this useful?

  • Instead of manually checking ball positions, p5play makes collision detection easy.

  • The game is now interactive and rewarding!


Step 7: Spawning More Balls Over Time

What’s new?

  • Every 60 frames (≈1 second), we add a new ball to keep the game challenging.

Code:

if (frameCount % 60 === 0) {
  addBall();
}

Why is this useful? This makes the game progressively harder, adding a natural difficulty curve.


🎉 Final Code

let paddle;
let balls = [];
let nBalls = 5;
let score = 0;

function addBall() {
  let ball = new Sprite(random(50, width - 50), 50, 30, 30);
  ball.shapeColor = color(200, 50, 100);
  ball.speed = 2;
  ball.direction = random(-200, -340);
  ball.restitution = 0;

  balls.push(ball);
}

function setup() {
  new Canvas("1:1");
  world.gravity.y = 1;

  paddle = new Sprite();
  paddle.width = 50;
  paddle.height = 50;
  paddle.collider = KINEMATIC;

  for (let i = 0; i < nBalls; i++) {
    addBall();
  }
}

function draw() {
  clear();

  if (kb.pressing("left")) paddle.vel.x = -5;
  else if (kb.pressing("right")) paddle.vel.x = 5;
  else paddle.vel.x = 0;

  paddle.position.x = constrain(paddle.position.x, paddle.width / 2, width - paddle.width / 2);

  for (let i = balls.length - 1; i >= 0; i--) {
    let ball = balls[i];

    if (paddle.collides(ball)) {
      ball.vel.y = -3;
      score++;
    }

    if (ball.position.y > height) {
      balls.splice(i, 1);
      score--;
    }
  }

  if (frameCount % 60 === 0) {
    addBall();
  }

  fill(0);
  textSize(24);
  text(`Score: ${score}`, 10, 30);
}

🎯 Recap

  • p5play simplifies physics!

  • No need to manually check collisions!

  • Smooth movement with vel.x instead of updating x directly.

  • Gravity, bouncing, and collision detection are built-in.

Would you like me to add extra challenges or an extension for this lesson? 😊

Last updated