jack and cappie both game

This sketch creates an interactive 2D game where you control Jack as he walks around a simple neighborhood with two houses. By pressing arrow keys or WASD, you move Jack left and right to interact with family members, his girlfriend Cappie, and his beloved retro gaming consoles, each triggering friendly messages.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up Jack's movement — Increase jackSpeed to make Jack walk faster when you press keys
  2. Make Jack much larger — Increase jackSize so Jack (and Cappie) appear as bigger characters on screen
  3. Change Jack's house color — Switch the house color from brown to blue or any other hex color code
  4. Increase interaction range — Make it so Jack can trigger messages from farther away by increasing interactionDistance
  5. Make houses wider — Increase houseWidth to make both houses appear much bigger on the landscape
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds a charming interactive game set in a simple 2001-era neighborhood with two houses. You control Jack using arrow keys or WASD to walk left and right, and whenever Jack gets close to another character or object, a message appears telling you what they're thinking. The sketch demonstrates how to organize game state using arrays of character objects, detect proximity-based interactions using the dist() function, and respond to keyboard input with keyIsDown().

The code is organized into a setup() function that initializes all characters and houses, a draw() loop that handles movement and rendering every frame, and several helper functions that draw houses, characters, and consoles. By reading this sketch, you'll learn how to build a simple game world with multiple interactive NPCs, handle real-time keyboard input, and use arrays to manage complex data about game entities.

⚙️ How It Works

  1. When the sketch loads, setup() creates a canvas and initializes an array called characters that holds data about Jack (the player), his parents, his girlfriend Cappie, her parents, and his old gaming consoles as interactive objects.
  2. Every frame, draw() renders the sky, ground, and two houses, then checks which arrow keys or WASD keys are pressed to update Jack's horizontal position.
  3. The code loops through all non-player characters and calculates the distance from Jack to each one using dist(). If Jack is within 60 pixels of any character or object, their message is stored and displayed at the bottom of the screen.
  4. Four helper functions handle the visual rendering: drawHouse() draws a house with a roof, window, door, and (for Jack's house) vintage gaming consoles; drawCharacter() renders a simple stick figure with a colored head, body, arms, and legs; and drawOldConsoles() draws three retro gaming systems inside Jack's house.
  5. When the browser window is resized, windowResized() recalculates the ground level and repositions all characters and houses relative to the new canvas dimensions so the game adapts to any screen size.

🎓 Concepts You'll Learn

Arrays and objects for game stateKeyboard input and real-time interactionDistance-based collision detection2D character and scene renderingCanvas responsiveness and window resizingGame loop and entity management

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It's where you initialize canvas size, set drawing modes, calculate positions based on screen dimensions, and populate your game's data structures. Notice how positions use percentages like width * 0.2 instead of hard-coded pixel numbers—this makes the game responsive to any screen size.

function setup() {
  createCanvas(windowWidth, windowHeight);
  rectMode(CENTER); // Draw rectangles from their center
  textAlign(CENTER, BOTTOM); // Align text for character names
  textSize(12); // Default text size for character names
  groundY = height * 0.7; // 70% down from the top is the ground level

  // Initialize house positions
  jackHouseX = width * 0.2; // Jack's house on the left
  cappieHouseX = width * 0.8; // Cappie's house on the right

  // Define all characters and interactive objects
  // Name, x, y, size, color, home (for house location), type, message
  characters.push({ name: "Jack", x: jackHouseX, y: groundY, size: jackSize, color: '#FFD700', home: "jackHouse", type: "player" });
  characters.push({ name: "Jack's Mom", x: jackHouseX - 60, y: groundY, size: 25, color: '#FF69B4', home: "jackHouse", type: "parent", message: "Jack's mom loves him so much!" });
  characters.push({ name: "Jack's Dad", x: jackHouseX + 60, y: groundY, size: 25, color: '#1E90FF', home: "jackHouse", type: "parent", message: "Jack's dad is proud of him!" });
  characters.push({ name: "Old Consoles", x: jackHouseX, y: groundY, size: 40, color: '#696969', home: "jackHouse", type: "object", message: "Jack loves his old Nintendo consoles: N64, GameCube, PS2!" });
  characters.push({ name: "Cappie's Mom", x: cappieHouseX - 60, y: groundY, size: 25, color: '#FF6347', home: "cappieHouse", type: "parent", message: "Cappie's mom thinks Jack is a sweet boy!" });
  characters.push({ name: "Cappie's Dad", x: cappieHouseX + 60, y: groundY, size: 25, color: '#32CD32', home: "cappieHouse", type: "parent", message: "Cappie's dad likes Jack's spirit!" });
  characters.push({ name: "Cappie", x: cappieHouseX, y: groundY, size: 30, color: '#FFC0CB', home: "cappieHouse", type: "girlfriend", message: "Cappie loves Jack! She's so happy to see him!" });
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

function-call Canvas and Drawing Mode Initialization createCanvas(windowWidth, windowHeight);

Creates a canvas that fills the entire browser window; windowWidth and windowHeight are built-in p5.js variables that get the current window dimensions

for-loop Character Array Population characters.push({ name: "Jack", x: jackHouseX, y: groundY, size: jackSize, color: '#FFD700', home: "jackHouse", type: "player" });

Creates character objects with properties like name, position, size, color, and home location, then adds them to the characters array; each character is a complete data record the game uses to render and interact

createCanvas(windowWidth, windowHeight);
Creates a canvas that matches the full window size, so the game fills the entire screen
rectMode(CENTER);
Tells p5.js to draw all rectangles from their center point instead of from their top-left corner—this makes positioning easier
textAlign(CENTER, BOTTOM);
Aligns all text to be centered horizontally and anchored at the bottom, so character names appear above their heads
groundY = height * 0.7;
Sets the ground level to 70% down from the top of the canvas, leaving 30% for sky and leaving room for the title
jackHouseX = width * 0.2;
Places Jack's house 20% from the left edge, making it the left house
cappieHouseX = width * 0.8;
Places Cappie's house 80% from the left edge, making it the right house with space between them
characters.push({ name: "Jack", x: jackHouseX, y: groundY, size: jackSize, color: '#FFD700', home: "jackHouse", type: "player" });
Creates an object that stores all of Jack's data (name, position, appearance, type) and adds it to the characters array—the first character in the array is always Jack, the player

draw()

draw() is the heart of any p5.js sketch—it runs 60 times per second and is where all animation, input handling, and rendering happens. Notice the pattern: clear the background first, update game state (movement, interactions), then draw everything. The loop through characters to check distances is a core game programming technique called proximity detection, often used for pickups, triggers, and NPC interactions.

🔬 These two if-statements control Jack's left and right movement. What happens if you change jackSpeed to 1? To 20? How does the movement feel different?

  if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) { // LEFT_ARROW or 'A'
    jackChar.x -= jackSpeed;
  }
  if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) { // RIGHT_ARROW or 'D'
    jackChar.x += jackSpeed;
  }

