Game Feel · Timing · core
Invincibility Frames
after a hit, ignore damage for a short, clearly signalled window
Invincibility frames (i-frames) make a character ignore damage for a short window after a hit or during a dodge, with blinking to show it.
Orange shots hit the cyan player on a steady rhythm. A hit flashes red, removes a green health pip and starts the yellow i-frame bar; while it drains the player blinks and shots pass through grey. Set the window to 0 to watch health melt, or shorten the gap between shots.
What it is
Without protection, a character standing in a stream of bullets or touching an enemy loses health every frame, and the player dies before they can react. Mercy invincibility gives a window after each hit, often 0.5–2 s, where further damage is ignored. The window gives the player time to escape and makes damage predictable: one mistake, one hit.
Dodge rolls use the same mechanism on purpose. Part of the roll animation, not all of it, is invulnerable, so dodging through an attack is a timing skill. In that case the window is a range of animation frames, not a timer after damage.
The window must be visible. The classic signal is blinking the sprite or mesh at 8–15 Hz, sometimes with a tint. Keep the hurtbox in place and let the damage check refuse the hit. Disabling collisions would also stop pickups and triggers (see hitboxes and hurtboxes).
Key formulas
- Damage accepted when
- Blink visibility
f is the blink rate in Hz (8–15 is typical).
Rules of thumb
- Mercy window after damage: 0.6–1.5 s in action games, up to 2 s in platformers.
- Dodge-roll i-frames: roughly 0.2–0.4 s inside a 0.5–0.8 s roll, starting a few frames in.
- Blink at 8–15 Hz. Stop blinking a few frames before the window ends only if you also tell the player it is ending.
- Store the window per damage source type if needed, e.g. let environmental hazards bypass it to prevent standing in lava forever.
Implementation
const IFRAMES = 1.0 // seconds
const BLINK_HZ = 10
class Health {
hp = 5
invulnerable = 0
damage(amount: number) {
if (this.invulnerable > 0) return false // ignored
this.hp -= amount
this.invulnerable = IFRAMES
return true
}
update(dt: number, mesh: THREE.Object3D) {
this.invulnerable = Math.max(0, this.invulnerable - dt)
const phase = Math.floor(this.invulnerable * BLINK_HZ * 2)
mesh.visible = this.invulnerable === 0 || phase % 2 === 0
}
}Common mistakes
- Giving no visual cue, so players cannot tell when they are safe and the rule feels random.
- Disabling the whole collider instead of ignoring damage, which also breaks pickups, triggers and enemy contact pushes.
- Blinking by toggling
visibleon a parent that also hides shadows or UI attached to the character. - Frame-counted windows that last half as long at 120 fps.
- Letting the player chain i-frames forever, e.g. dodge-cancelling into another dodge with no recovery.
Prompt
Implement invincibility frames for a TypeScript Three.js action game. Create a Health component with hp and an invulnerable timer in seconds. damage(amount, source) returns false and does nothing while invulnerable (unless source.bypassIFrames is true); otherwise it subtracts hp and starts a 1.0 s window. Add a dodge that sets invulnerable for 0.3 s starting 0.05 s into a 0.6 s roll. Blink the character mesh at 10 Hz while invulnerable (toggle material opacity, not visibility, so shadows stay). Write Vitest tests: hits every frame for 1 s at 60 fps with a 0.25 s window deal exactly 4 damage, and results match at 144 fps.
Related
Further reading
Updated 2026-11-01