I hate Anonymous and corbun and corbun 2.5

This is a fast-paced arcade shooter where players pilot a blue triangular ship to dodge falling enemy circles or destroy them with yellow projectiles. The game combines mouse/touch movement, collision detection, and scoring to create an engaging action experience.

🧪 Try This!

Experiment with the code by making these changes:

  1. Double enemy spawn rate — Enemies spawn twice as fast, making the game harder. Change the modulo check from 60 to 30 frames.
  2. Change ship color to red — The player triangle becomes red instead of blue, matching the enemies visually.
  3. Make projectiles bigger — Increase the projectile size from 10 pixels to 20, making bullets easier to see and hit larger targets.
  4. Reward more points per hit — Each destroyed enemy awards 50 points instead of 10, letting players rack up high scores faster.
  5. Change enemy names — Replace the enemy labels with your own custom names.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a fully playable arcade shooter game where you control a blue triangle ship with your mouse or touch input, shooting yellow projectiles at falling enemy circles labeled with silly names. The game combines several core p5.js techniques: object-oriented programming with classes, collision detection using the distance formula, array management for enemies and projectiles, and game state management to handle playing versus game-over screens.

The code is organized into three main classes—Player, Enemy, and Projectile—each handling their own movement, display, and collision logic. The draw loop orchestrates everything by updating player position, spawning enemies at regular intervals, moving and checking collisions for both enemies and projectiles, and displaying the score. By studying this sketch you will learn how to structure a complete interactive game, manage arrays of objects, and create responsive gameplay with touch and mouse input.

⚙️ How It Works

  1. When the sketch loads, setup() creates a canvas that fills the window and instantiates a Player object positioned at the bottom center, ready to receive movement commands.
  2. Every frame, draw() clears the background with a dark blue color and updates the game state: the player follows your mouse or touch input while staying within canvas bounds.
  3. Every 60 frames (approximately once per second), a new Enemy circle spawns at a random x position above the canvas and begins falling downward at a random speed.
  4. The game continuously checks two types of collisions: if an enemy touches the player ship, the game ends immediately; if an enemy reaches the bottom of the canvas without being shot, the game also ends.
  5. When you tap (on mobile) or click (on desktop), a new Projectile spawns from the player's position and moves upward. When a projectile hits an enemy, both are removed and the score increases by 10 points.
  6. If the game ends, a 'GAME OVER' screen displays your final score, and tapping or clicking anywhere restarts the game by resetting the score, clearing all enemies and projectiles, and returning to playing state.

🎓 Concepts You'll Learn

Object-oriented programming with ES6 classesCollision detection using distance formulaGame state managementArray manipulation and iterationTouch and mouse input handlingCanvas responsive design

📝 Code Breakdown

Player constructor()

The constructor runs once when you create a new Player object. It initializes all the ship's properties like size and starting position.

class Player {
  constructor() {
    this.size = 50;
    this.x = width / 2;
    this.y = height - this.size * 1.5;
    this.speed = 10;
  }
Line-by-line explanation (4 lines)
this.size = 50;
Sets the player ship's width and height to 50 pixels—used for drawing and collision detection.
this.x = width / 2;
Positions the ship horizontally at the center of the canvas.
this.y = height - this.size * 1.5;
Positions the ship vertically near the bottom (75 pixels up from the bottom edge).
this.speed = 10;
Stores the speed value used to constrain player movement—higher values allow tighter tracking of input.

Player.show()

show() is called every frame to redraw the player. The triangle function takes three coordinate pairs (x1, y1, x2, y2, x3, y3) to create a three-sided shape.

🔬 This triangle has three corners. What happens if you change the first y-coordinate from (this.y - this.size / 2) to (this.y - this.size) to make the tip point higher?

