Sketch 2026-04-04 11:46

This sketch creates an interactive animation of a tiger and dragon fighting each other on a dark canvas. The two creatures advance towards each other and retreat in a continuous cycle, while the mouse position controls the pitch and volume of a sine wave oscillator, creating synchronized sound and visuals.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up the fight cycle — Decreasing cycleDuration makes creatures advance and retreat faster, creating more frantic fighting
  2. Invert the audio pitch control — Reversing the frequency range so the mouse moves right to lower pitch instead of raising it
  3. Make the tiger bigger — Increasing the body and head dimensions so the tiger takes up more visual space
  4. Add more dragon body segments — Increasing the loop count to draw a longer, more serpentine dragon with more segments
  5. Remove the tiger's stripes — Commenting out the stripe-drawing section to simplify the tiger's appearance
  6. Brighten the background — Changing the dark background to a lighter gray makes the creatures more visible
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch brings two mythical creatures to life in an endless battle: a tiger advancing and retreating on the lower half of the canvas, and a dragon doing the same from the upper half. What makes it compelling is the layering of three p5.js techniques: procedural animation (drawing complex shapes from ellipses and triangles), cyclic movement (using lerp to smoothly interpolate between fight positions), and interactive sound design (mapping mouse position to frequency and amplitude of a sine wave oscillator from the p5.sound library).

The code is organized around a continuous animation cycle tracked by frameCount. Every frame, setup() initializes the oscillator once, while draw() updates sound parameters based on mouse position and orchestrates two custom drawing functions that bring the tiger and dragon to life with time-based transformations. By studying it, you will learn how to use phase-based animation to synchronize multiple moving parts, leverage trigonometric functions for organic movement, and bind user input directly to synthesized audio.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, initializes a sine wave oscillator, and sets the orange and blue colors for tiger and dragon
  2. On every frame, draw() calculates a cycle position (0 to 1) based on frameCount, creating a fight rhythm that repeats every 300 frames
  3. The mouse position is mapped to the oscillator's frequency (left-right controls pitch) and amplitude (up-down controls volume), so moving your mouse creates real-time sound
  4. Two if-statements divide the animation cycle in half: the first half advances both creatures toward each other using lerp, the second half retreats them back to start positions
  5. drawTiger() and drawDragon() use sin and cos functions with the animation phase to add organic bobbing and rotating motions on top of the linear advance/retreat
  6. Every frame, both creatures are redrawn at their new positions, creating the illusion of continuous fighting movement synchronized with your mouse-controlled soundtrack

🎓 Concepts You'll Learn

Animation cycles and phase trackingLerp for smooth interpolationProcedural shape drawingTrigonometric animation (sin/cos)p5.sound oscillators and synthesisMouse input mapping to audio parametersTransform stacks (push/pop/translate/rotate)Modulo operator for looping behavior

📝 Code Breakdown

setup()

setup() runs once at the very start of your sketch. Use it to initialize the canvas, set up objects like the oscillator, and precompute values that never change. p5.sound oscillators must be created and started in setup() before they can be used in draw().

