T.TAO
Back to Blog
/6 min read/Game Engine

Unity Engine #5 Physics

#Unity#GameEngine#CSharp

This note covers collision detection and the characteristics of the Rigidbody component.

When physics actually updates

Unity's physics does not run in Update; it runs in FixedUpdate. The difference:

  • Update is called once per rendered frame, so the interval floats with the frame rate.
  • FixedUpdate is called on a fixed timestep, 0.02 s (50 Hz) by default, independent of the frame rate. A slow frame may call it several times; a very fast frame may not call it at all.

From which one rule follows: every force and velocity change applied to a rigidbody belongs in FixedUpdate. Put it in Update and the number of times you apply a force varies with the frame rate, so the game feels different on different machines.

C#void FixedUpdate()
{
    rb.AddForce(direction * force, ForceMode.Force);
}

Conversely, input polling must live in Update. Input.GetKeyDown is only true for one frame, and FixedUpdate will miss it. The usual shape is to record the intent in Update and consume it in FixedUpdate:

C#private bool jumpQueued;

void Update()
{
    if (Input.GetKeyDown(KeyCode.Space)) jumpQueued = true;
}

void FixedUpdate()
{
    if (jumpQueued)
    {
        rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        jumpQueued = false;
    }
}

Time.fixedDeltaTime can be lowered for more accurate physics, at a linear cost in CPU time. On mobile you usually go the other way and raise it to 0.033 (30 Hz).

Rigidbody

Attaching a Rigidbody hands the object over to the physics engine. The properties that matter:

Mass. It only affects momentum exchange during collisions, not how fast something falls β€” in a vacuum a feather and an iron ball fall at the same rate, and Unity agrees. Raising mass to make something "fall faster" does nothing; change Gravity Scale (2D) or add your own force instead.

Drag / Angular Drag. Each physics step damps the velocity by velocity *= (1 - drag * fixedDeltaTime). It is not a model of air resistance, just a feel knob.

Is Kinematic. The body stops responding to forces and collisions, but it still pushes other bodies and still fires collision callbacks. Moving platforms and animation-driven characters usually use this. One caveat: move a kinematic body with rb.MovePosition(), not by assigning transform.position. The former interpolates inside the physics system so collision detection stays correct; the latter is a teleport and goes straight through walls.

Interpolate. Physics updates at 50 Hz while rendering may run at 120 Hz, so on the frames in between the body does not move and the motion looks stepped. Interpolation makes rendering interpolate between two physics steps, at the cost of being one physics step behind. Turn it on for the player-controlled body and leave it off for everything else.

Collision detection

Discrete vs continuous

The default Discrete mode checks once per physics step whether the current positions overlap. A fast object such as a bullet can cross an entire wall within one step, with no overlap at either sample. That is tunneling.

The fix is to change Collision Detection to:

  • Continuous β€” sweep tests against static colliders, which stops tunnelling through level geometry.
  • Continuous Dynamic β€” also sweeps against other dynamic bodies that are themselves continuous.
  • Continuous Speculative β€” based on speculative contacts, cheaper than the other two and it handles rotation as well. This is the recommended default these days.

Continuous detection is expensive; enable it only on the handful of objects that genuinely need it. The other approach is to skip the rigidbody entirely and Physics.Raycast each frame from the previous position to the current one β€” which is what most bullets actually do.

Colliders and triggers

Collision callbacks fire only if both objects have a Collider and at least one has a non-kinematic Rigidbody. Two colliders with no rigidbody between them produce nothing at all β€” this is the single most common cause of "my callback never fires".

Ticking Is Trigger removes the physical response and leaves only overlap reporting:

Solid collisionTrigger
OnCollisionEnterOnTriggerEnter
OnCollisionStayOnTriggerStay
OnCollisionExitOnTriggerExit

OnCollisionEnter(Collision other) carries contact points, normals and impulse; OnTriggerEnter(Collider other) gives you only the other collider. When you need to know how hard the hit was β€” to pick an impact sound, say β€” use collision.impulse from the former.

Collider shapes

Cheapest to most expensive: Sphere < Capsule < Box < convex Mesh < concave Mesh (static only).

A Mesh Collider is concave by default and cannot take part in dynamic collisions; ticking Convex lets it carry a rigidbody but caps it at 255 vertices. The practical rule is to approximate with primitives whenever possible and reserve Mesh Colliders for static level geometry. One capsule is enough for a character; individual fingers do not need colliders.

The layer collision matrix

The matrix under Project Settings β†’ Physics decides which layer pairs are tested at all. It is the cheapest physics optimization there is: untick the bullet/pickup pair and the engine stops considering that combination in the broad phase.

In a project with many layers, the default all-ticked state means every O(nΒ²) pair has to be considered. Ten minutes spent on this table often beats optimizing any single script.

Practical notes

  1. Do not do heavy work inside OnCollisionEnter. The callback runs inside the physics step; a burst of instantiation or Destroy calls stretches that step directly. Record the event and handle it in Update or on the next frame.
  2. Physics.autoSimulation can be turned off. For turn-based or deterministic gameplay, calling Physics.Simulate(step) yourself makes the simulation fully reproducible.
  3. Rigidbody.Sleep. Resting bodies fall asleep and drop out of the solver. If something refuses to be pushed, it is usually asleep β€” WakeUp() it or apply a large enough force.
  4. Scale is a trap. Non-uniform scale (x, y and z not equal) makes Sphere and Capsule colliders behave unpredictably, because Unity picks one component to approximate with. When physics misbehaves on a character with non-uniform scale, check this first.

Nearly every "haunted" physics bug comes down to one of four things: the wrong update hook (Update vs FixedUpdate), a detection mode that is not strong enough (discrete vs continuous), an unmet callback precondition (no rigidbody), or an unclean scale.

In this series

Unity Engine β†’
  1. 01Unity Engine #0 C#
  2. 02Unity Engine #1 Memory
  3. 03Unity Engine #2 System & Networking
  4. 04Unity Engine #3 Design Patterns
  5. 05Unity Engine #4 MonoBehaviour
  6. 06Unity Engine #5 Physics
  7. 07Unity Inspector #1 Basic Operations