    triangle(this.x, this.y - this.size / 2,
             this.x - this.size / 2, this.y + this.size / 2,
             this.x + this.size / 2, this.y + this.size / 2);
  show() {
    fill(0, 150, 255); // Blue ship
    noStroke();
    // Simple ship shape
    triangle(this.x, this.y - this.size / 2,
             this.x - this.size / 2, this.y + this.size / 2,
             this.x + this.size / 2, this.y + this.size / 2);
  }
Line-by-line explanation (3 lines)
fill(0, 150, 255); // Blue ship
Sets the fill color to bright cyan blue (0 red, 150 green, 255 blue in RGB).
noStroke();
Removes any outline around the triangle so only the filled shape is drawn.
triangle(this.x, this.y - this.size / 2, this.x - this.size / 2, this.y + this.size / 2, this.x + this.size / 2, this.y + this.size / 2);
Draws a triangle pointing upward with three corners: tip at (x, y-25), bottom-left at (x-25, y+25), and bottom-right at (x+25, y+25).

Player.move()

move() is called every frame to update the ship's position. It demonstrates responsive input handling: prioritizing touch on mobile devices while falling back to mouse on desktop.

🔬 This code chooses between touch or mouse input. What happens if you always use mouseX instead—remove the entire if-else block and just assign this.x = mouseX?

    if (touches.length > 0) {
      this.x = touches[0].x;
    } else {
      this.x = mouseX;
    }
  move() {
    // Player follows touch/mouse X position
    if (touches.length > 0) {
      this.x = touches[0].x;
    } else {
      this.x = mouseX;
    }
    this.x = constrain(this.x, this.size / 2, width - this.size / 2);
  }
Line-by-line explanation (5 lines)

🔧 Subcomponents:

conditional Touch Input Detection if (touches.length > 0) { this.x = touches[0].x; } else { this.x = mouseX; }

Checks if the screen is being touched; if so, use touch position; otherwise use mouse position

calculation Keep Player On Canvas this.x = constrain(this.x, this.size / 2, width - this.size / 2);

Prevents the ship from moving off the left or right edges by clamping x between boundaries

if (touches.length > 0) {
Checks if any fingers are currently touching the screen (touches is a p5.js array of touch events).
this.x = touches[0].x;
If touching, set the ship's x position to the x coordinate of the first touch point.
} else {
If not touching, use the mouse input instead.
this.x = mouseX;
Set the ship's x position to the current mouse x coordinate.
this.x = constrain(this.x, this.size / 2, width - this.size / 2);
Clamp the x position between (size/2) and (width - size/2) to keep the ship fully visible and prevent it from escaping off-screen.

Player.hits(enemy)

hits() is a collision detection method using distance-based circles. If the distance between centers is less than the sum of their radii, they collide. This is called a circle-circle collision check.

  // Check collision with an enemy
  hits(enemy) {
    let d = dist(this.x, this.y, enemy.x, enemy.y);
    return (d < this.size / 2 + enemy.size / 2);
  }
Line-by-line explanation (2 lines)
let d = dist(this.x, this.y, enemy.x, enemy.y);
Uses p5.js's dist() function to calculate the straight-line distance between the player's center and the enemy's center.
return (d < this.size / 2 + enemy.size / 2);
Returns true if the distance is less than the sum of both radii (size/2), meaning they are overlapping.

Enemy constructor(name)

The Enemy constructor creates falling circles with random properties. Each enemy gets a unique speed and a name label, making the game feel more dynamic and personalized.

class Enemy {
  constructor(name) {
    this.name = name;
    this.size = 40;
    this.x = random(this.size / 2, width - this.size / 2);
    this.y = -this.size / 2; // Start above canvas
    this.speed = random(2, 5);
    this.color = color(random(200, 255), 50, 50); // Reddish hues
  }
Line-by-line explanation (6 lines)
this.name = name;
Stores the enemy's name (e.g., 'Anonymous', 'Corbyn') to display on the circle.
this.size = 40;
Sets the enemy circle's diameter to 40 pixels.
this.x = random(this.size / 2, width - this.size / 2);
Spawns the enemy at a random horizontal position, constrained so the circle stays fully visible horizontally.
this.y = -this.size / 2; // Start above canvas
Positions the enemy just above the top of the canvas so it falls down into view.
this.speed = random(2, 5);
Assigns a random falling speed between 2 and 5 pixels per frame to add variety.
this.color = color(random(200, 255), 50, 50); // Reddish hues
Creates a reddish color with red component between 200-255, keeping green and blue low (50 each).

Enemy.show()

show() is called every frame to redraw the enemy. It combines two drawing operations: an ellipse for the body and text for the name label.

🔬 This block draws white text in the center of the enemy. What happens if you change textSize(14) to textSize(20) to make enemy names bigger and more visible?

    fill(255);
    textAlign(CENTER, CENTER);
    textSize(14);
    text(this.name, this.x, this.y);
  show() {
    fill(this.color);
    noStroke();
    ellipse(this.x, this.y, this.size); // Simple circle enemy
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(14);
    text(this.name, this.x, this.y);
  }
Line-by-line explanation (7 lines)
fill(this.color);
Sets the fill color to the enemy's reddish color created in the constructor.
noStroke();
Removes any outline around the enemy circle.
ellipse(this.x, this.y, this.size); // Simple circle enemy
Draws a circle at the enemy's current position with diameter equal to this.size (40 pixels).
fill(255);
Changes the fill color to white (255, 255, 255 in RGB) for drawing the text.
textAlign(CENTER, CENTER);
Ensures the text is centered both horizontally and vertically at the given x, y position.
textSize(14);
Sets the font size to 14 pixels for the enemy name.
text(this.name, this.x, this.y);
Draws the enemy's name (e.g., 'Anonymous') centered on the circle.

Enemy.move()

move() is called every frame to update the enemy's position. The simple += operation moves the enemy down the screen at a constant speed.

  move() {
    this.y += this.speed;
  }
Line-by-line explanation (1 lines)
this.y += this.speed;
Increases the enemy's y position by its speed each frame, moving it downward (lower y values are higher on screen).

Enemy.offscreen()

offscreen() checks if an enemy has fallen off the bottom of the canvas. In the game, if this returns true, the game ends immediately—you lose if enemies escape!

  offscreen() {
    return this.y > height + this.size / 2;
  }
Line-by-line explanation (1 lines)
return this.y > height + this.size / 2;
Returns true if the enemy's y position is beyond the bottom of the canvas, indicating it has exited the game area.

Projectile constructor(x, y)

The Projectile constructor is called whenever the player shoots. It captures the current player position as the bullet's spawn point.

class Projectile {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.size = 10;
    this.speed = 8;
  }
Line-by-line explanation (4 lines)
this.x = x;
Stores the projectile's starting x position (passed from the player's current x).
this.y = y;
Stores the projectile's starting y position (passed as the player's y minus offset for the gun position).
this.size = 10;
Sets the projectile's diameter to 10 pixels—a small bright circle.
this.speed = 8;
Sets the projectile's upward speed to 8 pixels per frame.

Projectile.show()

show() draws the projectile every frame. The bright yellow color makes it stand out against the dark background, giving clear visual feedback when you shoot.

