Game feel
Add feedback cues that make interaction feel responsive.
Game feel
Game feel is the layer of feedback that makes actions feel tangible: a flash when the player takes damage, a shake when something explodes, a sound that clicks with each tween step, particles that trail behind a moving object. In ExoJS, these effects are compositions of primitives you already know — tweens, filters, particles, view manipulation, audio — applied at the moment of an event.
Damage flash
When the player takes damage, flash the sprite red for a fraction of a second and tween back to normal. Use a ColorFilter on the sprite and animate its color with a tween:
import { ColorFilter, Signal } from '@codexo/exojs';
init(loader) {
this.player = new Sprite(loader.get(Texture, 'hero'));
this.flash = new ColorFilter(new Color(255, 255, 255, 1));
this.player.filters = [this.flash];
this.onDamage = new Signal();
this.onDamage.add(() => {
// Flash red instantly
this.flash.color.set(255, 80, 80, 1);
// Tween back to white over 200ms
this.app.tweens.create(this.flash.color)
.to({ r: 255, g: 255, b: 255 }, 0.2)
.start();
});
}
The key insight: the ColorFilter multiplies the sprite’s rendered output by the filter color. White ([255, 255, 255, 1]) is identity — no tint. Red mutes green and blue. The tween interpolates the r, g, b components of the Color object back to 255 over 200ms, producing a smooth recovery. Since ColorFilter exposes its internal Color object as a live getter, you can tween or mutate it directly — no filter reconstruction needed.
Screen shake on explosion
View.shake(intensity, durationMs, { frequency, decay }) is the simplest feedback effect in ExoJS. Fire it on any dramatic event:
this.app.input.onPointerTap.add(pointer => {
// Position the burst at the click location (relative to system position)
this.burstPos.set(pointer.x - this.particles.position.x, pointer.y - this.particles.position.y);
this.burst.reset();
// Shake the view: 22px intensity, 280ms, 26Hz oscillation, with decay
this.view.shake(22, 280, { frequency: 26, decay: true });
});
Combine it with a particle burst for a complete explosion feel — particles provide the visual debris, the shake provides the impact. The shake displaces the view center; the particles render inside that view, so they move with the shake. The effect is coherent: the whole scene rattles.
Tween-heavy feedback
Tweens are the workhorse of game feel. A few reusable patterns:
// Scale punch — grow on hit, settle back
this.app.tweens.create(this.sprite.scale)
.to({ x: 1.4, y: 1.4 }, 0.08)
.yoyo()
.repeat(1)
.easing(Ease.cubicOut)
.start();
// Tilt wobble — oscillate rotation
this.app.tweens.create(this.sprite)
.to({ rotation: 8 }, 0.06)
.easing(Ease.cubicOut)
.yoyo()
.repeat(2)
.start();
// Fade flash — full white overlay that fades out
this.overlay.alpha = 1;
this.app.tweens.create(this.overlay)
.to({ alpha: 0 }, 0.3)
.start();
Each pattern is fire-and-forget — call .start() inside an event handler and the tween runs to completion. No per-frame tracking, no cleanup. The existing tween manager handles all active tweens each frame.
Continuous feedback: particles + audio
For held actions — thrust, charging, spinning up — continuous feedback creates presence:
update(delta) {
const thrustMag = Math.hypot(this.thrust.x, this.thrust.y);
if (thrustMag > 0.05) {
// Particles trail behind the ship
this.rate.value = 900 * thrustMag;
this.particles.setPosition(
this.ship.x - Math.cos(this.angle) * 28,
this.ship.y - Math.sin(this.angle) * 28,
);
// Audio hum scales with thrust
this.engine.volume = 0.08 + thrustMag * 0.32;
} else {
this.rate.value = 0;
this.engine.volume = 0;
}
this.particles.update(delta);
}
A RateSpawn with a Constant rate lets you dynamically change rate.value each frame — the spawn module reads the current value, not a snapshot. The engine hum uses an OscillatorSound with volume tied to thrust magnitude. The combined effect: moving produces particles and sound; stopping kills both. The player feels the connection between input and response.
Layering timing
The best feedback combines immediate and gradual responses. When an event fires:
- Frame 0: Screen shake starts (instant displacement). Particle burst spawns (instant visual). Filter flash fires (instant color change).
- Next 200ms: Tween recovers the filter back to white. Shake decays.
- Next 500ms: Particles fade out via
AlphaFadeOverLifetime.
The player experiences a sharp impact followed by a smooth settle — contrast makes the impact feel stronger. No orchestration framework needed: just call .start() on each effect from the same event handler.
Examples
Click to trigger a red flash on a sprite — ColorFilter animated via tween.
Click anywhere to spawn particles and shake the view — the one-two punch of visual debris and camera impact.
Where to go next
The next recipe, UI patterns, covers in-canvas UI construction — dialog systems, typewriter text, progress indicators, and when to use DOM instead.