Sketch 2026-07-03 15:55

This sketch is a classic space shooter game where you pilot a blue triangle spaceship across the bottom of the screen, firing yellow bullets upward to destroy red enemy blocks that fall from above. Points are awarded for each enemy destroyed, and the game uses arrow keys to move and spacebar to shoot.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make enemies spawn faster — Lower spawn interval values create more enemies on screen, increasing difficulty and intensity.
  2. Award more points per hit — Higher point values make destroying enemies feel more rewarding and let you reach high scores faster.
  3. Make the spaceship move faster — Higher speed values let you dodge enemies more easily and respond to threats quicker.
  4. Slow down falling enemies — Lower enemy speeds give you more time to aim and shoot, making the game less hectic.
  5. Make bullets travel faster — Higher bullet speeds (more negative values) reach enemies quicker and feel more responsive.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a fully playable space shooter game featuring a blue triangle spaceship you control at the bottom of the canvas, yellow bullets you fire upward, and red enemy blocks falling from the top. The visuals are simple but the game is engaging: dodge enemies that slip past you and blast as many as possible to rack up points. It demonstrates several essential p5.js techniques: object-oriented programming with classes, keyboard and touch input handling, collision detection between bullets and enemies, and dynamic object management with arrays.

The code is organized into three classes—Player, Bullet, and Enemy—each responsible for its own movement, collision, and drawing. The main draw() function orchestrates the game loop: it spawns enemies at intervals, updates all game objects, detects collisions, and renders the score. By studying this sketch you will learn how to structure a game with classes, how to manage multiple moving objects in arrays, how to detect when objects collide, and how to respond to player input in real time.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas and initializes the player spaceship at the bottom center, ready for control.
  2. Every frame, draw() clears the background to black and updates the player's bullets, checking for collisions with enemies.
  3. The game spawns a new red enemy block at the top of the screen at regular intervals (every 1000 milliseconds by default), each with random horizontal position, speed, and size.
  4. Enemies fall downward each frame—if a bullet's distance to an enemy is less than their combined radii, they collide: the bullet and enemy are both removed, and the score increases by 10.
  5. Arrow key presses move the player left or right (constrained to stay on-screen), and holding spacebar fires bullets upward from the player's center position with a shooting delay to prevent bullet spam.
  6. The score is displayed in the top-left corner and updates every time an enemy is destroyed.
  7. On mobile devices, touching the left half of the screen moves the player left while touching the right half moves them right, and any touch also fires a bullet.

🎓 Concepts You'll Learn

Object-oriented programmingClass designArray managementCollision detectionKeyboard inputGame loopTiming and delays

📝 Code Breakdown

Player constructor()

The constructor runs once when a new Player object is created. It initializes all the spaceship's properties: position, size, movement speed, and shooting mechanics. These properties are stored as 'this' variables so every method in the class can access and modify them.

  constructor() {
    this.width = 40;
    this.height = 30;
    this.x = width / 2 - this.width / 2; // Center horizontally
    this.y = height - 50; // Near the bottom
    this.speed = 5;
    this.bullets = [];
    this.lastShotTime = 0;
    this.shotDelay = 200; // Milliseconds between shots
  }
Line-by-line explanation (8 lines)
this.width = 40;
Sets the spaceship's width to 40 pixels—this is used for drawing the triangle and keeping it on-screen.
this.height = 30;
Sets the spaceship's height to 30 pixels—helps define the triangle shape and collision bounds.
this.x = width / 2 - this.width / 2; // Center horizontally
Positions the spaceship at the horizontal center of the canvas by subtracting half its width from the canvas center.
this.y = height - 50; // Near the bottom
Places the spaceship 50 pixels from the bottom of the screen, leaving room for the bottom boundary.
this.speed = 5;
Sets how many pixels the spaceship moves each time move() is called—higher values make it move faster.
this.bullets = [];
Initializes an empty array to store all active bullets fired by the player.
this.lastShotTime = 0;
Tracks the time of the last shot in milliseconds, used to enforce the shooting delay.
this.shotDelay = 200; // Milliseconds between shots
Sets the minimum time in milliseconds between consecutive shots—prevents bullet spam and balances game difficulty.

