AI Bouncy Ball Drop - Satisfying Physics

This sketch lets you click anywhere on the screen to drop a colorful ball that falls under gravity, bounces off the floor and side walls while losing a bit of energy each time, and gradually fades out and disappears. Every click spawns a brand-new independent ball, so rapid clicking fills the screen with a bouncing, fading crowd.

🧪 Try This!

Experiment with the code by making these changes:

  1. Add a ceiling bounce — Right now balls can fly off the top of the screen forever - adding a fourth boundary check makes them bounce off the ceiling too.
  2. Make balls linger longer before fading — Increasing the divisor used to calculate age slows down the fade, so balls stick around much longer after landing.
  3. Leave motion trails instead of clearing each frame — Using a semi-transparent background instead of a fully opaque one lets previous ball positions blend into faint trails.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch turns mouse clicks into colorful balls that fall, bounce, and fade away in a satisfying physics-toy style animation. Each click spawns a new ball with a random color, size, and slight sideways velocity, and gravity pulls it downward until it collides with the floor or side edges of the canvas. The bounce isn't perfect - a damping factor shaves off some speed on every impact, so balls settle down over time - and each ball fades to transparent and vanishes about five seconds after it's born. The key p5.js techniques on display are the array-of-objects pattern, per-frame physics using velocity and gravity, boundary collision checks, and alpha-based fading using millis().

The code is compact but dense: a single global array called balls stores every active ball as a plain JavaScript object with position, velocity, radius, color, and birth-time properties. setup() just creates a full-window canvas, mousePressed() adds a new ball object to the array on every click, and draw() loops through the array backwards each frame to update physics, handle wall and floor bounces, fade and remove old balls, and draw everything that's still alive. Studying this sketch teaches you how to manage a growing and shrinking collection of independent objects, how to fake realistic-looking bouncing physics with just a few lines, and why looping backwards through an array is important when you're deleting items from it mid-loop.

⚙️ How It Works

  1. When the page loads, setup() creates a canvas that fills the entire browser window and the balls array starts out empty.
  2. Nothing is drawn until you click - mousePressed() fires on every click and pushes a new ball object into the balls array, giving it the click position, a small random horizontal velocity, zero vertical velocity, a random radius, a random RGB color, and the current time in milliseconds.
  3. Every frame, draw() first paints a solid gray background to erase the previous frame, then loops through the balls array from the last item to the first.
  4. For each ball, gravity (g) is added to its vertical velocity, and that velocity is applied to move the ball's x and y position - this is what makes it accelerate downward like real gravity.
  5. If the ball's edge crosses the left, right, or bottom boundary of the canvas, its position is clamped back inside and its velocity along that axis is reversed and multiplied by the damping factor d, producing a bounce that loses a bit of energy every time.
  6. The sketch calculates each ball's age based on how long ago it was created; once a ball is older than one second (or however age is scaled) it's removed from the array with splice(), and until then it's drawn as a circle whose transparency increases with age, creating the fade-out effect.

🎓 Concepts You'll Learn

Array of objectsReverse for-loop with spliceVelocity and gravity simulationBoundary collision detectionAlpha-based fade with millis()Random color and size generation

📝 Code Breakdown

setup()

setup() runs once when the sketch first loads. Here it's kept minimal - just building a full-window canvas - because all the interesting state (the balls array) is created dynamically later by clicks.

function setup(){
  createCanvas(windowWidth,windowHeight);
}
Line-by-line explanation (1 lines)
createCanvas(windowWidth,windowHeight);
Creates a canvas that exactly matches the current browser window size, so the sketch fills the whole page.

draw()

draw() runs continuously at roughly 60 frames per second and is the heart of this sketch's physics simulation. Looping backwards through the balls array while removing dead entries with splice() is a classic p5.js pattern any time you manage a dynamic list of particles or objects.

🔬 These three lines handle bouncing off the left wall, right wall, and floor. What happens if you add a fourth line to also bounce off the ceiling (b.y<b.r)? Try it and see if balls can now bounce upward off the top too.

    if(b.x<b.r){b.x=b.r;b.vx*=-d;}
    if(b.x>width-b.r){b.x=width-b.r;b.vx*=-d;}
    if(b.y>height-b.r){b.y=height-b.r;b.vy*=-d;}

🔬 The fade uses 255*(1-age), a linear fade. What happens visually if you square age, like 255*(1-age*age), so balls stay solid longer and then fade quickly at the end?

    if(age>1) balls.splice(i,1);
    else{
      noStroke();
      fill(b.c[0],b.c[1],b.c[2],255*(1-age));
      circle(b.x,b.y,b.r*2);
    }
