Something happen more coming tomorrow

This sketch creates an animated sequence where a rock is threatened by either a descending UFO or rising lava. After a 20-second delay, one of two events plays out: the UFO flies down to destroy the rock and flies away, or lava rises from the bottom to consume it. A reset button lets you toggle between events and restart the sequence.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up the UFO — Increasing ufoSpeed makes the UFO reach the rock faster—watch how responsiveness changes.
  2. Change lava color to bright red — The first argument in fill(255, 100, 0) is red intensity—changing it makes the lava a different shade.
  3. Shorten the countdown to 5 seconds — The event will trigger much faster, letting you test sequences without waiting.
  4. Make the rock circular — Changing the multiplier from 0.8 to 1.0 makes the main rock body a perfect circle instead of flattened.
  5. Make the UFO glow fully opaque — The fourth argument in fill(100, 200, 255, 150) controls transparency—255 makes it completely solid.
  6. Move the rock higher on the screen — Changing 0.7 to 0.5 centers the rock vertically, making it easier for events to reach it.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch animates two apocalyptic scenarios: a UFO descending to destroy a rock, or lava rising from the bottom to engulf it. The visual storytelling is powered by p5.js drawing functions (ellipse and rect), trigonometry for angled movement, and a state machine that orchestrates the sequence. Every element scales responsively to the window size, making it work equally well on phones and desktops.

The code is organized around a central gameState variable that controls which part of the animation plays. You will learn how a switch statement manages complex, multi-stage animations, how timers trigger events after delays, how objects move toward targets using angle calculations, and how the windowResized() function keeps everything proportional when the browser resizes.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, positions a rock in the center-lower area, and hides the UFO and lava off-screen. A timer begins counting down from 20 seconds.
  2. The draw() function runs 60 times per second, displaying the current game state in the top-left corner and drawing the rock. If the state is 'initial', it checks whether 20 seconds have passed.
  3. Once the timer finishes, gameState changes to either 'ufoApproaching' or 'lavaApproaching' depending on currentEventType. The reset button becomes disabled to prevent interference.
  4. If a UFO event is active, the UFO draws and moves toward the rock using trigonometry (atan2, cos, sin). When it reaches the rock, gameState becomes 'rockDestroyed' and the UFO flies off-screen, then the event completes.
  5. If a lava event is active, the lava draws as an orange rectangle and rises from the bottom. When it reaches the rock, gameState becomes 'rockDestroyedByLava' and the lava recedes downward until the event completes.
  6. At any time, clicking the reset button restores the initial state, toggles to the next event type, and re-enables the countdown timer.

🎓 Concepts You'll Learn

State machine (gameState switch statement)Timer and delay (millis, EVENT_DELAY_MILLIS)Trigonometry for movement (atan2, cos, sin)Collision detection (dist comparison)Responsive canvas (windowResized, proportional sizing)Drawing primitives (ellipse, rect)DOM interaction (button styling and callbacks)

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It is the perfect place to initialize variables, create the canvas, and prepare UI elements like buttons. Everything calculated here sets up the initial state that draw() will use.

