function updateGame() {
// Player 1 controls (WASD)
if (key === 'w' && p1.onGround) p1.vy = -jumpPower;
if (key === 'a') p1.vx = -moveSpeed;
if (key === 'd') p1.vx = moveSpeed;
if (key === ' ') p1.vy = -jumpPower;
// Player 2 controls (Arrow keys)
if (keyIsDown(UP_ARROW) && p2.onGround) p2.vy = -jumpPower;
if (keyIsDown(LEFT_ARROW)) p2.vx = -moveSpeed;
if (keyIsDown(RIGHT_ARROW)) p2.vx = moveSpeed;
// Apply gravity
p1.vy += gravity;
p2.vy += gravity;
// Update positions
p1.x += p1.vx;
p1.y += p1.vy;
p2.x += p2.vx;
p2.y += p2.vy;
// Reset ground state
p1.onGround = false;
p2.onGround = false;
// Boundary collisions
if (p1.x < 25) p1.x = 25;
if (p1.x > width - 25) p1.x = width - 25;
if (p1.y > height - 50) {
p1.y = height - 50;
p1.onGround = true;
p1.vy = 0;
}
if (p2.x < 25) p2.x = 25;
if (p2.x > width - 25) p2.x = width - 25;
if (p2.y > height - 50) {
p2.y = height - 50;
p2.onGround = true;
p2.vy = 0;
}
// Collision detection between players
let d = dist(p1.x, p1.y, p2.x, p2.y);
if (d < 50 && !p1.hitCooldown) {
p2.takeDamage(damage, p1.x);
p1.hitCooldown = 10;
createParticles(p2.x, p2.y, 8);
}
if (d < 50 && !p2.hitCooldown) {
p1.takeDamage(damage, p2.x);
p2.hitCooldown = 10;
createParticles(p1.x, p1.y, 8);
}
// Cooldown timer
if (p1.hitCooldown > 0) p1.hitCooldown--;
if (p2.hitCooldown > 0) p2.hitCooldown--;
// Update particles
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update();
if (particles[i].life <= 0) particles.splice(i, 1);
}
// Victory condition
if (p1.hp <= 0) {
declareWinner('P2');
gameActive = false;
}
if (p2.hp <= 0) {
declareWinner('P1');
gameActive = false;
}
}
Line-by-line explanation (11 lines)
🔧 Subcomponents:
calculation
Gravity Application
p1.vy += gravity;
p2.vy += gravity;
Constantly pulls both players downward each frame, simulating gravity
calculation
Position Update
p1.x += p1.vx;
p1.y += p1.vy;
Moves each player by adding their velocity to their current position
conditional
Boundary Collision Detection
if (p1.x < 25) p1.x = 25;
Prevents players from leaving the arena by clamping their x and y coordinates
conditional
Player-to-Player Collision
if (d < 50 && !p1.hitCooldown) {
Detects when two players touch and triggers damage with hit cooldown to prevent instant repeat hits
conditional
Victory Condition
if (p1.hp <= 0) {
Checks if either player's HP reaches zero and ends the game
if (key === 'w' && p1.onGround) p1.vy = -jumpPower;
- When 'w' is pressed and P1 is touching ground, set vertical velocity to negative jumpPower to make them jump upward
if (key === 'a') p1.vx = -moveSpeed;
- When 'a' is pressed, set P1's horizontal velocity to negative moveSpeed, moving them left
p1.vy += gravity;
- Every frame, increase P1's downward velocity slightly—this creates the illusion of falling
p1.x += p1.vx;
- Move P1 horizontally by adding their velocity to their x position
p1.onGround = false;
- Reset the ground state each frame; it will be set to true only if they collide with the ground
if (p1.x < 25) p1.x = 25;
- If P1 moves too far left, snap them back to x=25 (arena wall boundary)
if (p1.y > height - 50) {
- If P1 falls below ground level (height - 50), place them on the ground and stop their downward movement
let d = dist(p1.x, p1.y, p2.x, p2.y);
- Calculate the distance between the two players using p5.js's dist() function
if (d < 50 && !p1.hitCooldown) {
- If distance is less than 50 pixels (collision) AND P1 hasn't hit P2 recently, trigger damage on P2
p1.hitCooldown = 10;
- Set a 10-frame cooldown so the same collision doesn't deal damage multiple times per second
if (p1.hp <= 0) {
- If P1's health reaches zero, declare P2 as the winner and stop the game
function createParticles(x, y, count) {
for (let i = 0; i < count; i++) {
let angle = random(TWO_PI);
let speed = random(2, 6);
let vx = cos(angle) * speed;
let vy = sin(angle) * speed;
particles.push(new Particle(x, y, vx, vy));
}
}
Line-by-line explanation (7 lines)
🔧 Subcomponents:
for-loop
Particle Creation Loop
for (let i = 0; i < count; i++) {
Repeats the particle spawning logic 'count' times to create a burst of particles
calculation
Random Direction Calculation
let angle = random(TWO_PI);
Chooses a random angle between 0 and 360 degrees for each particle
calculation
Velocity Vector Calculation
let vx = cos(angle) * speed;
Converts the random angle into x and y velocity components using trigonometry
function createParticles(x, y, count) {
- Defines a function that takes the spawn position (x, y) and how many particles to create (count)
for (let i = 0; i < count; i++) {
- Loops 'count' times (e.g., 8 times), each iteration creates one new particle
let angle = random(TWO_PI);
- Chooses a random angle from 0 to 360 degrees—this is why particles burst in all directions
let speed = random(2, 6);
- Each particle gets a random speed between 2 and 6 pixels per frame so they don't all move identically
let vx = cos(angle) * speed;
- Uses cosine to convert the angle into a horizontal velocity component
let vy = sin(angle) * speed;
- Uses sine to convert the angle into a vertical velocity component—together these create diagonal motion
particles.push(new Particle(x, y, vx, vy));
- Creates a new Particle object and adds it to the particles array so it will be updated and drawn each frame
function initArena(color, name) {
arenaColor = color;
p1 = new Player(100, height / 2, '#ff0000');
p2 = new Player(width - 100, height / 2, '#0000ff');
gameActive = true;
particles = [];
document.getElementById('menu').style.display = 'none';
document.getElementById('stage-select').style.display = 'none';
document.getElementById('ui').style.display = 'flex';
document.getElementById('p1-tag').textContent = 'P1 ' + name;
document.getElementById('p2-tag').textContent = 'P2 ' + name;
}
Line-by-line explanation (6 lines)
🔧 Subcomponents:
calculation
Player Spawning
p1 = new Player(100, height / 2, '#ff0000');
Creates Player 1 at the left side of the arena with red color
calculation
UI State Toggle
document.getElementById('ui').style.display = 'flex';
Shows the health bars and hides the stage select menu
arenaColor = color;
- Sets the global arenaColor variable to the selected battlefield's color
p1 = new Player(100, height / 2, '#ff0000');
- Creates Player 1 as a new Player object positioned 100 pixels from the left, centered vertically, with red color
p2 = new Player(width - 100, height / 2, '#0000ff');
- Creates Player 2 on the right side (width - 100) with blue color so they start facing each other
gameActive = true;
- Sets gameActive to true, enabling the game loop to start running
particles = [];
- Clears the particles array so leftover particles from previous rounds don't appear
document.getElementById('ui').style.display = 'flex';
- Shows the health bar UI by changing its CSS display from 'none' to 'flex'
class Player {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.color = color;
this.hp = maxHealth;
this.maxHealth = maxHealth;
this.onGround = false;
this.hitCooldown = 0;
this.size = 30;
}
takeDamage(amount, hitFrom) {
this.hp -= amount;
this.flashTime = 10; // Flash effect duration
}
display() {
if (this.flashTime > 0) {
fill(255, 100, 100); // Light red flash
this.flashTime--;
} else {
fill(this.color);
}
circle(this.x, this.y, this.size);
// Draw outline
stroke(this.color);
strokeWeight(2);
noFill();
circle(this.x, this.y, this.size);
// Update HP bar in HTML
let hpPercent = (this.hp / this.maxHealth) * 100;
if (this.color === '#ff0000') {
document.getElementById('p1-hp').style.width = hpPercent + '%';
} else {
document.getElementById('p2-hp').style.width = hpPercent + '%';
}
}
}
class Particle {
constructor(x, y, vx, vy) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.life = 30; // frames until death
this.maxLife = 30;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.vy += 0.2; // gravity on particles
this.life--;
}
display() {
let alpha = (this.life / this.maxLife) * 255; // Fade out
fill(255, 0, 0, alpha);
noStroke();
circle(this.x, this.y, 5);
}
}
Line-by-line explanation (17 lines)
🔧 Subcomponents:
calculation
Player Constructor
constructor(x, y, color) {
Initializes a new Player with position, color, health, and movement properties
calculation
Take Damage Method
takeDamage(amount, hitFrom) {
Reduces HP and triggers a visual flash effect when hit
conditional
Player Display Method
if (this.flashTime > 0) {
Draws the player as a circle, with a red flash if recently hit, and updates the HTML health bar
calculation
Particle Constructor
constructor(x, y, vx, vy) {
Creates a small projectile with position, velocity, and a lifespan
calculation
Particle Update Method
update() {
Moves the particle and applies gravity, counting down its remaining lifespan
class Player {
- Defines the Player class—a blueprint for creating player objects with properties and methods
constructor(x, y, color) {
- The constructor function runs when a new Player is created, setting up initial values
this.hp = maxHealth;
- Sets the player's starting HP to the global maxHealth constant
this.onGround = false;
- Initially sets the player as not on ground (used to check if they can jump)
this.hp -= amount;
- Reduces health by the damage amount when takeDamage() is called
this.flashTime = 10;
- Triggers a 10-frame red flash visual to show the player was just hit
if (this.flashTime > 0) {
- If flashTime is still counting down, draw the player in light red to show the hit effect
fill(this.color);
- Otherwise, fill the player circle with their normal color (red or blue)
circle(this.x, this.y, this.size);
- Draws the player as a circle at their current position with their size
let hpPercent = (this.hp / this.maxHealth) * 100;
- Calculates what percentage of health remains (0-100) to update the HTML health bar width
document.getElementById('p1-hp').style.width = hpPercent + '%';
- Updates the P1 health bar element's width to match the HP percentage
class Particle {
- Defines the Particle class—a blueprint for small projectiles created by hit bursts
this.life = 30;
- Each particle lasts for 30 frames before disappearing
this.x += this.vx;
- Moves the particle by its velocity each frame
this.vy += 0.2;
- Applies downward gravity to particles so they arc and fall realistically
let alpha = (this.life / this.maxLife) * 255;
- Calculates transparency that decreases as life counts down, creating a fade-out effect
fill(255, 0, 0, alpha);
- Fills particles with red color with transparency based on remaining life