Rhythm Game
Hit the right note at the right time and feel the music move through your fingers | Beat Lane
"A rhythm game is a musical instrument where the song plays itself and your only job is to not ruin it."
Week 1: History & Design Theory
The Origin
In 1996, Masaya Matsuura created PaRappa the Rapper and proved that pressing buttons in time with music could be a game. The concept was deceptively simple: a song plays, icons scroll across the screen indicating which button to press and when, and the player presses the matching button in time with the beat. What made PaRappa revolutionary was the feedback loop it created between the player and the music. Hit the notes and the song sounds right; miss them and the track falls apart. Matsuura understood that the joy of a rhythm game is not precision for its own sake but the feeling that you are inside the music.
How the Genre Evolved
PaRappa the Rapper (1996) — Established the genre's essential contract: the game provides a song and visual cues for each beat; the player provides the input. The core loop was fully formed: audio plays, visual indicator scrolls, player presses button, game scores the accuracy.
Guitar Hero (2005) — Harmonix gave the genre a physical interface: a plastic guitar. The "note highway" — a scrolling track where colored notes approach a target line — became the genre's standard visual language. Guitar Hero solved the accessibility problem: Easy used three buttons, Expert used five with complex patterns. The same song was a fundamentally different challenge at each difficulty.
Beat Saber (2018) — Beat Games brought rhythm gameplay into VR. Instead of pressing buttons, players slashed colored blocks with lightsabers. The physicality made timing feel visceral. Beat Saber proved the genre was far from exhausted and demonstrated that the core concept adapts to any input modality.
What Makes It "Great"
A great rhythm game creates flow state — where the player stops thinking about which button to press and their hands just move with the music. This requires three things: charting that matches the music, timing windows that are fair but meaningful, and visual/audio feedback that is immediate and satisfying.
The Essential Mechanic
Hitting inputs in time with musical beats within precise timing windows.
Week 2: Build the MVP
What You're Building
A 4-lane note highway rhythm game. Notes scroll down the screen toward a target line at the bottom. When a note reaches the target, the player presses the corresponding key (D, F, J, K). The game scores each input as Perfect, Great, Good, or Miss. Consecutive hits build a combo multiplier. After the song, a results screen shows accuracy and grade.
Core Concepts
1. Audio Synchronization
The entire game is driven by the song's current playback position, not by frame counting. Every frame, the game queries the audio system for the current position in milliseconds and uses that as the single source of truth.
function getSongPosition():
return audioEngine.track.getPlaybackPosition() + audioEngine.offset
function gameLoop():
songPositionMs = getSongPosition()
updateNotes(songPositionMs)
checkInputs(songPositionMs)
renderFrame(songPositionMs) Why it matters: If notes drift even 20 milliseconds from the music, the game feels "off." Frame-based timing accumulates drift over a three-minute song. Audio-position-based timing does not.
A note scrolls toward the target line. Press Spacebar to "hit" when it reaches the line. The display shows your timing offset in milliseconds and the judgment (Perfect/Great/Good/Miss). The colored bar shows the timing windows visually.
2. Note Highway / Chart Format
A song chart lists every note: its timestamp, its lane, and its type. The chart is loaded at song start and notes are spawned as they approach. The chart format is the bridge between the music and the gameplay.
sampleChart = {
bpm: 120,
notes: [
{timeMs: 500, lane: 0, type: "tap"},
{timeMs: 750, lane: 1, type: "tap"},
{timeMs: 1000, lane: 2, type: "tap"},
{timeMs: 1000, lane: 3, type: "tap"}, // Chord
{timeMs: 1250, lane: 0, type: "tap"}
]
}
APPROACH_TIME = 1500 // ms before hit time the note appears
function spawnUpcomingNotes(songPositionMs):
while nextNoteIndex < chart.notes.length:
note = chart.notes[nextNoteIndex]
if songPositionMs >= note.timeMs - APPROACH_TIME:
activeNotes.add(note)
nextNoteIndex++
else:
break Why it matters: The chart is the game's level design. A good chart makes a song feel playable; a bad chart makes it feel random. The data-driven approach means adding new songs requires zero code changes.
3. Timing Windows
When the player presses a key, the game checks the closest note in that lane. The difference in milliseconds determines the judgment: Perfect (20ms), Great (50ms), Good (100ms), or Miss (beyond 100ms).
TIMING_WINDOWS = {
PERFECT: 20, // +/- 20ms
GREAT: 50, // +/- 50ms
GOOD: 100, // +/- 100ms
MISS: 150 // Beyond this = miss
}
function judgeInput(lane, songPositionMs):
closestNote = findClosestUnhitNote(lane)
diff = abs(songPositionMs - closestNote.timeMs)
if diff <= TIMING_WINDOWS.PERFECT: judgment = "PERFECT"
else if diff <= TIMING_WINDOWS.GREAT: judgment = "GREAT"
else if diff <= TIMING_WINDOWS.GOOD: judgment = "GOOD"
else: return // too far from any note Why it matters: Timing windows are what separate a rhythm game from a reaction-time test. The tiered system rewards precision without requiring perfection.
4. Input Latency Calibration
Different hardware introduces different delays. The calibration system lets the player adjust an offset (in milliseconds) to compensate.
function calculateCalibration():
offsets = []
for each tap in calibration.taps:
nearestBeat = findNearest(metronomeHits, tap)
offsets.add(tap - nearestBeat)
calibration.audioOffset = -round(average(offsets)) Why it matters: A 40ms audio delay means the player hears the beat late, presses late, and gets scored as inaccurate — even though their timing relative to what they heard was perfect.
5. Combo and Scoring System
Each hit increments a combo counter. The multiplier increases at 10, 30, and 50 combo. A single Miss resets the combo. The final score, max combo, and hit percentage determine a letter grade.
COMBO_THRESHOLDS = [
{combo: 0, multiplier: 1},
{combo: 10, multiplier: 2},
{combo: 30, multiplier: 4},
{combo: 50, multiplier: 8}
]
function applyScore(judgment):
if judgment != "MISS":
scoring.combo += 1
scoring.score += SCORE_VALUES[judgment] * getMultiplier()
else:
scoring.combo = 0 Why it matters: The combo system transforms "hit notes" into "do not miss a single note." A 200-combo streak means every note matters intensely.
6. BPM-Based Event Scheduling
Notes are authored at musical positions (beat 1, beat 2.5) and converted to millisecond timestamps using BPM. This allows charts to be authored in musical terms.
function beatToMs(beat, bpm):
return (beat / bpm) * 60000
// Scroll speed adjusts visual approach without changing timing
function getScrollSpeed(baseSpeed, userSpeedMod):
return baseSpeed * userSpeedMod Why it matters: BPM-based scheduling is what makes charts feel musical. Scroll speed modification is only possible because visual position is derived from BPM timing, not hard-coded.
7. Visual Feedback Synchronized to Music
Hit effects, background pulses, and screen flashes all trigger on beat. The background pulses on every beat (from BPM), hit effects play at the judgment moment. This visual layer is what makes the game feel alive.
function triggerBeatPulse():
backgroundFlash.alpha = 0.3
backgroundFlash.fadeOut(duration=200)
targetLine.scale = 1.1
targetLine.tweenTo(scale=1.0, duration=150)
function showJudgmentFeedback(judgment, lane):
popup = createText(judgment, lanePositions[lane])
popup.color = JUDGMENT_COLORS[judgment]
if judgment == "MISS": camera.shake(intensity=3) Why it matters: Visual feedback makes a rhythm game feel like a performance rather than a typing test. The hit effects celebrate accuracy and make Perfect feel different from Good in a visceral way.
4 lanes mapped to D, F, J, K keys. Notes scroll down — press the matching key when they reach the target line. Combo counter and score are shown. This is a visual-only demo (no audio).
Stretch Goals
- Hold notes — Press and hold until the note ends.
- Chart editor — Players create their own note charts.
- Difficulty selection — Easy/Normal/Hard with different charts.
- Health bar — Drains on misses and ends the song early.
MVP Spec
| Component | Minimum Viable Version |
|---|---|
| Lanes | 4 lanes (D, F, J, K) with colored indicators |
| Note Highway | Notes scroll from top to bottom |
| Timing Windows | Perfect (20ms), Great (50ms), Good (100ms), Miss |
| Scoring | Points per judgment, combo multiplier at 10/30/50 |
| Calibration | Audio offset setting |
| Visual Feedback | Beat pulse, hit particles, judgment popups |
| Results Screen | Score, accuracy %, max combo, letter grade |
Deliverable
A playable rhythm game where notes scroll down a 4-lane highway. The player presses lane keys in time with the beat and receives millisecond-accurate timing judgments. A combo system multiplies score. Visual feedback reinforces the connection between input and music. The game must feel tight — inputs should feel connected to the music.
Discussion Questions
- The "Feel" Problem: Two rhythm games can have identical timing windows yet one feels tight and the other laggy. What contributes to subjective "feel" beyond audio sync?
- Charting as Level Design: What makes a good chart? Should every audible sound be a note, or should charts be selective?
- Accessibility vs. Competitive Depth: How do you design a system welcoming to newcomers and rewarding for experts?
- Audio as a Technical Constraint: Web browsers and Bluetooth audio introduce unpredictable latency. At what point does latency make a rhythm game unplayable?