  show() {
    fill(255, 255, 0); // Yellow projectile
    noStroke();
    ellipse(this.x, this.y, this.size);
  }
Line-by-line explanation (3 lines)
fill(255, 255, 0); // Yellow projectile
Sets the fill color to bright yellow (255 red, 255 green, 0 blue in RGB).
noStroke();
Removes any outline around the projectile circle.
ellipse(this.x, this.y, this.size);
Draws a yellow circle with diameter 10 at the projectile's current position.

Projectile.move()

move() is called every frame to update the projectile's position, moving it upward toward enemies.

  move() {
    this.y -= this.speed;
  }
Line-by-line explanation (1 lines)
this.y -= this.speed;
Decreases the projectile's y position by its speed each frame, moving it upward (smaller y is higher on screen).

Projectile.offscreen()

offscreen() checks if a projectile has flown off the top of the canvas. When true, the projectile is removed from the array to save memory.

  offscreen() {
    return this.y < -this.size / 2;
  }
Line-by-line explanation (1 lines)
return this.y < -this.size / 2;
Returns true if the projectile's y position is above the top of the canvas, indicating it has exited the game area and should be removed.

Projectile.hits(enemy)

hits() is a collision detection method identical in logic to Player.hits(). It checks if the projectile and enemy circles overlap using the distance formula.

