10 KiB
Game Feel & Juice (screenshake, hitstop, tweening/easing, particles, squash & stretch, camera, SFX layering)
The canon: Steve Swink's Game Feel, Vlambeer/"Juice it or lose it" (Martin Jonasson & Petri Purho), and Jan Willem Nijman's "The art of screenshake" (see Sources). "Juice" = generous, layered, non-gameplay-affecting feedback that makes every input feel physical and satisfying. Focus: what an AI builder should reflexively add so games feel alive instead of static, without hurting readability or perf.
1. The core principle
Every meaningful action should produce disproportionate, multi-sensory feedback. A single "hit" should simultaneously trigger several of: sound, particles, screenshake, hitstop, flash, knockback, squash/stretch, and a number/pop. The gameplay math can stay simple — the feel comes from the feedback layer stacked on top. The classic demo takes the same identical Breakout and makes it feel amazing purely by adding these layers.
Rule: Separate "simulation" from "presentation." Feedback (shake, tween, particles, flash) must never change gameplay outcomes — it's cosmetic. This keeps physics deterministic (important for netcode/replays) while letting you pile on juice freely.
2. Screenshake (the highest impact-per-effort effect)
- Offset the camera (or a camera container), not the world objects. Apply a random offset each frame from a trauma value that decays over time.
- Use trauma², not linear (Squirrel Eiserloh's "Juicing Your Cameras"): store
traumain [0,1], setshake = trauma * trauma(ortrauma³). Add trauma on events (small hit +0.3, explosion +0.8), decaytrauma -= decay * dteach frame. Squaring makes small hits subtle and big hits explosive. - Offset with noise, not pure random, for smoother shake:
offsetX = maxOffset * shake * (noise(t) ); also shake rotation slightly (maxAngle * shake * noise) — rotational shake reads as more violent. - Directional shake for directional hits (kick the camera opposite the impact) reads better than omnidirectional for melee/recoil.
- Cap it & scale it down for accessibility. Excessive shake causes motion sickness — expose a "screen shake" slider/toggle. Never shake so hard UI/targets become unreadable.
- Scale offsets by
dt-independent amount but decay bydtso it's frame-rate independent.
Nijman's rules also include: more bullets, bigger bullets, muzzle flash, impact effects, permanent decals, camera lerp/lookahead, camera kick, sleep/hitstop, gun delay, knockback, screen shake, and even a little randomness in everything.
3. Hitstop / hitstun / "sleep" (freeze frames on impact)
- On a big hit, freeze the game (or just the two involved entities) for a few frames (~30–120ms), then resume. This sells impact enormously — the brain reads the pause as force.
- Implement as a global time scale or a short "freeze frames" counter: while frozen, skip the gameplay update (or set
timeScale=0) but keep rendering and keep the flash/particles visible. Common: freeze ~2–6 frames for normal hits, longer for finishers. - Keep hitstop short; too long feels laggy/unresponsive. Often paired with a brief hit flash (tint the sprite white for 1–2 frames) and knockback that starts after the freeze.
- Distinguish hitstop (brief freeze for feel) from hitstun (gameplay state where a hit character can't act) — the latter is real gameplay, the former is pure juice.
4. Tweening & easing (motion that isn't linear)
- Almost nothing should move linearly. Real, satisfying motion accelerates/decelerates. Use easing functions for UI transitions, pickups flying to the HUD, menus, popups, camera moves, damage numbers.
- Common curves (see easings.net for exact formulas/graphs):
- easeOutQuad/Cubic — arrive gently (great for things settling into place).
- easeInQuad/Cubic — start slow (things launching).
- easeOutBack — slight overshoot then settle (pop-in for UI/pickups — very juicy).
- easeOutElastic / easeOutBounce — springy/bouncy (buttons, coin pickups). Use sparingly.
- easeInOutQuad — smooth both ends (camera, generic).
- Use a tween library rather than hand-rolling: GSAP (best-in-class, huge easing set, timelines), @tweenjs/tween.js (lightweight, common in Three.js), or the engine's built-in (Phaser
this.tweens.add). Tweens compose (chains, delays, yoyo, repeat) and auto-handle dt. - Kill/clean up tweens when the target is destroyed or the scene shuts down (leaked tweens mutating dead objects = crashes/bugs). Phaser: kill on
SHUTDOWN; GSAP:.kill(). - A spring/damped-lerp (
x += (target - x) * (1 - exp(-k*dt))) is a great procedural alternative for continuous following (cameras, cursors, UI) — always frame-rate correct if you use theexpform rather thanx += (target-x)*0.1(the naive lerp is frame-rate dependent — see §6).
5. Particles & squash-and-stretch
- Particles everywhere: dust on landing/footsteps, sparks on hit, debris on death, trails on projectiles, confetti on win, ambient motes. They cost little and add enormous life. Use the engine's particle system (Phaser
add.particles, Three.js points/instanced sprites) and pool them (no per-emit allocation — seethreejs-foundational.md). - Squash & stretch (Disney's foundational animation principle): deform on acceleration to convey weight/speed while preserving apparent volume.
- Jump: stretch vertically (taller/thinner) on takeoff, squash (shorter/wider) on landing, then spring back via
easeOutBack/elastic. - Preserve volume: if you scale
ybys, scalexby ~1/s, so it doesn't look like it's growing. - Drive it from velocity (stretch along the movement direction) or trigger on discrete events (land, hit, shoot recoil).
- Jump: stretch vertically (taller/thinner) on takeoff, squash (shorter/wider) on landing, then spring back via
- Anticipation + follow-through: wind up before a big action (tiny reverse/squash) and overshoot after — even a couple frames sells it.
- Pop numbers & flashes: floating damage numbers that rise + fade + scale-pop, combo counters, "+10" pickups. Cheap, huge readability + satisfaction win.
6. Camera feel (lerp, lookahead, deadzone, punch)
- Never hard-snap the camera to the player. Smoothly follow (
lerp/spring) toward the target so motion is fluid.- Frame-rate-correct smoothing:
pos += (target - pos) * (1 - Math.exp(-k * dt)). The naivepos += (target - pos) * 0.1is frame-rate dependent (faster on 144Hz than 60Hz) — a common bug; use the exp form or a fixed-timestep camera update.
- Frame-rate-correct smoothing:
- Lookahead: offset the camera slightly in the direction of movement / where the player is aiming, so the player sees more of what's ahead. Ease the offset in/out.
- Deadzone / soft zone: don't move the camera until the player leaves a central box — prevents nauseating micro-jitter from small movements.
- Camera punch/kick on impacts (a quick zoom-in or positional kick that eases back) complements screenshake.
- Clamp the camera to level bounds; round the final camera position to whole pixels for pixel-art games to avoid shimmer.
7. SFX layering & audio feel
- Layer sounds for richness: a single "shoot" = a low thump + a mid body + a high click/transient. Impacts = hit + debris + a tail.
- Pitch-randomize repeated sounds (±5–15%) so rapid fire / footsteps don't sound robotic (
playbackRate/ Howlerrate). Slightly randomize volume too. - Sound is feedback: every action should have audio, and it should be tight (low latency) and satisfy on first frame of the action (see the audio skill for Web Audio/Howler, mobile unlock, buses).
- Duck music briefly under big events; add a subtle low-frequency "boom" on explosions.
8. Don't over-juice (readability & perf guardrails)
- Juice must not obscure gameplay. If shake/flash/particles hide the player, enemies, or projectiles, dial back. Readability > spectacle.
- Accessibility: provide toggles/sliders for screen shake, flashing, and reduced motion (respect
prefers-reduced-motion). Heavy flashing risks photosensitivity — avoid rapid full-screen strobing. - Perf: pool particles/tweens, cap concurrent particles, don't allocate in the hit path. Juice is cheap but "thousands of unpooled particles per explosion" is not.
- Keep hitstop short and prediction-safe in multiplayer (apply juice on the client only; never let it alter the authoritative sim).
Defaults to apply
- Auto-juice defaults: whenever the builder generates a hit/pickup/death/land/win event, reflexively attach the stack: sound + particles + short hitstop + white flash + screenshake(trauma²) + easeOutBack pop / squash-stretch + floating number. Same simple gameplay, dramatically better feel — this is the single biggest perceived-quality lever.
- Ship a tiny reusable "juice" toolkit in generated games: a
trauma-based shake (squared, noise-driven, decaying), ahitstop(frames)helper (timeScale/freeze-count), aflash(sprite)helper, easing/tween helpers (or GSAP/tween.js), and frame-rate-correct camera follow with lerp + lookahead + deadzone. - Always use the
exp-based lerp for cameras/followers so smoothing is frame-rate independent — bake this in to avoid the classic 144Hz-vs-60Hz bug. - Separate presentation from simulation so juice never affects gameplay/netcode. Provide screenshake/flash/reduced-motion toggles by default for accessibility.
Sources
- Martin Jonasson & Petri Purho — "Juice it or lose it" (GDC talk): https://www.youtube.com/watch?v=Fy0aCDmgnxg ; companion "Game feel" write-up on grapefrukt.
- Jan Willem Nijman (Vlambeer) — "The art of screenshake": https://www.youtube.com/watch?v=AJdEqssNZ-U
- Steve Swink — Game Feel: A Game Designer's Guide to Virtual Sensation (book), gamefeelbook.com.
- Squirrel Eiserloh — "Juicing Your Cameras With Math" (GDC, trauma² screenshake): https://www.youtube.com/watch?v=tu-Qe66AvtY
- easings.net — easing function reference with formulas & graphs: https://easings.net/
- Robert Penner's easing equations (origin of the standard easings): http://robertpenner.com/easing/
- GSAP docs (tweening/easing/timelines): https://gsap.com/docs/v3/ ; tween.js: https://github.com/tweenjs/tween.js
- Disney's 12 principles of animation (squash & stretch, anticipation, follow-through): https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation
- MDN —
prefers-reduced-motion: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion - "The Juice Factor" / GDC Vault talks on feedback & game feel.