🔬 This loop checks distance to every character and stores a message if Jack is close enough. What would happen if you removed the 'break' statement? Could multiple messages appear at once? Try it and see what happens.

  for (let i = 1; i < characters.length; i++) { // Start from index 1 to skip Jack
    let otherChar = characters[i];
    let d = dist(jackChar.x, jackChar.y, otherChar.x, otherChar.y);
    if (d < interactionDistance) {
      interactionText = otherChar.message;
      break; // Only one interaction message at a time
    }
  }
function draw() {
  background(135, 206, 235); // Sky Blue

  // Draw ground
  noStroke();
  fill(107, 142, 35); // Olive Drab
  rect(width / 2, groundY + (height - groundY) / 2, width, height - groundY);

  // Draw Jack's house
  drawHouse(jackHouseX, groundY, jackHouseColor, jackRoofColor);
  // Draw Cappie's house
  drawHouse(cappieHouseX, groundY, cappieHouseColor, cappieRoofColor);

  // Update and draw characters, handle interactions
  let jackChar = characters[0]; // Jack is always the first character

  // Jack's movement
  if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) { // LEFT_ARROW or 'A'
    jackChar.x -= jackSpeed;
  }
  if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) { // RIGHT_ARROW or 'D'
    jackChar.x += jackSpeed;
  }
  jackChar.x = constrain(jackChar.x, 0, width); // Keep Jack within canvas bounds

  // Check interaction with other characters/objects
  let interactionDistance = 60; // How close Jack needs to be to interact
  interactionText = ""; // Clear text each frame unless interacting
  for (let i = 1; i < characters.length; i++) { // Start from index 1 to skip Jack
    let otherChar = characters[i];
    let d = dist(jackChar.x, jackChar.y, otherChar.x, otherChar.y);
    if (d < interactionDistance) {
      interactionText = otherChar.message;
      break; // Only one interaction message at a time
    }
  }

  // Draw all characters
  for (let char of characters) {
    drawCharacter(char.x, char.y, char.size, char.color, char.name);
  }

  // Display interaction text at the bottom
  if (interactionText) {
    fill(0);
    noStroke();
    textSize(20); // Larger text for interactions
    text(interactionText, width / 2, height - 30);
    textSize(12); // Reset text size for other UI
  }

  // Display game title
  fill(0);
  noStroke();
  textSize(30);
  text("Jack's 2001 Adventure", width / 2, 50);
  textSize(12); // Reset text size
}
Line-by-line explanation (19 lines)

