Your sword connects. The enemy's health bar drops by 12. The animation plays out at a constant speed, the enemy stands exactly where it stood, and the whole exchange feels like pushing a button in a spreadsheet. The damage number is correct. The hit is not.
Impact is not a side effect of damage — it is a separate system you design, budget and tune. Most of it is measured in frames, costs almost nothing in art, and can be built in an afternoon. What it needs is a spec, so that "make it feel better" becomes a set of numbers somebody can argue about.
The feedback stack
Every good hit is a short stack of cues firing in a fixed order. Think of it as a timeline that starts on the frame contact is detected, with everything measured at 60 fps.
| Layer | Starts | Typical duration | What it communicates |
|---|---|---|---|
| Hit-stop (freeze) | Frame 0 | 2–12 frames | Weight and force |
| Impact SFX | Frame 0 | — | Material, size, crunch |
| Hit flash on target | Frame 0 | 3–6 frames | "That one registered" |
| Particle / spark | Frame 0–1 | 10–20 frames | Direction and type of damage |
| Camera shake | Frame 1–2 | 6–12 frames | Scale of the event |
| Knockback / stagger | After the freeze | 8–30 frames | Spacing and your turn to act |
| Damage number / bar drain | After the freeze | 10–20 frames | Exact arithmetic |
Two rules follow from the table. First, the cues that say "contact happened" must land on frame 0 — a sound that arrives three frames late reads as a different event, not a late version of the same one. Second, the cues that say "and here is what it cost" should land after the freeze, because during the freeze the player's eye is locked on the collision point and cannot read a number.
Hit-stop is the cheapest impact you can buy
Hit-stop means pausing the animation of the attacker, the target, or both for a handful of frames at the moment of contact. Fighting games have used it for decades — it is why a Street Fighter heavy punch feels heavy even though it is a sprite moving across a flat plane — and it is the single highest-return line of code in melee feel.
The reason it works: during the freeze, the player's input is still buffered but the world is not moving, so the brain reads the pause as resistance. Something stopped the weapon.
Starting durations
Durations worth starting from:
- Light attack / jab: 2–3 frames (33–50 ms)
- Medium attack: 4–6 frames
- Heavy attack / charged swing: 8–12 frames
- Parry or perfect block: 10–16 frames, plus a desaturation or slow-motion ramp
- Killing blow: 12–20 frames, often with a one-off camera push
- Hitting a wall or an invulnerable part: 6–10 frames with no damage cue — the freeze alone tells the player "this does not work"
Freeze the attacker, the target, or both?
Keep two separate values in your data: attackerFreeze and targetFreeze. Freezing only the attacker makes a hit feel absorbed and spongey. Freezing only the target makes the attacker feel like it swung through butter. Freezing both at roughly the same length reads as a collision, and freezing the target slightly longer (say 6 frames against 4) sells a stun without changing any combat numbers.
Important caveat: never freeze the input system. Hold the attacker's animation, but keep reading the buffer, or a 10-frame heavy hit will eat the player's follow-up and the game will feel like it drops commands.
Shake, flash and the rest of the garnish
Jan Willem Nijman's talk The Art of Screenshake is the standard reference here, and the core lesson is that these cues are additive — each one is almost invisible alone and the stack is what sells the hit. The second lesson, learned by every team that watches a playtester get motion sick, is that shake needs a budget.
Constraints for camera shake
Practical constraints for camera shake:
- Express amplitude as a fraction of screen height, not pixels, or it scales wrongly across resolutions: roughly 0.4% of screen height for a light hit, 1.5% for an explosion.
- Decay it. A shake that starts at full amplitude and falls to zero over 8–12 frames reads as an impact; constant-amplitude shake reads as a broken camera.
- Cap concurrent shakes: sum the requests, then clamp the total, or six enemies dying at once will throw the camera off the map.
- Ship a slider that scales all of it, defaulting to 100%, and let players set it to 0. This is an accessibility requirement, not a nicety, and it costs you one multiplier.
Hit flash is even cheaper: tint the target's sprite or mesh white (or a hot colour that is not in your palette) for 3–6 frames. It survives any visual noise and it is the cue that reads on a phone screen in daylight.
For audio, split each impact into two samples — a transient (the crack) and a body (the thud or ring) — and randomise pitch by about ±5% per hit. Identical samples fired five times a second become a machine-gun rattle that players call "cheap" without being able to say why. Tune with audio on, always: half the impact lives there.
Knockback and recovery are balance, not polish
The moment you add knockback, feedback stops being cosmetic and starts changing your game. Knockback sets spacing; spacing sets which attack is available next; that is combat design.
Spell it out in the same units as the rest of your metrics — character widths or tiles, never raw engine units:
- Light hit: 0.25 character widths, target recovers in 8 frames
- Heavy hit: 1.0 character width, recovers in 20 frames
- Launcher: 2.5 widths plus airborne state until landing
Then check the arithmetic. If a heavy hit pushes the enemy 1.0 widths away but your follow-up attack only reaches 0.8 widths, your own combo is impossible and no amount of hit-stop will fix the complaint that combat "doesn't flow". This is exactly the kind of interaction worth writing into the mechanics entry for each attack in your design doc — GameDesignerX's mechanics and balance sheets are a reasonable home for it, because the feedback values and the damage values want to live in the same row where you can see them conflict.
A feedback spec you can hand to a programmer
One row per attack, filled in before implementation:
| Attack | atkFreeze | tgtFreeze | Shake | Flash | Knockback | Recovery |
|---|---|---|---|---|---|---|
| Jab | 2f | 3f | 0.4% | 4f | 0.25w | 8f |
| Heavy | 9f | 11f | 1.2% | 6f | 1.0w | 20f |
| Parry | 14f | 14f | 0.6% | 6f | 0.1w | 6f |
| Blocked | 7f | 4f | 0.3% | none | 0.4w | 10f |
Numbers beat adjectives, and they give you something to diff: when somebody says combat felt better last month, you can find out what changed.
Tuning pass checklist
Run this with one build, one tester and a thirty-minute window:
- Turn everything off — no freeze, no shake, no flash, no sound. Play for two minutes. This is your baseline and it will feel terrible.
- Add hit-stop only. Tune until a light and a heavy hit are distinguishable with your eyes closed to everything else.
- Add impact audio. Re-check the freeze lengths; good audio usually lets you shorten them.
- Add flash and particles.
- Add shake last, and tune it down until you are slightly uncomfortable with how subtle it is.
- Play a crowded fight for five minutes straight and watch for nausea, lost readability and stacked shake.
- Have one person who has never played do the same, and record which cue they mention first. If nobody mentions anything, it is working; if they mention the shake, it is too strong.
Failure modes to watch for
- Freeze-eating inputs. Symptom: "the controls feel laggy." Cause: input disabled during hit-stop.
- Shake on every frame of a sustained effect. A flamethrower that shakes continuously is unplayable; shake on ignition, then stop.
- Feedback on the wrong actor. Freezing the attacker for a projectile hit two screens away breaks the connection between cause and effect.
- No negative feedback. Hitting armour, a wall or an immune phase needs its own distinct stack, or players will assume the game dropped their attack.
Impact feel has a reputation for being the fuzzy, intuitive part of design. It is not. It is six numbers per attack and an ordering rule, and once they are in a table, "this hit feels weak" becomes a change you can make on purpose.