Two iterations to add professional controls and authentic Superman theme music
Stage 9 โ 9a โ 9b
The Stage 9 series transformed our passive animation into an interactive experience with professional controls and synchronized audio, addressing usability concerns and animation direction issues through iterative refinement.
Evolution Summary:
Request: "In stage 9 of the simulation, let's create a 'Start'/'Pause' button. As the app opens, the star field should be animated and it will look like are flying through space. Once 'Start' is pushed, the title should animate followed by the credits. Also, with 'Start' let's play the supermanTheme.mp3. (Provide a subtle mute button as well.) With 'Start' the button becomes a 'Pause' which will stop all animations and stop the music."
Key Requirements Identified:
Design Goal: Create a professional, user-controlled experience that mimics the original Superman film's opening sequence, allowing users to start/stop the presentation at will.
Core Approach: Add HTML control buttons, integrate p5.sound library for audio, and implement state management for play/pause functionality.
Key Additions:
// *** STAGE 9: AUDIO CONFIGURATION ***
// Audio file path
const AUDIO_FILE = "supermanTheme.mp3";
// Audio volume (0.0 to 1.0)
const AUDIO_VOLUME = 0.7;
// State variables
let isAnimating = false; // Controls whether title/credits animate
let isPaused = false; // Track if animation is paused
let supermanMusic; // Will hold the audio file
let isMuted = false; // Track mute state
let pausedTime = 0; // Store elapsed time when paused
let totalPausedDuration = 0; // Track total paused time
/**
* STAGE 9: Preload audio file before setup()
*/
function preload() {
// Load the Superman theme music
supermanMusic = loadSound(AUDIO_FILE,
() => {
console.log("โ
Audio loaded successfully");
},
(err) => {
console.error("โ Error loading audio:", err);
}
);
}
/**
* STAGE 9: Setup button event listeners
*/
function setupButtons() {
let startPauseBtn = document.getElementById('startPauseBtn');
let muteBtn = document.getElementById('muteBtn');
// Start/Pause button click handler
startPauseBtn.addEventListener('click', () => {
if (!isAnimating) {
startAnimation();
startPauseBtn.textContent = 'Pause';
} else if (isPaused) {
resumeAnimation();
startPauseBtn.textContent = 'Pause';
} else {
pauseAnimation();
startPauseBtn.textContent = 'Resume';
}
});
// Mute button click handler
muteBtn.addEventListener('click', () => {
toggleMute();
muteBtn.classList.toggle('muted');
muteBtn.textContent = isMuted ? '๐ Muted' : '๐ Sound';
});
}
/**
* STAGE 9: Start animation and music
*/
function startAnimation() {
isAnimating = true;
isPaused = false;
// Activate the movie title
movieTitle.active = true;
// Record animation start time
animationStartTime = millis();
totalPausedDuration = 0;
// Play the Superman theme
if (supermanMusic && !isMuted) {
supermanMusic.setVolume(AUDIO_VOLUME);
supermanMusic.play();
}
}
/**
* STAGE 9: Pause animation and music
*/
function pauseAnimation() {
isPaused = true;
pausedTime = millis();
if (supermanMusic && supermanMusic.isPlaying()) {
supermanMusic.pause();
}
}
/**
* Resume animation and music
*/
function resumeAnimation() {
isPaused = false;
// Calculate how long we were paused
let pauseDuration = millis() - pausedTime;
totalPausedDuration += pauseDuration;
if (supermanMusic && !isMuted) {
supermanMusic.play();
}
}
1. p5.sound Library Integration:
2. State Management Pattern:
3. DOM Event Handling:
4. Time Compensation:
Problem Observed: "All seems ok but the student credits don't seem to be moving toward the back as they once did. Maybe I need to click on the screen again? Also, a press of 'Pause' just restarts the app from the beginning rather than pause."
Design Intent: Credits should animate like in previous versions (Stage 8b) while also responding to Start/Pause controls. Pause button should pause, not reset.
Fix 1: Add Credit Animation Logic
// *** STAGE 9a: CREDIT ANIMATION CONFIGURATION ***
// Credit growth rate (pixels per frame)
const CREDIT_GROWTH_RATE = 0.8;
// Credit final size (pixels)
const CREDIT_FINAL_SIZE = 200;
// Credit upward speed (pixels per frame)
const CREDIT_UPWARD_SPEED = 0.5;
// Credit fade speed
const CREDIT_FADE_SPEED = 0.5;
/**
* STAGE 9a: Enhanced credit update with animation
*/
function updateCredit(credit) {
if (!credit.active) return;
// *** GROWTH ANIMATION: Zoom toward viewer ***
if (credit.size < CREDIT_FINAL_SIZE) {
credit.size += CREDIT_GROWTH_RATE;
}
// *** UPWARD MOVEMENT: Drift up while growing ***
credit.y -= CREDIT_UPWARD_SPEED;
// Fade out effect
credit.opacity -= CREDIT_FADE_SPEED;
// Decay tilt angle
credit.tiltAngle *= TILT_DECAY;
}
/**
* STAGE 9a: Initialize credits with lower starting position
*/
function initializeCredits() {
credits = [];
for (let i = 0; i < studentNames.length; i++) {
credits.push({
name: studentNames[i],
hue: random(0, 360),
size: INITIAL_FONT_SIZE,
y: height/2 + 50, // *** Start below center for upward motion ***
tiltAngle: random(-MAX_TILT, MAX_TILT),
opacity: 100,
active: false,
startTime: (i + 1) * CREDIT_DELAY_MS + TITLE_DURATION_MS + CREDITS_PAUSE
});
}
}
Fix 2: Prevent Button Clicks from Triggering mousePressed()
/**
* STAGE 9a: Modified mousePressed() with position check
* Only reset if click is on canvas, not on buttons
*/
function mousePressed() {
// *** Check if click is below button area ***
if (mouseY < height - 100) {
resetAnimation();
}
}
/**
* STAGE 9a: Separate reset function
*/
function resetAnimation() {
// Stop music
if (supermanMusic && supermanMusic.isPlaying()) {
supermanMusic.stop();
}
// Reset state
isAnimating = false;
isPaused = false;
// Reinitialize
initializeMovieTitle();
movieTitle.active = false;
initializeCredits();
// Reset button text
document.getElementById('startPauseBtn').textContent = 'Start';
}
1. Credit Animation Restoration:
2. Event Bubbling Prevention:
3. Configuration-Driven Animation:
Feedback: "Actually, the title should fly toward us, and the student credits should fly away from us as they did in version 8. Can you make that adjustment in version 9b? The Start/Pause is correct now."
Current State (9a):
Design Goal: Match the authentic Superman (1978) effect where title rushes at you and credits recede into the distance.
Solution: Invert credit animation from growthโshrink and upโdown to create recession effect.
| Parameter | Stage 9a (Incorrect) | Stage 9b (Correct) |
|---|---|---|
| Size change | CREDIT_GROWTH_RATE = 0.8 | CREDIT_SHRINK_RATE = 0.6 |
| Size limit | CREDIT_FINAL_SIZE = 200 | CREDIT_MIN_SIZE = 20 |
| Movement direction | CREDIT_UPWARD_SPEED = 0.5 | CREDIT_DOWNWARD_SPEED = 0.3 |
| Starting Y position | height/2 + 50 (below center) | height/2 - 30 (above center) |
| Visual effect | Credits fly TOWARD viewer | Credits fly AWAY from viewer |
// *** STAGE 9b: CREDIT ANIMATION CONFIGURATION ***
// *** CREDIT SHRINK AND MOVEMENT ***
// Controls how credits shrink and move away from viewer
// Credit shrink rate (pixels per frame)
// How quickly credits get smaller as they recede
const CREDIT_SHRINK_RATE = 0.6;
// Credit minimum size (pixels)
// Minimum size before fading out
const CREDIT_MIN_SIZE = 20;
// Credit downward speed (pixels per frame)
// How fast credits drift downward as they shrink
const CREDIT_DOWNWARD_SPEED = 0.3;
// Credit fade speed
const CREDIT_FADE_SPEED = 0.4;
/**
* STAGE 9b: Updated credit animation - SHRINK and move DOWN
*/
function updateCredit(credit) {
if (!credit.active) return;
// *** SHRINK ANIMATION: Recede into distance ***
if (credit.size > CREDIT_MIN_SIZE) {
credit.size -= CREDIT_SHRINK_RATE; // *** SUBTRACT to shrink ***
}
credit.size = max(credit.size, CREDIT_MIN_SIZE);
// *** DOWNWARD MOVEMENT: Drift down while shrinking ***
credit.y += CREDIT_DOWNWARD_SPEED; // *** ADD to move down ***
// Fade out effect
credit.opacity -= CREDIT_FADE_SPEED;
// Decay tilt angle
credit.tiltAngle *= TILT_DECAY;
}
/**
* STAGE 9b: Initialize credits with higher starting position
*/
function initializeCredits() {
credits = [];
for (let i = 0; i < studentNames.length; i++) {
credits.push({
name: studentNames[i],
hue: random(0, 360),
size: INITIAL_FONT_SIZE,
y: height/2 - 30, // *** Start above center for downward motion ***
tiltAngle: random(-MAX_TILT, MAX_TILT),
opacity: 100,
active: false,
startTime: (i + 1) * CREDIT_DELAY_MS + TITLE_DURATION_MS + CREDITS_PAUSE
});
}
}
1. Perspective Illusion:
2. Mathematical Inversion:
3. Starting Position Strategy:
4. Dual Animation Directions:
| Feature | Stage 9 | Stage 9a | Stage 9b |
|---|---|---|---|
| Start/Pause Button | โ Added | โ Working | โ Working |
| Mute Button | โ Added | โ Working | โ Working |
| Audio Integration | โ Superman theme | โ Superman theme | โ Superman theme |
| Credit Animation | โ Static (no movement) | โ ๏ธ Growing/upward (wrong direction) | โ Shrinking/downward (correct!) |
| Pause Button Behavior | โ Triggers reset | โ Pauses correctly | โ Pauses correctly |
| Title Direction | โ Toward viewer | โ Toward viewer | โ Toward viewer |
| Credit Direction | โ No animation | โ Toward viewer (wrong) | โ Away from viewer (correct!) |
| Starfield Animation | โ Before Start button | โ Before Start button | โ Before Start button |
| Superman Authenticity | โ Missing animations | โ ๏ธ Wrong direction | โ Perfect match! |
Code Size Comparison:
1. Library Integration:
2. User Interface Design:
3. State Management:
4. Iterative Debugging:
1. Directional Animation:
2. Synchronized Media:
3. Event-Driven Architecture:
Modify these constants to change animation feel:
Question: How does changing shrink rate vs. downward speed affect the perceived motion?
Add a volume slider to the UI:
<input type="range" min="0" max="100" id="volumeSlider">volumeSlider.addEventListener('input', (e) => {...})supermanMusic.setVolume(e.target.value / 100)Challenge: Make the slider position reflect the current volume!
What if we want credits to fly toward viewer and title to recede?
Question: Does this feel as cinematic as the original? Why or why not?
Load different music for different stages:
loadSound("track1.mp3"), loadSound("track2.mp3")Challenge: Implement smooth crossfade between tracks!
Add fast-forward and slow-motion buttons:
let animationSpeed = 1.0supermanMusic.rate(animationSpeed) to sync audioQuestion: How does changing speed affect the dramatic impact?
What We Built:
Technical Skills Demonstrated:
Problem-Solving Journey:
Educational Value:
Stage 9 Series Status: โ Complete and polished
Current Feature Set:
What's Next?
The interactive foundation is solid! Potential Stage 10 directions:
The possibilities are endless! What would you like to add next? ๐ฌโจ