Sketch 2026-01-26 02:56

This sketch creates a fully playable Pong game where two players control paddles on opposite sides of the screen to bounce a ball back and forth. The game tracks scores and resets the ball whenever it goes out of bounds on either side.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the ball faster — A faster ball creates more challenge and excitement—watch it zip across the court
  2. Make paddles taller
  3. Add paddle color — Using fill() before drawing the paddles makes them appear in your chosen color instead of white
  4. Make the ball red — Adding fill(255, 0, 0) before the ellipse() call draws the ball in red instead of white
  5. Widen the court — A wider canvas makes the paddles harder to hit and gives players more space to move
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch recreates the classic Pong arcade game in just 100 lines of p5.js code. Two players compete by moving paddles up and down to bounce a ball back and forth across the screen. The game combines object properties, collision detection, keyboard input handling, and scoring logic—making it an ideal project for learning how real games are structured.

The code is organized around three key objects: a ball that moves and bounces, two paddle objects controlled by keyboard input, and score variables that track each player's points. By studying it, you will learn how to detect collisions between moving shapes, handle multiple keyboard inputs simultaneously with keyIsDown(), manage game state through objects and variables, and structure the draw loop to handle physics, input, and scoring in the right order.

⚙️ How It Works

  1. When the sketch loads, setup() creates a 600x400 pixel canvas and initializes the ball in the center with a horizontal and vertical speed, plus two paddle objects positioned on the left and right sides.
  2. Every frame, draw() clears the background and draws a dotted center line and the ball as a filled circle.
  3. The ball's x and y position are updated by adding its xspeed and yspeed each frame, making it glide across the screen.
  4. Two if-statements check if the ball has hit the top or bottom edge and flip its yspeed to bounce it vertically.
  5. Two more if-statements use bounding box collision detection to check if the ball touches either paddle—if it does, the ball's xspeed is reversed to bounce it back.
  6. If the ball travels past the left edge (x < 0), the right player scores a point and resetBall() repositions it in the center; if it passes the right edge (x > width), the left player scores.
  7. Both paddles are drawn and then moved based on which keys are pressed (W/S for left player, UP_ARROW/DOWN_ARROW for right player), and the current scores are displayed at the top.

🎓 Concepts You'll Learn

Object properties and state managementCollision detection (bounding box)Keyboard input with keyIsDown()Game loops and frame-based animationScore tracking and game events

📝 Code Breakdown

setup()

setup() runs once at the very beginning. It is where you define the canvas size and initialize all your game objects with their starting properties. Using objects (like ball = { ... }) keeps related data organized instead of scattering variables everywhere.

