Lucky Robots Blog Open Roles

2.7 · Physics (Box2D)

Two-dimensional rigid-body physics for 2D gameplay — small enough that the Scene talks to Box2D directly instead of through an abstraction layer.

Module: Hazel/src/Hazel/Scene/ Scene.h Components.h Library: Box2D b2World
Scene CreateBox2DWorld() b2World owned by Scene b2Body + fixtures b2PolygonShape b2CircleShape Components on entities RigidBody2DComponent BoxCollider2DComponent CircleCollider2DComponent TimeManager callbacks UpdateBox2DPhysics Update2DPhysicsTransforms
The Scene owns a b2World directly. Entity components describe bodies and colliders; two TimeManager runners step the world and sync transforms.

Overview

Box2D powers the engine's 2D mechanics. Unlike Jolt — which sits behind PhysicsAPI / PhysicsScene — Box2D is consumed directly: Scene calls CreateBox2DWorld and DestroyBox2DWorld to own a b2World, and the 2D components own raw Box2D handles. There is no Physics2DAPI interface.

Why no abstraction

The whole 2D surface area is one world type plus three components. Adding an interface layer for a library that's already minimal would buy nothing in exchange for double the headers and double the indirection. If a second 2D backend ever shows up, that's the moment to introduce Physics2DAPI — not before.

Components

ComponentWrapsNotes
RigidBody2DComponentb2Body*Body type (static / kinematic / dynamic), fixed rotation flag.
BoxCollider2DComponentb2PolygonShape fixtureHalf-extents, offset, density / friction / restitution.
CircleCollider2DComponentb2CircleShape fixtureRadius, offset, density / friction / restitution.

Lifecycle & stepping

The b2World is built when the scene enters runtime and torn down on stop. Stepping happens in two TimeManager runners:

RunnerWhat it does
UpdateBox2DPhysicsCalls b2World::Step(dt, vel_iters, pos_iters) — the integration step.
Update2DPhysicsTransformsCopies b2Body positions and angles back into entity TransformComponents.

Note that Box2D doesn't follow the same five-phase Acquisition → Control → Physics → Validation → Export pipeline that Jolt and MuJoCo use. The 2D surface is small enough that a step-and-sync pair is sufficient.

Pitfalls

Body handles are not entt entities

The b2Body* stored in RigidBody2DComponent belongs to the b2World, not the registry. Don't outlive the world with one — recreate the body on scene reload.

Recreate fixtures on collider change

Box2D fixtures are immutable. Editing a 2D collider at runtime means destroying and recreating the fixture on the existing body.

Mixing 2D and 3D

Box2D and Jolt step independently; they don't share contacts or transforms. Treat them as parallel worlds. Mixing them on a single entity is supported but the two physics representations only meet in the entity's TransformComponent.

Extending

  • New 2D collider: add a component (e.g. PolygonCollider2DComponent), serialize it, draw it in SceneHierarchyPanel, then create the matching fixture in the Box2D world setup path.
  • Joints: model joints as their own components and register a runner that creates / destroys b2Joint instances alongside the bodies.
  • Second backend: that's the point at which extracting a Physics2DAPI abstraction is worth the cost. Mirror the layout of Physics (Jolt).