When a hit lands and nothing moves, combat feels like poking a wall. Knockback, a brief stun, a white flash and a tiny freeze frame turn the same hit into something players feel. This guide adds Unity knockback and hit reactions for Rigidbody, Rigidbody2D and CharacterController characters: pushing the target away from the hit, locking input briefly so the push isn’t cancelled, a hit flash and hit stop.
Working Out the Knockback Direction
Push the target away from whatever hit it. The usual direction is from the attacker (or hit point) to the target, flattened so ground characters aren’t pushed into the floor, plus a little upward lift:
Vector3 KnockDirection(Vector3 from, Vector3 to, float lift = 0.3f)
{
Vector3 dir = to - from;
dir.y = 0f;
if (dir.sqrMagnitude < 0.0001f) dir = -transform.forward; // attacker exactly on top
return (dir.normalized + Vector3.up * lift).normalized;
}
In 2D platformers, players usually expect a fixed arc: up and away horizontally, regardless of exact angles. Use new Vector2(Mathf.Sign(to.x - from.x), 0.6f).normalized.
For projectiles, using the projectile’s velocity direction often feels better than the hit point, since a shot from behind should push forward.
Knockback With a Rigidbody
The cleanest method is an instant velocity change, ignoring mass:
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class Knockback : MonoBehaviour
{
[SerializeField] float stunTime = 0.25f;
[SerializeField] float resistance = 0f; // 0 = full knockback, 1 = immune
Rigidbody rb;
public bool IsStunned => Time.time < stunnedUntil;
float stunnedUntil;
void Awake() => rb = GetComponent<Rigidbody>();
public void Apply(Vector3 direction, float strength)
{
float s = strength * (1f - resistance);
if (s <= 0f) return;
rb.linearVelocity = Vector3.zero; // consistent result every hit
rb.AddForce(direction * s, ForceMode.VelocityChange);
stunnedUntil = Time.time + stunTime;
}
}
ForceMode.VelocityChange adds velocity directly, so a strength of 8 always means “8 units per second”, whatever the mass. Use ForceMode.Impulse if you want heavier enemies to move less automatically. Zeroing velocity first makes every knockback consistent; without it, a running player gets knocked back less than a standing one.
The 2D version is the same with Rigidbody2D and ForceMode2D.Impulse. Rigidbody2D has no VelocityChange mode, so either multiply by mass or set rb.linearVelocity = direction * strength directly.
Stun: Stop Movement Code Fighting the Push
The most common knockback bug: you apply the force and nothing visibly happens. Your movement script sets velocity every frame from input, overwriting the knockback on the very next physics step.
The fix is a short stun. While stunned, the movement script doesn’t write velocity:
void FixedUpdate()
{
if (knockback.IsStunned) return; // let physics carry the push
Vector3 v = moveInput * speed;
rb.linearVelocity = new Vector3(v.x, rb.linearVelocity.y, v.z);
}
Stun time is a design choice: 0.1 to 0.2 seconds for the player (enough to feel the hit without losing control), longer for enemies so players can follow up. Drag or friction slows the body during the stun. If your Rigidbody has zero linear damping, knocked back enemies slide forever on flat ground; give them some damping or apply your own deceleration during the stun.
For enemies using a NavMeshAgent, disable the agent’s movement during the stun (agent.isStopped = true and agent.updatePosition = false with a Rigidbody, or move the agent manually with agent.Move), then resume. The agent otherwise snaps the enemy back to its path.
Knockback With a CharacterController
A CharacterController has no physics velocity, so keep your own knockback velocity and decay it:
using UnityEngine;
public class CCKnockback : MonoBehaviour
{
[SerializeField] float decay = 8f; // how fast knockback fades
CharacterController cc;
Vector3 velocity;
void Awake() => cc = GetComponent<CharacterController>();
public bool Active => velocity.sqrMagnitude > 0.05f;
public void Apply(Vector3 direction, float strength) => velocity = direction * strength;
void Update()
{
if (!Active) return;
cc.Move(velocity * Time.deltaTime);
velocity = Vector3.Lerp(velocity, Vector3.zero, decay * Time.deltaTime);
}
}
Your normal movement script can still run; the two Move calls add up. If you want a hard stun, skip input movement while Active is true. Vertical knockback should feed into your controller’s own vertical velocity, so gravity takes over naturally.
Hit Flash
A quick white flash tells the player “that hit landed”. With a sprite or a URP Lit material, swapping the colour is easiest:
using System.Collections;
using UnityEngine;
public class HitFlash : MonoBehaviour
{
[SerializeField] Renderer[] renderers;
[SerializeField] Color flashColor = Color.white;
[SerializeField] float duration = 0.08f;
static readonly int BaseColor = Shader.PropertyToID("_BaseColor");
static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor");
MaterialPropertyBlock block;
void Awake() => block = new MaterialPropertyBlock();
public void Flash() { StopAllCoroutines(); StartCoroutine(DoFlash()); }
IEnumerator DoFlash()
{
Set(EmissionColor, flashColor * 2f);
yield return new WaitForSeconds(duration);
Set(EmissionColor, Color.black);
}
void Set(int id, Color c)
{
foreach (var r in renderers)
{
r.GetPropertyBlock(block);
block.SetColor(id, c);
r.SetPropertyBlock(block);
}
}
}
This uses a MaterialPropertyBlock so it doesn’t create a material copy per enemy. It requires Emission to be enabled on the material so the emission keyword is on. For sprites, set spriteRenderer.color instead, or use a sprite shader with a “flash amount” property made in Shader Graph. Note that property blocks break SRP Batcher compatibility for that renderer, which is fine for the short duration of a flash. The SRP Batcher guide explains the trade-off.
Hit Stop
Hit stop is a tiny freeze (30 to 100 ms) at the moment of impact. It makes heavy hits feel heavy and is used in almost every action game.
using System.Collections;
using UnityEngine;
public class HitStop : MonoBehaviour
{
static HitStop instance;
bool running;
void Awake() => instance = this;
public static void Do(float seconds)
{
if (instance && !instance.running) instance.StartCoroutine(instance.Freeze(seconds));
}
IEnumerator Freeze(float seconds)
{
running = true;
float old = Time.timeScale;
Time.timeScale = 0f;
yield return new WaitForSecondsRealtime(seconds);
Time.timeScale = old;
running = false;
}
}
Keep it short and only on meaningful hits. Freezing on every bullet becomes stutter. Be careful combining this with a pause menu: if the player pauses during a hit stop, the restore might unpause the game. Have the pause system own timeScale, or check a paused flag before restoring. Pair hit stop with a small camera shake from the Cinemachine Impulse guide and more ideas from the game feel guide.
Wiring It to the Damage System
If you use the Health component from the damage system guide, add a reaction component that listens to its Damaged event and calls knockback, flash and hit stop. The DamageInfo.Point and Source fields give you the direction. Keeping reactions in a separate component means you can give a boss knockback resistance or a crate no knockback at all without touching Health.
📚 Make combat feel great
Hit stop, knockback and screen shake are classic game feel tools. Several books on our game design reading list explain why they work.
Troubleshooting
AddForce knockback does nothing
Your movement code overwrites velocity every frame. Skip movement while stunned.
Enemy snaps back after knockback
A NavMeshAgent is still controlling position. Stop the agent during the stun.
Knockback strength varies
Existing velocity adds to the push. Zero velocity before applying it, and use VelocityChange for mass-independent results.
Hit flash doesn’t show
The material’s emission is disabled, or the shader property name is different. Check the shader’s property names in the material’s Inspector.
Game stays frozen after a hit stop
The coroutine’s object was disabled before restoring timeScale. Run hit stop from a persistent object, and restore timeScale in OnDisable.
FAQ
Impulse or VelocityChange?
VelocityChange for consistent knockback regardless of mass. Impulse when heavier things should move less.
How long should hit stop be?
About 0.03 to 0.1 seconds. Longer for big finishing blows.
Should the player get knockback too?
Usually yes, but lighter, with a short stun and invincibility frames so they can recover.
Can I knock back kinematic Rigidbodies?
No, forces don’t affect them. Move them yourself with MovePosition during the stun.
Does hit stop affect UI animations?
Only those using scaled time. UI on unscaled time keeps moving.
Reader questions
Asked by readers of this article and answered by AI. Behavior can differ between Unity versions, so test in yours.




Leave a Reply