Player.move(direction)

The move() method accepts a direction parameter (typically -1 or 1) to move left or right. The constrain() function is the safety net that keeps the spaceship bounded.

  move(direction) {
    this.x += direction * this.speed;
    this.x = constrain(this.x, 0, width - this.width); // Keep player on screen
  }
Line-by-line explanation (2 lines)

🔧 Subcomponents:

calculation Boundary Constraint this.x = constrain(this.x, 0, width - this.width); // Keep player on screen

Prevents the spaceship from moving beyond the left or right edges of the canvas

this.x += direction * this.speed;
Updates the x position by multiplying the direction (-1 for left, 1 for right) by the movement speed and adding it to current position.
this.x = constrain(this.x, 0, width - this.width); // Keep player on screen
The constrain() function clamps the x value between 0 (left edge) and width - this.width (right edge), ensuring the spaceship never leaves the screen.

Player.shoot()

The shoot() method implements a fire-rate limiter using timestamps. When spacebar is held down, this method is called every frame, but the if-check ensures bullets only spawn every 200ms, creating a controlled firing rate.

🔬 This delay check prevents bullet spam. What happens if you change > to >= or even remove the whole if-statement so every frame spawns a bullet instantly?

    let currentTime = millis();
    if (currentTime - this.lastShotTime > this.shotDelay) {
  shoot() {
    let currentTime = millis();
    if (currentTime - this.lastShotTime > this.shotDelay) {
      // Create a new bullet at the player's position
      let bullet = new Bullet(this.x + this.width / 2, this.y, -10); // Bullet moves up (-10 speed)
      this.bullets.push(bullet);
      this.lastShotTime = currentTime;
    }
  }
Line-by-line explanation (5 lines)

🔧 Subcomponents:

conditional Shooting Delay Check if (currentTime - this.lastShotTime > this.shotDelay) {

Ensures enough time has passed since the last shot before allowing a new bullet to be fired

let currentTime = millis();
Gets the current time in milliseconds since the sketch started—used to track when the last shot was fired.
if (currentTime - this.lastShotTime > this.shotDelay) {
Checks if the time since the last shot exceeds shotDelay (200ms)—only allows firing if enough time has passed, preventing bullet spam.
let bullet = new Bullet(this.x + this.width / 2, this.y, -10); // Bullet moves up (-10 speed)
Creates a new Bullet object at the spaceship's center (x + width/2) and its y position, with a speed of -10 (negative means upward motion).
this.bullets.push(bullet);
Adds the newly created bullet to the player's bullets array so it will be updated and displayed each frame.
this.lastShotTime = currentTime;
Records the current time as the last shot time, resetting the delay counter for the next bullet.

Player.update()

The update() method manages the lifetime of all bullets. It moves each bullet and cleans up those that leave the screen, preventing memory leaks in longer play sessions.

🔬 Why is this loop going backwards (i--) instead of forwards (i++)? Try changing it to a forward loop and watch what happens to bullets when they go off-screen.

    for (let i = this.bullets.length - 1; i >= 0; i--) {
      this.bullets[i].update();
      // Remove bullets that go off-screen
      if (this.bullets[i].y < 0) {
        this.bullets.splice(i, 1);
      }
    }
  update() {
    // Update all bullets
    for (let i = this.bullets.length - 1; i >= 0; i--) {
      this.bullets[i].update();
      // Remove bullets that go off-screen
      if (this.bullets[i].y < 0) {
        this.bullets.splice(i, 1);
      }
    }
  }
Line-by-line explanation (4 lines)

🔧 Subcomponents:

for-loop Bullet Update Loop for (let i = this.bullets.length - 1; i >= 0; i--) {

Iterates through all active bullets in reverse order so removal doesn't skip any bullets

conditional Off-Screen Bullet Removal if (this.bullets[i].y < 0) {

Detects when a bullet has traveled above the canvas and removes it to free memory

for (let i = this.bullets.length - 1; i >= 0; i--) {
Loops through the bullets array backwards (from end to start)—this is essential because splice() removes items, which would skip forward loops.
this.bullets[i].update();
Calls the update() method on each bullet, which moves it upward by its speed value.
if (this.bullets[i].y < 0) {
Checks if the bullet has moved above the top of the canvas (y less than 0).
this.bullets.splice(i, 1);
Removes the bullet from the array using splice(), preventing off-screen bullets from accumulating in memory.

Player.display()

The display() method draws both the spaceship and all its bullets. It uses a triangle() to render the spaceship pointing upward, with the tip at the top and the base at the bottom.

  display() {
    // Draw player spaceship
    fill(0, 0, 255); // Blue
    triangle(
      this.x, this.y + this.height,
      this.x + this.width, this.y + this.height,
      this.x + this.width / 2, this.y
    );

    // Display all bullets
    for (let bullet of this.bullets) {
      bullet.display();
    }
  }
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Triangle Spaceship triangle( this.x, this.y + this.height, this.x + this.width, this.y + this.height, this.x + this.width / 2, this.y );

Draws the blue spaceship as a triangle with the point facing upward

for-loop Bullet Display Loop for (let bullet of this.bullets) {

Renders every bullet in the array to make them visible on the canvas

fill(0, 0, 255); // Blue
Sets the fill color to pure blue (RGB: 0, 0, 255) for the spaceship.
this.x, this.y + this.height,
First vertex of the triangle: the bottom-left corner at (this.x, this.y + this.height).
this.x + this.width, this.y + this.height,
Second vertex: the bottom-right corner at (this.x + this.width, this.y + this.height).
this.x + this.width / 2, this.y
Third vertex: the top point at the center of the spaceship (this.x + this.width / 2, this.y)—this creates the upward-pointing tip.
for (let bullet of this.bullets) {
Uses a for-of loop to iterate through every bullet in the bullets array.
bullet.display();
Calls the display() method on each bullet, drawing it as a yellow circle.

Bullet constructor(x, y, speed)

The Bullet constructor is simple and stores all the information needed for a bullet to move and collide: position, velocity, and size.

  constructor(x, y, speed) {
    this.x = x;
    this.y = y;
    this.speed = speed;
    this.radius = 4;
  }
Line-by-line explanation (4 lines)
this.x = x;
Stores the bullet's starting horizontal position (passed from the player's center).
this.y = y;
Stores the bullet's starting vertical position (passed from the player's y coordinate).
this.speed = speed;
Stores the bullet's velocity—typically -10 to move upward (negative y direction).
this.radius = 4;
Sets the bullet's radius to 4 pixels, used for drawing the circle and collision detection.

Bullet.update()

This one-liner moves the bullet. Because speed is negative (-10), adding it to y moves the bullet upward toward the top of the screen.

  update() {
    this.y += this.speed;
  }
Line-by-line explanation (1 lines)
this.y += this.speed;
Moves the bullet upward each frame by adding its speed (negative value) to y—since speed is -10, y decreases by 10 pixels per frame.

Bullet.display()

The display() method draws the bullet as a small yellow circle. The ellipse() function's third argument is the width (diameter), so multiplying radius by 2 creates the full diameter.

  display() {
    fill(255, 255, 0); // Yellow
    ellipse(this.x, this.y, this.radius * 2);
  }
Line-by-line explanation (2 lines)
fill(255, 255, 0); // Yellow
Sets the fill color to yellow (RGB: 255, 255, 0) for every bullet.
ellipse(this.x, this.y, this.radius * 2);
Draws a circle at the bullet's position with a diameter of this.radius * 2 (8 pixels)—the third argument to ellipse() is diameter, not radius.

Enemy constructor(x, y, speed, size)

The Enemy constructor stores position, falling speed, and visual size. Enemies are randomized at spawn time to create variety in gameplay.

  constructor(x, y, speed, size) {
    this.x = x;
    this.y = y;
    this.speed = speed;
    this.size = size;
  }
Line-by-line explanation (4 lines)
this.x = x;
Stores the enemy's horizontal position (randomized at spawn time).
this.y = y;
Stores the enemy's vertical position (typically -20, above the top of the canvas).
this.speed = speed;
Stores the enemy's falling speed—a positive value that increases y each frame, moving downward.
this.size = size;
Stores the enemy's width and height (same value makes it a square), randomized between 20 and 40 pixels.

Enemy.update()

The update() method is identical in concept to Bullet.update()—it simply moves the enemy by its velocity each frame. Since speed is positive, y increases and the enemy falls downward.

  update() {
    this.y += this.speed;
  }
Line-by-line explanation (1 lines)
this.y += this.speed;
Moves the enemy downward each frame by adding its speed (positive value) to y, making it fall toward the bottom of the screen.

Enemy.display()

The display() method draws enemies as red squares. Using rectMode(CENTER) makes the math simpler: the x,y position is the center, not a corner.

  display() {
    fill(255, 0, 0); // Red
    rectMode(CENTER);
    rect(this.x, this.y, this.size, this.size);
  }
Line-by-line explanation (3 lines)
fill(255, 0, 0); // Red
Sets the fill color to red (RGB: 255, 0, 0) for every enemy.
rectMode(CENTER);
Tells rect() to interpret its x,y arguments as the center of the rectangle (not the top-left corner)—makes positioning easier.
rect(this.x, this.y, this.size, this.size);
Draws a red square centered at (this.x, this.y) with width and height both equal to this.size.

setup()

setup() runs once when the sketch starts. It initializes the canvas and game objects, preparing everything for the draw loop.

function setup() {
  createCanvas(windowWidth, windowHeight);
  player = new Player();
  enemySpawnTimer = millis();
}
Line-by-line explanation (3 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the game responsive to window size.
player = new Player();
Creates a new Player object with its default position (bottom center) and initializes all its properties.
enemySpawnTimer = millis();
Records the current time in milliseconds, starting the enemy spawn timer so enemies appear at the right interval.

draw()

The draw() function is the heartbeat of the game. It runs 60 times per second and orchestrates all game logic: player input, enemy spawning, collision detection, and rendering. This is where the magic happens—every visual update and every game mechanic flows through draw().

🔬 This code spawns enemies every 1000 milliseconds. What happens if you change enemySpawnInterval to 300 or even 100? Try different numbers to find the hardest difficulty you can handle.

  // Handle enemy spawning
  if (millis() - enemySpawnTimer > enemySpawnInterval) {
    let enemyX = random(player.width, width - player.width);
    let enemyY = -20; // Start off-screen at the top
    let enemySpeed = random(1, 3);
    let enemySize = random(20, 40);
    enemies.push(new Enemy(enemyX, enemyY, enemySpeed, enemySize));
    enemySpawnTimer = millis();
  }

🔬 When a bullet hits an enemy, the score goes up by 10. What happens if you change this to 50, or even 1? Try different point values to feel how much reward is satisfying.

      if (distance < bullet.radius + enemy.size / 2) {
        // Bullet hit enemy
        player.bullets.splice(i, 1); // Remove bullet
        enemies.splice(j, 1);       // Remove enemy
        score += 10;                // Increase score
function draw() {
  background(0); // Black background

  // Update and display player
  player.update();
  player.display();

  // Handle player movement with arrow keys
  if (keyIsDown(LEFT_ARROW)) {
    player.move(-1);
  }
  if (keyIsDown(RIGHT_ARROW)) {
    player.move(1);
  }

  // Handle player shooting with spacebar
  if (keyIsDown(32)) { // ASCII code for spacebar
    player.shoot();
  }

  // Handle enemy spawning
  if (millis() - enemySpawnTimer > enemySpawnInterval) {
    let enemyX = random(player.width, width - player.width);
    let enemyY = -20; // Start off-screen at the top
    let enemySpeed = random(1, 3);
    let enemySize = random(20, 40);
    enemies.push(new Enemy(enemyX, enemyY, enemySpeed, enemySize));
    enemySpawnTimer = millis();
  }

  // Update and display enemies
  for (let i = enemies.length - 1; i >= 0; i--) {
    enemies[i].update();
    enemies[i].display();

    // Remove enemies that go off-screen at the bottom
    if (enemies[i].y > height + enemies[i].size / 2) {
      enemies.splice(i, 1);
      // Optionally, deduct score or end game if enemy passes
    }
  }

  // Collision detection: bullets vs. enemies
  for (let i = player.bullets.length - 1; i >= 0; i--) {
    let bullet = player.bullets[i];
    for (let j = enemies.length - 1; j >= 0; j--) {
      let enemy = enemies[j];

      // Simple circular/rectangular collision check
      let distance = dist(bullet.x, bullet.y, enemy.x, enemy.y);
      if (distance < bullet.radius + enemy.size / 2) {
        // Bullet hit enemy
        player.bullets.splice(i, 1); // Remove bullet
        enemies.splice(j, 1);       // Remove enemy
        score += 10;                // Increase score
        break; // Exit inner loop, bullet is gone
      }
    }
  }

  // Display score
  fill(255);
  textSize(24);
  textAlign(LEFT);
  text("Score: " + score, 10, 30);
}
Line-by-line explanation (34 lines)

🔧 Subcomponents:

calculation Player Update and Display player.update(); player.display();

Updates all bullets and draws the spaceship and its bullets each frame

conditional Left Arrow Movement if (keyIsDown(LEFT_ARROW)) { player.move(-1); }

Moves the player left when the left arrow key is held down

conditional Right Arrow Movement if (keyIsDown(RIGHT_ARROW)) { player.move(1); }

Moves the player right when the right arrow key is held down

conditional Spacebar Shooting if (keyIsDown(32)) { // ASCII code for spacebar player.shoot(); }

Fires a bullet when spacebar is held, limited by the shooting delay inside shoot()

conditional Enemy Spawning if (millis() - enemySpawnTimer > enemySpawnInterval) { let enemyX = random(player.width, width - player.width); let enemyY = -20; // Start off-screen at the top let enemySpeed = random(1, 3); let enemySize = random(20, 40); enemies.push(new Enemy(enemyX, enemyY, enemySpeed, enemySize)); enemySpawnTimer = millis(); }

Spawns a new enemy at random horizontal position with random speed and size every 1000 milliseconds

for-loop Enemy Update and Display Loop for (let i = enemies.length - 1; i >= 0; i--) { enemies[i].update(); enemies[i].display(); // Remove enemies that go off-screen at the bottom if (enemies[i].y > height + enemies[i].size / 2) { enemies.splice(i, 1); } }

Updates and draws all enemies, and removes those that fall off the bottom of the screen

for-loop Collision Detection for (let i = player.bullets.length - 1; i >= 0; i--) { let bullet = player.bullets[i]; for (let j = enemies.length - 1; j >= 0; j--) { let enemy = enemies[j]; // Simple circular/rectangular collision check let distance = dist(bullet.x, bullet.y, enemy.x, enemy.y); if (distance < bullet.radius + enemy.size / 2) { // Bullet hit enemy player.bullets.splice(i, 1); // Remove bullet enemies.splice(j, 1); // Remove enemy score += 10; // Increase score break; // Exit inner loop, bullet is gone } } }

Checks every bullet against every enemy, and removes both plus adds points if they collide

calculation Score Display // Display score fill(255); textSize(24); textAlign(LEFT); text("Score: " + score, 10, 30);

Renders the current score in white text at the top-left corner of the screen

background(0); // Black background
Clears the canvas to black every frame, erasing the previous frame's drawing and preventing motion trails.
player.update();
Updates all active bullets (moves them and removes off-screen ones).
player.display();
Draws the spaceship and all its bullets to the canvas.
if (keyIsDown(LEFT_ARROW)) {
Checks if the left arrow key is currently pressed (held down).
player.move(-1);
Moves the player left by 1 unit (multiplied by speed = 5, so 5 pixels left per frame).
if (keyIsDown(RIGHT_ARROW)) {
Checks if the right arrow key is currently pressed.
player.move(1);
Moves the player right by 1 unit (5 pixels right per frame).
if (keyIsDown(32)) { // ASCII code for spacebar
Checks if spacebar (ASCII 32) is held down—calls shoot() every frame, but the method's internal delay prevents spam.
if (millis() - enemySpawnTimer > enemySpawnInterval) {
Checks if enough time has passed (1000ms) since the last enemy spawn—if so, spawns a new enemy.
let enemyX = random(player.width, width - player.width);
Picks a random horizontal position for the new enemy, avoiding the far edges to ensure it spawns on-screen.
let enemyY = -20; // Start off-screen at the top
Places the new enemy above the canvas so it enters from the top as it falls.
let enemySpeed = random(1, 3);
Randomizes the falling speed between 1 and 3 pixels per frame, creating variety.
let enemySize = random(20, 40);
Randomizes the enemy's size between 20 and 40 pixels, making some larger targets and some smaller challenges.
enemies.push(new Enemy(enemyX, enemyY, enemySpeed, enemySize));
Creates a new Enemy object with the randomized properties and adds it to the enemies array.
enemySpawnTimer = millis();
Resets the spawn timer to the current time, so the next spawn won't happen until 1000ms passes.
for (let i = enemies.length - 1; i >= 0; i--) {
Loops backwards through all enemies—necessary because splice() will modify the array.
enemies[i].update();
Moves the enemy downward by its speed value.
enemies[i].display();
Draws the enemy as a red square.
if (enemies[i].y > height + enemies[i].size / 2) {
Checks if the enemy has fallen below the visible canvas (accounting for its size).
enemies.splice(i, 1);
Removes the off-screen enemy from the array, freeing memory.
for (let i = player.bullets.length - 1; i >= 0; i--) {
Outer loop: iterates through all bullets in reverse order.
let bullet = player.bullets[i];
Stores a reference to the current bullet for cleaner code.
for (let j = enemies.length - 1; j >= 0; j--) {
Inner loop: for each bullet, checks against all enemies (reverse order).
let enemy = enemies[j];
Stores a reference to the current enemy being tested for collision.
let distance = dist(bullet.x, bullet.y, enemy.x, enemy.y);
Calculates the distance between the bullet and enemy using the dist() function (Euclidean distance).
if (distance < bullet.radius + enemy.size / 2) {
Checks if distance is less than the sum of their radii—if so, they overlap and have collided.
player.bullets.splice(i, 1); // Remove bullet
Removes the bullet from the array so it won't be updated or drawn anymore.
enemies.splice(j, 1); // Remove enemy
Removes the enemy from the array.
score += 10; // Increase score
Awards 10 points for destroying the enemy.
break; // Exit inner loop, bullet is gone
Breaks out of the inner loop because the bullet is destroyed and can't hit any more enemies.
fill(255);
Sets the text color to white.
textSize(24);
Sets the text size to 24 pixels.
textAlign(LEFT);
Aligns text to the left, so the x coordinate is the left edge of the text.
text("Score: " + score, 10, 30);
Displays the score string concatenated with the current score variable at position (10, 30) on the canvas.

touchStarted()

The touchStarted() function provides mobile support. When a user touches the screen, it checks which half they touched and moves the player accordingly, while also firing a bullet. This creates a simple tap-to-play interface for mobile devices.

function touchStarted() {
  // Check if touch is on left or right half of the screen for movement
  if (mouseX < width / 2) {
    player.move(-1);
  } else {
    player.move(1);
  }
  // Also shoot on touch
  player.shoot();
  return false; // Prevent default browser behavior
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

conditional Touch Direction Detection if (mouseX < width / 2) {

Determines whether the touch was on the left or right half of the screen to decide movement direction

function touchStarted() {
This p5.js function is called whenever the user touches the screen on a mobile device.
if (mouseX < width / 2) {
Checks if the touch occurred on the left half of the canvas by comparing mouseX to the canvas center.
player.move(-1);
If the touch is on the left, move the player left by 1 unit (5 pixels).
} else {
If the touch is not on the left, it must be on the right half.
player.move(1);
Moves the player right by 1 unit (5 pixels).
player.shoot();
Fires a bullet when the touch starts, allowing players to shoot while moving.
return false; // Prevent default browser behavior
Returns false to prevent the browser from treating the touch as a page scroll or other gesture.

touchEnded()

The touchEnded() function is currently a placeholder. It prevents default browser behavior but doesn't do anything else. The current game design only requires tap-to-shoot action, which works well for mobile play.

function touchEnded() {
  // If you want continuous movement with touch, you'd need a different approach
  // For now, it's a tap-to-move/shoot mechanic.
  return false;
}
Line-by-line explanation (2 lines)
function touchEnded() {
This p5.js function is called when the user lifts their finger off the screen.
return false;
Returns false to prevent default browser behavior (like scrolling or page gestures).

windowResized()

The windowResized() function ensures the game stays responsive when the window changes size. It resizes the canvas and re-centers the player so the game works correctly at any window dimension.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Re-center player if canvas size changes significantly
  player.x = width / 2 - player.width / 2;
  player.y = height - 50;
}
Line-by-line explanation (4 lines)
function windowResized() {
This p5.js function is called automatically whenever the browser window is resized.
resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the new window dimensions, keeping the game full-screen.
player.x = width / 2 - player.width / 2;
Re-centers the player horizontally based on the new canvas width.
player.y = height - 50;
Repositions the player 50 pixels from the bottom of the new canvas height.

📦 Key Variables

width number

A p5.js variable that stores the canvas width in pixels—used for boundary checking and centering objects.

let x = width / 2; // Center horizontally
height number

A p5.js variable that stores the canvas height in pixels—used for positioning objects from the bottom.

let y = height - 50; // 50 pixels from bottom
player object

Stores the Player object that the user controls—has properties for position, speed, bullets, and methods for movement and shooting.

let player = new Player();
enemies array

An array holding all active Enemy objects currently falling on screen—enemies are added when spawned and removed when hit or off-screen.

let enemies = [];
score number

Tracks the player's current score—incremented by 10 each time an enemy is destroyed, displayed at the top-left corner.

let score = 0;
enemySpawnTimer number

Records the time of the last enemy spawn in milliseconds—used to determine when the next enemy should spawn.

let enemySpawnTimer = 0;
enemySpawnInterval number

The delay in milliseconds between enemy spawns—1000ms means a new enemy appears every 1 second.

let enemySpawnInterval = 1000; // Milliseconds between enemy spawns

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG draw() enemy spawning

Enemies can spawn at the far left or far right edges, sometimes partially off-screen depending on their size.

💡 Adjust the spawn bounds to account for enemy size: let enemyX = random(player.width + 30, width - player.width - 30);

PERFORMANCE draw() collision detection

Nested loops check every bullet against every enemy (O(n²) complexity)—with many objects this becomes slow.

💡 Implement spatial partitioning (divide canvas into grid cells) or limit collision checks to nearby objects to improve performance.

STYLE touchStarted()

Touch controls only work for single taps—holding and dragging doesn't provide continuous movement feedback.

💡 Track which half of the screen is touched and move the player continuously while touched, requiring additional state tracking with touchStarted/touchEnded.

FEATURE draw()

There is no game-over condition—the game never ends even if enemies reach the bottom.

💡 Add a lives system: deduct lives when enemies pass the player, and end the game when lives reach zero. Display the remaining lives on screen.

FEATURE draw()

Difficulty never increases during gameplay—the game stays the same difficulty the entire time.

💡 Gradually increase enemySpawnInterval or enemy speeds as the score increases, creating a progressive difficulty curve that challenges players more as they improve.

STYLE Player class

Shot delay is hard-coded in the constructor—changing fire rate requires editing the class definition.

💡 Make shotDelay a parameter passed to the constructor, or add a changeShotDelay(newDelay) method for easier tuning without modifying the class.

🔄 Code Flow

Code flow showing playerconstructor, playermove, playershoot, playerupdate, playerdisplay, bulletconstructor, bulletupdate, bulletdisplay, enemyconstructor, enemyupdate, enemydisplay, setup, draw, touchstarted, touchended, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> player-update-display[player-update-display] player-update-display --> left-arrow-input[left-arrow-input] player-update-display --> right-arrow-input[right-arrow-input] player-update-display --> spacebar-shooting[spacebar-shooting] player-update-display --> playerupdate[playerupdate] player-update-display --> playerdisplay[playerdisplay] player-update-display --> bullet-loop[bullet-loop] bullet-loop --> bulletupdate[bulletupdate] bullet-loop --> bullet-display-loop[bullet-display-loop] bullet-display-loop --> bulletdisplay[bulletdisplay] bullet-display-loop --> offscreen-removal[offscreen-removal] draw --> enemy-update-loop[enemy-update-loop] enemy-update-loop --> enemyupdate[enemyupdate] enemy-update-loop --> enemydisplay[enemydisplay] enemy-update-loop --> collision-detection[collision-detection] draw --> enemy-spawn-timer[enemy-spawn-timer] draw --> score-display[score-display] draw --> touchstarted[touchstarted] draw --> touchended[touchended] draw --> windowresized[windowresized] click setup href "#fn-setup" click draw href "#fn-draw" click player-update-display href "#sub-player-update-display" click left-arrow-input href "#sub-left-arrow-input" click right-arrow-input href "#sub-right-arrow-input" click spacebar-shooting href "#sub-spacebar-shooting" click playerupdate href "#fn-playerupdate" click playerdisplay href "#fn-playerdisplay" click bullet-loop href "#sub-bullet-loop" click bulletupdate href "#fn-bulletupdate" click bullet-display-loop href "#sub-bullet-display-loop" click bulletdisplay href "#fn-bulletdisplay" click offscreen-removal href "#sub-offscreen-removal" click enemy-update-loop href "#sub-enemy-update-loop" click enemyupdate href "#fn-enemyupdate" click enemydisplay href "#fn-enemydisplay" click collision-detection href "#sub-collision-detection" click enemy-spawn-timer href "#sub-enemy-spawn-timer" click score-display href "#sub-score-display" click touchstarted href "#fn-touchstarted" click touchended href "#fn-touchended" click windowresized href "#fn-windowresized"

❓ Frequently Asked Questions

What visual elements are present in the Sketch 2026-07-03 15:55?

The sketch features a blue triangle spaceship at the bottom of the screen, yellow bullets fired upward, and red enemy blocks that fall from above.

How can users control the spaceship in this sketch?

Users can move the spaceship left and right using the arrow keys and shoot bullets upward by holding the space bar.

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

The sketch showcases object-oriented programming by using classes for the player, bullets, and enemies, as well as basic game mechanics like shooting and collision handling.

Preview

Sketch 2026-07-03 15:55 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch 2026-07-03 15:55 - Code flow showing playerconstructor, playermove, playershoot, playerupdate, playerdisplay, bulletconstructor, bulletupdate, bulletdisplay, enemyconstructor, enemyupdate, enemydisplay, setup, draw, touchstarted, touchended, windowresized
Code Flow Diagram