Skip to content

2D physics

Box2D-backed rigidbodies, colliders, collision/trigger callbacks, and raycast/overlap queries, all driven from C#.

Fixed timestep

Physics runs on a fixed timestep (Time.FixedDeltaTime, 1/50s by default), independent of your frame rate. A jump tuned at 60fps behaves identically at 30fps or 144fps.

Two update methods matter for physics scripting:

MethodRunsUse for
OnUpdate(float ts)Once per rendered frameInput, camera, anything visual
OnFixedUpdate(float ts)Once per physics substep (0, 1, or several times per frame)Forces and impulses
csharp
protected override void OnFixedUpdate(float ts)
{
    if (m_WantsJump)
    {
        GetComponent<Rigidbody2DComponent>()?.ApplyLinearImpulse(new Vector2(0, m_JumpForce), true);
        m_WantsJump = false;
    }
}

Applying an impulse in OnUpdate instead makes its strength depend on frame rate — the exact bug the fixed timestep exists to avoid. Read input in OnUpdate, apply forces in OnFixedUpdate.

Rendering interpolates between physics substeps automatically (Time.FixedDeltaTime at 50Hz on a 144Hz display would otherwise judder) — nothing to opt into, and nothing to worry about: gameplay code always reads the true simulated position, never a smoothed one.

FixedDelta and MaxFrameDelta (the spiral-of-death clamp for a debugger pause or a stutter) are both authored in Project Settings → Physics Settings.

Colliders and sensors

BoxCollider2D/CircleCollider2D on an entity with a Rigidbody2D define its shape. Checking Is Sensor in the Properties panel turns a collider into a trigger: it reports overlap but applies no physical force, and other bodies pass straight through it. Use sensors for coins, checkpoints, death zones, and level exits.

Placeholder: the Properties panel showing a Rigidbody2D and Collider2D component, with the Is Sensor checkbox

Collision and trigger callbacks

csharp
public class Player : GameEntity
{
    protected override void OnCollisionEnter(Collision2D collision)
    {
        if (collision.Entity?.HasComponent<Damage>() == true)
            TakeHit();
    }

    protected override void OnTriggerEnter(GameEntity? other)
    {
        if (other?.Name == "Coin")
            Collect(other);
    }
}
CallbackFires forCollision2D/entity carries
OnCollisionEnter / OnCollisionExitA solid contact begins/ends (neither side is a sensor)Normal, contact point, and impulse
OnTriggerEnter / OnTriggerExitA sensor overlap begins/ends (at least one side is a sensor)Just the other entity

Five rules worth knowing:

  1. Both entities get the callback, once each, per event. There's no ordering guarantee between the two sides — don't write gameplay that assumes one fires before the other.
  2. The other entity can be null. If it was destroyed earlier in the same physics step (two bullets hitting one enemy, say), the side that resolves second sees a null Collision2D.Entity/other — never a dangling reference.
  3. Destroying an entity from inside a callback is safe. A bullet that destroys itself on impact is the common case, not an edge case — the actual destroy is deferred to the end of the physics step, same as destroying an entity from OnUpdate.
  4. Sensors don't push. IsSensor makes a collider report-only; it never deflects the other body.
  5. Native (C++) ScriptableEntity gets the same four virtuals, for scripts written in that language instead of C#.

Queries

The other half of gameplay physics — the half that isn't event-driven.

csharp
// Ground check - the single most-used query in any platformer
if (Physics2D.Raycast(pos, Vector2.Down, 0.1f, out var hit, groundMask))
    m_Grounded = true;

Physics2D.OverlapCircle(pos, 3.0f, hitEntities, enemyMask);       // explosion radius
Physics2D.CircleCast(pos, 0.2f, dir, 10.0f, hits);                // thick projectile
Physics2D.RaycastAll(pos, dir, 20.0f, hits);                      // pierce

RaycastAll, OverlapCircle, OverlapBox, and CircleCast all fill a caller-supplied array and return the total number of matches, which may exceed the array's length — compare the return value against the array length to detect truncation. None of them allocate: a ground check runs once per entity per frame, and an allocation on that path is exactly the kind of thing that turns into a stutter blamed on the physics engine. See Physics2D for the full method-by-method reference, including OverlapBox and GetLayerMask (not shown above).

Queries are legal from OnUpdate and OnFixedUpdate alike, and run against the current, already-stepped world state.

Remembering what a query found

A query only tells you what's true right now — an OverlapCircle sweep that finds nearby collectibles, say, has no memory of which ones the player already picked up on an earlier pass. Pair it with Session to record that: set a flag per item when it's collected, and check it before acting on the same item again, including after a Scenes.Load.

Enable Show physics queries in the Settings panel to draw every cast issued this frame as a line with its hit point — the fastest way to see why a ground check is failing.

Collision layers

Named layers and a collision matrix live in Project Settings → Physics Settings. Each collider picks one layer; the matrix decides which layer pairs collide at all (a bullet not hitting other bullets, an enemy not colliding with other enemies, etc.). The matrix is always symmetric — the UI enforces it, so there's no way to author a one-directional rule.

csharp
ulong mask = Physics2D.GetLayerMask("Enemy") | Physics2D.GetLayerMask("EnemyBullet");
Physics2D.Raycast(pos, dir, range, out var hit, mask);

One-way platforms

Check One Way on a BoxCollider2D to make it a platform a character can jump up through and land on top of. Two rules:

  • Jumping up through it always passes through.
  • Landing on top from above is solid.

To let the player drop through deliberately (the classic "down + jump" input), call:

csharp
GetComponent<Rigidbody2DComponent>()?.SetIgnoreOneWayPlatforms(true);
// ... next frame, once the drop-through is done:
GetComponent<Rigidbody2DComponent>()?.SetIgnoreOneWayPlatforms(false);

This flag doesn't reset itself — clear it once the drop is complete.

See also

Two other API Reference topics come up often alongside physics scripting:

  • Physics2D — the full method-by-method reference for Raycast, RaycastAll, OverlapCircle, OverlapBox, and CircleCast, all used above.
  • Session — a trigger's OnTriggerEnter (a checkpoint, a collected item) commonly needs to remember something past the current scene; Session is the recommended way to carry that value across a Scenes.Load.