The Journey from Intro Sequence to Educational Credits
Versions 11 → 11e → 12 → 13
This stage introduces the concept of sequential timing - making multiple events happen one after another with precise control. The challenge is ensuring each phrase:
This is a common pattern in game development, interactive media, and user interface design.
Goal: Add three intro phrases with delays
Status: ❌ BROKEN - Timing Bug
Issue: Delays were not properly cumulative. Each phrase's start time wasn't accounting for the DURATION of previous phrases, only their delays.
Problem: Original timing calculation:
phrase2StartTime = phrase1StartTime + DELAY_2
This doesn't account for how long phrase 1 RUNS (3000ms). Phrases would overlap!
Correct Formula:
phrase2StartTime = phrase1StartTime + TITLE_DURATION_MS + DELAY_2
This ensures phrase 2 waits for phrase 1 to finish (TITLE_DURATION_MS) PLUS the delay.
Goal: Fix timing calculations
Status: ❌ FILE CORRUPTED
Issue: File corruption occurred around line 1032, causing multiple syntax errors.
Location: Line 1032 in drawBackgroundWithTrails() function
Symptom: Missing code in the else block:
} else {
// MISSING: background(0, 0, 0);
// MISSING: updateAndDrawStars();
}
Impact: Function incomplete, leading to cascading syntax errors throughout file.
Added back the essential background clearing and star rendering:
} else {
// No trail effect - draw clean background
background(0, 0, 0);
updateAndDrawStars();
}
Also removed duplicate code blocks in initialization functions.
Post-Fix Status: ⚠️ Still Not Working - Corruption fixed, but intro phrases don't appear!
Goal: Implement correct timing with TITLE_DURATION_MS
Status: ⚠️ Working Timing, Still No Display
Created By: Copying fixed 11a and applying 8 timing corrections
The correct sequential timing pattern:
// First phrase starts after initial delay
phrase1StartTime = DELAY_1; // 500ms
// Second phrase waits for phrase 1 to finish + gap
phrase2StartTime = phrase1StartTime + TITLE_DURATION_MS + DELAY_2;
// = 500 + 3000 + 250 = 3750ms
// Third phrase waits for phrase 2 to finish + gap
phrase3StartTime = phrase2StartTime + TITLE_DURATION_MS + DELAY_3;
// = 3750 + 3000 + 250 = 7000ms
// Title waits for phrase 3 to finish + gap
titleStartTime = phrase3StartTime + TITLE_DURATION_MS + DELAY_4;
// = 7000 + 3000 + 100 = 10100ms
Each start time is the sum of ALL previous animations and delays.
Problem: Even with perfect timing, the intro phrases still don't appear on screen! Time to debug...
Strategy: Comment out everything except the FIRST intro phrase to isolate the issue
Status: ✅ CRITICAL BUGS DISCOVERED AND FIXED!
Original Code:
let introPhrase1 = "Tech Novice Tools"; // WRONG!
let introPhrase2 = "& Copilot"; // WRONG!
let introPhrase3 = "Present"; // WRONG!
Problem: These are strings, not objects! When code tries to check introPhrase1.active, it fails because strings don't have an active property.
Correct Code:
let introPhrase1 = null; // Will be created by initializeIntroPhrases()
let introPhrase2 = null; // Will be created by initializeIntroPhrases()
let introPhrase3 = null; // Will be created by initializeIntroPhrases()
Original Logic in draw():
if (introPhrase1 && introPhrase1.active) {
updateTitleObject(introPhrase1); // Check timing and activate
displayTitleObject(introPhrase1); // Display if active
}
The Problem: This creates a paradox!
if statement only runs updateTitleObject() when active is trueactive is SET TO TRUE inside updateTitleObject()!active starts as false, updateTitleObject() NEVER RUNSupdateTitleObject() never runs, active NEVER BECOMES TRUEThis is a classic state management bug! We're checking a condition that can only become true if we run the code that checks the condition!
New Logic:
if (introPhrase1) {
// ALWAYS check timing (this can activate the phrase)
updateTitleObject(introPhrase1);
// ONLY display if active
if (introPhrase1.active) {
displayTitleObject(introPhrase1);
}
}
Why This Works:
updateTitleObject() runs EVERY FRAME to check elapsed timeactive = trueif statement displays the phraseResult: TNT phrase appears! ✅ SUCCESS!
This bug teaches an important principle in programming:
Never gate the function that SETS a condition behind checking that same condition.
In other words:
if (isReady) { checkIfReady(); } ← Chicken-and-egg!checkIfReady(); if (isReady) { doSomething(); } ← Always check, conditionally actThis pattern appears everywhere:
Goal: Test all three intro phrases together
Status: ✅ ALL PHRASES WORKING!
Configuration: Title and credits still commented out for testing
Timeline Achieved:
Result: Beautiful sequential intro! Each phrase appears, zooms, and fades before the next begins.
Goal: Re-enable title and student credits for full experience
Status: ✅ COMPLETE AND WORKING!
Full Timeline:
Final Result: A complete cinematic intro sequence perfectly synchronized with the Superman theme music!
This unified function creates title-like objects (intro phrases and main title):
function createTitleObject(text, delay) {
return {
text: text, // The text to display
size: TITLE_INITIAL_SIZE, // Start small (8 pixels)
opacity: 100, // Full opacity
active: false, // Not active yet
blurActive: false, // Blur effect not started
startTime: delay, // When to activate (ms from start)
hasCompleted: false // Track if animation finished
};
}
This function checks timing and manages the phrase lifecycle:
function updateTitleObject(titleObj) {
if (!titleObj.active && !titleObj.hasCompleted) {
// Check if it's time to activate
let elapsed = millis() - animationStartTime;
if (elapsed >= titleObj.startTime) {
titleObj.active = true; // Activate the phrase!
console.log("🔵 Phrase activated:", titleObj.text);
}
return; // Not active yet, nothing to update
}
if (titleObj.active && !titleObj.hasCompleted) {
// Grow the text
titleObj.size += (titleObj.blurActive ?
TITLE_ACCELERATED_GROWTH : TITLE_GROWTH_RATE);
// Start blur when size reaches threshold
if (titleObj.size >= TITLE_BLUR_START_SIZE) {
titleObj.blurActive = true;
}
// Fade out during blur
if (titleObj.blurActive) {
titleObj.opacity -= CREDIT_FADE_SPEED * 2;
}
// Mark as completed when fully faded
if (titleObj.opacity <= 0) {
titleObj.active = false;
titleObj.hasCompleted = true;
}
}
}
The delay constants make timing adjustments easy:
// Delay before first phrase (ms)
const DELAY_1 = 500;
// Delay after phrase 1 completes before phrase 2 starts (ms)
const DELAY_2 = 250;
// Delay after phrase 2 completes before phrase 3 starts (ms)
const DELAY_3 = 250;
// Delay after phrase 3 completes before title starts (ms)
const DELAY_4 = 100;
// Each phrase/title animates for this duration (ms)
const TITLE_DURATION_MS = 3000;
// Pause between title exit and first credit (ms)
const CREDITS_PAUSE = 11500;
This stage demonstrated a professional debugging workflow:
Key Takeaway: When facing a complex bug, simplify the problem by isolating components until you find the issue.
Version 11e delivered a complete cinematic experience with the intro sequence. Then we continued enhancing:
Goal: Add professional fonts for movie credits feel
Status: ✅ SUCCESS!
Selected Bebas Neue as primary font:
Additional fonts available:
Implementation:
// Load via Google Fonts CDN
Result: Professional cinematic typography transforms the visual impact!
Goal: Feature programming concepts students learned
Status: ✅ COMPLETE!
Core Concepts (10):
Honorable Mentions (5):
1. Dual Credit System:
// Two separate arrays
let studentNames = ["STUDENT 1", ...];
let technicalConcepts = ["VARIABLES", "FUNCTIONS", ...];
// Combined into single credits[] array with flags
credits.push({
name: studentNames[i],
isTechnical: false, // Student credit
font: CREDITS_FONT, // Bebas Neue/Oswald
hue: random(0, 360) // Full spectrum
});
credits.push({
name: technicalConcepts[i],
isTechnical: true, // Technical credit
font: TECHNICAL_FONT, // Courier New
hue: random(GREEN_HUE_MIN, GREEN_HUE_MAX) // Green only!
});
2. Retro Green Aesthetic:
// Green hue range (HSB color mode)
const GREEN_HUE_MIN = 100; // Yellow-green
const GREEN_HUE_MAX = 140; // Cyan-green
// Pure green ≈ 120° (classic terminal screens)
3. Monospace Font:
// Courier New - built-in, always available
const TECHNICAL_FONT = "Courier New";
// Applied in draw functions
if (credit.isTechnical) {
textFont(credit.font); // Monospace
} else {
textFont(CREDITS_FONT); // Cinematic
}
Problem: Version didn't load - "Technical Font" couldn't be loaded
Cause: Attempted to use "Source Code Pro" from Google Fonts, but p5.js had trouble loading it
Solution: Switched to "Courier New" - a system font that's always available
Problem: Student credits worked, but technical concepts never appeared
Cause: initializeCredits() function wasn't updated - only created student credits
Solution: Added second loop to create technical concept credits with proper properties
Final Sequence:
The final version (13) celebrates both:
Version 11e delivers a complete cinematic experience: