Platform Game

This is a 22-level platform game where the player controls a blue square that must navigate through increasingly complex levels by jumping, avoiding lava, and reaching green goal tiles. The game features gravity-based physics, collision detection, eight different tile types, and progressive difficulty.

🧪 Try This!

Experiment with the code by making these changes:

  1. Double the gravity — The player will fall much faster and jumps will be much shorter, making the game harder
  2. Make the player huge — The blue square will be much larger, making platforming harder because it collides with tiles more easily
  3. Triple the jump height — The player will jump much higher, making it easier to clear large gaps
  4. Slow down horizontal movement — The player will move more slowly left and right, making precise platforming easier but requiring longer input holds
  5. Make lava tiles blue instead of red — Lava will be hard to see—visually harder but gameplay doesn't change
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a fully playable platform game with 22 levels where you control a blue square character navigating obstacle courses. The game uses gravity and velocity to create realistic jumping physics, collision detection to prevent movement through walls and lava, and a tile-based grid system to design levels. What makes this sketch special is that it combines multiple p5.js techniques—the draw loop, keyboard input, 2D arrays, and conditional logic—into a complete, polished game.

The code is organized into three main parts: setup() loads all 22 level designs as 2D arrays where each number represents a different tile type, draw() renders the current level and player each frame, and tick() handles all the physics calculations and collision detection that drive gameplay. By reading this sketch you will learn how to structure a multi-level game, implement gravity-based movement, check collisions in a grid system, and load different game states.

⚙️ How It Works

  1. When the sketch loads, setup() creates a 600×600 pixel canvas and populates a level array containing the designs of all 22 levels—each level is a 20×20 grid where each cell holds a number representing a tile type (A=empty, G=ground, D=lava, C=goal, E=bounce pad, F=special goal).
  2. On each frame, draw() clears the background and loops through every tile in the current level, coloring each one according to its type: black for ground, red for lava, green for goals, orange for bounce pads. It also draws the player as a blue square at position x, y.
  3. Every 30 milliseconds, tick() runs and updates the player's position: it reads keyboard input to set horizontal velocity (dx) and checks if the player can jump by looking at the tiles below them.
  4. tick() applies gravity by adding a small value to vertical velocity (dy) each tick, simulating constant downward acceleration—this is the core of realistic jumping physics.
  5. Before moving, tick() performs collision detection by checking what tile type exists at the player's new position in all four directions. If the new position contains lava (D) or the goal (C), the level ends; if it contains ground or bounce pads (G or E), the player stops moving in that direction instead of passing through.
  6. Finally, if all collision checks pass, the player's x and y positions are updated. If the player reaches the goal, runLevel(true) advances to the next level; if they touch lava or fall off the bottom, runLevel(false) restarts the current level and resets the player's spawn position.

🎓 Concepts You'll Learn

Tile-based level designGravity and velocity physicsCollision detection2D arrays for game gridsKeyboard input handlingGame state managementLevel progression

📝 Code Breakdown

setup()

setup() runs once when the sketch first loads. It is the perfect place to initialize the canvas size and load all your game data—in this case, all 22 level designs as arrays. The three-dimensional structure (array of levels, each containing an array of rows, each containing an array of tile values) is a common pattern for storing multi-level games.

function setup() {
  createCanvas(600, 600);
  level = [
    [
      [A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A],
      [A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A],
      ...
      [G, G, G, G, G, G, G, G, G, G, G, G, G, G, G, G, G, G, G, G],
    ],
    // 21 more levels...
  ];
}
Line-by-line explanation (2 lines)
createCanvas(600, 600);
Creates a 600×600 pixel canvas—this is the playing field where all tiles and the player are drawn
level = [ [ [ ... ] ] ];
Initializes a 3D array: the outer array holds 22 complete levels, each level is a 20×20 2D array, and each cell contains a number representing a tile type (A=empty, G=ground, D=lava, C=goal, E=bounce pad, F=special goal)