function setup() {
  createCanvas(windowWidth, windowHeight);
  
  // Initialize lavaY here, after createCanvas has set 'height'
  lavaY = height; // Initial lava position (off-screen bottom)

  // Calculate rock position and size
  rockX = width / 2;
  rockY = height * 0.7; // Position the rock lower on the screen
  rockSize = min(width, height) * 0.2; // Rock size is 20% of the smaller canvas dimension

  // Calculate UFO size
  ufoSize = min(width, height) * 0.15; // UFO size is 15% of the smaller canvas dimension
  
  // Initialize UFO position off-screen (top-left)
  ufoX = -ufoSize;
  ufoY = -ufoSize;

  eventStartTime = millis(); // Start the timer immediately
  console.log(`Waiting for ${EVENT_DELAY_SECONDS} seconds...`);

  // Create the reset button
  resetBtn = createButton('Reset (Next: Lava Event)'); // Initial text
  resetBtn.position(10, 50); // Position below the state/timer display
  resetBtn.style('font-size', '16px');
  resetBtn.style('padding', '10px 15px');
  resetBtn.style('border-radius', '5px');
  resetBtn.style('cursor', 'pointer');
  resetBtn.style('background-color', '#4CAF50'); // Green
  resetBtn.style('color', 'white');
  resetBtn.style('border', 'none');
  resetBtn.mousePressed(resetSketch);
  resetBtn.touchStarted(resetSketch); // For touch devices
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

function-call Canvas setup createCanvas(windowWidth, windowHeight);

Creates a canvas that fills the entire browser window

calculation Rock position and size rockX = width / 2; rockY = height * 0.7; rockSize = min(width, height) * 0.2;

Calculates the rock's center position and diameter based on canvas size

calculation UFO positioning ufoSize = min(width, height) * 0.15; ufoX = -ufoSize; ufoY = -ufoSize;

Sets UFO size and hides it off-screen (top-left corner)

function-call Button setup resetBtn = createButton('Reset (Next: Lava Event)');

Creates a clickable reset button that lets the user restart and toggle events

createCanvas(windowWidth, windowHeight);
Creates a canvas the size of the entire browser window. windowWidth and windowHeight are p5.js variables that update whenever the browser resizes.
lavaY = height;
Initializes the lava position to the bottom of the canvas (off-screen). Must happen after createCanvas so 'height' is defined.
rockX = width / 2;
Places the rock horizontally at the center of the canvas.
rockY = height * 0.7;
Places the rock vertically at 70% down the canvas, keeping it in the lower portion so lava and UFO can approach from outside.
rockSize = min(width, height) * 0.2;
Makes the rock 20% of the smaller canvas dimension. Using min() ensures the rock stays proportional whether the window is wide or tall.
ufoSize = min(width, height) * 0.15;
Sets the UFO to be 15% of the smaller canvas dimension, keeping it slightly smaller than the rock.
ufoX = -ufoSize;
Positions the UFO completely off-screen to the left, so it doesn't appear until the event starts.
eventStartTime = millis();
Records the current time in milliseconds. Later, draw() will compare millis() to this value to trigger the event after a delay.
resetBtn.mousePressed(resetSketch);
Attaches the resetSketch function to the button, so clicking it calls resetSketch().
resetBtn.touchStarted(resetSketch);
Also allows touch devices (phones, tablets) to trigger reset by tapping the button.

draw()

draw() is the animation loop, running 60 times per second. The switch statement is the heart of this sketch: it acts as a state machine, routing all animation logic based on gameState. This pattern—check state, execute appropriate behavior, change state—is used in countless games and interactive programs.

🔬 This conditional hides the rock once it's destroyed. What happens if you remove the condition and just call drawRock(); unconditionally? The rock will appear even after destruction—try it and explain why that's visually confusing.

  // Draw the rock first if it's not destroyed by either event
  if (gameState !== "rockDestroyed" && gameState !== "rockDestroyedByLava") {
    drawRock();
  }

🔬 This case moves the UFO toward the rock. What if you change the target to (width / 2, -ufoSize) instead of (rockX, rockY)? The UFO would fly straight up instead of toward the rock—can you predict which case would then activate next?

    case "ufoApproaching":
      drawUFO();
      moveUFO(rockX, rockY); // Move UFO towards the rock
      break;
function draw() {
  background(220); // Light grey background

  // Display current state and remaining time for debugging (optional, can be removed)
  fill(0);
  textSize(16);
  textAlign(LEFT, TOP);
  text("State: " + gameState, 10, 10);
  if (gameState === "initial") {
    let elapsedTime = millis() - eventStartTime;
    let remainingTime = EVENT_DELAY_MILLIS - elapsedTime;
    text("Time until event: " + nf(remainingTime / 1000, 1, 1) + " seconds", 10, 30);
  }
  
  // Draw the rock first if it's not destroyed by either event
  if (gameState !== "rockDestroyed" && gameState !== "rockDestroyedByLava") {
    drawRock();
  }

  // Use a switch statement to manage the different stages of the event
  switch (gameState) {
    case "initial":
      checkTimer();
      break;
    case "ufoApproaching":
      drawUFO();
      moveUFO(rockX, rockY); // Move UFO towards the rock
      break;
    case "rockDestroyed":
      drawUFO();
      moveUFO(width + ufoSize, -ufoSize); // Move UFO off-screen (top-right)
      break;
    case "lavaApproaching":
      drawLava();
      moveLava(rockY - rockSize / 2); // Lava moves up to the bottom of the rock
      break;
    case "rockDestroyedByLava":
      drawLava();
      moveLava(height + 50); // Lava moves off-screen downwards
      break;
    case "eventComplete":
      // The event is over, nothing more happens in this state
      // Ensure the button is visible and enabled
      resetBtn.show();
      resetBtn.removeAttribute('disabled');
      break;
  }
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

calculation Debug text display text("State: " + gameState, 10, 10);

Shows the current game state and countdown timer so you can see what stage the animation is in

conditional Conditional rock drawing if (gameState !== "rockDestroyed" && gameState !== "rockDestroyedByLava") { drawRock(); }

Only draws the rock if it hasn't been destroyed—hides it once either event destroys it

switch-case Game state switch switch (gameState) { ... }

Routes the animation logic based on which stage of the event is currently active

background(220); // Light grey background
Clears the entire canvas with light grey color at the start of each frame. This erases the previous frame so you see fresh animation.
fill(0);
Sets the text color to black (0 = dark grey in RGB).
text("State: " + gameState, 10, 10);
Writes the current gameState value at position (10, 10), near the top-left. This is helpful for debugging.
if (gameState === "initial") {
Only displays the countdown timer during the initial waiting state. Once an event starts, this block is skipped.
let elapsedTime = millis() - eventStartTime;
Calculates how many milliseconds have passed since the sketch started by subtracting eventStartTime from the current time.
let remainingTime = EVENT_DELAY_MILLIS - elapsedTime;
Subtracts elapsed time from the total delay time to get how many milliseconds are left before the event triggers.
text("Time until event: " + nf(remainingTime / 1000, 1, 1) + " seconds", 10, 30);
Displays the remaining seconds as text. nf() formats the number to 1 decimal place (e.g., 5.3 seconds).
if (gameState !== "rockDestroyed" && gameState !== "rockDestroyedByLava") {
Checks if the rock is still alive. If gameState is NOT one of the destroyed states, drawRock() is called.
switch (gameState) {
Evaluates the current gameState and executes the matching case block. This is the 'brain' that decides which animation to play.
case "initial": checkTimer(); break;
If waiting for the event, call checkTimer() to see if the delay has elapsed. If it has, gameState changes and a new case activates next frame.
case "ufoApproaching": drawUFO(); moveUFO(rockX, rockY);
During UFO approach, draw the UFO and move it toward the rock's position. This repeats every frame, creating smooth animation.
case "rockDestroyed": moveUFO(width + ufoSize, -ufoSize);
After the UFO destroys the rock, it still draws and moves, but now the target is off-screen (top-right) so it flies away.
case "lavaApproaching": drawLava(); moveLava(rockY - rockSize / 2);
During lava approach, draw the lava and move it upward until it reaches the bottom of the rock.
case "rockDestroyedByLava": drawLava(); moveLava(height + 50);
After lava destroys the rock, it moves downward off-screen (below the canvas bottom at y = height + 50).
case "eventComplete": resetBtn.show(); resetBtn.removeAttribute('disabled');
Once the event is over, make sure the reset button is visible and clickable so the user can start again.

drawRock()

drawRock() demonstrates how layering simple shapes (ellipses) with overlapping offsets creates complex, organic forms. This technique—building complexity from simple primitives—is fundamental to procedural and generative art.

🔬 This code layers three ellipses to create an irregular shape. What happens if you add a fourth ellipse below the main one, like ellipse(rockX, rockY + rockSize * 0.3, rockSize * 0.6, rockSize * 0.3)? You'll add a bump on the bottom—try it and see the rock become more lumpy.

  ellipse(rockX, rockY, rockSize, rockSize * 0.8);
  ellipse(rockX - rockSize * 0.2, rockY - rockSize * 0.1, rockSize * 0.5, rockSize * 0.4);
  ellipse(rockX + rockSize * 0.2, rockY - rockSize * 0.1, rockSize * 0.5, rockSize * 0.4);
function drawRock() {
  fill(100); // Dark grey color for the rock
  noStroke(); // No outline for the rock
  // Draw a few overlapping ellipses to create an irregular rock shape
  ellipse(rockX, rockY, rockSize, rockSize * 0.8);
  ellipse(rockX - rockSize * 0.2, rockY - rockSize * 0.1, rockSize * 0.5, rockSize * 0.4);
  ellipse(rockX + rockSize * 0.2, rockY - rockSize * 0.1, rockSize * 0.5, rockSize * 0.4);
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Main rock ellipse ellipse(rockX, rockY, rockSize, rockSize * 0.8);

Draws the large central body of the rock, slightly wider than tall

calculation Left bumpy edge ellipse(rockX - rockSize * 0.2, rockY - rockSize * 0.1, rockSize * 0.5, rockSize * 0.4);

Adds an irregular bump on the left side to make the rock look craggy

calculation Right bumpy edge ellipse(rockX + rockSize * 0.2, rockY - rockSize * 0.1, rockSize * 0.5, rockSize * 0.4);

Adds an irregular bump on the right side for visual asymmetry

fill(100); // Dark grey color for the rock
Sets the fill color to dark grey (100 = medium darkness on the 0–255 scale).
noStroke(); // No outline for the rock
Removes the border around the ellipses, so only the filled color is drawn.
ellipse(rockX, rockY, rockSize, rockSize * 0.8);
Draws the main rock body centered at (rockX, rockY) with width rockSize and height rockSize * 0.8, creating a slightly flattened ellipse.
ellipse(rockX - rockSize * 0.2, rockY - rockSize * 0.1, rockSize * 0.5, rockSize * 0.4);
Draws a smaller ellipse offset to the left and slightly up, creating a bump that overlaps the main body for an irregular shape.
ellipse(rockX + rockSize * 0.2, rockY - rockSize * 0.1, rockSize * 0.5, rockSize * 0.4);
Draws a mirror-image bump on the right side, completing the craggy, asymmetrical rock look.

drawUFO()

drawUFO() shows how to use RGB color (three values) and RGBA color (four values with alpha transparency). Layering shapes with different colors and transparency creates depth and visual interest. The three ellipses at different sizes and positions assemble a recognizable UFO silhouette from simple geometry.

function drawUFO() {
  fill(150, 150, 200); // Silver/light blue for the main body
  noStroke();
  ellipse(ufoX, ufoY, ufoSize * 1.5, ufoSize * 0.7); // Main elliptical body
  
  fill(100, 100, 150); // Darker blue/grey for the top dome
  ellipse(ufoX, ufoY - ufoSize * 0.2, ufoSize * 0.8, ufoSize * 0.4); // Top dome

  fill(100, 200, 255, 150); // Semi-transparent glowing lights
  ellipse(ufoX, ufoY + ufoSize * 0.25, ufoSize * 1.2, ufoSize * 0.1); // Bottom glow
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

calculation Main UFO hull ellipse(ufoX, ufoY, ufoSize * 1.5, ufoSize * 0.7); // Main elliptical body

Draws the wide, flat main body of the UFO in light blue

calculation Top dome ellipse(ufoX, ufoY - ufoSize * 0.2, ufoSize * 0.8, ufoSize * 0.4); // Top dome

Draws a darker dome protruding from the top center of the UFO

calculation Bottom glow ellipse(ufoX, ufoY + ufoSize * 0.25, ufoSize * 1.2, ufoSize * 0.1); // Bottom glow

Draws a semi-transparent glowing strip at the bottom using alpha transparency

fill(150, 150, 200); // Silver/light blue for the main body
Sets the fill to a light blue color using RGB: 150 red, 150 green, 200 blue. The extra blue makes it bluish.
noStroke();
Removes outlines so the UFO is solid color without borders.
ellipse(ufoX, ufoY, ufoSize * 1.5, ufoSize * 0.7); // Main elliptical body
Draws the main UFO hull centered at (ufoX, ufoY), much wider (1.5× size) than it is tall (0.7× size), creating a flat saucer shape.
fill(100, 100, 150); // Darker blue/grey for the top dome
Changes the fill to a darker blue-grey before drawing the next shape.
ellipse(ufoX, ufoY - ufoSize * 0.2, ufoSize * 0.8, ufoSize * 0.4); // Top dome
Draws the dome 0.2× size above the hull center. It is smaller (0.8× wide, 0.4× tall) and creates a raised cockpit effect.
fill(100, 200, 255, 150); // Semi-transparent glowing lights
Changes fill to bright cyan with 150 alpha (semi-transparent, where 255 is fully opaque). The fourth argument controls transparency.
ellipse(ufoX, ufoY + ufoSize * 0.25, ufoSize * 1.2, ufoSize * 0.1); // Bottom glow
Draws a thin glowing bar below the main hull (0.25× size down) that is very wide (1.2×) and very flat (0.1× tall), simulating an alien light beam.

moveUFO(targetX, targetY)

moveUFO() demonstrates a classic game-programming technique: vector-based movement toward a target. atan2(), cos(), and sin() are the trigonometric foundation—they convert a goal direction into x and y velocity components. This same pattern powers enemy AI, missile tracking, and any 'seek' behavior in interactive graphics.

🔬 These four lines implement 'seek' behavior: the UFO moves toward a target using trigonometry. What happens if you swap cos and sin, like ufoX += ufoSpeed * sin(angle)? The UFO will move at a 90-degree angle to the target—try it and watch it spiral.

  let angle = atan2(targetY - ufoY, targetX - ufoX);
  
  // Move the UFO in that direction at ufoSpeed
  ufoX += ufoSpeed * cos(angle);
  ufoY += ufoSpeed * sin(angle);
function moveUFO(targetX, targetY) {
  // Calculate the angle from the UFO to the target
  let angle = atan2(targetY - ufoY, targetX - ufoX);
  
  // Move the UFO in that direction at ufoSpeed
  ufoX += ufoSpeed * cos(angle);
  ufoY += ufoSpeed * sin(angle);

  // Check if the UFO has reached (or passed) the target
  let d = dist(ufoX, ufoY, targetX, targetY);
  if (d < ufoSpeed) { // If distance is less than speed, it means we're close enough
    if (gameState === "ufoApproaching") {
      gameState = "rockDestroyed"; // UFO reached rock, destroy it
      console.log("UFO reached the rock! Rock destroyed.");
    } else if (gameState === "rockDestroyed") {
      gameState = "eventComplete"; // UFO left the screen
      console.log("UFO left. Event complete.");
    }
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Direction to target let angle = atan2(targetY - ufoY, targetX - ufoX);

Calculates the angle in radians from the UFO's current position to the target position

calculation Position update ufoX += ufoSpeed * cos(angle); ufoY += ufoSpeed * sin(angle);

Moves the UFO a small amount each frame in the direction of the target angle

conditional Arrival detection let d = dist(ufoX, ufoY, targetX, targetY); if (d < ufoSpeed) {

Checks if the UFO is close enough to the target and triggers state changes if so

conditional Event progression if (gameState === "ufoApproaching") { ... } else if (gameState === "rockDestroyed") { ... }

Changes gameState to advance the animation sequence when the UFO arrives

let angle = atan2(targetY - ufoY, targetX - ufoX);
atan2() calculates the angle in radians from the UFO to the target. It takes (y-difference, x-difference) and returns a number between -π and π.
ufoX += ufoSpeed * cos(angle);
Moves the UFO horizontally. cos(angle) gives the x-component of a unit direction vector, multiplied by ufoSpeed to control distance per frame.
ufoY += ufoSpeed * sin(angle);
Moves the UFO vertically using sin(angle), which gives the y-component of motion. Together, cos and sin break the angle into x and y movement.
let d = dist(ufoX, ufoY, targetX, targetY);
Calculates the straight-line distance from the UFO to the target in pixels using the distance formula.
if (d < ufoSpeed) {
If the distance is less than ufoSpeed (the movement per frame), the UFO has reached or passed the target, so a state change should trigger.
gameState = "rockDestroyed";
Transitions from 'ufoApproaching' to 'rockDestroyed', causing draw() to run the 'rockDestroyed' case next frame (moving UFO away).

checkTimer()

checkTimer() is a timer trigger pattern: test elapsed time, and if it has passed, transition to a new state. This is used throughout interactive media to delay events, pace animations, and control when things happen. The conditional branching also demonstrates how to toggle between multiple modes (UFO vs. lava) using a variable.

🔬 This timer check runs every frame during the 'initial' state. What if you add another event type, like currentEventType === "meteor"? You'd need to add another else-if case and change gameState to 'meteorApproaching'—try adding a third event type and see how the branching structure scales.

  if (millis() - eventStartTime > EVENT_DELAY_MILLIS) {
    if (currentEventType === "ufo") {
      gameState = "ufoApproaching"; // Timer elapsed, start UFO sequence
      console.log(`Timer finished! UFO approaching...`);
    } else { // currentEventType === "lava"
      gameState = "lavaApproaching"; // Timer elapsed, start Lava sequence
      console.log(`Timer finished! Lava approaching...`);
    }
function checkTimer() {
  if (millis() - eventStartTime > EVENT_DELAY_MILLIS) {
    if (currentEventType === "ufo") {
      gameState = "ufoApproaching"; // Timer elapsed, start UFO sequence
      console.log(`Timer finished! UFO approaching...`);
    } else { // currentEventType === "lava"
      gameState = "lavaApproaching"; // Timer elapsed, start Lava sequence
      console.log(`Timer finished! Lava approaching...`);
    }
    // Disable the reset button once an event starts
    resetBtn.attribute('disabled', '');
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

conditional Timer comparison if (millis() - eventStartTime > EVENT_DELAY_MILLIS) {

Tests whether enough milliseconds have passed to trigger the event

conditional Event type dispatch if (currentEventType === "ufo") { ... } else { ... }

Chooses which event to start (UFO or lava) based on the currentEventType variable

function-call Button disable resetBtn.attribute('disabled', '');

Disables the reset button so the user cannot interrupt an ongoing event

if (millis() - eventStartTime > EVENT_DELAY_MILLIS) {
millis() returns the current time in milliseconds since the sketch started. Subtracting eventStartTime gives elapsed milliseconds. If this exceeds EVENT_DELAY_MILLIS, the delay has passed and the event should trigger.
if (currentEventType === "ufo") {
Checks the currentEventType variable. If it's "ufo", the UFO event will start. Otherwise, the else block (lava) executes.
gameState = "ufoApproaching";
Changes gameState to 'ufoApproaching', which causes draw()'s switch statement to run the UFO movement code next frame.
gameState = "lavaApproaching";
In the else block, sets gameState to 'lavaApproaching' to trigger the lava event instead.
resetBtn.attribute('disabled', '');
Adds the 'disabled' attribute to the button, making it unclickable and grayed out during the event.

resetSketch()

resetSketch() demonstrates state reset and toggle logic. It is the callback function attached to the reset button—when clicked, it restores all variables and flips currentEventType to alternate between two modes. This pattern scales: to add a third event type, add another branch to the toggle logic.

function resetSketch() {
  gameState = "initial";
  eventStartTime = millis();
  ufoX = -ufoSize;
  ufoY = -ufoSize;
  lavaY = height; // Reset lava position

  // Toggle the event type for the next run
  if (currentEventType === "ufo") {
    currentEventType = "lava";
    resetBtn.html('Reset (Next: UFO Event)'); // Corrected button text for next event
    console.log("Resetting sketch. Next event will be: Lava Event");
  } else {
    currentEventType = "ufo";
    resetBtn.html('Reset (Next: Lava Event)'); // Corrected button text for next event
    console.log("Resetting sketch. Next event will be: UFO Event");
  }

  // Re-enable the reset button in "initial" state
  resetBtn.removeAttribute('disabled');
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Variable reset gameState = "initial"; eventStartTime = millis(); ufoX = -ufoSize; ufoY = -ufoSize; lavaY = height;

Restores all animation variables to their starting values

conditional Event type toggle if (currentEventType === "ufo") { ... } else { ... }

Flips currentEventType between 'ufo' and 'lava' for the next run

function-call Button text and state resetBtn.html('Reset (Next: UFO Event)'); resetBtn.removeAttribute('disabled');

Updates the button's text and re-enables it for the next cycle

gameState = "initial";
Resets the game state to 'initial', causing the countdown timer to appear in draw() again.
eventStartTime = millis();
Records the current time as the new starting point, restarting the countdown from zero.
ufoX = -ufoSize;
Moves the UFO back off-screen to the left so it doesn't appear until the next event.
lavaY = height;
Resets the lava to the bottom of the canvas, off-screen, ready for the next lava event.
if (currentEventType === "ufo") {
Checks which event just finished. If it was the UFO event, set up for the lava event next time.
currentEventType = "lava";
Toggles the event type to 'lava' so the next timer trigger will start a lava sequence instead.
resetBtn.html('Reset (Next: UFO Event)');
Updates the button's text to tell the user which event will play if they click reset again.
resetBtn.removeAttribute('disabled');
Re-enables the button so it is clickable during the countdown waiting state.

windowResized()

windowResized() is a p5.js lifecycle function that runs automatically when the browser window changes size. It is essential for responsive design—recalculating all proportions ensures your sketch looks good on any device. The use of relative sizing (rockSize = min(width, height) * 0.2) instead of fixed pixels is the key to true responsiveness.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  
  // Re-calculate positions and sizes based on the new canvas size
  rockX = width / 2;
  rockY = height * 0.7;
  rockSize = min(width, height) * 0.2;

  ufoSize = min(width, height) * 0.15;
  
  // If the event hasn't started yet, reset UFO to its initial off-screen position
  if (gameState === "initial") {
    ufoX = -ufoSize;
    ufoY = -ufoSize;
  }
  // Also update button position
  resetBtn.position(10, 50);
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

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

Adjusts the canvas dimensions to match the new window size

calculation Element repositioning rockX = width / 2; rockY = height * 0.7; rockSize = min(width, height) * 0.2;

Recalculates the rock's position and size so it stays proportional to the new canvas

conditional UFO reset during initial if (gameState === "initial") { ufoX = -ufoSize; ufoY = -ufoSize; }

Ensures the UFO returns off-screen if the window resizes before the event starts

function windowResized() {
This is a special p5.js function that automatically runs whenever the browser window is resized.
resizeCanvas(windowWidth, windowHeight);
Updates the canvas size to match the new window dimensions. windowWidth and windowHeight are updated automatically by p5.js.
rockX = width / 2;
Re-centers the rock horizontally based on the new canvas width.
rockSize = min(width, height) * 0.2;
Re-calculates the rock size to remain 20% of the smaller dimension, ensuring it stays proportional on phones and wide monitors.
if (gameState === "initial") {
Only resets the UFO position if the sketch is in the waiting state. During an active event, the UFO should stay where it is.
resetBtn.position(10, 50);
Repositions the button so it stays in the same relative location on the screen after resizing.

drawLava()

drawLava() is simple but illustrative: rect() is a flexible function that draws rectangles at any position and size. The key insight is that lavaY (the surface height) changes every frame via moveLava(), so the rectangle grows taller or shorter, creating the visual effect of lava rising or falling.

🔬 This draws a rectangle from lavaY down to the canvas bottom. What if you add transparency: fill(255, 100, 0, 150) instead? The lava becomes semi-transparent, and you can see through it—try it and describe what becomes visible.

  fill(255, 100, 0); // Orange/red color for lava
  noStroke();
  rect(0, lavaY, width, height - lavaY);
function drawLava() {
  fill(255, 100, 0); // Orange/red color for lava
  noStroke();
  rect(0, lavaY, width, height - lavaY);
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

calculation Lava color fill(255, 100, 0);

Sets the fill color to orange-red (high red, low green, no blue)

calculation Lava rectangle rect(0, lavaY, width, height - lavaY);

Draws a rectangle from lavaY to the bottom of the canvas, filling the lower portion

fill(255, 100, 0); // Orange/red color for lava
Sets fill color to orange: maximum red (255), medium green (100), no blue (0).
noStroke();
Removes borders so the lava is a solid color block.
rect(0, lavaY, width, height - lavaY);
Draws a rectangle starting at x=0 (left edge), y=lavaY (lava surface), with width=width (full canvas width) and height=height - lavaY (from lava surface to bottom). As lavaY decreases (moveLava moves it up), the rectangle grows taller.

moveLava(targetY)

moveLava() mirrors the structure of moveUFO() but is simpler because lava moves only vertically. The two-phase design (approaching, then receding) creates a complete narrative arc: threat, destruction, retreat. Adding a pause between phases would intensify the drama.

🔬 This moves lava up by subtracting lavaSpeed each frame. What if you add a delay before the lava starts receding, like adding a timer inside the 'rockDestroyedByLava' case? The lava would pause at the rock for a moment—try modifying this to create suspense.

  if (gameState === "lavaApproaching") {
    lavaY -= lavaSpeed; // Move up
    if (lavaY <= targetY) {
function moveLava(targetY) {
  if (gameState === "lavaApproaching") {
    lavaY -= lavaSpeed; // Move up
    if (lavaY <= targetY) {
      lavaY = targetY; // Ensure it stops exactly at the target
      gameState = "rockDestroyedByLava"; // Lava reached rock, destroy it
      console.log("Lava reached the rock! Rock destroyed.");
    }
  } else if (gameState === "rockDestroyedByLava") {
    lavaY += lavaSpeed; // Move down
    if (lavaY >= height) {
      lavaY = height; // Ensure it goes fully off-screen
      gameState = "eventComplete"; // Lava left the screen
      console.log("Lava receded. Event complete.");
    }
  }
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional Lava rising phase if (gameState === "lavaApproaching") { lavaY -= lavaSpeed; if (lavaY <= targetY) { ... }

While lava is approaching, move it up (decrease lavaY). When it reaches the rock, trigger destruction.

conditional Lava receding phase } else if (gameState === "rockDestroyedByLava") { lavaY += lavaSpeed; if (lavaY >= height) { ... }

After destroying the rock, move lava down (increase lavaY) until it goes off-screen

if (gameState === "lavaApproaching") {
Only execute this block if the lava event is in the 'approaching' phase.
lavaY -= lavaSpeed; // Move up
Decreases lavaY (moving it up on the screen, since y increases downward). The lava surface moves toward the rock.
if (lavaY <= targetY) {
When lavaY reaches or passes the target (the rock), the collision has occurred.
lavaY = targetY; // Ensure it stops exactly at the target
Sets lavaY to exactly the target to prevent overshooting and ensure precise positioning.
gameState = "rockDestroyedByLava";
Transitions to the receding phase. Next frame, the else-if block below will execute instead.
} else if (gameState === "rockDestroyedByLava") {
Execute this block if lava is in the receding phase (after destroying the rock).
lavaY += lavaSpeed; // Move down
Increases lavaY (moving it down, off-screen), as if the lava has consumed the rock and is retreating.
if (lavaY >= height) {
When lavaY exceeds the canvas height, the lava has fully left the screen.
gameState = "eventComplete";
Signals the end of the lava event, allowing the reset button to be enabled again.

📦 Key Variables

rockX number

Stores the rock's horizontal position on the canvas (center x-coordinate)

let rockX = width / 2;
rockY number

Stores the rock's vertical position on the canvas (center y-coordinate)

let rockY = height * 0.7;
rockSize number

Stores the rock's width in pixels; used to scale the ellipses that draw it

let rockSize = min(width, height) * 0.2;
ufoX number

Stores the UFO's horizontal position on the canvas

let ufoX = -ufoSize;
ufoY number

Stores the UFO's vertical position on the canvas

let ufoY = -ufoSize;
ufoSize number

Stores the UFO's width in pixels; used to scale the ellipses that draw it

let ufoSize = min(width, height) * 0.15;
ufoSpeed number

Stores how many pixels the UFO moves toward its target each frame

let ufoSpeed = 3;
lavaY number

Stores the height at which the lava surface is drawn; decreases as it rises, increases as it recedes

let lavaY = height;
lavaSpeed number

Stores how many pixels per frame the lava moves up or down

let lavaSpeed = 2;
resetBtn object

Stores a reference to the reset button DOM element, used to style and control it

let resetBtn = createButton('Reset');
currentEventType string

Stores which event type should play next: either 'ufo' or 'lava'

let currentEventType = 'ufo';
gameState string

Stores the current phase of the animation: 'initial', 'ufoApproaching', 'rockDestroyed', 'lavaApproaching', 'rockDestroyedByLava', or 'eventComplete'

let gameState = 'initial';
eventStartTime number

Stores the timestamp (in milliseconds) when the countdown started, used to calculate elapsed time

let eventStartTime = millis();
EVENT_DELAY_SECONDS number

Stores the number of seconds to wait before the event triggers (a tunable constant)

const EVENT_DELAY_SECONDS = 20;
EVENT_DELAY_MILLIS number

Stores the delay converted to milliseconds for comparison with millis()

const EVENT_DELAY_MILLIS = EVENT_DELAY_SECONDS * 1000;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG moveUFO() arrival detection

If ufoSpeed is very large (e.g., 15+ pixels/frame), the UFO can overshoot the target without triggering arrival, leaving it stuck in motion forever.

💡 Use a fixed arrival distance instead: change if (d < ufoSpeed) to if (d < 20) so arrival triggers consistently regardless of speed.

PERFORMANCE draw() state display text

Text is drawn and recalculated every frame even though it changes only rarely. Once an event starts, the timer text is no longer needed but the fill/textSize calls still execute.

💡 Move state text display into a separate function and call it only when gameState is 'initial', or use a flag to skip it after startup.

STYLE Game state values

State strings like 'ufoApproaching' and 'rockDestroyed' are repeated in multiple places (draw switch, moveUFO, checkTimer). If you want to add a third event, you must change many lines.

💡 Define state constants at the top of the file: const STATE = { INITIAL: 'initial', UFO_APPROACHING: 'ufoApproaching', ... } and use them everywhere for consistency and easier maintenance.

FEATURE Event system

Only two event types are supported (UFO and lava). Adding a third requires duplicating conditional logic in checkTimer(), resetSketch(), and draw().

💡 Refactor the event system into an object or class: let events = { ufo: { trigger() {...}, update() {...} }, lava: {...} } and dispatch based on currentEventType, making new events easy to add.

BUG windowResized() UFO reset

If the window resizes during an active UFO event, the UFO position is not recalculated. It may end up off-screen relative to the new canvas size.

💡 Remove the 'if (gameState === "initial")' check and always recalculate UFO position proportionally: ufoX and ufoY should scale with the canvas if the UFO is off-screen, using fixed offsets like ufoX = -ufoSize.

🔄 Code Flow

Code flow showing setup, draw, drawrock, drawufo, moveufo, checktimer, resetsketch, windowresized, drawlava, movelava

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

graph TD start[Start] --> setup[setup] setup --> canvas-creation[Canvas Creation] setup --> rock-positioning[Rock Positioning] setup --> ufo-initialization[UFO Initialization] setup --> button-creation[Button Creation] setup --> state-display[State Display] setup --> draw[draw loop] click setup href "#fn-setup" click canvas-creation href "#sub-canvas-creation" click rock-positioning href "#sub-rock-positioning" click ufo-initialization href "#sub-ufo-initialization" click button-creation href "#sub-button-creation" click state-display href "#sub-state-display" draw --> rock-draw-check[Rock Draw Check] draw --> state-machine[State Machine] draw --> drawrock[drawRock] draw --> drawufo[drawUFO] draw --> drawlava[drawLava] click draw href "#fn-draw" click rock-draw-check href "#sub-rock-draw-check" click state-machine href "#sub-state-machine" click drawrock href "#fn-drawrock" click drawufo href "#fn-drawufo" click drawlava href "#fn-drawlava" state-machine --> state-transitions[State Transitions] state-machine --> event-type-selection[Event Type Selection] click state-transitions href "#sub-state-transitions" click event-type-selection href "#sub-event-type-selection" drawrock --> main-rock-body[Main Rock Body] drawrock --> left-protrusion[Left Protrusion] drawrock --> right-protrusion[Right Protrusion] click main-rock-body href "#sub-main-rock-body" click left-protrusion href "#sub-left-protrusion" click right-protrusion href "#sub-right-protrusion" drawufo --> ufo-body[UFO Body] drawufo --> ufo-dome[UFO Dome] drawufo --> ufo-glow[UFO Glow] click ufo-body href "#sub-ufo-body" click ufo-dome href "#sub-ufo-dome" click ufo-glow href "#sub-ufo-glow" moveufo --> angle-calculation[Angle Calculation] moveufo --> velocity-update[Velocity Update] moveufo --> distance-check[Distance Check] click moveufo href "#fn-moveufo" click angle-calculation href "#sub-angle-calculation" click velocity-update href "#sub-velocity-update" click distance-check href "#sub-distance-check" checktimer --> elapsed-time-check[Elapsed Time Check] click checktimer href "#fn-checktimer" click elapsed-time-check href "#sub-elapsed-time-check" resetsketch --> state-reset[State Reset] resetsketch --> event-toggle[Event Toggle] resetsketch --> button-update[Button Update] click resetsketch href "#fn-resetsketch" click state-reset href "#sub-state-reset" click event-toggle href "#sub-event-toggle" click button-update href "#sub-button-update" windowresized --> canvas-resize[Canvas Resize] windowresized --> position-recalculation[Position Recalculation] windowresized --> ufo-reset[UFO Reset] click windowresized href "#fn-windowresized" click canvas-resize href "#sub-canvas-resize" click position-recalculation href "#sub-position-recalculation" click ufo-reset href "#sub-ufo-reset" drawlava --> lava-fill[Lava Fill] drawlava --> lava-rectangle[Lava Rectangle] drawlava --> lava-approach[Lava Approach] drawlava --> lava-recede[Lava Recede] click drawlava href "#fn-drawlava" click lava-fill href "#sub-lava-fill" click lava-rectangle href "#sub-lava-rectangle" click lava-approach href "#sub-lava-approach" click lava-recede href "#sub-lava-recede"

❓ Frequently Asked Questions

What visual elements can users expect to see in the 'Something happen more coming tomorrow' p5.js sketch?

This sketch features a rock and a UFO, with the UFO approaching the rock, and eventually a lava event that unfolds over time.

How can users interact with the 'Something happen more coming tomorrow' sketch?

Users can click the reset button to transition from the UFO event to the lava event, allowing them to experience the next phase of the sketch.

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

The sketch demonstrates event-driven programming and state management, as it transitions between different states based on timing and user interaction.

Preview

Something  happen more coming tomorrow - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Something  happen more coming tomorrow - Code flow showing setup, draw, drawrock, drawufo, moveufo, checktimer, resetsketch, windowresized, drawlava, movelava
Code Flow Diagram