🔧 Subcomponents:

conditional Keyboard Input Handling if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) { // LEFT_ARROW or 'A' jackChar.x -= jackSpeed; } if (keyIsDown(RIGHT_ARROW) || keyIsDown(68)) { // RIGHT_ARROW or 'D' jackChar.x += jackSpeed; }

Checks which keys are currently held down and moves Jack left or right accordingly; keyIsDown() is continuous (works while held), unlike keyPressed() which fires once per key press

for-loop Proximity Detection Loop for (let i = 1; i < characters.length; i++) { // Start from index 1 to skip Jack let otherChar = characters[i]; let d = dist(jackChar.x, jackChar.y, otherChar.x, otherChar.y); if (d < interactionDistance) { interactionText = otherChar.message; break; // Only one interaction message at a time } }

Loops through every character except Jack and calculates the distance from Jack to each; if distance is less than 60 pixels, that character's message is stored and the loop exits so only one message appears at a time

for-loop Character Drawing Loop for (let char of characters) { drawCharacter(char.x, char.y, char.size, char.color, char.name); }

Uses a for-of loop to draw every character (Jack, parents, Cappie, consoles) by calling drawCharacter() with their stored position, size, color, and name

background(135, 206, 235); // Sky Blue
Fills the entire canvas with sky blue, erasing everything drawn in the previous frame so new frames don't leave trails
noStroke();
Removes the border/outline from the next shapes drawn—the ground will have no visible outline
fill(107, 142, 35); // Olive Drab
Sets the fill color to olive drab (greenish-brown) for the ground rectangle
rect(width / 2, groundY + (height - groundY) / 2, width, height - groundY);
Draws a rectangle for the ground: x-center at width/2 (middle), y-center at the midpoint between groundY and the bottom, with width matching the full canvas and height equal to the space below groundY
drawHouse(jackHouseX, groundY, jackHouseColor, jackRoofColor);
Calls drawHouse() to render Jack's house at position jackHouseX with the brown house color and sienna roof
let jackChar = characters[0]; // Jack is always the first character
Stores a reference to Jack's character object (always at index 0) in a variable for easier access in movement and interaction code
if (keyIsDown(LEFT_ARROW) || keyIsDown(65)) { // LEFT_ARROW or 'A'
Checks if either the left arrow key or the 'A' key is currently being held down—if true, the next line runs
jackChar.x -= jackSpeed;
Subtracts jackSpeed from Jack's x position, moving him left by 5 pixels per frame while the key is held
jackChar.x = constrain(jackChar.x, 0, width); // Keep Jack within canvas bounds
Prevents Jack from walking off either side of the screen by limiting his x position between 0 (left edge) and width (right edge)
let interactionDistance = 60; // How close Jack needs to be to interact
Defines a variable that sets how close Jack needs to get to another character to trigger their message—60 pixels
interactionText = ""; // Clear text each frame unless interacting
Resets the interaction message to an empty string at the start of each frame so old messages don't stay on screen
for (let i = 1; i < characters.length; i++) { // Start from index 1 to skip Jack
Loops through every character starting at index 1 (skipping Jack at index 0) and ending before the array's length
let d = dist(jackChar.x, jackChar.y, otherChar.x, otherChar.y);
Calculates the distance from Jack to the current character using the dist() function, which measures pixels between two points
if (d < interactionDistance) {
If the calculated distance is less than 60 pixels, Jack is close enough to trigger an interaction
break; // Only one interaction message at a time
Exits the loop immediately so only the first character Jack touches will show their message, preventing multiple messages from appearing at once
for (let char of characters) {
A for-of loop that iterates through every item in the characters array, assigning each to the variable 'char'
drawCharacter(char.x, char.y, char.size, char.color, char.name);
Calls drawCharacter() to render the current character using their stored position (x, y), appearance (size, color), and name
if (interactionText) {
If interactionText is not empty (meaning Jack is close to someone), the following lines display the message at the bottom of the screen
text(interactionText, width / 2, height - 30);
Draws the interaction message text centered horizontally at the bottom of the screen (height - 30 positions it 30 pixels from the bottom)

drawHouse()

drawHouse() demonstrates the push()/pop() pattern combined with translate()—this lets you draw a complex shape (house) as if it's at the origin, making the code cleaner and easier to reuse. The nested coordinate system is like drawing on a piece of paper (the translated space) instead of on the canvas directly. The condition checking x === jackHouseX shows how to make one helper function behave differently based on parameters.

function drawHouse(x, y, houseColor, roofColor) {
  push();
  translate(x, y); // Translate origin to the house's ground center

  // House body
  fill(houseColor);
  noStroke();
  rect(0, -houseHeight / 2 + houseGroundOffset, houseWidth, houseHeight);

  // Roof
  fill(roofColor);
  triangle(
    -houseWidth / 2, -houseHeight + houseGroundOffset,
    houseWidth / 2, -houseHeight + houseGroundOffset,
    0, -houseHeight - 50 + houseGroundOffset // Peak of the roof
  );

  // Window (simple rectangle)
  fill(173, 216, 230); // LightBlue
  rect(0, -houseHeight / 2 + houseGroundOffset - 30, 40, 40);

  // Door (rectangle)
  fill(139, 69, 19); // SaddleBrown
  rect(0, houseGroundOffset, 60, 80);

  // Draw old consoles specifically in Jack's house
  if (x === jackHouseX) {
    drawOldConsoles(); // Consoles are drawn relative to the translated origin
  }

  pop();
}
Line-by-line explanation (16 lines)

🔧 Subcomponents:

calculation House Body Rectangle rect(0, -houseHeight / 2 + houseGroundOffset, houseWidth, houseHeight);

Draws the main house body centered at the origin after translate(); negative y positions things above the ground center

calculation Roof Triangle triangle( -houseWidth / 2, -houseHeight + houseGroundOffset, houseWidth / 2, -houseHeight + houseGroundOffset, 0, -houseHeight - 50 + houseGroundOffset // Peak of the roof );

Draws a triangular roof with three vertices: two at the top corners of the house body and one at the peak directly above center

conditional Console Drawing Condition if (x === jackHouseX) { drawOldConsoles(); // Consoles are drawn relative to the translated origin }

Checks if this is Jack's house (by comparing the x parameter to jackHouseX) and only draws consoles if true, keeping them exclusive to his house

push();
Saves the current drawing state (position, rotation, scale) so any changes made inside this function don't affect drawings outside it
translate(x, y); // Translate origin to the house's ground center
Moves the coordinate system so that (0, 0) is now at the house's ground center position, making it easier to draw the house relative to a logical origin
fill(houseColor);
Sets the fill color to the houseColor parameter (brown for Jack's house, light steel blue for Cappie's)
rect(0, -houseHeight / 2 + houseGroundOffset, houseWidth, houseHeight);
Draws the main house body as a rectangle; the negative y value (0 - houseHeight/2) positions it above the ground, and houseGroundOffset allows part of it to appear slightly below ground
fill(roofColor);
Changes the fill color to the roofColor parameter (dark sienna for Jack's house, light slate gray for Cappie's)
triangle(
Begins drawing a triangular roof with three vertex points
-houseWidth / 2, -houseHeight + houseGroundOffset,
Left corner of the roof base: negative x places it at the left edge, negative y places it at the top of the house body
houseWidth / 2, -houseHeight + houseGroundOffset,
Right corner of the roof base: positive x places it at the right edge, same y as the left corner
0, -houseHeight - 50 + houseGroundOffset // Peak of the roof
Peak of the roof: x=0 centers it, and the y value (-houseHeight - 50) places it 50 pixels above the house body's top edge
fill(173, 216, 230); // LightBlue
Changes the fill color to light blue for the window
rect(0, -houseHeight / 2 + houseGroundOffset - 30, 40, 40);
Draws a small square window centered horizontally on the house body, positioned 30 pixels above the center to sit on the upper half of the house
fill(139, 69, 19); // SaddleBrown
Changes fill color to saddle brown for the door
rect(0, houseGroundOffset, 60, 80);
Draws a rectangular door centered at the bottom of the house, positioned slightly below the ground center (using houseGroundOffset)
if (x === jackHouseX) {
Checks if the x parameter matches jackHouseX to determine if this is Jack's house
drawOldConsoles(); // Consoles are drawn relative to the translated origin
If this is Jack's house, calls drawOldConsoles() to render N64, GameCube, and PS2 inside the house
pop();
Restores the previous drawing state, so the coordinate system returns to normal and any transformations made here don't affect other drawings

drawOldConsoles()

drawOldConsoles() demonstrates how to position multiple objects relative to a translated coordinate system. Since this function runs within a translated context (inside drawHouse()), all positions are relative to the house's origin, not the canvas. The three consoles use slightly different heights and positions to create visual variety while staying inside the house boundaries.

function drawOldConsoles() {
  fill(69, 69, 69); // Dark grey
  stroke(30);
  strokeWeight(2);

  // The house body is drawn with rect(0, -houseHeight / 2 + houseGroundOffset, houseWidth, houseHeight)
  // Its bottom edge (the "floor" inside) is at y = houseHeight / 2 + houseGroundOffset
  // Let's place the consoles slightly above that floor, say 15 pixels up.
  let consoleBaseY = houseHeight / 2 + houseGroundOffset - 15;
  textSize(10); // Smaller text for console labels

  // N64
  rect(-houseWidth / 2 + 50, consoleBaseY, 50, 30); // N64 box
  fill(255);
  noStroke();
  text("N64", -houseWidth / 2 + 50, consoleBaseY - 20);

  // GameCube
  fill(100, 0, 100); // Purple
  rect(-houseWidth / 2 + 120, consoleBaseY, 40, 40); // GameCube box
  fill(255);
  text("GC", -houseWidth / 2 + 120, consoleBaseY - 25);

  // PS2 (tall and thin)
  fill(0);
  rect(houseWidth / 2 - 50, consoleBaseY - 5, 30, 50); // PS2 box
  fill(255);
  text("PS2", houseWidth / 2 - 50, consoleBaseY - 30);

  noFill();
  noStroke(); // Reset for other drawings
}
Line-by-line explanation (16 lines)

🔧 Subcomponents:

calculation Console Base Y Position let consoleBaseY = houseHeight / 2 + houseGroundOffset - 15;

Calculates the y position where consoles sit by finding the house floor (houseHeight/2) and positioning 15 pixels above it

calculation N64 Console rect(-houseWidth / 2 + 50, consoleBaseY, 50, 30); // N64 box

Draws the N64 console as a 50×30 rectangle positioned 50 pixels from the left edge of the house

calculation GameCube Console rect(-houseWidth / 2 + 120, consoleBaseY, 40, 40); // GameCube box

Draws the GameCube as a 40×40 square, positioned 120 pixels from the left edge (between the N64 and right wall)

calculation PS2 Console rect(houseWidth / 2 - 50, consoleBaseY - 5, 30, 50); // PS2 box

Draws the PS2 as a tall thin 30×50 rectangle positioned 50 pixels from the right edge, slightly higher than the other consoles

fill(69, 69, 69); // Dark grey
Sets the fill color to dark grey for the console boxes
stroke(30);
Sets the outline color to very dark grey (nearly black) so the console edges stand out
strokeWeight(2);
Sets the outline thickness to 2 pixels
let consoleBaseY = houseHeight / 2 + houseGroundOffset - 15;
Calculates where the consoles should sit vertically; houseHeight/2 is the house floor, and -15 lifts them 15 pixels up from that floor
textSize(10); // Smaller text for console labels
Makes the console labels (N64, GC, PS2) smaller than other text so they fit well inside or above the boxes
rect(-houseWidth / 2 + 50, consoleBaseY, 50, 30); // N64 box
Draws the N64 console as a 50-pixel-wide by 30-pixel-tall rectangle at a specific position inside the house
fill(255);
Changes fill color to white for the console labels
noStroke();
Removes the outline so the text labels don't have borders
text("N64", -houseWidth / 2 + 50, consoleBaseY - 20);
Draws the text 'N64' above the N64 box (consoleBaseY - 20 lifts it 20 pixels up)
fill(100, 0, 100); // Purple
Changes fill color to purple for the GameCube
rect(-houseWidth / 2 + 120, consoleBaseY, 40, 40); // GameCube box
Draws the GameCube as a 40×40 square, positioned further right than the N64 (120 pixels from the left edge)
fill(0);
Changes fill color to black for the PS2 console
rect(houseWidth / 2 - 50, consoleBaseY - 5, 30, 50); // PS2 box
Draws the PS2 as a 30-pixel-wide by 50-pixel-tall rectangle positioned 50 pixels from the right edge; the -5 lifts it slightly higher than the other consoles
text("PS2", houseWidth / 2 - 50, consoleBaseY - 30);
Draws the 'PS2' label above the PS2 console, lifted 30 pixels up from the console base
noFill();
Removes fill so subsequent shapes drawn with this function active won't be filled—resets the drawing state for other code
noStroke(); // Reset for other drawings
Removes stroke so subsequent shapes won't have outlines, ensuring this helper function doesn't accidentally affect other drawings

drawCharacter()

drawCharacter() shows how to compose a complex shape from simpler primitives (ellipse for head, rectangles for limbs). The use of size as a scaling parameter means the same function can draw characters of any size—notice how Jack and Cappie are both drawn with this function but with different size values.

🔬 These four rectangles draw two arms and two legs symmetrically. What happens if you change size / 8 to size / 4 in the leg rects? They become wider. Try it and describe how the character looks different.

  // Arms
  rect(-size / 4, 0, size / 8, size / 2);
  rect(size / 4, 0, size / 8, size / 2);

  // Legs
  rect(-size / 8, size, size / 8, size / 2);
  rect(size / 8, size, size / 8, size / 2);
function drawCharacter(x, y, size, color, name) {
  push();
  translate(x, y); // Translate origin to the character's ground center

  fill(color);
  noStroke();

  // Head
  ellipse(0, -size / 2, size, size);

  // Body
  rect(0, size / 2, size / 2, size);

  // Arms
  rect(-size / 4, 0, size / 8, size / 2);
  rect(size / 4, 0, size / 8, size / 2);

  // Legs
  rect(-size / 8, size, size / 8, size / 2);
  rect(size / 8, size, size / 8, size / 2);

  // Name tag
  fill(0);
  noStroke();
  textSize(12);
  text(name, 0, -size - 10);

  pop();
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

calculation Head Circle ellipse(0, -size / 2, size, size);

Draws a circular head centered horizontally at the origin; negative y positions it above the body

calculation Body Rectangle rect(0, size / 2, size / 2, size);

Draws the torso/body centered horizontally; positive y positions it below the head

calculation Arms and Legs // Arms rect(-size / 4, 0, size / 8, size / 2); rect(size / 4, 0, size / 8, size / 2); // Legs rect(-size / 8, size, size / 8, size / 2); rect(size / 8, size, size / 8, size / 2);

Draws four rectangles: two for arms extending left and right from the shoulders, and two for legs extending downward from the hips

push();
Saves the current drawing state so transformations made inside this function don't affect other drawings
translate(x, y); // Translate origin to the character's ground center
Moves the coordinate system so (0, 0) is at the character's feet, making it easy to draw character parts relative to a logical center
fill(color);
Sets the fill color to the color parameter (gold for Jack, pink for Cappie, etc.)
noStroke();
Removes outlines from all the character parts so they appear solid colored
ellipse(0, -size / 2, size, size);
Draws a circle for the head: centered at x=0, positioned 'size/2' pixels above the origin (negative y), with width and height both equal to size
rect(0, size / 2, size / 2, size);
Draws the body as a rectangle: centered at x=0, positioned 'size/2' pixels below the origin (positive y), with width 'size/2' and height 'size'
rect(-size / 4, 0, size / 8, size / 2);
Draws the left arm at x=-size/4 (to the left), with small width (size/8) and height (size/2)
rect(size / 4, 0, size / 8, size / 2);
Draws the right arm at x=size/4 (to the right), mirroring the left arm's size and shape
rect(-size / 8, size, size / 8, size / 2);
Draws the left leg at x=-size/8, positioned below the body (y=size), making it thinner than the body
rect(size / 8, size, size / 8, size / 2);
Draws the right leg at x=size/8, mirroring the left leg
fill(0);
Changes fill color to black for the character's name tag
textSize(12);
Sets text size to 12 pixels for the name labels
text(name, 0, -size - 10);
Draws the character's name centered above their head: x=0 centers it horizontally, and y=-size-10 places it 10 pixels above the top of the head
pop();
Restores the previous drawing state so the translation doesn't affect other drawings

windowResized()

windowResized() is a p5.js built-in function that fires every time the browser window is resized. This sketch uses it to keep the game responsive—all positions use percentages of width/height so they scale proportionally. Without windowResized(), the game would break if you resized your browser.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  groundY = height * 0.7; // Recalculate ground position

  // Update house positions relative to new canvas size
  jackHouseX = width * 0.2;
  cappieHouseX = width * 0.8;

  // Update character positions relative to new canvas size and groundY
  for (let char of characters) {
    char.y = groundY; // All characters stand on the ground
    if (char.type === "player") {
      char.x = jackHouseX; // Reset Jack to his house center
    } else if (char.home === "jackHouse") {
      if (char.name === "Jack's Mom") char.x = jackHouseX - 60;
      else if (char.name === "Jack's Dad") char.x = jackHouseX + 60;
      else if (char.name === "Old Consoles") char.x = jackHouseX; // Interaction point is at house center
    } else if (char.home === "cappieHouse") {
      if (char.name === "Cappie's Mom") char.x = cappieHouseX - 60;
      else if (char.name === "Cappie's Dad") char.x = cappieHouseX + 60;
      else if (char.name === "Cappie") char.x = cappieHouseX;
    }
  }
}
Line-by-line explanation (14 lines)

🔧 Subcomponents:

function-call Canvas Resize resizeCanvas(windowWidth, windowHeight);

Adjusts the p5.js canvas to match the new browser window dimensions

for-loop Character Position Update Loop for (let char of characters) { char.y = groundY; // All characters stand on the ground if (char.type === "player") { char.x = jackHouseX; // Reset Jack to his house center } else if (char.home === "jackHouse") { // ... position updates } else if (char.home === "cappieHouse") { // ... position updates } }

Loops through every character and recalculates their positions based on the new canvas size and character type/home location

resizeCanvas(windowWidth, windowHeight);
Tells p5.js to resize the canvas to match the new browser window dimensions
groundY = height * 0.7; // Recalculate ground position
Recalculates the ground level based on the new height—70% down from the top no matter what the new canvas size is
jackHouseX = width * 0.2;
Recalculates Jack's house x position based on the new width—always 20% from the left
cappieHouseX = width * 0.8;
Recalculates Cappie's house x position—always 80% from the left, maintaining spacing as the canvas resizes
for (let char of characters) {
Loops through every character in the characters array
char.y = groundY; // All characters stand on the ground
Updates every character's y position to the recalculated ground level
if (char.type === "player") {
Checks if this character is the player (Jack)
char.x = jackHouseX; // Reset Jack to his house center
If Jack was standing elsewhere, this resets him to his house center position
} else if (char.home === "jackHouse") {
Checks if the character's home is Jack's house
if (char.name === "Jack's Mom") char.x = jackHouseX - 60;
Positions Jack's Mom 60 pixels to the left of the house center
else if (char.name === "Jack's Dad") char.x = jackHouseX + 60;
Positions Jack's Dad 60 pixels to the right of the house center
else if (char.name === "Old Consoles") char.x = jackHouseX;
Keeps the 'Old Consoles' interactive object at the house center
} else if (char.home === "cappieHouse") {
Checks if the character's home is Cappie's house
if (char.name === "Cappie's Mom") char.x = cappieHouseX - 60;
Positions Cappie's Mom 60 pixels left of her house center

📦 Key Variables

jackSize number

Controls the size (width/height in pixels) of Jack and Cappie's character drawings

let jackSize = 30;
jackSpeed number

How many pixels Jack moves left or right each frame when arrow keys or WASD are pressed

let jackSpeed = 5;
groundY number

The y-coordinate where the ground level appears and where all characters stand

let groundY;
jackHouseColor string

The hex color code for Jack's house body

let jackHouseColor = '#8B4513';
cappieHouseColor string

The hex color code for Cappie's house body

let cappieHouseColor = '#B0C4DE';
jackRoofColor string

The hex color code for Jack's house roof

let jackRoofColor = '#A0522D';
cappieRoofColor string

The hex color code for Cappie's house roof

let cappieRoofColor = '#778899';
houseWidth number

The width in pixels of both houses

let houseWidth = 200;
houseHeight number

The height in pixels of both houses

let houseHeight = 150;
jackHouseX number

The x-coordinate (horizontal position) where Jack's house is drawn

let jackHouseX;
cappieHouseX number

The x-coordinate (horizontal position) where Cappie's house is drawn

let cappieHouseX;
houseGroundOffset number

How many pixels of each house appear below the ground line for a partially-buried effect

let houseGroundOffset = 10;
interactionText string

Stores the message from a character that Jack is currently close to, displayed at the bottom of the screen

let interactionText = '';
characters array

An array of objects storing data about every character and interactive object in the game (Jack, parents, Cappie, consoles)

let characters = [];

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

BUG draw() keyboard input

Jack can move off-screen if he gets past the constrain() boundaries due to high jackSpeed combined with simultaneous left/right presses, allowing both movements to apply.

💡 Add a maximum speed check or better boundary detection at the edge, not just constraining the position. Consider: if (keyIsDown(LEFT_ARROW) && keyIsDown(RIGHT_ARROW)) { /* do nothing */ }

PERFORMANCE draw() character loop

Every frame, the code loops through characters twice: once for interaction detection and once for drawing. If there were 100+ characters, this would be expensive.

💡 Combine both loops into one: calculate distances and draw in a single for-loop to reduce iterations.

STYLE characters array initialization in setup()

The characters array is populated with seven individual push() calls that are nearly identical in structure, making the code verbose and hard to maintain.

💡 Create an array of character data objects and loop through them with forEach(), pushing each into the characters array. This reduces duplication and makes adding new characters easier.

FEATURE draw() interaction system

Only the first character Jack touches will display a message (because of 'break'), so you can't see overlapping interactions or build a priority system.

💡 Remove 'break' and instead find the closest character within interaction range and display only that message, giving priority to the nearest NPC.

🔄 Code Flow

Code flow showing setup, draw, drawhouse, drawoldconsoles, drawcharacter, windowresized

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

graph TD start[Start] --> setup[setup] setup --> canvas-setup[Canvas and Drawing Mode Initialization] canvas-setup --> character-initialization[Character Array Population] character-initialization --> draw[draw loop] draw --> keyboard-input[Keyboard Input Handling] draw --> interaction-detection[Proximity Detection Loop] draw --> character-rendering[Character Drawing Loop] draw --> windowresized[windowResized] windowresized --> canvas-resize[Canvas Resize] draw --> drawhouse[drawHouse] drawhouse --> house-body[House Body Rectangle] house-body --> roof-triangle[Roof Triangle] roof-triangle --> console-conditional[Console Drawing Condition] console-conditional --> console-base-calc[Console Base Y Position] console-base-calc --> n64-drawing[N64 Console] console-base-calc --> gamecube-drawing[GameCube Console] console-base-calc --> ps2-drawing[PS2 Console] drawhouse --> drawoldconsoles[drawOldConsoles] drawoldconsoles --> console-conditional drawoldconsoles --> console-base-calc drawoldconsoles --> n64-drawing drawoldconsoles --> gamecube-drawing drawoldconsoles --> ps2-drawing draw --> position-recalculation[Character Position Update Loop] position-recalculation --> character-rendering character-rendering --> drawcharacter[drawCharacter] drawcharacter --> head-drawing[Head Circle] head-drawing --> body-drawing[Body Rectangle] body-drawing --> limbs-drawing[Arms and Legs] click setup href "#fn-setup" click draw href "#fn-draw" click canvas-setup href "#sub-canvas-setup" click character-initialization href "#sub-character-initialization" click keyboard-input href "#sub-keyboard-input" click interaction-detection href "#sub-interaction-detection" click character-rendering href "#sub-character-rendering" click windowresized href "#fn-windowresized" click canvas-resize href "#sub-canvas-resize" click drawhouse href "#fn-drawhouse" click house-body href "#sub-house-body" click roof-triangle href "#sub-roof-triangle" click console-conditional href "#sub-console-conditional" click console-base-calc href "#sub-console-base-calc" click n64-drawing href "#sub-n64-drawing" click gamecube-drawing href "#sub-gamecube-drawing" click ps2-drawing href "#sub-ps2-drawing" click drawoldconsoles href "#fn-drawoldconsoles" click position-recalculation href "#sub-position-recalculation" click drawcharacter href "#fn-drawcharacter" click head-drawing href "#sub-head-drawing" click body-drawing href "#sub-body-drawing" click limbs-drawing href "#sub-limbs-drawing"

❓ Frequently Asked Questions

What visual elements can users expect to see in the 'jack and cappie both game' sketch?

The sketch features colorful houses, characters including Jack and his parents, and a ground level set at 70% of the canvas height, creating a vibrant scene.

How can users interact with the characters in the sketch?

Users can hover over the characters to see interaction messages, enhancing the storytelling aspect of the game.

What creative coding techniques are demonstrated in this p5.js sketch?

The sketch showcases object-oriented programming by using an array to manage character properties and utilizes the p5.js framework for dynamic canvas rendering.

Preview

jack and cappie both game - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of jack and cappie both game - Code flow showing setup, draw, drawhouse, drawoldconsoles, drawcharacter, windowresized
Code Flow Diagram