  // Check collision with an enemy
  hits(enemy) {
    let d = dist(this.x, this.y, enemy.x, enemy.y);
    return (d < this.size / 2 + enemy.size / 2);
  }
Line-by-line explanation (2 lines)
let d = dist(this.x, this.y, enemy.x, enemy.y);
Calculates the distance between the projectile's center and the enemy's center using p5.js's dist() function.
return (d < this.size / 2 + enemy.size / 2);
Returns true if the distance is less than the sum of their radii, meaning they are overlapping and the projectile has hit the enemy.

preload()

preload() runs once before setup() and is ideal for loading assets. This sketch uses p5.Oscillator to synthesize sound effects instead of loading audio files—it's a great way to create sounds programmatically when you don't have audio files available.

function preload() {
  // Load sounds
  soundFormats('wav', 'ogg');
  // You can replace these with your own sound URLs if you have them
  // For now, I'll use placeholder comments.
  // shootSound = loadSound('assets/shoot.wav');
  // gameOverSound = loadSound('assets/gameOver.wav');
  // bgMusic = loadSound('assets/bgMusic.ogg');

  // Placeholder for sounds if not loaded
  shootSound = new p5.Oscillator('sine');
  shootSound.freq(440);
  shootSound.amp(0); // Start silent
  gameOverSound = new p5.Oscillator('triangle');
  gameOverSound.freq(220);
  gameOverSound.amp(0); // Start silent
}
Line-by-line explanation (7 lines)
soundFormats('wav', 'ogg');
Tells p5.sound which audio file formats to look for when loading sounds (useful for cross-browser compatibility).
shootSound = new p5.Oscillator('sine');
Creates a sine wave oscillator that generates a sound tone—used as a placeholder for the shoot sound effect.
shootSound.freq(440);
Sets the oscillator's frequency to 440 Hz (the musical note A4) to define the tone quality.
shootSound.amp(0); // Start silent
Sets the oscillator's amplitude (volume) to 0 so it doesn't play until intentionally triggered.
gameOverSound = new p5.Oscillator('triangle');
Creates a triangle wave oscillator for the game-over sound—triangle waves sound different (more buzzy) than sine waves.
gameOverSound.freq(220);
Sets the game-over sound's frequency to 220 Hz (A3, one octave below the shoot sound).
gameOverSound.amp(0); // Start silent
Starts the game-over oscillator at zero volume so it won't play until the game ends.

setup()

setup() runs once when the sketch starts. It's the perfect place to initialize the canvas, create game objects, and set default drawing properties like text alignment.

function setup() {
  createCanvas(windowWidth, windowHeight);
  player = new Player();
  textAlign(CENTER, CENTER);
}
Line-by-line explanation (3 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the game responsive to different screen sizes.
player = new Player();
Instantiates a new Player object and stores it in the global player variable, initializing the ship at the bottom center of the canvas.
textAlign(CENTER, CENTER);
Sets the default text alignment to center both horizontally and vertically—applies to all text() calls afterward.

draw()

draw() is the heart of the game—it runs 60 times per second and orchestrates everything: updating game objects, checking collisions, spawning enemies, and rendering the display. The reverse-order loops (i starting at length - 1 and decrementing) are crucial: they allow safe removal of array elements without skipping items.

🔬 This section spawns one random enemy every 60 frames. What happens if you change the modulo from 60 to 30? How does the game difficulty change?

    // Spawn enemies
    if (frameCount % 60 === 0) { // Every second
      let enemyName = random(['Anonymous', 'Corbyn', 'Corbyn 2.5']);
      enemies.push(new Enemy(enemyName));
    }
function draw() {
  background(25, 25, 50); // Dark blue background

  if (gameState === 'playing') {
    player.move();
    player.show();

    // Spawn enemies
    if (frameCount % 60 === 0) { // Every second
      let enemyName = random(['Anonymous', 'Corbyn', 'Corbyn 2.5']);
      enemies.push(new Enemy(enemyName));
    }

    // Move and show enemies
    for (let i = enemies.length - 1; i >= 0; i--) {
      enemies[i].move();
      enemies[i].show();

      // Check if enemy hits player
      if (player.hits(enemies[i])) {
        gameState = 'gameOver';
        if (gameOverSound) {
          gameOverSound.start();
          gameOverSound.amp(0.5, 0.1);
          gameOverSound.amp(0, 0.5);
        }
      }

      // Check if enemy goes offscreen
      if (enemies[i].offscreen()) {
        enemies.splice(i, 1);
        // Game over if enemy passes player
        gameState = 'gameOver';
        if (gameOverSound) {
          gameOverSound.start();
          gameOverSound.amp(0.5, 0.1);
          gameOverSound.amp(0, 0.5);
        }
      }
    }

    // Move and show projectiles
    for (let i = projectiles.length - 1; i >= 0; i--) {
      let projectileRemovedThisFrame = false; // Flag to track if the current projectile was removed

      // Check if projectile hits enemy
      for (let j = enemies.length - 1; j >= 0; j--) {
        if (projectiles[i].hits(enemies[j])) {
          score += 10; // Increase score
          enemies.splice(j, 1); // Remove enemy
          projectiles.splice(i, 1); // Remove projectile
          projectileRemovedThisFrame = true; // Mark that it was removed
          break; // Stop checking for this projectile against other enemies
        }
      }

      // If the projectile was removed in the inner loop, skip the rest of this outer loop iteration
      if (projectileRemovedThisFrame) {
        continue; // Move to the next projectile in the outer loop
      }

      // Only move, show, and check offscreen if projectile was NOT removed by collision
      projectiles[i].move();
      projectiles[i].show();

      // Check if projectile goes offscreen
      if (projectiles[i].offscreen()) {
        projectiles.splice(i, 1);
      }
    }

    // Display score
    fill(255);
    textSize(20);
    text('Score: ' + score, width / 2, 30);
  } else if (gameState === 'gameOver') {
    fill(255, 50, 50);
    textSize(48);
    text('GAME OVER', width / 2, height / 2 - 50);
    textSize(24);
    text('Final Score: ' + score, width / 2, height / 2 + 10);
    text('Tap to Restart', width / 2, height / 2 + 60);
  }
}
Line-by-line explanation (34 lines)

🔧 Subcomponents:

conditional Game State Check if (gameState === 'playing') {

Determines whether to run the active game loop or show the game-over screen

conditional Enemy Spawning if (frameCount % 60 === 0) { // Every second

Spawns a new enemy approximately once per second based on frame count

for-loop Enemy Movement and Collision Loop for (let i = enemies.length - 1; i >= 0; i--)

Iterates through all enemies in reverse order to safely remove them while checking collisions

for-loop Projectile Movement and Collision Loop for (let i = projectiles.length - 1; i >= 0; i--)

Iterates through all projectiles in reverse order, checking collisions and removing off-screen bullets

for-loop Projectile-Enemy Collision Check for (let j = enemies.length - 1; j >= 0; j--)

Nested loop that checks every projectile against every enemy for collisions

background(25, 25, 50); // Dark blue background
Clears the canvas with a dark blue color (25 red, 25 green, 50 blue), erasing the previous frame so animation flows smoothly.
if (gameState === 'playing') {
Checks if the game is currently running. If true, execute the active game loop; if false (gameOver), show the game-over screen instead.
player.move();
Updates the player's x position to track the mouse or touch input.
player.show();
Draws the player ship (blue triangle) at its current position.
if (frameCount % 60 === 0) { // Every second
Spawns a new enemy every 60 frames (at 60 FPS, this is approximately once per second). frameCount increments every frame, so modulo 60 creates a regular spawn pattern.
let enemyName = random(['Anonymous', 'Corbyn', 'Corbyn 2.5']);
Randomly selects one name from the array to assign to the newly spawned enemy.
enemies.push(new Enemy(enemyName));
Creates a new Enemy object with the selected name and adds it to the enemies array.
for (let i = enemies.length - 1; i >= 0; i--) {
Loops through all enemies in reverse order (from end to start) so removing an enemy doesn't skip the next iteration.
enemies[i].move();
Updates each enemy's y position, moving it downward.
enemies[i].show();
Draws each enemy as a red circle with its name label.
if (player.hits(enemies[i])) {
Checks if the player collides with this enemy using the distance-based collision detection.
gameState = 'gameOver';
Sets the game state to 'gameOver', which will cause draw() to display the game-over screen on the next frame.
if (enemies[i].offscreen()) {
Checks if the enemy has fallen off the bottom of the canvas.
enemies.splice(i, 1);
Removes the offscreen enemy from the array using splice, which deletes 1 element starting at index i.
for (let i = projectiles.length - 1; i >= 0; i--) {
Loops through all projectiles in reverse order to safely remove collided or offscreen projectiles.
let projectileRemovedThisFrame = false; // Flag to track if the current projectile was removed
Creates a boolean flag to track whether this projectile was removed during collision checking, preventing double-processing.
for (let j = enemies.length - 1; j >= 0; j--) {
Nested loop that checks this projectile against every enemy.
if (projectiles[i].hits(enemies[j])) {
Checks if the projectile and current enemy overlap using distance-based collision detection.
score += 10; // Increase score
Awards 10 points to the player for successfully hitting an enemy.
enemies.splice(j, 1); // Remove enemy
Removes the hit enemy from the enemies array.
projectiles.splice(i, 1); // Remove projectile
Removes the projectile from the projectiles array so it doesn't collide with other enemies.
projectileRemovedThisFrame = true; // Mark that it was removed
Sets the flag to true so the outer loop knows this projectile is gone and shouldn't be processed further.
break; // Stop checking for this projectile against other enemies
Exits the inner loop early since this projectile has already collided and been removed—no need to check other enemies.
if (projectileRemovedThisFrame) {
Checks if this projectile was removed due to collision.
continue; // Move to the next projectile in the outer loop
Skips the rest of this iteration and moves to the next projectile, avoiding errors from accessing a deleted array element.
projectiles[i].move();
Updates the projectile's y position, moving it upward.
projectiles[i].show();
Draws the projectile as a yellow circle.
if (projectiles[i].offscreen()) {
Checks if the projectile has flown off the top of the canvas.
text('Score: ' + score, width / 2, 30);
Displays the current score in white text at the top center of the canvas.
} else if (gameState === 'gameOver') {
If the game is over, execute this block to display the game-over screen instead of the active game.
fill(255, 50, 50);
Sets the fill color to a red shade (255 red, 50 green, 50 blue) for the game-over text.
text('GAME OVER', width / 2, height / 2 - 50);
Displays 'GAME OVER' in large red text centered on the screen, 50 pixels above the canvas center.
text('Final Score: ' + score, width / 2, height / 2 + 10);
Displays the final score below the game-over message.
text('Tap to Restart', width / 2, height / 2 + 60);
Displays instructions to restart the game by tapping the screen (or clicking on desktop).

touchStarted()

touchStarted() is a p5.js event handler that gives players control: they can shoot when playing or restart when defeated. The sound playback uses p5.Oscillator's amp() function to create a dynamic envelope—ramp up then down—for audio feedback.

// Handle touch for shooting and restarting
function touchStarted() {
  userStartAudio(); // Required for mobile audio playback

  if (gameState === 'playing') {
    projectiles.push(new Projectile(player.x, player.y - player.size / 2));
    if (shootSound) {
      shootSound.start();
      shootSound.amp(0.3, 0.05); // Play a short sound
      shootSound.amp(0, 0.2);
    }
  } else if (gameState === 'gameOver') {
    // Restart the game
    gameState = 'playing';
    score = 0;
    enemies = [];
    projectiles = [];
    if (gameOverSound) {
      gameOverSound.stop();
    }
  }
  return false; // Prevent default browser behavior (e.g., scrolling)
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

conditional Shooting Action if (gameState === 'playing') { projectiles.push(new Projectile(player.x, player.y - player.size / 2)); if (shootSound) { shootSound.start(); shootSound.amp(0.3, 0.05); shootSound.amp(0, 0.2); } }

When playing, create a new projectile and play the shoot sound effect

conditional Restart Action } else if (gameState === 'gameOver') { gameState = 'playing'; score = 0; enemies = []; projectiles = []; if (gameOverSound) { gameOverSound.stop(); } }

When game over, reset all game state variables and return to playing mode

function touchStarted() {
This p5.js event function is called whenever the user touches the screen (on mobile or touch devices).
userStartAudio(); // Required for mobile audio playback
Enables audio playback on mobile devices—required by browser security policies before any sound can play.
if (gameState === 'playing') {
Checks if the game is currently active (not in game-over state).
projectiles.push(new Projectile(player.x, player.y - player.size / 2));
Creates a new Projectile at the player's x position and slightly above the ship (y - 25 pixels), then adds it to the projectiles array.
if (shootSound) {
Checks if the shootSound oscillator exists before trying to play it.
shootSound.start();
Starts the oscillator, beginning to generate sound.
shootSound.amp(0.3, 0.05); // Play a short sound
Sets the oscillator's amplitude to 0.3 (30% volume) over 0.05 seconds, creating a ramp-up for the sound effect.
shootSound.amp(0, 0.2);
Ramps the amplitude back down to 0 (silent) over 0.2 seconds, creating a decay effect that makes the sound short and punchy.
} else if (gameState === 'gameOver') {
If the game is over and the user taps, restart the game.
gameState = 'playing';
Sets the game state back to 'playing' so draw() will start running the active game loop again.
score = 0;
Resets the score to 0 for the new game.
enemies = [];
Clears the enemies array, removing all falling enemies.
projectiles = [];
Clears the projectiles array, removing all bullets on screen.
if (gameOverSound) { gameOverSound.stop(); }
Stops the game-over sound so it doesn't continue playing during the new game.
return false; // Prevent default browser behavior (e.g., scrolling)
Returns false to prevent the browser from executing its default touch behavior like scrolling or zooming—keeps the game interaction exclusive.

touchMoved()

touchMoved() prevents unwanted browser behavior during gameplay. Returning false is a simple but effective way to keep touch interaction focused entirely on the game.

// Prevent default browser behavior (e.g., scrolling) when touching/dragging
function touchMoved() {
  return false;
}
Line-by-line explanation (2 lines)
function touchMoved() {
This p5.js event function is called whenever the user moves their finger while touching the screen.
return false; // Prevent default browser behavior (e.g., scrolling)
Returns false to prevent the browser's default behavior (page scrolling or zooming) when the player drags their finger, keeping all interaction within the game.

windowResized()

windowResized() makes the game responsive to screen size changes. It's called automatically by p5.js whenever the window is resized, ensuring the game adapts to orientation changes on mobile devices.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  player.y = height - player.size * 1.5; // Reposition player
}
Line-by-line explanation (3 lines)
function windowResized() {
This p5.js event function is called automatically whenever the browser window is resized.
resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the new window dimensions, ensuring the game fills the screen on any device.
player.y = height - player.size * 1.5; // Reposition player
Recalculates the player's y position to keep it near the bottom of the newly sized canvas.

📦 Key Variables

player object

Stores the Player object, representing the blue triangular ship the player controls with mouse or touch input.

let player;
enemies array

Stores all active Enemy objects currently falling from the top of the canvas. Enemies are added every 60 frames and removed when destroyed or when they reach the bottom.

let enemies = [];
projectiles array

Stores all active Projectile objects currently moving upward from the player's ship. Projectiles are added when the player shoots and removed when they hit enemies or leave the canvas.

let projectiles = [];
score number

Tracks the player's current score, incremented by 10 points for each enemy destroyed. Displayed at the top of the screen and shown again when the game ends.

let score = 0;
gameState string

Stores the current game mode as either 'playing' (active game) or 'gameOver' (defeat screen). Controls which code path executes in draw().

let gameState = 'playing';
shootSound object

Stores a p5.Oscillator object that generates the sound effect when the player shoots a projectile.

let shootSound;
gameOverSound object

Stores a p5.Oscillator object that generates the sound effect when the player loses the game.

let gameOverSound;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG Enemy collision detection (Player.hits and Projectile.hits)

The collision detection uses simple distance-based circles, but enemies are displayed as ellipses and may collide from further away than they visually appear, frustrating the player.

💡 If you want pixel-perfect collision, consider using p5.js's collideCircleCircle from the p5.collide2d library, or adjust the collision radius to match visual appearance more closely.

PERFORMANCE draw() projectile collision checking

The nested loops (projectile outer loop, enemy inner loop) check every projectile against every enemy every frame, which becomes slow with many objects. This is O(n*m) complexity.

💡 For large numbers of bullets and enemies, consider using spatial partitioning (dividing the canvas into a grid) or implementing a more efficient broad-phase collision system.

FEATURE Game mechanics

Game over happens instantly when ANY enemy reaches the bottom, making it difficult to survive long. Players may feel punished unfairly if they're focused on one area.

💡 Consider giving the player a few 'lives' or a shield mechanic that lets them survive 3 hits instead of just one. Or, let enemies pass harmlessly and only end the game after missing 3 enemies.

STYLE touchStarted() and draw()

Game-over sound playback uses manual amp() ramping, which is verbose. The code could be cleaner.

💡 Consider creating a dedicated playSound(sound) helper function that encapsulates the amp ramp-up and ramp-down logic, reducing duplication.

BUG windowResized()

When the window is resized during gameplay, enemies and projectiles keep their old positions and may disappear if the canvas gets much smaller.

💡 Consider adjusting all enemy and projectile positions proportionally when the canvas resizes, or clearing them entirely to avoid visual glitches.

🔄 Code Flow

Code flow showing player_constructor, player_show, player_move, player_hits, enemy_constructor, enemy_show, enemy_move, enemy_offscreen, projectile_constructor, projectile_show, projectile_move, projectile_offscreen, projectile_hits, preload, setup, draw, touchstarted, touchmoved, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> playing_state_check[Game State Check] playing_state_check -->|Playing| enemy_spawning[Enemy Spawning] playing_state_check -->|Game Over| restart_action[Restart Action] enemy_spawning --> enemy_constructor[enemy_constructor] enemy_constructor --> enemy_show[enemy_show] enemy_show --> enemy_move[enemy_move] enemy_move --> enemy_loop[Enemy Movement and Collision Loop] enemy_loop --> enemy_offscreen[enemy_offscreen] enemy_loop --> player_hits[player_hits] enemy_loop --> projectile_loop[Projectile Movement and Collision Loop] projectile_loop --> projectile_enemy_collision[Projectile-Enemy Collision Check] projectile_loop --> projectile_offscreen[projectile_offscreen] projectile_loop --> projectile_constructor[projectile_constructor] projectile_constructor --> projectile_show[projectile_show] projectile_show --> projectile_move[projectile_move] touch_input[Touch Input Detection] --> player_move[player_move] player_move --> constrain_x[Keep Player On Canvas] draw --> touchstarted[touchstarted] draw --> touchmoved[touchmoved] draw --> windowresized[windowresized] click setup href "#fn-setup" click draw href "#fn-draw" click playing_state_check href "#sub-playing_state_check" click enemy_spawning href "#sub-enemy_spawning" click enemy_loop href "#sub-enemy_loop" click projectile_loop href "#sub-projectile_loop" click projectile_enemy_collision href "#sub-projectile_enemy_collision" click touch_input href "#sub-touch_input" click constrain_x href "#sub-constrain_x" click restart_action href "#sub-restart_action"

❓ Frequently Asked Questions

What visual elements can users expect from the I hate Anonymous and corbun and corbun 2.5 sketch?

The sketch features a blue triangular ship piloted by the player, while colorful enemy circles with hate labels fall from above, creating a vibrant and engaging visual experience.

How can players interact with the I hate Anonymous and corbun and corbun 2.5 game?

Users can control the blue ship's movement using their mouse or touch, dodging enemy circles and firing yellow projectiles to blast them.

What creative coding concepts are showcased in this sketch?

The sketch demonstrates responsive movement, collision detection, and simple game mechanics, all of which are essential elements in arcade-style game design.

Preview

I hate Anonymous and corbun and corbun 2.5 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of I hate Anonymous and corbun and corbun 2.5 - Code flow showing player_constructor, player_show, player_move, player_hits, enemy_constructor, enemy_show, enemy_move, enemy_offscreen, projectile_constructor, projectile_show, projectile_move, projectile_offscreen, projectile_hits, preload, setup, draw, touchstarted, touchmoved, windowresized
Code Flow Diagram