function setup() {
  createCanvas(windowWidth, windowHeight);
  // Initialize p5.sound
  osc = new p5.Oscillator();
  osc.setType('sine'); // Sine wave for a smooth sound
  osc.amp(0); // Start with 0 volume
  osc.start(); // Start the oscillator
  userStartAudio(); // Required to allow audio playback

  tigerColor = color(255, 165, 0); // Orange
  dragonColor = color(0, 150, 200); // Blue
  noStroke();
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

calculation Oscillator initialization osc = new p5.Oscillator();

Creates a new sine wave oscillator object that will generate the sound controlled by mouse position

calculation Audio context startup userStartAudio();

Enables audio playback by requesting user permission, required by modern browsers for sound to work

createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the animation fullscreen
osc = new p5.Oscillator();
Creates a new oscillator object from the p5.sound library that will produce sound waves
osc.setType('sine');
Sets the oscillator to produce a sine wave, which sounds smooth and pure without harsh edges
osc.amp(0);
Starts the oscillator at 0 volume so sound doesn't blast immediately when the sketch loads
osc.start();
Begins the oscillator running; it is now ready to play sound but stays silent at amp(0)
userStartAudio();
Requests permission from the browser to play audio; without this, the oscillator cannot produce sound
tigerColor = color(255, 165, 0); // Orange
Pre-computes the tiger's color (orange) so it does not get recalculated every frame in draw()
dragonColor = color(0, 150, 200); // Blue
Pre-computes the dragon's color (blue) for efficiency
noStroke();
Disables outlines on all shapes so only filled colors are drawn, giving a smoother appearance

draw()

draw() runs 60 times per second and orchestrates everything: clearing the canvas, mapping mouse to sound, calculating animation cycle positions, and calling the creature drawing functions. This is where the synchronization between motion and audio happens. The key insight is that currentFrameInCycle uses modulo (%) to loop the cycle endlessly, and lerp makes motion smooth by interpolating between two positions based on progress.

🔬 This block uses lerp to smoothly move creatures. What happens if you change the lerp end values, like changing tigerEnd from 0.4 to 0.2 (meaning the tiger doesn't advance as far)? Try editing just one of these two lerp lines.

  // Phase 1: Advance towards each other (first half of cycle)
  if (currentFrameInCycle < cycleDuration / 2) {
    let progress = map(currentFrameInCycle, 0, cycleDuration / 2, 0, 1);
    tigerX = lerp(tigerStart, tigerEnd, progress);
    dragonX = lerp(dragonStart, dragonEnd, progress);
function draw() {
  background(20); // Dark background

  // Map mouse position to sound parameters
  let freq = map(mouseX, 0, width, 100, 1000); // Frequency from 100Hz to 1000Hz
  let amp = map(mouseY, 0, height, 0, 0.5, true); // Amplitude from 0 to 0.5, constrained
  osc.freq(freq);
  osc.amp(amp, 0.1); // Smooth transition over 0.1 seconds

  // --- Animation Cycle for Fighting ---
  let cycleDuration = 300; // Frames for one full fight cycle (5 seconds at 60 fps)
  let currentFrameInCycle = frameCount % cycleDuration;

  let tigerStart = width * 0.15;
  let tigerEnd = width * 0.4;
  let dragonStart = width * 0.85;
  let dragonEnd = width * 0.6;

  let tigerX, dragonX;
  let tigerY = height * 0.7;
  let dragonY = height * 0.3;

  // Phase 1: Advance towards each other (first half of cycle)
  if (currentFrameInCycle < cycleDuration / 2) {
    let progress = map(currentFrameInCycle, 0, cycleDuration / 2, 0, 1);
    tigerX = lerp(tigerStart, tigerEnd, progress);
    dragonX = lerp(dragonStart, dragonEnd, progress);
  } else { // Phase 2: Retreat or hold position (second half of cycle)
    let progress = map(currentFrameInCycle, cycleDuration / 2, cycleDuration, 0, 1);
    tigerX = lerp(tigerEnd, tigerStart, progress);
    dragonX = lerp(dragonEnd, dragonStart, progress);
  }

  // Draw tiger and dragon with dynamic positions
  drawTiger(tigerX, tigerY, currentFrameInCycle);
  drawDragon(dragonX, dragonY, currentFrameInCycle);
}
Line-by-line explanation (22 lines)

🔧 Subcomponents:

calculation Mouse position to audio mapping let freq = map(mouseX, 0, width, 100, 1000);

Converts the mouse's horizontal position into a frequency value between 100Hz and 1000Hz, so moving left-right changes pitch

calculation Animation cycle phase let currentFrameInCycle = frameCount % cycleDuration;

Uses modulo to loop frameCount back to 0 every 300 frames, creating a repeating fight cycle

conditional Advance towards each other if (currentFrameInCycle < cycleDuration / 2) {

During the first half of the cycle, both creatures move toward the center using lerp

conditional Retreat to starting positions } else {

During the second half of the cycle, both creatures move back to their starting positions

background(20); // Dark background
Fills the entire canvas with a very dark gray (almost black) so the previous frame disappears and animation appears smooth
let freq = map(mouseX, 0, width, 100, 1000);
Converts mouseX (0 to canvas width) into a frequency (100Hz to 1000Hz); moving the mouse right increases pitch
let amp = map(mouseY, 0, height, 0, 0.5, true);
Converts mouseY (0 to canvas height) into amplitude (0 to 0.5); moving the mouse down increases volume
osc.freq(freq);
Sets the oscillator's frequency to the calculated freq value, changing the pitch you hear
osc.amp(amp, 0.1);
Sets the oscillator's amplitude to the calculated amp value over 0.1 seconds, creating smooth volume transitions
let cycleDuration = 300;
Defines how many frames one complete fight cycle takes (300 frames ≈ 5 seconds at 60 fps)
let currentFrameInCycle = frameCount % cycleDuration;
Uses modulo (%) to wrap frameCount between 0 and 299, so the cycle repeats endlessly
let tigerStart = width * 0.15;
Sets the tiger's resting position at 15% from the left edge of the canvas
let tigerEnd = width * 0.4;
Sets how far the tiger advances toward center (40% from the left) during the attack phase
let dragonStart = width * 0.85;
Sets the dragon's resting position at 85% from the left edge (15% from the right)
let dragonEnd = width * 0.6;
Sets how far the dragon advances toward center (60% from the left) during the attack phase
let tigerY = height * 0.7;
Places the tiger at 70% down the canvas (lower portion for ground fighting)
let dragonY = height * 0.3;
Places the dragon at 30% down the canvas (upper portion for aerial fighting)
if (currentFrameInCycle < cycleDuration / 2) {
Checks if we are in the first half of the cycle (frames 0-149 out of 0-299)
let progress = map(currentFrameInCycle, 0, cycleDuration / 2, 0, 1);
Converts the current frame into a 0-to-1 progress value; 0 = start position, 1 = fully advanced
tigerX = lerp(tigerStart, tigerEnd, progress);
Smoothly interpolates tiger's x position from start to end based on progress, creating fluid motion
dragonX = lerp(dragonStart, dragonEnd, progress);
Smoothly interpolates dragon's x position from start to end, both creatures meet in the middle
let progress = map(currentFrameInCycle, cycleDuration / 2, cycleDuration, 0, 1);
For the second half, maps frames 150-299 onto a new 0-to-1 progress to move creatures backward
tigerX = lerp(tigerEnd, tigerStart, progress);
Retreat phase: smoothly moves tiger back to starting position
dragonX = lerp(dragonEnd, dragonStart, progress);
Retreat phase: smoothly moves dragon back to starting position
drawTiger(tigerX, tigerY, currentFrameInCycle);
Calls the drawTiger function with the calculated position and current cycle phase
drawDragon(dragonX, dragonY, currentFrameInCycle);
Calls the drawDragon function with the calculated position and current cycle phase

drawTiger(x, y, animationPhase)

drawTiger() is a custom function that draws the tiger at a given position with time-based animation. The key technique here is using sin(animationPhase * scale) to drive organic motion: the scale factor (0.05 or 0.03) controls how fast the oscillation is relative to the animation cycle. The push/pop pair ensures transformations stay local to this function. This is procedural drawing: building complex shapes from simple primitives like ellipse, rect, and triangle.

🔬 These four rectangles draw the tiger's stripes. What happens if you change 'fill(0)' to 'fill(255)' so the stripes become white instead of black? Or add a new rect line to put a stripe somewhere else, like 'rect(-40, 0, 5, 20)' for a left side stripe?

  // Stripes (simple lines)
  fill(0);
  rect(0, -10, 5, 20);
  rect(-20, -5, 5, 20);
  rect(20, 5, 5, 20);
  rect(40, -30, 5, 10);
function drawTiger(x, y, animationPhase) {
  push();
  translate(x, y);

  // Time-based animation for tiger's 'pounce' or 'growl'
  let pounceOffset = sin(animationPhase * 0.05) * 20; // Vertical bob
  let rotateAngle = sin(animationPhase * 0.03) * 0.1; // Slight rotation

  translate(0, pounceOffset);
  rotate(rotateAngle);

  fill(tigerColor);

  // Body
  ellipse(0, 0, 100, 60);

  // Head
  ellipse(50, -20, 40, 40);
  fill(255); // White muzzle
  ellipse(60, -10, 20, 15);
  fill(tigerColor);
  rect(60, -15, 10, 5); // Nose

  // Ears
  triangle(40, -40, 50, -50, 60, -40);

  // Stripes (simple lines)
  fill(0);
  rect(0, -10, 5, 20);
  rect(-20, -5, 5, 20);
  rect(20, 5, 5, 20);
  rect(40, -30, 5, 10); // Head stripe

  // Eyes (simple dots)
  ellipse(45, -25, 5, 5);
  ellipse(55, -25, 5, 5);

  pop();
}
Line-by-line explanation (22 lines)

🔧 Subcomponents:

calculation Vertical bobbing motion let pounceOffset = sin(animationPhase * 0.05) * 20;

Uses sine wave to create a smooth up-and-down bobbing motion that repeats with the animation phase

calculation Slight body rotation let rotateAngle = sin(animationPhase * 0.03) * 0.1;

Creates a subtle rocking rotation to make the tiger look like it is tensing and preparing to pounce

calculation Body and head shapes ellipse(0, 0, 100, 60);

Draws the main body shape as a horizontal ellipse at the origin

push();
Saves the current transformation state (position, rotation, etc.) so changes in this function don't affect other drawings
translate(x, y);
Moves the origin to the tiger's current x and y position, so all subsequent shapes are drawn relative to this point
let pounceOffset = sin(animationPhase * 0.05) * 20;
Uses a sine wave scaled by animation phase to create bobbing; sin() oscillates between -1 and 1, multiplied by 20 gives ±20 pixels vertical movement
let rotateAngle = sin(animationPhase * 0.03) * 0.1;
Uses a slower sine wave (0.03 is smaller than 0.05) to create gentle rotation; 0.1 radians ≈ 5.7 degrees maximum tilt
translate(0, pounceOffset);
Applies the bobbing offset, moving the tiger up and down as the pounceOffset value changes each frame
rotate(rotateAngle);
Rotates all subsequent shapes by the calculated angle, making the tiger rock back and forth
fill(tigerColor);
Sets the fill color to the pre-calculated orange tiger color
ellipse(0, 0, 100, 60);
Draws the tiger's main body as an ellipse 100 pixels wide and 60 pixels tall, centered at origin
ellipse(50, -20, 40, 40);
Draws the tiger's head 50 pixels to the right and 20 pixels up, as a 40×40 circle
fill(255);
Changes fill color to white for the muzzle
ellipse(60, -10, 20, 15);
Draws a white muzzle patch on the face
fill(tigerColor);
Switches back to tiger color for the nose
rect(60, -15, 10, 5);
Draws a small rectangular nose at the front of the face
triangle(40, -40, 50, -50, 60, -40);
Draws a triangular ear pointing upward
fill(0);
Changes fill to black for the stripes
rect(0, -10, 5, 20);
Draws a vertical stripe on the body
rect(-20, -5, 5, 20);
Draws a stripe on the left side of the body
rect(20, 5, 5, 20);
Draws a stripe on the right side of the body
rect(40, -30, 5, 10);
Draws a small stripe on the head
ellipse(45, -25, 5, 5);
Draws the left eye as a small black circle
ellipse(55, -25, 5, 5);
Draws the right eye as a small black circle
pop();
Restores the transformation state saved by push(), returning to the original coordinate system

drawDragon(x, y, animationPhase)

drawDragon() draws the dragon at a given position with time-based snaking animation. Notice it uses cos() instead of sin() for the tiger, which creates a different phase relationship: when the tiger bobs up, the dragon might be in the middle of its sway. The body loop is particularly instructive: it demonstrates how a for-loop with decreasing dimensions creates a tapered multi-segment structure. This is the most complex procedural drawing in the sketch.

🔬 This loop draws the dragon's body as 5 segments that get smaller. What happens if you change '60 - i * 10' to just '60' so all segments have the same width instead of tapering? Or change '0' (y position) to 'sin(i) * 20' to make the body undulate vertically?

  // Body segments (simplified)
  for (let i = 0; i < 5; i++) {
    ellipse(-i * 50 - 50, 0, 60 - i * 10, 50 - i * 8);
  }
function drawDragon(x, y, animationPhase) {
  push();
  translate(x, y);

  // Time-based animation for dragon's 'snaking' movement
  let snakeOffset = cos(animationPhase * 0.04) * 30; // Horizontal bob
  let rotateAngle = cos(animationPhase * 0.02) * 0.15; // Body rotation

  translate(snakeOffset, 0);
  rotate(rotateAngle);

  fill(dragonColor);

  // Head
  ellipse(0, 0, 80, 60);
  fill(255); // White eye
  ellipse(20, -10, 15, 15);
  fill(0);
  ellipse(22, -10, 5, 5); // Pupil
  fill(dragonColor);
  rect(20, 10, 10, 5); // Snout

  // Horns
  fill(100);
  triangle(-10, -20, 0, -40, 10, -20);
  triangle(-20, -10, -10, -30, 0, -10);

  // Body segments (simplified)
  for (let i = 0; i < 5; i++) {
    ellipse(-i * 50 - 50, 0, 60 - i * 10, 50 - i * 8);
  }

  // Tail (simple triangle)
  triangle(-280, 0, -300, -20, -300, 20);

  pop();
}
Line-by-line explanation (21 lines)

🔧 Subcomponents:

calculation Horizontal snaking motion let snakeOffset = cos(animationPhase * 0.04) * 30;

Uses cosine to create a smooth left-right undulating motion, giving the dragon a slithering appearance

calculation Body rotation for snaking let rotateAngle = cos(animationPhase * 0.02) * 0.15;

Creates a slow rocking rotation to complement the horizontal snaking movement

for-loop Segmented body construction for (let i = 0; i < 5; i++) {

Draws 5 ellipses in a row extending left from the head, creating a segmented dragon body that gets progressively smaller

push();
Saves the transformation state so the dragon's transformations don't affect other drawings
translate(x, y);
Moves the origin to the dragon's current position, so all shapes are drawn relative to this point
let snakeOffset = cos(animationPhase * 0.04) * 30;
Uses cosine (instead of sine used for the tiger) to create a different phase of oscillation; multiplied by 30 gives ±30 pixels horizontal movement
let rotateAngle = cos(animationPhase * 0.02) * 0.15;
Uses an even slower cosine wave (0.02) to create a gentle rocking motion; 0.15 radians ≈ 8.6 degrees maximum rotation
translate(snakeOffset, 0);
Applies horizontal movement, making the dragon sway left and right as if slithering
rotate(rotateAngle);
Rotates the entire dragon, creating a snaking, undulating effect
fill(dragonColor);
Sets fill to the pre-calculated blue dragon color
ellipse(0, 0, 80, 60);
Draws the dragon's head as an 80×60 ellipse at the origin
fill(255);
Changes fill to white for the eye
ellipse(20, -10, 15, 15);
Draws a large white eye circle 20 pixels to the right and 10 pixels up
fill(0);
Changes fill to black for the pupil
ellipse(22, -10, 5, 5);
Draws a small black pupil inside the white eye
fill(dragonColor);
Switches back to dragon color
rect(20, 10, 10, 5);
Draws the snout as a small rectangle below the eye
fill(100);
Changes fill to dark gray for the horns
triangle(-10, -20, 0, -40, 10, -20);
Draws the first horn pointing upward and to the left
triangle(-20, -10, -10, -30, 0, -10);
Draws the second horn, also pointing upward but angled differently
for (let i = 0; i < 5; i++) {
Loops 5 times (i = 0, 1, 2, 3, 4) to draw 5 body segments extending to the left
ellipse(-i * 50 - 50, 0, 60 - i * 10, 50 - i * 8);
Draws an ellipse that moves 50 pixels left with each iteration; width and height decrease each iteration so the body tapers toward the tail
triangle(-280, 0, -300, -20, -300, 20);
Draws the tail as a triangle pointing to the left, 280 pixels away from the head
pop();
Restores the transformation state, returning to the original coordinate system

windowResized()

windowResized() is a special p5.js function that is called automatically whenever the browser window size changes. Calling resizeCanvas() inside it keeps your sketch responsive. This is essential for fullscreen animations like this one.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
Automatically adjusts the canvas size when the browser window is resized, so the animation stays fullscreen

📦 Key Variables

osc p5.Oscillator

Stores the sine wave oscillator object that generates sound; its frequency and amplitude are controlled by mouse position

let osc = new p5.Oscillator();
tigerColor p5.Color

Pre-calculated orange color used to fill all tiger shapes; stored here for efficiency so it is not recalculated every frame

let tigerColor = color(255, 165, 0);
dragonColor p5.Color

Pre-calculated blue color used to fill all dragon shapes; stored here for efficiency

let dragonColor = color(0, 150, 200);
cycleDuration number

Number of frames in one complete advance-and-retreat cycle; currently 300 frames creates a 5-second loop at 60 fps

let cycleDuration = 300;
currentFrameInCycle number

Uses modulo arithmetic to track the position within the current fight cycle (0 to 299), resetting after each cycle completes

let currentFrameInCycle = frameCount % cycleDuration;
progress number

A 0-to-1 normalized value calculated for each phase indicating how far through that phase we are; used by lerp to interpolate position

let progress = map(currentFrameInCycle, 0, cycleDuration / 2, 0, 1);
tigerX number

Tiger's current x position on the canvas, updated each frame by lerp based on the animation phase

let tigerX = lerp(tigerStart, tigerEnd, progress);
dragonX number

Dragon's current x position on the canvas, updated each frame by lerp based on the animation phase

let dragonX = lerp(dragonStart, dragonEnd, progress);
pounceOffset number

Calculated vertical offset for the tiger created by a sine wave, making it bob up and down during the fight

let pounceOffset = sin(animationPhase * 0.05) * 20;
snakeOffset number

Calculated horizontal offset for the dragon created by a cosine wave, making it sway left and right

let snakeOffset = cos(animationPhase * 0.04) * 30;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG draw() - audio amplitude mapping

The amplitude map uses the 'true' parameter (constrain mode) but mouseY inverted logic: moving the mouse DOWN increases volume. Most users expect moving UP to increase volume.

💡 Change to 'let amp = map(mouseY, height, 0, 0, 0.5, true);' to invert the mapping, so UP = louder and DOWN = quieter, matching user expectations.

PERFORMANCE setup() - p5.sound initialization

Calling userStartAudio() in setup() works but may not be the most user-friendly pattern. Some browsers require explicit user interaction before audio plays.

💡 Consider moving userStartAudio() to a mousePressed() or keyPressed() function to tie audio enablement to explicit user action, which is more reliable across browsers.

STYLE drawTiger() and drawDragon()

The color fill/reset pattern (fill(255), then ellipse(), then fill(tigerColor), then rect()) is repetitive and could be cleaner.

💡 Create helper functions like drawEyePair(x, y) or drawSnout(x, y) to encapsulate multi-shape color sequences and reduce repetition.

FEATURE draw() - oscillator configuration

The oscillator is always a sine wave. Users might enjoy hearing different waveforms as the fight progresses.

💡 Add a line that changes the waveform based on cycle phase: 'if (currentFrameInCycle < cycleDuration / 2) { osc.setType('sine'); } else { osc.setType('triangle'); }' to vary the timbre during fighting phases.

BUG draw() - lerp boundaries

If creatures meet exactly at the center (tigerEnd = 0.4, dragonEnd = 0.6), they overlap visually. The center point is not symmetric.

💡 Adjust to tigerEnd = 0.35 and dragonEnd = 0.65 to give them equal distance from center (width * 0.5), creating a more balanced visual encounter.

🔄 Code Flow

Code flow showing setup, draw, drawtiger, drawdragon, windowresized

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

graph TD start[Start] --> setup[setup] setup --> oscillator-init[Oscillator Initialization] setup --> audio-enablement[Audio Enablement] setup --> draw[draw loop] click setup href "#fn-setup" click oscillator-init href "#sub-oscillator-init" click audio-enablement href "#sub-audio-enablement" draw --> mouse-to-audio[Mouse to Audio Mapping] draw --> cycle-tracking[Cycle Tracking] draw --> advance-phase[Advance Phase] draw --> retreat-phase[Retreat Phase] draw --> pounce-animation[Pounce Animation] draw --> rotation-animation[Rotation Animation] draw --> drawtiger[drawTiger] draw --> drawdragon[drawDragon] click draw href "#fn-draw" click mouse-to-audio href "#sub-mouse-to-audio" click cycle-tracking href "#sub-cycle-tracking" click advance-phase href "#sub-advance-phase" click retreat-phase href "#sub-retreat-phase" click pounce-animation href "#sub-pounce-animation" click rotation-animation href "#sub-rotation-animation" click drawtiger href "#fn-drawtiger" click drawdragon href "#fn-drawdragon" drawtiger --> tiger-body[Tiger Body and Head Shapes] drawtiger --> pounce-animation drawtiger --> rotation-animation click tiger-body href "#sub-tiger-body" drawdragon --> snake-animation[Snake Animation] drawdragon --> dragon-rotation[Dragon Rotation] drawdragon --> body-segment-loop[Body Segment Loop] click snake-animation href "#sub-snake-animation" click dragon-rotation href "#sub-dragon-rotation" click body-segment-loop href "#sub-body-segment-loop" windowresized[windowResized] --> resizeCanvas[Resize Canvas] click windowresized href "#fn-windowresized" click resizeCanvas href "#sub-resize-canvas"

Preview

Sketch 2026-04-04 11:46 - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch 2026-04-04 11:46 - Code flow showing setup, draw, drawtiger, drawdragon, windowresized
Code Flow Diagram