function draw(){
  background(200);
  let now=millis();
  for(let i=balls.length-1;i>=0;i--){
    let b=balls[i],age=(now-b.t)/5000;
    b.vy+=g;
    b.x+=b.vx;
    b.y+=b.vy;
    if(b.x<b.r){b.x=b.r;b.vx*=-d;}
    if(b.x>width-b.r){b.x=width-b.r;b.vx*=-d;}
    if(b.y>height-b.r){b.y=height-b.r;b.vy*=-d;}
    if(age>1) balls.splice(i,1);
    else{
      noStroke();
      fill(b.c[0],b.c[1],b.c[2],255*(1-age));
      circle(b.x,b.y,b.r*2);
    }
  }
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

for-loop Update And Draw Each Ball for(let i=balls.length-1;i>=0;i--){

Loops through every ball backwards so that removing a ball mid-loop (with splice) doesn't cause the next ball to be skipped.

conditional Left Wall Bounce if(b.x<b.r){b.x=b.r;b.vx*=-d;}

Stops the ball from leaving the left edge and reverses its horizontal velocity, dampened by d.

conditional Right Wall Bounce if(b.x>width-b.r){b.x=width-b.r;b.vx*=-d;}

Stops the ball from leaving the right edge and reverses its horizontal velocity, dampened by d.

conditional Floor Bounce if(b.y>height-b.r){b.y=height-b.r;b.vy*=-d;}

Stops the ball from falling through the floor and reverses its vertical velocity, dampened by d, simulating a bounce.

conditional Remove Faded Ball if(age>1) balls.splice(i,1);

Deletes a ball from the array once it has fully faded out, keeping the array from growing forever.

background(200);
Paints the whole canvas light gray every frame, erasing the previous frame's drawings so old ball positions don't leave trails.
let now=millis();
Grabs the current time in milliseconds since the sketch started - used to calculate how old each ball is.
for(let i=balls.length-1;i>=0;i--){
Loops through the balls array from the last item to the first. Going backwards is important because splice() will remove items during the loop.
let b=balls[i],age=(now-b.t)/5000;
Grabs a shorthand reference b to the current ball, and computes its age as a fraction from 0 to 1+ based on how long ago it was created (divided by 5000ms).
b.vy+=g;
Adds gravity to the ball's vertical velocity every frame, making it accelerate downward like a falling object.
b.x+=b.vx;
Moves the ball horizontally by its current horizontal velocity.
b.y+=b.vy;
Moves the ball vertically by its current vertical velocity (which keeps growing due to gravity).
if(b.x<b.r){b.x=b.r;b.vx*=-d;}
If the ball's left edge has crossed the left wall, snap it back inside and reverse its horizontal velocity, losing some speed via the damping factor d.
if(b.x>width-b.r){b.x=width-b.r;b.vx*=-d;}
Same idea for the right wall - clamp the position and bounce the velocity back with damping.
if(b.y>height-b.r){b.y=height-b.r;b.vy*=-d;}
If the ball hits the floor, clamp its position to the floor line and reverse its vertical velocity with damping, creating the bounce effect. Notice there's no matching check for the ceiling.
if(age>1) balls.splice(i,1);
Once the ball's age fraction passes 1 (meaning it's fully faded), remove it permanently from the array so it stops being updated and drawn.
fill(b.c[0],b.c[1],b.c[2],255*(1-age));
Sets the fill color to the ball's stored random RGB values, with alpha (opacity) decreasing linearly as age approaches 1, creating the fade-out.
circle(b.x,b.y,b.r*2);
Draws the ball as a circle at its current position, using r*2 as the diameter since r represents the radius.

mousePressed()

mousePressed() is a p5.js event function that automatically runs once every time the mouse button is clicked. It's the perfect place to spawn new objects into an array in response to user interaction.

🔬 vx starts as a small random value between -2 and 2. What happens if you widen that range to random(-8,8) - do balls scatter sideways much more dramatically when dropped?

  balls.push({x:mouseX,y:mouseY,vx:random(-2,2),vy:0,
              r:random(15,30),c:[random(255),random(255),random(255)],t:millis()});
function mousePressed(){
  balls.push({x:mouseX,y:mouseY,vx:random(-2,2),vy:0,
              r:random(15,30),c:[random(255),random(255),random(255)],t:millis()});
}
Line-by-line explanation (2 lines)
balls.push({x:mouseX,y:mouseY,vx:random(-2,2),vy:0,
Creates a new plain object representing a ball, starting at the mouse's click position with a small random horizontal velocity and zero vertical velocity, then adds it to the end of the balls array.
r:random(15,30),c:[random(255),random(255),random(255)],t:millis()});
Finishes the ball object: gives it a random radius between 15 and 30, a random RGB color as an array of three numbers, and records its creation time using millis() so its age can be tracked later.

windowResized()

windowResized() is a built-in p5.js callback that fires whenever the browser window is resized, letting you keep a full-window canvas responsive without any extra event-listener code.

function windowResized(){
  resizeCanvas(windowWidth,windowHeight);
}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth,windowHeight);
Automatically called by p5.js whenever the browser window changes size; this resizes the canvas to match, so the sketch always fills the window.

📦 Key Variables

balls array

Stores every currently active ball as an object with position, velocity, radius, color, and creation time. Grows when you click and shrinks as balls fade out.

let balls=[];
g number

The gravity constant added to each ball's vertical velocity every frame, controlling how fast balls accelerate downward.

let g=0.4;
d number

The damping (bounce energy loss) factor applied to velocity whenever a ball hits a wall or the floor - values closer to 1 keep more bounce energy.

let d=0.8;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

BUG draw() floor/wall bounce checks

There is no check for the ball crossing the top of the canvas (b.y<b.r), so a ball with strong upward velocity (from a hard bounce) can fly off the top of the screen and never come back since gravity will eventually pull it back down off-screen, wasting frames updating an invisible ball.

💡 Add a matching conditional like `if(b.y<b.r){b.y=b.r;b.vy*=-d;}` to bounce it back down, or simply skip drawing/updating balls once they're far outside the canvas.

STYLE global variables and mousePressed()

Magic numbers like 0.4, 0.8, 5000, 15, and 30 are scattered through the code without explanation, making the physics tuning harder to understand at a glance.

💡 Extract them into named constants such as `let GRAVITY=0.4, BOUNCE_DAMPING=0.8, FADE_MS=5000, MIN_RADIUS=15, MAX_RADIUS=30;` and reference those names throughout for clarity.

PERFORMANCE draw()

Every ball recalculates `255*(1-age)` and constructs its fill color every frame even when barely changing, and colors are stored as raw arrays rather than p5.Color objects, which is fine here but won't scale well if hundreds of balls are on screen at once.

💡 For heavier use, consider using p5's `color()` objects created once per ball and adjusting only the alpha channel, or batching similar draws, to reduce per-frame overhead.

FEATURE mousePressed()

Balls can currently only be spawned one at a time per click, and mouseDragged is not implemented, so holding the mouse down while moving doesn't create a stream of balls.

💡 Add a `mouseDragged(){ mousePressed(); }` function (or copy its body) so dragging the mouse continuously spawns balls for an even more satisfying effect.

🔄 Code Flow

Code flow showing setup, draw, mousepressed, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> ballloop[ball-loop] ballloop --> wallbounceleft[wall-bounce-left] wallbounceleft --> wallbounceright[wall-bounce-right] wallbounceright --> floorbounce[floor-bounce] floorbounce --> removeoldball[remove-old-ball] removeoldball --> draw click setup href "#fn-setup" click draw href "#fn-draw" click ballloop href "#sub-ball-loop" click wallbounceleft href "#sub-wall-bounce-left" click wallbounceright href "#sub-wall-bounce-right" click floorbounce href "#sub-floor-bounce" click removeoldball href "#sub-remove-old-ball" draw --> mousepressed[mousepressed] mousepressed --> draw click mousepressed href "#fn-mousepressed" draw --> windowresized[windowresized] windowresized --> draw click windowresized href "#fn-windowresized"

❓ Frequently Asked Questions

What visual experience does the AI Bouncy Ball Drop sketch provide?

The sketch creates a colorful display of bouncy balls that fall and bounce off the walls and floor, gradually fading away as they lose height with each bounce.

How can I interact with the AI Bouncy Ball Drop sketch?

Users can interact by clicking anywhere on the canvas to drop colorful bouncy balls, creating a satisfying visual effect as they react to gravity and bounce.

What coding concepts does the AI Bouncy Ball Drop sketch illustrate?

This sketch demonstrates basic physics principles, including gravity and collision detection, along with the use of arrays to manage multiple objects in a creative coding environment.

Preview

AI Bouncy Ball Drop - Satisfying Physics - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Bouncy Ball Drop - Satisfying Physics - Code flow showing setup, draw, mousepressed, windowresized
Code Flow Diagram