function setup() {
  createCanvas(600, 400);
  
  ball = {
    x: width / 2,
    y: height / 2,
    r: 10,
    xspeed: 4,
    yspeed: 3
  };
  
  leftPaddle = {
    x: 20,
    y: height / 2 - 40,
    w: 10,
    h: 80
  };
  
  rightPaddle = {
    x: width - 30,
    y: height / 2 - 40,
    w: 10,
    h: 80
  };
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

calculation Canvas Setup createCanvas(600, 400);

Creates the 600x400 pixel game board where all action happens

calculation Ball Initialization ball = { x: width / 2, y: height / 2, r: 10, xspeed: 4, yspeed: 3 };

Creates an object storing the ball's position (x, y), radius (r), and velocity (xspeed, yspeed)

calculation Paddle Initialization leftPaddle = { x: 20, y: height / 2 - 40, w: 10, h: 80 }; rightPaddle = { x: width - 30, y: height / 2 - 40, w: 10, h: 80 };

Creates two paddle objects with position (x, y), width (w), and height (h)

createCanvas(600, 400);
Creates a 600 pixel wide by 400 pixel tall canvas—the playing field for Pong
ball = {
Starts defining the ball as an object that will store all its properties
x: width / 2,
Sets the ball's starting x position to the exact center of the canvas (300 pixels)
y: height / 2,
Sets the ball's starting y position to the exact center vertically (200 pixels)
r: 10,
Stores the ball's radius as 10 pixels, so its diameter when drawn will be 20 pixels
xspeed: 4,
The ball will move 4 pixels to the right each frame (or left when bouncing)
yspeed: 3
The ball will move 3 pixels down each frame (or up when bouncing off top/bottom)
leftPaddle = {
Creates an object to store all the left paddle's properties in one place
x: 20,
Positions the left paddle 20 pixels from the left edge of the canvas
y: height / 2 - 40,
Positions the left paddle vertically centered (height / 2) then nudges it up 40 pixels so it's centered on its middle
w: 10,
The paddle is 10 pixels wide, making it a thin vertical rectangle
h: 80
The paddle is 80 pixels tall, giving it a reasonable target size for hitting the ball

draw()

draw() is the heartbeat of your sketch—it runs 60 times per second and handles every animation frame. This is where you update positions, check collisions, handle input, and redraw everything. The order matters: update positions first, then check collisions, then draw. If you draw first and check collisions second, the visual won't match the logic.

🔬 This code flips yspeed when the ball hits top or bottom. What happens if you change *= -1 to *= -0.8, so the ball loses some energy on each bounce?

  // Ball bounce top/bottom
  if (ball.y < ball.r || ball.y > height - ball.r) {
    ball.yspeed *= -1;
  }

🔬 These two lines draw the paddles as rectangles. What happens if you swap the leftPaddle and rightPaddle so the controls feel reversed?

  // Paddles
  rect(leftPaddle.x, leftPaddle.y, leftPaddle.w, leftPaddle.h);
  rect(rightPaddle.x, rightPaddle.y, rightPaddle.w, rightPaddle.h);
function draw() {
  background(0);
  
  // Center line
  stroke(255);
  for (let y = 0; y < height; y += 20) {
    line(width / 2, y, width / 2, y + 10);
  }
  
  // Ball
  ellipse(ball.x, ball.y, ball.r * 2);
  ball.x += ball.xspeed;
  ball.y += ball.yspeed;
  
  // Ball bounce top/bottom
  if (ball.y < ball.r || ball.y > height - ball.r) {
    ball.yspeed *= -1;
  }
  
  // Paddle collision
  if (ball.x - ball.r < leftPaddle.x + leftPaddle.w &&
      ball.y > leftPaddle.y &&
      ball.y < leftPaddle.y + leftPaddle.h) {
    ball.xspeed *= -1;
  }
  
  if (ball.x + ball.r > rightPaddle.x &&
      ball.y > rightPaddle.y &&
      ball.y < rightPaddle.y + rightPaddle.h) {
    ball.xspeed *= -1;
  }
  
  // Score
  if (ball.x < 0) {
    rightScore++;
    resetBall();
  }
  
  if (ball.x > width) {
    leftScore++;
    resetBall();
  }
  
  // Paddles
  rect(leftPaddle.x, leftPaddle.y, leftPaddle.w, leftPaddle.h);
  rect(rightPaddle.x, rightPaddle.y, rightPaddle.w, rightPaddle.h);
  
  // Controls
  if (keyIsDown(87)) leftPaddle.y -= 5; // W
  if (keyIsDown(83)) leftPaddle.y += 5; // S
  if (keyIsDown(UP_ARROW)) rightPaddle.y -= 5;
  if (keyIsDown(DOWN_ARROW)) rightPaddle.y += 5;
  
  // Scores
  textSize(24);
  text(leftScore, width / 2 - 50, 30);
  text(rightScore, width / 2 + 30, 30);
}
Line-by-line explanation (19 lines)

🔧 Subcomponents:

for-loop Center Line Loop for (let y = 0; y < height; y += 20) { line(width / 2, y, width / 2, y + 10); }

Draws a dashed line down the middle of the court, typical of Pong aesthetics

calculation Ball Position Update ball.x += ball.xspeed; ball.y += ball.yspeed;

Moves the ball by adding its velocity to its position each frame

conditional Top/Bottom Wall Bounce if (ball.y < ball.r || ball.y > height - ball.r) { ball.yspeed *= -1; }

Flips the ball's vertical direction when it hits the top or bottom edge

conditional Left Paddle Collision if (ball.x - ball.r < leftPaddle.x + leftPaddle.w && ball.y > leftPaddle.y && ball.y < leftPaddle.y + leftPaddle.h) { ball.xspeed *= -1; }

Checks if the ball touches the left paddle using three conditions (x overlap and y overlap) and bounces it

conditional Right Paddle Collision if (ball.x + ball.r > rightPaddle.x && ball.y > rightPaddle.y && ball.y < rightPaddle.y + rightPaddle.h) { ball.xspeed *= -1; }

Checks if the ball touches the right paddle and bounces it back

conditional Right Player Scores if (ball.x < 0) { rightScore++; resetBall(); }

When the ball goes off the left edge, right player gains a point and ball resets

conditional Left Player Scores if (ball.x > width) { leftScore++; resetBall(); }

When the ball goes off the right edge, left player gains a point and ball resets

conditional Paddle Controls if (keyIsDown(87)) leftPaddle.y -= 5; // W if (keyIsDown(83)) leftPaddle.y += 5; // S if (keyIsDown(UP_ARROW)) rightPaddle.y -= 5; if (keyIsDown(DOWN_ARROW)) rightPaddle.y += 5;

Moves paddles based on which keys are currently pressed, allowing smooth simultaneous input

background(0);
Clears the canvas to black each frame, erasing the previous ball position so it doesn't leave a trail
stroke(255);
Sets the line color to white (255) for drawing the center dividing line
for (let y = 0; y < height; y += 20) {
Starts a loop that runs from y = 0 up to the canvas height, incrementing by 20 each time
line(width / 2, y, width / 2, y + 10);
Draws a 10-pixel-tall white line at the center of the canvas, then repeats with a 20-pixel gap to create the dashed effect
ellipse(ball.x, ball.y, ball.r * 2);
Draws the ball as a circle at its current x and y position with diameter ball.r * 2 (20 pixels)
ball.x += ball.xspeed;
Updates the ball's x position by adding its horizontal speed (4 pixels), making it move left or right
ball.y += ball.yspeed;
Updates the ball's y position by adding its vertical speed (3 pixels), making it move up or down
if (ball.y < ball.r || ball.y > height - ball.r) {
Checks if the ball has gone above the top (y < 10) or below the bottom (y > 390), indicating a wall hit
ball.yspeed *= -1;
Flips the sign of vertical speed: if it was +3, it becomes -3, reversing the bounce direction
if (ball.x - ball.r < leftPaddle.x + leftPaddle.w &&
Checks if the ball's left edge (ball.x - 10) has reached or passed the right edge of the left paddle
ball.y > leftPaddle.y &&
Checks if the ball's vertical position is lower than the paddle's top edge
ball.y < leftPaddle.y + leftPaddle.h) {
Checks if the ball's vertical position is higher than the paddle's bottom edge—all three conditions must be true to register a hit
if (keyIsDown(87)) leftPaddle.y -= 5; // W
If the W key is currently held down, move the left paddle up 5 pixels per frame
if (keyIsDown(83)) leftPaddle.y += 5; // S
If the S key is currently held down, move the left paddle down 5 pixels per frame
if (keyIsDown(UP_ARROW)) rightPaddle.y -= 5;
If the UP arrow is pressed, move the right paddle up 5 pixels per frame
if (keyIsDown(DOWN_ARROW)) rightPaddle.y += 5;
If the DOWN arrow is pressed, move the right paddle down 5 pixels per frame
textSize(24);
Sets the font size to 24 pixels for displaying the scores
text(leftScore, width / 2 - 50, 30);
Draws the left player's score 50 pixels to the left of center, 30 pixels from the top
text(rightScore, width / 2 + 30, 30);
Draws the right player's score 30 pixels to the right of center, 30 pixels from the top

resetBall()

resetBall() is a helper function that the draw() function calls whenever someone scores. Breaking out common tasks into helper functions makes your code cleaner and easier to reuse. Notice that this function only does one job—reset the ball. That is good function design.

function resetBall() {
  ball.x = width / 2;
  ball.y = height / 2;
  ball.xspeed *= -1;
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Reset Position ball.x = width / 2; ball.y = height / 2;

Moves the ball back to the center of the canvas

calculation Reverse Direction ball.xspeed *= -1;

Flips the ball's horizontal direction so it moves toward the player who just scored

ball.x = width / 2;
Sets the ball's x position back to the exact center of the canvas horizontally
ball.y = height / 2;
Sets the ball's y position back to the exact center of the canvas vertically
ball.xspeed *= -1;
Multiplies xspeed by -1 to reverse its direction—if it was moving right, it now moves left toward the other player

📦 Key Variables

ball object

Stores all the ball's properties: position (x, y), radius (r), and velocity (xspeed, yspeed). Using an object groups related data together.

let ball = { x: 300, y: 200, r: 10, xspeed: 4, yspeed: 3 };
leftPaddle object

Stores the left paddle's position (x, y) and dimensions (width w, height h). It is updated by keyboard input each frame.

let leftPaddle = { x: 20, y: 160, w: 10, h: 80 };
rightPaddle object

Stores the right paddle's position (x, y) and dimensions (width w, height h). It is updated by keyboard input each frame.

let rightPaddle = { x: 570, y: 160, w: 10, h: 80 };
leftScore number

Tracks how many points the left player has scored. Incremented whenever the ball goes off the right edge.

let leftScore = 0;
rightScore number

Tracks how many points the right player has scored. Incremented whenever the ball goes off the left edge.

let rightScore = 0;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG draw() paddle collision detection

The ball can phase through a paddle if it is moving faster than the paddle is wide, especially at corners. This happens because we only check the ball's position after it moves—if the ball travels far enough in one frame, it can skip over the collision zone entirely.

💡 Implement continuous collision detection by checking if the ball's path crossed the paddle this frame, rather than just checking its final position. For a quick fix, ensure ball speed is never greater than paddle width (10 pixels).

BUG draw() paddle movement

Paddles can move off-screen. If a player holds W or S for long enough, the paddles will slide off the top or bottom of the canvas.

💡 Add boundary checking after paddle movement: constrain(leftPaddle.y, 0, height - leftPaddle.h) to keep paddles on-screen.

STYLE draw() comments

Comments are helpful, but the code has several commented sections that could be organized better—some magic numbers like 5 (paddle speed) and 10 (paddle size) are duplicated and hard to change.

💡 Define constants at the top for PADDLE_SPEED = 5, PADDLE_WIDTH = 10, etc., and use them throughout. This makes tuning the game much easier and clearer.

FEATURE resetBall function

After a score, the ball always resets with the same xspeed and yspeed. In real Pong, the ball often speeds up slightly after each rally to increase difficulty.

💡 Gradually increase ball.xspeed and ball.yspeed as the game progresses: ball.xspeed *= 1.02; ball.yspeed *= 1.02; after each score to make long rallies more exciting.

PERFORMANCE draw() fill color

No explicit fill() or noFill() is called, so all shapes use the default white fill. If the canvas background is black, this works, but it is relying on p5.js defaults.

💡 Add fill(255); at the start of draw() to explicitly set white for all shapes, making the code more readable and less dependent on defaults.

🔄 Code Flow

Code flow showing setup, draw, resetball

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

graph TD start[Start] --> setup[setup] setup --> canvas-creation[Canvas Setup] setup --> ball-init[Ball Initialization] setup --> paddle-init[Paddle Initialization] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-creation href "#sub-canvas-creation" click ball-init href "#sub-ball-init" click paddle-init href "#sub-paddle-init" draw --> center-line[Center Line Loop] draw --> ball-update[Ball Position Update] draw --> wall-bounce[Top/Bottom Wall Bounce] draw --> left-collision[Left Paddle Collision] draw --> right-collision[Right Paddle Collision] draw --> score-left[Right Player Scores] draw --> score-right[Left Player Scores] draw --> keyboard-input[Paddle Controls] click draw href "#fn-draw" click center-line href "#sub-center-line" click ball-update href "#sub-ball-update" click wall-bounce href "#sub-wall-bounce" click left-collision href "#sub-left-collision" click right-collision href "#sub-right-collision" click score-left href "#sub-score-left" click score-right href "#sub-score-right" click keyboard-input href "#sub-keyboard-input" ball-update --> reset-position[Reset Position] ball-update --> reverse-direction[Reverse Direction] click reset-position href "#sub-reset-position" click reverse-direction href "#sub-reverse-direction" left-collision --> resetball[resetBall] right-collision --> resetball score-left --> resetball score-right --> resetball resetball --> ball-init click resetball href "#fn-resetball"

❓ Frequently Asked Questions

What visual elements are present in the p5.js sketch?

The sketch features a simple pong game with a black background, two paddles, a bouncing ball, and a center line for visual reference.

How can users control the paddles in the sketch?

Users can move the left paddle up and down using the 'W' and 'S' keys, while the right paddle can be controlled with the up and down arrow keys.

What creative coding concepts are illustrated in this sketch?

This sketch demonstrates basic collision detection, object movement, and scorekeeping in a simple game environment.

Preview

Sketch 2026-01-26 02:56 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch 2026-01-26 02:56 - Code flow showing setup, draw, resetball
Code Flow Diagram