im sorry if it doesn't work

This sketch creates an interactive ragdoll character that responds to mouse interactions in a physics-driven environment. You can drag the buddy around or punch it with quick clicks to earn points, watching its limbs flail realistically as gravity and collisions affect each body part independently.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the buddy heavier — Increasing the body's mass makes it harder to knock around—it will sink through the air more slowly and resist punches
  2. Slow down gravity — Reducing gravity makes the buddy float and fall more gently, like it's underwater
  3. Make punches superstrong — Higher punchForce values send body parts flying across the screen with minimal effort
  4. Paint the buddy red — Changing the body color makes the entire character a different shade
  5. Earn more points for dragging — Higher drag score values reward you faster for moving the buddy around
  6. Speed up punch cooldown — Lowering hitCooldown lets you punch more rapidly and rack up points quicker
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a playable ragdoll character called "buddy" that you can punch and drag around a canvas with gravity. The character is made of six separate body parts (head, body, two arms, two legs) connected by joints, all governed by physics simulation. It demonstrates p5play's Sprite system, physics gravity, constraint joints, collision detection, and score tracking—essential techniques for building interactive games and simulations.

The code is organized into a setup() function that initializes the physics world and creates the buddy, a createBuddy() helper that assembles the ragdoll from individual sprites and joints, and a draw() loop that renders everything and detects mouse interactions like dragging and punching. By studying it, you'll learn how to assemble complex characters from simple shapes, connect them with joints, apply forces with mouse input, and track player interactions as a score.

⚙️ How It Works

  1. When the sketch loads, setup() creates a canvas sized to the window, enables gravity in the physics world, and builds four static walls (ground, ceiling, left and right walls) that the buddy cannot pass through.
  2. createBuddy() constructs the ragdoll by spawning six Sprite objects—a circular head, rectangular body, two arms, and two legs—positioned around a center point. Each part has its own mass so they respond differently to forces.
  3. Joint objects connect neighboring parts together (neck joints head to body, shoulders join arms to body, hips join legs to body), allowing them to move somewhat independently while staying attached.
  4. Every frame, draw() renders all sprites and checks for mouse interaction: if the mouse is pressed and dragging, the score increases slowly; if the mouse is just clicked (not dragging), the code finds which buddy part is under the cursor and applies a punch force away from the mouse.
  5. A cooldown system (hitCooldown) prevents the score from spiking too rapidly, ensuring each punch must wait at least 500 milliseconds before the next one counts.
  6. The windowResized() function watches for screen resize events and recreates the entire buddy and environment so the sketch stays responsive on different device sizes.

🎓 Concepts You'll Learn

Physics simulation with gravitySprite-based ragdoll characterConstraint joints connecting body partsMouse interaction and force applicationCollision detection and boundary checkingGame scoring and cooldown mechanics

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It initializes the physics world, creates static boundary sprites, and calls createBuddy() to assemble the interactive character. The 'static' collider type means these walls never move—only the buddy responds to collisions and gravity.