draw()

draw() is called every frame (60 times per second by default). It must redraw everything because the canvas is cleared at the start of each frame. The nested loops that render tiles are a classic pattern for grid-based games—loop through every row and column, check what's at that position, and draw it. The timer that calls tick() every 30ms (not every frame) is important because it separates the visual refresh rate from the physics update rate, keeping movement consistent even if frame rate varies.

🔬 Each of these if-statements draws a different tile type in a different color. What happens if you swap two fill colors—for example, make lava (D) green and goals (C) red? Try it and see if the level feels backwards!

      if (l[i][j] == G) {
        fill(0);
        square(j * 30, i * 30, 30);
      }
      if (l[i][j] == D) {
        fill(255, 0, 0);
        square(j * 30, i * 30, 30);
      }
      if (l[i][j] == C) {
        fill(0, 255, 0);
        square(j * 30, i * 30, 30, 5);
      }
      if (l[i][j] == E) {
        fill(255, 150, 0);
        square(j * 30, i * 30, 30);
      }

🔬 The last argument '30' is the tile size in pixels. What happens if you change it to 20 or 40? How does the level look different?

      if (l[i][j] == G) {
        fill(0);
        square(j * 30, i * 30, 30);
function draw() {
  background(220);
  noStroke();
  if (sa==true){
    alert("Hi. This is the tour of my game\n\n  1. All levels are possible besides the one where you are trapped.\n   Really. \n Trust me. \n Even that one level\n\n  2. Left arrow to move left, Right arrow to move right, and Up arrow to jump\n\n 3. The Red is lava, and it is bad for you\n\n 4. Orange makes you jump higher\n\n 5. Black is normal ground \n\n 6. If you get to the Green, you get teleported to the next level\n\n That's it! Have fun!")
    sa=false
  }
  if (millis() > nextTick) {
    nextTick = millis() + 30;
    tick();
  }
  var l = level[levelOn];
  for (i = 0; i < l.length; i++) {
    for (j = 0; j < l[i].length; j++) {
      if (l[i][j] == G) {
        fill(0);
        square(j * 30, i * 30, 30);
      }
      if (l[i][j] == D) {
        fill(255, 0, 0);
        square(j * 30, i * 30, 30);
      }
      if (l[i][j] == C) {
        fill(0, 255, 0);
        square(j * 30, i * 30, 30, 5);
      }
      if (l[i][j] == E) {
        fill(255, 150, 0);
        square(j * 30, i * 30, 30);
      }
      if (l[i][j]==F){
        fill(0,255,0)
        square(j * 30, i * 30, 30,5);
      }
    }
  }
  fill(0, 0, 175);
  square(x, y, heroSize, 5);
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

conditional First-time tutorial if (sa==true){ alert(...); sa=false; }

Displays game instructions on the first run only by checking the sa flag, then sets it to false so the alert only shows once

conditional Physics tick timer if (millis() > nextTick) { nextTick = millis() + 30; tick(); }

Calls tick() every 30 milliseconds instead of every frame, allowing physics updates to run at a fixed rate independent of frame rate

for-loop Tile rendering nested loop for (i = 0; i < l.length; i++) { for (j = 0; j < l[i].length; j++) { ... } }

Loops through every row (i) and column (j) in the current level's 20×20 grid, coloring each tile based on its type

calculation Player rendering fill(0, 0, 175); square(x, y, heroSize, 5);

Draws the blue player square at the current x, y position with size heroSize

background(220);
Fills the entire canvas with a light gray color, erasing everything drawn in the previous frame so old tiles and player positions don't leave trails
noStroke();
Removes the black outline that normally appears around shapes, making tiles and the player appear as solid filled squares
if (sa==true){ alert(...); sa=false; }
On the very first frame, sa is true, so an alert dialog displays game instructions. After the alert, sa is set to false so this only happens once
if (millis() > nextTick) { nextTick = millis() + 30; tick(); }
Uses millis() to check if 30 milliseconds have passed since the last update. If so, calls tick() to update physics and movement, then sets nextTick to 30ms in the future. This decouples physics from frame rate
var l = level[levelOn];
Stores a reference to the current level's 2D array in variable l for easier access (levelOn is the index of the current level)
for (i = 0; i < l.length; i++) { for (j = 0; j < l[i].length; j++) {
Nested for loops: the outer loop iterates through each row (i) and the inner loop iterates through each column (j), visiting every tile in the 20×20 grid
if (l[i][j] == G) { fill(0); square(j * 30, i * 30, 30); }
If the tile is G (ground, value 1), fill it black (RGB 0,0,0) and draw a 30-pixel square at grid position (j, i)—multiplying by 30 converts grid coordinates to pixel coordinates
if (l[i][j] == D) { fill(255, 0, 0); square(j * 30, i * 30, 30); }
If the tile is D (lava, value 2), fill it red (RGB 255,0,0) and draw a 30-pixel square
if (l[i][j] == C) { fill(0, 255, 0); square(j * 30, i * 30, 30, 5); }
If the tile is C (goal, value 3), fill it green (RGB 0,255,0) and draw a square with rounded corners (the 4th parameter 5 sets corner radius)
if (l[i][j] == E) { fill(255, 150, 0); square(j * 30, i * 30, 30); }
If the tile is E (bounce pad, value 4), fill it orange (RGB 255,150,0) and draw a 30-pixel square
fill(0, 0, 175); square(x, y, heroSize, 5);
After all tiles are drawn, fill blue (RGB 0,0,175) and draw the player as a rounded square at position (x, y) with size heroSize

tick()

tick() is the physics and collision engine of the game. It runs 33 times per second (every 30 milliseconds) and handles all movement and interactions. The key insight is the four-direction collision detection: before moving in any direction, the code checks what tiles exist at the player's new position. If a tile would block movement or trigger a level end, the movement is prevented or the level state changes. Checking both corners (left and right for horizontal movement, top and bottom for vertical) ensures the player doesn't slip through cracks. The four separate if-blocks (right, left, down, up) allow the player to move in some directions while being blocked in others—for example, you can move right into a wall while still falling down due to gravity.

🔬 This code checks the tiles below the player and jumps if standing on ground (G) or bounce pad (E). The bounce pad jump is 1.3 times higher. What happens if you change 1.3 to 2.0? What if you change it to 0.8?

      bl = l[floor((y + heroSize) / 30)][floor(x / 30)];
      br = l[floor((y + heroSize) / 30)][floor((x + heroSize - 1) / 30)];
      if (levelOn<=12 && (bl == E || br == E)) {
        dy = -jumpSpeed*1.3;
      } else if (bl == G || br == G) {
        dy = -jumpSpeed;
function tick() {
  var l = level[levelOn];
  var dx = 0;
  if (y < height - 30) {
    if (keyIsDown(RIGHT_ARROW)) {
      dx = speed;
    }
    if (keyIsDown(LEFT_ARROW)) {
      dx = -speed;
    }
    if (keyIsDown(UP_ARROW)) {
      bl = l[floor((y + heroSize) / 30)][floor(x / 30)];
      br = l[floor((y + heroSize) / 30)][floor((x + heroSize - 1) / 30)];
      if (levelOn<=12 && (bl == E || br == E)) {
        dy = -jumpSpeed*1.3;
      } else if (bl == G || br == G) {
        dy = -jumpSpeed;
      } else if (levelOn>12 && (bl == E || br == E)){
        dy = -jumpSpeed*1.3;
      }
    }
    dy += gravity;

    if (dx > 0) {
      if (x + dx + heroSize - 1 > width) {
        dx = 0;
      }
      ntr = l[floor(y / 30)][floor((x + dx + heroSize - 1) / 30)];
      nbr = l[floor((y + heroSize - 1) / 30)][floor((x + dx + heroSize - 1) / 30)];
      if (ntr == C || nbr == C) {
        runLevel(true);
        return;
      }
      if (ntr == G || nbr == G || ntr == E || nbr == E) {
        dx = 0;
      }
      if (ntr == D || nbr == D) {
        runLevel(false);
        return;
      }
    }
    if (dx < 0) {
      if (x + dx < 0) {
        dx = 0;
      }
      ntl = l[floor(y / 30)][floor((x + dx) / 30)];
      nbl = l[floor((y + heroSize - 1) / 30)][floor((x + dx) / 30)];
      if (ntl == C || nbl == C) {
        runLevel(true);
        return;
      }
      if (nbl == G || ntl == G || nbl == E || ntl == E) {
        dx = 0;
      }
      if (ntl == D || nbl == D) {
        runLevel(false);
        return;
      }
    }

    if (dy > 0) {
      if (y + dy + heroSize - 1 > height) {
        runLevel(false);
        return;
      }
      nbl = l[floor((y + dy + heroSize - 1) / 30)][floor(x / 30)];
      nbr = l[floor((y + dy + heroSize - 1) / 30)][floor((x + heroSize - 1) / 30)];
      if (nbl == C || nbr == C) {
        runLevel(true);
        return;
      }
      if (nbl == G || nbr == G || nbl == E || nbr == E) {
        dy = 0;
      }
      if (nbl == D || nbr == D) {
        runLevel(false);
        return;
      }
    }

    if (dy < 0) {
      if (y + dy < 0) {
        dy = 0;
      }
      ntl = l[floor((y + dy) / 30)][floor(x / 30)];
      ntr = l[floor((y + dy) / 30)][floor((x + heroSize - 1) / 30)];
      if (ntl == C || ntr == C) {
        runLevel(true);
        return;
      }
      if (ntl == G || ntr == G|| ntl == E || ntr == E) {
        dy = 0;
      }
      if (ntr == D || ntl == D) {
        runLevel(false);
        return;
      }
    }
    x += dx;
    y += dy;
  } else {
    runLevel(false);
  }
}
Line-by-line explanation (28 lines)

🔧 Subcomponents:

conditional Horizontal keyboard input if (keyIsDown(RIGHT_ARROW)) { dx = speed; } if (keyIsDown(LEFT_ARROW)) { dx = -speed; }

Checks if left or right arrow keys are held down and sets dx to positive (right) or negative (left) velocity

conditional Jump detection and execution if (keyIsDown(UP_ARROW)) { bl = l[floor((y + heroSize) / 30)][floor(x / 30)]; ... if (bl == G || br == G) { dy = -jumpSpeed; } }

Checks if up arrow is pressed, tests what tile type is below the player, and if it's ground or bounce pad, sets vertical velocity upward

calculation Gravity application dy += gravity;

Adds gravity each tick to increase downward velocity, creating the falling acceleration effect

conditional Right-side collision detection if (dx > 0) { ... if (ntr == G || nbr == G || ntr == E || nbr == E) { dx = 0; } if (ntr == D || nbr == D) { runLevel(false); } }

When moving right, checks the two right-side corners for obstacles: stops movement if hitting ground/bounce, ends level if hitting lava/goal

conditional Left-side collision detection if (dx < 0) { ... if (nbl == G || ntl == G || nbl == E || ntl == E) { dx = 0; } if (ntl == D || nbl == D) { runLevel(false); } }

When moving left, checks the two left-side corners for obstacles: stops movement if hitting ground/bounce, ends level if hitting lava/goal

conditional Bottom collision detection if (dy > 0) { ... if (nbl == G || nbr == G || nbl == E || nbr == E) { dy = 0; } if (nbl == D || nbr == D) { runLevel(false); } }

When moving down (falling), checks the two bottom corners: stops vertical movement if hitting ground/bounce, ends level if hitting lava/goal

conditional Top collision detection if (dy < 0) { ... if (ntl == G || ntr == G|| ntl == E || ntr == E) { dy = 0; } if (ntr == D || ntl == D) { runLevel(false); } }

When moving up (jumping), checks the two top corners: stops vertical movement if hitting ground/bounce, ends level if hitting lava/goal

var l = level[levelOn];
Creates a reference to the current level's 2D tile array for easier access
var dx = 0;
Initializes horizontal velocity to 0—this will be set to positive (right) or negative (left) if arrow keys are pressed
if (y < height - 30) {
Only process player input and physics if the player hasn't fallen off the bottom of the screen (height is 600, so this checks if y < 570)
if (keyIsDown(RIGHT_ARROW)) { dx = speed; }
If the right arrow key is currently held down, set dx to the speed value (7 by default), making the player move right
if (keyIsDown(LEFT_ARROW)) { dx = -speed; }
If the left arrow key is currently held down, set dx to negative speed, making the player move left
bl = l[floor((y + heroSize) / 30)][floor(x / 30)];
Gets the tile directly below the player's left side by checking the grid cell one row below the player's bottom edge (dividing by 30 converts pixels to grid coordinates)
br = l[floor((y + heroSize) / 30)][floor((x + heroSize - 1) / 30)];
Gets the tile directly below the player's right side—checking both corners ensures the player is standing on solid ground
if (levelOn<=12 && (bl == E || br == E)) { dy = -jumpSpeed*1.3; }
If the current level is 12 or earlier AND the player is standing on a bounce pad (E), set upward velocity to 1.3 times the normal jump speed
else if (bl == G || br == G) { dy = -jumpSpeed; }
Otherwise if the player is standing on ground (G), set upward velocity to the normal jump speed (negative because up is negative y)
dy += gravity;
Every tick, increase downward velocity by the gravity amount (0.5 by default), simulating continuous downward acceleration whether the player is in the air or on the ground
if (dx > 0) {
The following collision checks only happen if the player is trying to move right (dx is positive)
if (x + dx + heroSize - 1 > width) { dx = 0; }
If moving right would push the player past the right edge of the canvas (width is 600), prevent the movement by setting dx to 0
ntr = l[floor(y / 30)][floor((x + dx + heroSize - 1) / 30)];
Checks the tile at the top-right corner of the player's new position to detect collisions with walls and obstacles
if (ntr == C || nbr == C) { runLevel(true); return; }
If either the top-right or bottom-right corner touches the goal tile (C), end the level successfully (true) and go to the next level
if (ntr == G || nbr == G || ntr == E || nbr == E) { dx = 0; }
If the right side would hit ground (G) or bounce pad (E), stop horizontal movement by setting dx to 0
if (ntr == D || nbr == D) { runLevel(false); return; }
If the right side would hit lava (D), end the level as a failure (false) and restart the current level
if (dx < 0) {
The following collision checks only happen if the player is trying to move left (dx is negative)
if (x + dx < 0) { dx = 0; }
If moving left would push the player past the left edge of the canvas, prevent the movement by setting dx to 0
ntl = l[floor(y / 30)][floor((x + dx) / 30)];
Checks the tile at the top-left corner of the player's new position after moving left
if (nbl == G || ntl == G || nbl == E || ntl == E) { dx = 0; }
If the left side would hit ground or bounce pad, stop horizontal movement
if (dy > 0) {
The following collision checks only happen when the player is moving downward (falling or moving down)
if (y + dy + heroSize - 1 > height) { runLevel(false); return; }
If the player would fall off the bottom of the screen, end the level as a failure and restart
nbl = l[floor((y + dy + heroSize - 1) / 30)][floor(x / 30)];
Checks the tile at the bottom-left corner of the player after moving down
if (nbl == G || nbr == G || nbl == E || nbr == E) { dy = 0; }
If the bottom side hits ground or bounce pad, stop downward movement (dy = 0) so the player lands on the tile and can jump again
if (dy < 0) {
The following collision checks only happen when the player is moving upward (jumping)
if (y + dy < 0) { dy = 0; }
If jumping would push the player off the top of the canvas, stop upward movement
x += dx; y += dy;
After all collision checks pass, update the player's position by adding the velocity values to the current x and y
} else { runLevel(false); }
If the player's y position is at or below height - 30 (fallen off the bottom), end the level as a failure

runLevel(nextLevel)

runLevel() is called whenever the game state needs to change—either when the player reaches a goal (nextLevel = true, advance to the next level) or when they die on lava or fall off (nextLevel = false, restart the current level). It resets physics variables and uses a simple if-statement lookup table to assign spawn coordinates for each level. This pattern (if level == X, set position X) is straightforward for 22 levels but could be refactored into an array of spawn points for cleaner code. The function returns nothing and instead modifies global variables (levelOn, x, y, dy, gravity), which directly affects what draw() and tick() see on the next frame.

🔬 This line advances to the next level when nextLevel is true. What happens if you change levelOn++ to levelOn += 2? You'd skip every other level! What if you change it to levelOn = 0 to always loop back to level 0?

  if (nextLevel) {
    levelOn++;
  }

🔬 This sets the spawn position for level 0. What happens if you change x = 90 to x = 0 or x = 570? The player spawns at a different horizontal position—try a position that's right next to a lava tile for instant failure!

  if (levelOn == 0) {
    x = 90;
    y = 540;
  }
function runLevel(nextLevel) {
  dy=0
  gravity = 0.5;
  if (nextLevel) {
    levelOn++;
  }
  if (levelOn == 0) {
    x = 90;
    y = 540;
  }
  if (levelOn == 1) {
    x = 480;
    y = 540;
  }
  if (levelOn == 2) {
    x = 300;
    y = 540;
  }
  if (levelOn == 3) {
    x = 100;
    y = 540;
  }
  if (levelOn == 4) {
    x = 180;
    y = 540;
  }
  if (levelOn == 5) {
    x = 180;
    y = 540;
  }
  if (levelOn == 6) {
    x = 180;
    y = 540;
  }
  if (levelOn == 7) {
    x = 180;
    y = 540;
  }
  if (levelOn == 8) {
    x = 0;
    y = 30;
  }
  if (levelOn == 9) {
    x = 0;
    y = 30;
  }
  if (levelOn == 10) {
    x = 0;
    y = 30;
  }
  if (levelOn == 11) {
    x = 300;
    y = 30;
  }
  if (levelOn == 12) {
    x = 0;
    y = 540;
  }
  if (levelOn == 13) {
    x = 0;
    y = 0;
  }
  if (levelOn == 14) {
    x = 320;
    y = 0;
  }
  if (levelOn == 15) {
    x = 0;
    y = 0;
  }
  if (levelOn == 16) {
    x = 0;
    y = 0;
  }
  if (levelOn == 17) {
    x = 70;
    y = 510;
  }
  if (levelOn == 18) {
    x = 70;
    y = 510;
  }
  if (levelOn == 19) {
    x = 0;
    y = 540;
  }
  if (levelOn == 20) {
    x = 0;
    y = 510;
  }
  if (levelOn == 21) {
    x = 300;
    y = 300;
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation State reset dy=0; gravity = 0.5;

Resets vertical velocity and gravity to default values so the next level starts with clean physics

conditional Level advancement if (nextLevel) { levelOn++; }

If nextLevel is true (player reached goal), increment levelOn to load the next level; if false (player died), levelOn stays the same to restart

switch-case Spawn position lookup if (levelOn == 0) { x = 90; y = 540; } if (levelOn == 1) { x = 480; y = 540; } ... if (levelOn == 21) { x = 300; y = 300; }

22 if-statements check the current levelOn value and set the player's spawn position (x, y) specific to that level

dy=0
Resets vertical velocity to 0 so the player doesn't start the next level with leftover downward or upward movement from the previous level
gravity = 0.5;
Resets gravity to its default value in case any level modified it (future features could change gravity per level)
if (nextLevel) { levelOn++; }
If nextLevel is true (player reached a goal tile), increment levelOn by 1 to load the next level. If false (player hit lava or fell), levelOn stays the same and the current level restarts
if (levelOn == 0) { x = 90; y = 540; }
When the game starts or level 0 is reached, spawn the player at pixel position (90, 540)—near the bottom left of the canvas
if (levelOn == 1) { x = 480; y = 540; }
When level 1 is reached, spawn the player at pixel position (480, 540)—near the bottom right
if (levelOn == 21) { x = 300; y = 300; }
When level 21 (the final level) is reached, spawn the player at pixel position (300, 300)—the center of the canvas

📦 Key Variables

A, G, D, C, E, F number

Tile type constants (0-5): A=empty, G=ground (black), D=lava (red), C=goal (green), E=bounce pad (orange), F=special goal. Each number represents a different tile type in the level arrays

var A = 0; var G = 1; var D = 2; var C = 3; var E = 4; var F = 5;
level array

A 3D array storing all 22 levels. Each level is a 20×20 grid of tile type numbers (A, G, D, C, E, F) that define the level layout

level[0] contains the first level as a 20×20 2D array
x, y number

The player's current pixel position on the canvas. x is horizontal (0-600), y is vertical (0-600). Updated by movement and gravity in tick()

var x = 300; var y = 300;
levelOn number

The index of the currently active level (0-21). Controls which level is drawn and which spawn position is used when a level loads or restarts

var levelOn = 0; // starts at level 0
nextTick number

Stores the time (in milliseconds) when the next physics update (tick) should run. Used to call tick() every 30ms instead of every frame

var nextTick = 30;
sa boolean

A flag that displays the tutorial alert only once at game start. Set to true initially, then set to false after the alert runs

var sa = true;
speed number

How many pixels the player moves left/right per tick when an arrow key is pressed. Controls horizontal movement speed

var speed = 7;
gravity number

How much downward velocity increases each tick. Higher values make the player fall faster and jump lower. Simulates gravitational acceleration

var gravity = 0.5;
dy number

Vertical velocity—how many pixels the player moves up or down each tick. Negative values move up (jump), positive values move down (fall)

var dy = 0; // starts stationary
jumpSpeed number

The initial upward velocity applied when the player jumps. Higher values make the player jump higher

var jumpSpeed = 10.0;
heroSize number

The width and height of the player's blue square in pixels. Used for drawing the player and calculating collision detection boundaries

var heroSize = 25;

🔧 Potential Improvements (7)

Here are some ways this code could be enhanced:

STYLE setup() level data

The 22 levels are hardcoded directly in setup() as a massive 3D array literal, making the code hard to read and maintain

💡 Extract level data into a separate levels.js file or compress it using a string encoding system. This would make setup() much cleaner and easier to edit individual levels

STYLE runLevel()

22 individual if-statements check levelOn and set spawn coordinates—this is repetitive and doesn't scale if you add more levels

💡 Create a single array of spawn coordinates: `var spawnPoints = [[90, 540], [480, 540], [300, 540], ...]` and use `x = spawnPoints[levelOn][0]; y = spawnPoints[levelOn][1];` instead of 22 if-statements

BUG tick() jump detection

The bounce pad jump boost only applies to levels 0-12. Levels 13+ check for E tiles again but the first condition prevents them from jumping higher on bounce pads

💡 Simplify the jump logic: `if (bl == E || br == E) { dy = -jumpSpeed * 1.3; } else if (bl == G || br == G) { dy = -jumpSpeed; }` applies boost to all levels uniformly

STYLE draw() tile rendering

The nested loops that check and color tiles are repeated for each tile type (G, D, C, E, F), adding 5 separate if-statements per tile per frame

💡 Create a tile color map: `var tileColors = { 1: [0, 0, 0], 2: [255, 0, 0], ... }` and loop once, looking up the color instead of multiple conditionals

PERFORMANCE draw() alert

The tutorial alert blocks the main thread and can appear multiple times if sa is not properly managed across reloads

💡 Replace the blocking alert() with a non-blocking UI element (div with CSS) that fades away after a few seconds, or use a text overlay drawn on the canvas instead

FEATURE runLevel()

No win condition—level 21 is the final level but there's no indication that the game is complete

💡 Add a check after runLevel: `if (levelOn > 21) { alert('You won!'); levelOn = 0; }` to show a victory screen and reset to level 0

STYLE Variable declarations

Global variables are declared at different places throughout the file, making it hard to see which variables exist and their initial values

💡 Group all global variables at the top of the file in one section before setup(), clearly labeled with comments explaining each one

🔄 Code Flow

Code flow showing setup, draw, tick, runlevel

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> tutorial-alert[tutorial-alert] draw --> tick-timer[tick-timer] draw --> tile-rendering-loop[tile-rendering-loop] draw --> player-drawing[player-drawing] tutorial-alert -->|First run check| draw tick-timer -->|Calls tick() every 30ms| tick[tick] tile-rendering-loop -->|Loops through rows| rowloop[Row Loop] rowloop -->|Loops through columns| colloop[Column Loop] colloop -->|Draws tile based on type| tilecheck[Tile Type Check] player-drawing -->|Draws player at x, y| playerpos[Player Position] tick --> horizontal-input[horizontal-input] tick --> jump-logic[jump-logic] tick --> gravity-application[gravity-application] tick --> right-collision[right-collision] tick --> left-collision[left-collision] tick --> bottom-collision[bottom-collision] tick --> top-collision[top-collision] tick --> state-reset[state-reset] tick --> level-advance[level-advance] horizontal-input -->|Checks left/right keys| inputcheck[Input Check] jump-logic -->|Checks up key and tile below| jumpcheck[Jump Check] gravity-application -->|Applies gravity| gravitycalc[Gravity Calculation] right-collision -->|Checks right-side corners| rightcheck[Right Corner Check] left-collision -->|Checks left-side corners| leftcheck[Left Corner Check] bottom-collision -->|Checks bottom corners| bottomcheck[Bottom Corner Check] top-collision -->|Checks top corners| topcheck[Top Corner Check] state-reset -->|Resets velocity and gravity| resetvars[Reset Variables] level-advance -->|Checks nextLevel status| levelcheck[Level Check] levelcheck -->|If true, increment levelOn| increment[Increment Level] levelcheck -->|If false, stay on level| stay[Stay on Level] tick --> draw click setup href "#fn-setup" click draw href "#fn-draw" click tick href "#fn-tick" click tutorial-alert href "#sub-tutorial-alert" click tick-timer href "#sub-tick-timer" click tile-rendering-loop href "#sub-tile-rendering-loop" click player-drawing href "#sub-player-drawing" click horizontal-input href "#sub-horizontal-input" click jump-logic href "#sub-jump-logic" click gravity-application href "#sub-gravity-application" click right-collision href "#sub-right-collision" click left-collision href "#sub-left-collision" click bottom-collision href "#sub-bottom-collision" click top-collision href "#sub-top-collision" click state-reset href "#sub-state-reset" click level-advance href "#sub-level-advance"

❓ Frequently Asked Questions

What visual elements are displayed in the Platform Game sketch?

The sketch creates a grid-based platform environment filled with blocks, which represent different types of terrain or obstacles.

How can players interact with the Platform Game sketch?

Users can navigate through the platform using keyboard inputs, although specific controls are not defined in the provided code.

What creative coding concepts are showcased in the Platform Game sketch?

This sketch demonstrates the use of multi-dimensional arrays to structure levels and the basics of game design in a grid format.

Preview

Platform Game - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Platform Game - Code flow showing setup, draw, tick, runlevel
Code Flow Diagram