Part 03 — The screen and the sound
You will build: the camera shake (the world that answers, without moving), the float text at feel-scale (the number that pops, drifts, and frees itself), and the sound's ride (the thock on the fact, the whoosh on the promise) — the three answers a hit gets beyond the flash and the shove. You'll learn: why the shake is on the camera, not the world · the frame-rate-independent decay · the tween curve vocabulary · and the event's four witnesses.
Why this exists#
Parts 01–02 gave the hit its target-side answers — the flash (the target says "I was hit") and the shove (the target says "that moved me"). This part is the world-side answers: the screen (the camera says "that registered") and the sound (the mix says "that landed"). A hit with all four — flash, shove, shake, thock — is a hit the player's eye, ear, and sense of consequence all confirm at once; a hit with the number and none of the four is a hit the player has to check. The four are the event's witnesses, and this part builds the two that aren't on the target.
Build it#
A — The shake: the camera lies, the world doesn't#
The shake is the camera's offset — a decaying random displacement applied to the camera, not the world:
# on the Camera2D (the survivor's stage 01 child camera)
var shake_magnitude := 0.0
func _process(delta: float) -> void:
if shake_magnitude > 0.1:
# a random direction, the magnitude, decaying frame-rate-independently
offset = Vector2.RIGHT.rotated(randf() * TAU) * shake_magnitude
shake_magnitude *= exp(-8.0 * delta) # the decay, part B
else:
offset = Vector2.ZERO
shake_magnitude = 0.0
func shake(amount: float) -> void:
shake_magnitude = maxf(shake_magnitude, amount) # the bigger hit wins, they don't add
The load-bearing fact is the camera, not the world. Shaking the world (every entity's
position, the whole Entities node) is a shake the physics sees — the golems' positions
change, the chase's direction_to reads the shaken positions, and the "visual" shake is now a
physical one (the enemies orbit the shake, the dead zone measures shaken distances). The
camera's offset is a render-only displacement — the world's positions are unchanged, the
physics reads the truth, and the eye sees the lie. The shake is the camera telling the
player "that registered" without the world agreeing — a lie that's honest, because the lie
is the presentation and the truth is the event (the audio shelf's part 04 split, the
screen's version). The shake is on the camera, and the world's positions are the physics'
business, untouched.
The shake(amount) is called from the event — the player's hurt (the survivor's stage 04
contact damage, a shake(4.0) on the health_depleted-adjacent signal), the big kill (a
clump-death, a shake(6.0)), the boss's slam (the boss fights shelf's active frame, the enemy's hit shaking your screen).
The shake rides the event (the overview's rule), and the magnitude is the design number —
part 04's tuning loop finds your 4 and your 6.
B — The decay, frame-rate-independent#
The shake_magnitude *= exp(-8.0 * delta) is the decay, and the exp is the reason it's
that form. A naive decay — shake_magnitude -= 10.0 * delta (a fixed amount per second) —
stops the shake at a fixed time after it started, which is fine, but a proportional decay
(*= (1 - rate * delta)) is the one that reads as settling (the shake eases out, the last
frames are small) — and the proportional form has a frame-rate trap: (1 - rate * delta) at
60 fps and (1 - rate * delta/2) at 120 fps (two half-steps) don't compound to the same
value — the 120 fps shake decays faster (two multiplications of a number closer to 1, but
the compounding is (1 - r·dt)² vs (1 - r·2dt), and the square is smaller). The
exp(-rate * delta) is the frame-rate-independent form — exp compounds exactly
(exp(-r·dt) · exp(-r·dt) = exp(-r·2dt)), so the shake decays at the same rate regardless
of the frame rate. The 3D farm's stage 03 stretch
met the same trap on the facing smoothing (lerp_angle with a fixed factor, the 30-vs-144
fps feel difference) — the exp form is the answer, and the answer is the same for the shake.
The rate (8.0) is the decay's speed — how fast the shake settles. A high rate (16) is a
shake that's over before it reads; a low rate (4) is a shake that lingers into the next hit.
Part 04's loop finds your 8 — and the finding is usually "the rate that makes the shake end
on the frame the player stops seeing the hit."
C — The float text, at feel-scale#
The survivor's stage 04 float text is the feel in its purest form — a number that pops
(scale 0→1.6, TRANS_BACK the overshoot), drifts (up, with a random left/right, TRANS_ QUAD the natural), and fades (modulate:a to 0, TRANS_QUAD EASE_IN the decay) — three
properties, one parallel tween, auto-free at the end. Stage 08 pooled it (the performance
shelf's part 03), and the pool is the feel at
count — the text is the confirmation (part 01's witness), and the pool is the confirmation
that survives the clump.
The curve vocabulary, the three the text uses, as the feel's tone:
| Curve | The feel | The text's use |
|---|---|---|
TRANS_BACK (the overshoot) | the pop — the scale goes past 1.6 and settles back | the entrance: the number arrives, the overshoot is the arrival's energy |
TRANS_QUAD (the natural) | the settle — the ease the eye expects from gravity | the drift: the number rises the way a thing rises |
TRANS_CUBIC (the confident) | the commit — the stronger ease, the deliberate move | the audio shelf's part 01 magnet arc, the survivor's stage 06 push: the coin commits to leaving |
The curve is the tone of the motion — the BACK is the exclamation, the QUAD is the
sentence, the CUBIC is the period. A float text on all-QUAD reads as flat (the number
rises and fades, no arrival); the BACK entrance is the arrival the eye confirms. Part 04's
loop is where you find the curve that's the right tone for the event — a damage number pops
(BACK), a heal number settles (QUAD), a level-up commits (CUBIC) — the tone is the
event's kind, made motion.
D — The sound's ride: the fact and the promise#
The audio shelf's part 04 is the sound's version of this part's rule, and the hit's sound is
the two together: the thock (the hit's confirmation) rides the fact — the
damage_received signal, the same frame as the flash and the shake (part A's shake call and
the audio's play are the same event's two witnesses). The whoosh (the swing's promise)
rides the presentation — the swing's start (the animation's frame 0, the render clock),
allowed to be two milliseconds early (the wind, the audio part 04's promise). The thock is
the fact and the whoosh is the promise, and the split is the boss fights shelf's tell/active on the audio — the whoosh sells the
windup, the thock confirms the hit.
The shake and the thock together are the hit's full confirmation: the screen answers (the camera's lie, part A) and the mix answers (the thock's fact, part D) on the same frame, and the player's eye and ear agree that the sword connected. A hit with the flash and the shove but no shake and no thock is a hit the target confirms and the world doesn't — the number moves, the golem hops, and the screen and the silence say "that was fine." The four witnesses are the hit; the four are the feel.
E — The event's four witnesses, as a checklist#
The part's deliverable is the checklist, because part 04 runs it: for your game's central hit (the sword, the crossbow, the golem's touch), name the four witnesses and the frame each rides:
| Witness | The frame it rides | The node it's on |
|---|---|---|
| The flash | the fact (damage_received) | the target's skin |
| The shove | the fact (the hitbox's direction) | the target's velocity |
| The shake | the fact (the event's caller) | the camera's offset |
| The thock | the fact (the signal) | the SFX bus (the pool's voice) |
All four on the fact frame (the overview's rule), the whoosh (the promise) on the presentation. A witness on the wrong frame is the feel's bug — the shake on the swing's start (the world answers before the hit), the thock on the animation (the audio part 04's 2.78 ms, the lie the ear files). The checklist is the audit: four witnesses, four fact frames, one promise on the presentation.
F — Commit#
git add . && git commit -m "feel 03: camera shake, the float text's curves, the sound's ride"
Checkpoint — definition of done#
- The shake is on the camera's
offset(part A) — the world's positions are unchanged during the shake (verify: a golem'sglobal_positionis the same with the shake on as off; the rendered position differs, the physical one doesn't) - The decay is
exp(-rate * delta)(part B) — the shake settles at the same rate at 60 and 144 fps (the frame-rate-independent form, the 3D farm's stage 03 trap met on the shake) - The float text's curves are named (part C's vocabulary) — the
BACKpop, theQUADdrift, theCUBICcommit, and the tone each is (the exclamation, the sentence, the period) - The thock rides the fact and the whoosh rides the presentation (part D) — the audio shelf's part 04 split, on the hit's two sounds
- The four-witness checklist (part E) is filled for your game's central hit — four witnesses, four fact frames, one promise, and the frame each rides named
- Zero warnings; committed
Stretch (no instructions)#
A shake that's directional: the camera's offset isn't a random direction (part A's
rotated(randf() * TAU)) but away from the hit — a hit from the left shakes the camera
right (the world answers the hit's direction, not just its fact). The direction is the
hitbox's (part 02's D, the shove's direction), and the shake is the shove's screen — the
target goes left, the camera goes right, and the relative motion is the impact. Find the
magnitude where the directional shake reads as the hit's direction and not as the camera
drifting — the tuning loop, part 04, on the direction's magnitude.
If you get stuck#
- The shake moves the enemies (the golems orbit the shake, the chase reads shaken
positions) → the shake is on the world (the
Entitiesnode's position, part A's lie made physical). The shake is on the camera'soffset— the render-only displacement, the world's positions untouched. The camera lies; the world doesn't. - The shake lingers (the next hit starts on a still-shaking camera, the shakes add) → the
decay's rate is too low (part B), or the
shake(amount)is adding (+=) instead of the bigger-wins (maxf). Part A'smaxfis the design — a new hit re-sets the magnitude to the bigger of the two, it doesn't stack; the stack is the part 02 knockback's (the force stacks), not the shake's (the presentation doesn't). - The shake feels different at 144 fps (faster decay, the part B trap) → the decay is the
naive
(1 - rate * delta)or the fixed-amount * delta, not theexpform. Theexpcompounds frame-rate-independently; the naive forms don't. The 3D farm's stage 03 stretch is the same trap on the facing; the answer is the sameexp. - The float text is flat (the number rises and fades, no arrival) → the entrance curve is
QUAD(the sentence) where it should beBACK(the exclamation). Part C's vocabulary — the pop is theBACK's overshoot, and the tone is the event's kind. A damage number that doesn't pop is a damage number that doesn't arrive.