function setup() {
  createCanvas(windowWidth, windowHeight);
  world.gravity.y = 10; // Set gravity for physics simulation

  // --- Create Environment ---
  // Ground
  ground = new Sprite(width / 2, height - 10, width, 20);
  ground.collider = 'static';
  ground.color = color(100, 100, 100);

  // Walls
  wallLeft = new Sprite(10, height / 2, 20, height);
  wallLeft.collider = 'static';
  wallLeft.color = color(100, 100, 100);

  wallRight = new Sprite(width - 10, height / 2, 20, height);
  wallRight.collider = 'static';
  wallRight.color = color(100, 100, 100);

  // Ceiling
  ceiling = new Sprite(width / 2, 10, width, 20);
  ceiling.collider = 'static';
  ceiling.color = color(100, 100, 100);

  // --- Create Buddy Ragdoll ---
  createBuddy();
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Canvas Setup createCanvas(windowWidth, windowHeight);

Creates a full-screen canvas that matches the browser window dimensions

assignment Physics Gravity world.gravity.y = 10;

Enables downward gravity in the physics world so sprites fall naturally

calculation Environment Boundary Sprites ground = new Sprite(width / 2, height - 10, width, 20);

Creates static walls, ceiling, and ground that trap the buddy inside the canvas

createCanvas(windowWidth, windowHeight);
Creates a canvas as tall and wide as the browser window, making the sketch responsive
world.gravity.y = 10;
Activates gravity in the p5play physics world; positive y pulls sprites downward at strength 10
ground = new Sprite(width / 2, height - 10, width, 20);
Creates a ground sprite centered horizontally near the bottom of the canvas (height - 10)
ground.collider = 'static';
Marks the ground as static so it doesn't move when the buddy collides with it
ground.color = color(100, 100, 100);
Colors the ground dark gray so it's visible against the light background
createBuddy();
Calls the helper function that assembles the buddy ragdoll character from individual parts

createBuddy()

createBuddy() assembles a ragdoll by creating six independent sprites with different masses and colors, then linking them with Joint constraints. Joints allow the parts to move with some freedom while staying attached—the more flexible you make them (lower masses, removed rotationLocks), the more chaotic the ragdoll becomes. This function is called once in setup() and again whenever the window resizes.

🔬 This creates one leg. What happens if you change buddy.legL.mass from 0.7 to 2? Do the legs fall differently when you punch the body?

  // Legs
  buddy.legL = new Sprite(startX - bodyW / 4, startY + bodyH / 2 + legH / 2, legW, legH, 'box');
  buddy.legL.mass = 0.7;
  buddy.legL.color = color(50, 50, 50); // Dark pants

🔬 The last line locks the arm's rotation. What happens if you delete that line or set rotationLock to false? Will the arm flail more wildly?

  buddy.armL = new Sprite(startX - bodyW / 2 - armH / 2, startY - bodyH / 4, armH, armW, 'box');
  buddy.armL.mass = 0.5;
  buddy.armL.color = color(255, 200, 150);
  buddy.armL.rotationLock = true;
function createBuddy() {
  // Clear any existing buddy parts
  if (buddy) {
    buddy.head.remove();
    buddy.body.remove();
    buddy.armL.remove();
    buddy.armR.remove();
    buddy.legL.remove();
    buddy.legR.remove();
    // Joints are automatically removed with sprites
  }

  // Define buddy parts
  const startX = width / 2;
  const startY = height / 2;
  const headSize = 40;
  const bodyW = 50;
  const bodyH = 80;
  const armW = 20;
  const armH = 60;
  const legW = 25;
  const legH = 80;

  buddy = {};

  // Head
  buddy.head = new Sprite(startX, startY - bodyH / 2 - headSize / 2, headSize, headSize, 'circle');
  buddy.head.mass = 1;
  buddy.head.color = color(255, 200, 150); // Skin tone

  // Body
  buddy.body = new Sprite(startX, startY, bodyW, bodyH, 'box');
  buddy.body.mass = 2;
  buddy.body.color = color(100, 100, 255); // Blue shirt

  // Arms
  buddy.armL = new Sprite(startX - bodyW / 2 - armH / 2, startY - bodyH / 4, armH, armW, 'box');
  buddy.armL.mass = 0.5;
  buddy.armL.color = color(255, 200, 150);
  buddy.armL.rotationLock = true; // Prevent arms from rotating freely at shoulder

  buddy.armR = new Sprite(startX + bodyW / 2 + armH / 2, startY - bodyH / 4, armH, armW, 'box');
  buddy.armR.mass = 0.5;
  buddy.armR.color = color(255, 200, 150);
  buddy.armR.rotationLock = true;

  // Legs
  buddy.legL = new Sprite(startX - bodyW / 4, startY + bodyH / 2 + legH / 2, legW, legH, 'box');
  buddy.legL.mass = 0.7;
  buddy.legL.color = color(50, 50, 50); // Dark pants

  buddy.legR = new Sprite(startX + bodyW / 4, startY + bodyH / 2 + legH / 2, legW, legH, 'box');
  buddy.legR.mass = 0.7;
  buddy.legR.color = color(50, 50, 50);

  // --- Create Joints ---
  // Neck joint (Head to Body)
  new Joint(buddy.head, buddy.body, {
    x: startX,
    y: startY - bodyH / 2
  });

  // Shoulders (Body to Arms)
  new Joint(buddy.body, buddy.armL, {
    x: startX - bodyW / 2,
    y: startY - bodyH / 4
  });
  new Joint(buddy.body, buddy.armR, {
    x: startX + bodyW / 2,
    y: startY - bodyH / 4
  });

  // Hips (Body to Legs)
  new Joint(buddy.body, buddy.legL, {
    x: startX - bodyW / 4,
    y: startY + bodyH / 2
  });
  new Joint(buddy.body, buddy.legR, {
    x: startX + bodyW / 4,
    y: startY + bodyH / 2
  });
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Clean Up Old Sprites if (buddy) { buddy.head.remove(); buddy.body.remove(); ... }

Removes any existing buddy parts before creating new ones, preventing duplicate sprites from piling up

calculation Body Part Dimensions const headSize = 40; const bodyW = 50; const bodyH = 80;

Stores the width and height of each body part so they're easy to adjust globally

calculation Head Sprite Creation buddy.head = new Sprite(startX, startY - bodyH / 2 - headSize / 2, headSize, headSize, 'circle');

Creates a circular head sprite positioned above the body using size offsets

calculation Joint Creation Loop new Joint(buddy.head, buddy.body, { x: startX, y: startY - bodyH / 2 });

Connects two sprites with a constraint joint that keeps them together while allowing movement

if (buddy) {
Checks if a buddy already exists (e.g., after a window resize) before creating a new one
buddy.head.remove();
Removes the old head sprite from the physics world and canvas
const startX = width / 2;
Stores the horizontal center of the canvas as the buddy's starting x position
const headSize = 40;
Defines the head diameter once so it's consistent and easy to change later
buddy = {};
Creates an empty object that will store all buddy body parts as properties (head, body, armL, etc.)
buddy.head = new Sprite(startX, startY - bodyH / 2 - headSize / 2, headSize, headSize, 'circle');
Creates a circular sprite for the head; the y position (startY - bodyH / 2 - headSize / 2) places it above the body
buddy.head.mass = 1;
Sets the head's mass to 1, making it medium-weight so it falls and swings realistically
buddy.armL.rotationLock = true;
Prevents the arm from spinning freely, keeping it more rigid than it would be otherwise
new Joint(buddy.head, buddy.body, { x: startX, y: startY - bodyH / 2 });
Creates a neck joint connecting the head to the body at the attachment point; the x and y define where they meet

draw()

draw() is the main loop that runs 60 times per second. Each frame it clears the background, renders all sprites, and checks for mouse input. The interaction system detects hits by looping through all buddy parts and testing if the mouse is inside their collision bounds (using distance for circles and rectangular bounds for boxes). The cooldown system (checking millis() - lastHitTime) prevents rapid-fire scoring, making each punch meaningful.

🔬 This loop finds the first body part under the mouse cursor and stops (break). What happens if you delete the break statement? Could you punch multiple parts at once?

        // Find which part of the buddy the mouse is over
        let targetSprite = null;
        for (let part of Object.values(buddy)) {
          if (dist(mouse.x, mouse.y, part.x, part.y) < part.radius || // For circle (head)
              (mouse.x > part.x - part.w / 2 && mouse.x < part.x + part.w / 2 &&
               mouse.y > part.y - part.h / 2 && mouse.y < part.y + part.h / 2)) { // For boxes
            targetSprite = part;
            break;
          }
        }

🔬 This applies the punch force and awards 10 points. What happens if you change score += 10 to score += 100? Do easy punches suddenly pay off big?

        if (targetSprite) {
          // Apply a force from the mouse position to the sprite
          let forceX = (targetSprite.x - mouse.x) * punchForce;
          let forceY = (targetSprite.y - mouse.y) * punchForce;
          targetSprite.applyForce(forceX, forceY);
          score += 10; // Add to score for a punch
          lastHitTime = millis(); // Update last hit time
        }
function draw() {
  background(220); // Light grey background

  // Draw all sprites
  drawSprites();

  // Handle mouse/touch interaction
  if (mouse.pressed) {
    // If a sprite is being dragged by the mouse, add to score
    if (mouse.dragging) {
      score += 0.1; // Increment score slowly while dragging
    } else {
      // If mouse is just pressed (not dragging yet), check for a "punch"
      // Apply a force only if enough time has passed since the last hit
      if (millis() - lastHitTime > hitCooldown) {
        // Find which part of the buddy the mouse is over
        let targetSprite = null;
        for (let part of Object.values(buddy)) {
          if (dist(mouse.x, mouse.y, part.x, part.y) < part.radius || // For circle (head)
              (mouse.x > part.x - part.w / 2 && mouse.x < part.x + part.w / 2 &&
               mouse.y > part.y - part.h / 2 && mouse.y < part.y + part.h / 2)) { // For boxes
            targetSprite = part;
            break;
          }
        }

        if (targetSprite) {
          // Apply a force from the mouse position to the sprite
          let forceX = (targetSprite.x - mouse.x) * punchForce;
          let forceY = (targetSprite.y - mouse.y) * punchForce;
          targetSprite.applyForce(forceX, forceY);
          score += 10; // Add to score for a punch
          lastHitTime = millis(); // Update last hit time
        }
      }
    }
  }

  // Display score and instructions
  fill(0);
  textSize(24);
  textAlign(LEFT, TOP);
  text('Score: ' + floor(score), 20, 20);
  textSize(16);
  text('Drag to move, click to punch!', 20, 50);
  text('Refresh the page to reset the buddy.', 20, 70);
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

calculation Frame Clear background(220);

Erases the previous frame's drawing so only the current positions of all sprites are visible

conditional Dragging Score Check if (mouse.dragging) { score += 0.1; }

Awards points slowly while the player is dragging any sprite around

conditional Punch Hit Detection if (millis() - lastHitTime > hitCooldown) {

Ensures the player can't punch too rapidly by checking that enough milliseconds have elapsed since the last successful punch

for-loop Sprite Collision Check for (let part of Object.values(buddy)) {

Loops through every body part to find which one the mouse is currently over

conditional Circle Distance Check if (dist(mouse.x, mouse.y, part.x, part.y) < part.radius ||

Uses distance formula to check if the mouse is inside the circular head sprite

conditional Box Bounds Check (mouse.x > part.x - part.w / 2 && mouse.x < part.x + part.w / 2 &&

Checks if the mouse position falls inside the rectangular bounds of body parts like arms and legs

calculation Punch Force Calculation let forceX = (targetSprite.x - mouse.x) * punchForce;

Calculates the magnitude and direction of the force based on the distance between the mouse and the body part

background(220);
Fills the canvas with light gray (220 out of 255) each frame, erasing previous sprite positions
drawSprites();
Renders all p5play sprites (buddy parts and environment) at their current positions and orientations
if (mouse.pressed) {
Enters this block only when the mouse button is held down (not released)
if (mouse.dragging) {
True if the mouse has moved while pressed; dragging automatically selects a sprite under the cursor
score += 0.1;
Adds 0.1 to the score every frame while dragging, creating a steady reward for moving the buddy around
if (millis() - lastHitTime > hitCooldown) {
Calculates milliseconds elapsed since the last punch and only proceeds if it exceeds the cooldown threshold (500ms by default)
for (let part of Object.values(buddy)) {
Iterates through all six body parts stored in the buddy object (head, body, armL, armR, legL, legR)
if (dist(mouse.x, mouse.y, part.x, part.y) < part.radius ||
Uses p5.js dist() to measure the distance between the mouse and the sprite center; if less than the radius (for circles), the mouse is inside
(mouse.x > part.x - part.w / 2 && mouse.x < part.x + part.w / 2 &&
For box-shaped sprites, checks if the mouse x-coordinate falls between the left and right edges of the sprite
let forceX = (targetSprite.x - mouse.x) * punchForce;
The force direction points away from the mouse toward the sprite; multiplied by punchForce to scale the impact
let forceY = (targetSprite.y - mouse.y) * punchForce;
Vertical component of the punch force, calculated the same way as horizontal
targetSprite.applyForce(forceX, forceY);
Applies the calculated force to the selected body part, causing it to accelerate in the punch direction
score += 10;
Immediately awards 10 points for a successful punch (compared to 0.1 per frame for dragging)
lastHitTime = millis();
Records the current time in milliseconds so the cooldown timer resets
text('Score: ' + floor(score), 20, 20);
Displays the current score at coordinates (20, 20); floor() rounds down the decimal score to a whole number

windowResized()

windowResized() is a p5.js callback that fires automatically whenever the user resizes their browser window. By calling resizeCanvas() and setup(), the sketch adapts gracefully to different screen sizes, keeping the buddy centered and the walls proportional.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Recreate environment and buddy on resize
  setup();
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

calculation Canvas Resize resizeCanvas(windowWidth, windowHeight);

Adjusts the canvas dimensions to match the new browser window size

calculation Re-initialize Scene setup();

Calls setup() again to recreate the buddy and walls at the new canvas dimensions

resizeCanvas(windowWidth, windowHeight);
Resizes the canvas to match the current browser window dimensions, preventing white space or clipping
setup();
Re-runs the setup function to rebuild the buddy and environment at the new canvas size

📦 Key Variables

buddy object

Stores references to all six body part sprites (head, body, armL, armR, legL, legR) so they can be manipulated individually or looped through

buddy = { head: sprite, body: sprite, armL: sprite, ... }
ground Sprite

A static environment sprite representing the floor—the buddy collides with and rests on it

ground = new Sprite(width / 2, height - 10, width, 20)
wallLeft Sprite

A static vertical wall sprite on the left side of the canvas preventing the buddy from leaving the screen

wallLeft = new Sprite(10, height / 2, 20, height)
wallRight Sprite

A static vertical wall sprite on the right side of the canvas

wallRight = new Sprite(width - 10, height / 2, 20, height)
ceiling Sprite

A static horizontal wall sprite at the top of the canvas preventing the buddy from floating away

ceiling = new Sprite(width / 2, 10, width, 20)
score number

Tracks the player's cumulative points earned by punching and dragging the buddy

let score = 0
lastHitTime number

Stores the millisecond timestamp of the most recent punch to enforce the hitCooldown delay between successive punches

let lastHitTime = 0
hitCooldown number

The minimum milliseconds required between punches (500ms by default)—prevents score spam from rapid clicking

const hitCooldown = 500
punchForce number

Multiplier for the magnitude of force applied when punching—higher values create more dramatic impacts

const punchForce = 15

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG draw() collision detection

The collision check for boxes uses part.w and part.h, but p5play sprites store these as 'w' and 'h' only for rectangles—circles don't have a 'w' property, risking undefined values and errors

💡 Add a type check before accessing width/height: check if part.w exists, or use getWidth() and getHeight() methods if available in your p5play version

BUG createBuddy() joint positioning

Joint positions are hardcoded manually, and if body dimensions change, joints may not align properly with the new sprite sizes

💡 Calculate joint positions dynamically based on the sprite's current bounds (e.g., buddy.body.x, buddy.body.y, buddy.body.w / 2) rather than using constants

PERFORMANCE draw() punch detection

The collision detection loop runs every frame even when the mouse isn't being clicked, wasting computation

💡 Move the collision detection loop inside the `if (mouse.pressed)` block so it only calculates when necessary

STYLE createBuddy()

Dimensions are stored as separate const variables rather than grouped into a single object, making the code harder to read

💡 Use an object: const sizes = { head: 40, bodyW: 50, bodyH: 80, ... } and reference as sizes.head for cleaner code

FEATURE general

The ragdoll always spawns in the exact same position and orientation, making repeated play feel identical

💡 Add random initial velocities or rotation to the buddy parts when created: buddy.head.velocity.y = random(-3, 3)

FEATURE draw()

Score can grow very large and becomes visually overwhelming on the canvas

💡 Add a score multiplier or bonus threshold (e.g., 'Combo x2 if you punch within 1 second') to add gameplay depth

🔄 Code Flow

Code flow showing setup, createbuddy, draw, windowresized

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

graph TD start[Start] --> setup[setup] setup --> canvas-creation[Canvas Setup] setup --> gravity-enable[Physics Gravity] setup --> environment-sprites[Environment Boundary Sprites] setup --> createbuddy[createBuddy] setup --> windowresized[windowResized] draw[draw loop] --> background-clear[Frame Clear] draw --> collision-loop[Sprite Collision Check] collision-loop --> distance-check[Circle Distance Check] collision-loop --> box-bounds-check[Box Bounds Check] collision-loop --> punch-detection[Punch Hit Detection] punch-detection --> drag-score[Dragging Score Check] draw --> setup-recall[Re-initialize Scene] click setup href "#fn-setup" click createbuddy href "#fn-createbuddy" click draw href "#fn-draw" click windowresized href "#fn-windowresized" click canvas-creation href "#sub-canvas-creation" click gravity-enable href "#sub-gravity-enable" click environment-sprites href "#sub-environment-sprites" click cleanup-old-buddy href "#sub-cleanup-old-buddy" click define-dimensions href "#sub-define-dimensions" click create-head href "#sub-create-head" click create-joints href "#sub-create-joints" click background-clear href "#sub-background-clear" click drag-score href "#sub-drag-score" click punch-detection href "#sub-punch-detection" click collision-loop href "#sub-collision-loop" click distance-check href "#sub-distance-check" click box-bounds-check href "#sub-box-bounds-check" click force-application href "#sub-force-application" click canvas-resize href "#sub-canvas-resize" click setup-recall href "#sub-setup-recall"

❓ Frequently Asked Questions

What visual elements are present in the p5.js sketch 'im sorry if it doesn't work'?

The sketch features a ragdoll character named 'buddy' composed of various body parts, set against a simple environment with a ground, walls, and a ceiling.

How can users interact with the 'im sorry if it doesn't work' sketch?

Users can interact with the sketch by tapping the screen, which applies a punch force to the ragdoll character, making it move in response.

What coding concepts does the creative sketch 'im sorry if it doesn't work' illustrate?

This sketch demonstrates concepts of physics simulation, sprite manipulation, and basic collision detection within a creative coding environment.

Preview

im sorry if it doesn't work - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of im sorry if it doesn't work - Code flow showing setup, createbuddy, draw, windowresized
Code Flow Diagram