Class CharacterMover2D
A platformer character that is not a rigid body: a capsule the game moves itself, Quake style,
with Box2D asked only what it touches. Ported from the Box2D.NET samples' Mover
(MIT, (c) 2022 Erin Catto, (c) 2025 Choi Ikpil).
public sealed class CharacterMover2D : IBox2DSimulationUpdate
- Inheritance
-
CharacterMover2D
- Implements
Examples
var mover = new CharacterMover2D(new Vector2(2, 8)) { Entity = hero };
simulation.RegisterSimulationUpdate(mover);
// per frame
mover.Throttle = (input.IsKeyDown(Keys.D) ? 1 : 0) - (input.IsKeyDown(Keys.A) ? 1 : 0);
if (input.IsKeyPressed(Keys.Space)) mover.Jump();
Remarks
Each step: ground friction and acceleration towards Throttle times
MaxSpeed, gravity, then a pogo - a shape cast straight down whose hit
distance drives a spring that floats the capsule at PogoRestLength above the
ground and tells it whether it is standing; then up to five rounds of collecting contact
planes (b2World_CollideMover), solving a translation that respects them
(b2SolvePlanes) and sweeping it (b2World_CastMover); finally the velocity is
clipped against those planes so it stops pushing into walls.
Register it with RegisterSimulationUpdate(IBox2DSimulationUpdate) and it steps itself after every fixed physics step, so kinematic platforms and the mover agree on time. Set Throttle and call Jump() from your frame update; give it an Entity and it writes the transform after each step.
The mover collides with shapes whose category bits pass CollideFilter. Shapes carry the sample's categories by default: give the level StaticCategory, moving platforms DynamicCategory, things the mover should kick but walk through DebrisCategory. How hard a shape pushes back is per shape, through SetResponse(B2ShapeId, float, bool).
Constructors
CharacterMover2D(Vector2, float, float)
Creates a mover standing at position.
public CharacterMover2D(Vector2 position, float halfHeight = 0.5, float radius = 0.3)
Parameters
positionVector2World-space centre of the capsule.
halfHeightfloatHalf the distance between the capsule's two centres.
radiusfloatThe capsule's radius.
Fields
DebrisCategory
Category bit for loose things the mover passes through and can kick.
public static readonly ulong DebrisCategory
Field Value
DynamicCategory
Category bit for moving obstacles the mover collides with and stands on: elevators, bridges.
public static readonly ulong DynamicCategory
Field Value
MoverCategory
Category bit for movers; a mover overlaps others but does not sweep against them, which is what makes the push soft.
public static readonly ulong MoverCategory
Field Value
StaticCategory
Category bit for the level: chains, floors, walls.
public static readonly ulong StaticCategory
Field Value
Properties
Accelerate
Acceleration towards the wanted speed, as a multiple of MaxSpeed per second.
public float Accelerate { get; set; }
Property Value
AirSteer
Fraction of Accelerate available in the air.
public float AirSteer { get; set; }
Property Value
Bottom
World-space lower centre of the capsule.
public Vector2 Bottom { get; }
Property Value
CastFilter
What the mover sweeps against. Not other movers, so two movers push each other softly rather than blocking.
public B2QueryFilter CastFilter { get; set; }
Property Value
- B2QueryFilter
CollideFilter
What the mover collects contact planes from. Level, moving obstacles and other movers by default.
public B2QueryFilter CollideFilter { get; set; }
Property Value
- B2QueryFilter
Entity
The entity whose transform follows the mover, if any.
public Entity? Entity { get; set; }
Property Value
Friction
Ground friction, in units of 1 per second.
public float Friction { get; set; }
Property Value
Gravity
Downward acceleration on the mover. Separate from the world's gravity, since the mover is not a body.
public float Gravity { get; set; }
Property Value
GroundPushForce
Force pressed into whatever the mover stands on, newtons, so a bridge sags under it.
public float GroundPushForce { get; set; }
Property Value
HalfHeight
Half the distance between the capsule's two centres.
public float HalfHeight { get; }
Property Value
IsOnGround
Whether the pogo found ground under the feet in the last step.
public bool IsOnGround { get; }
Property Value
IterationsLastStep
How many plane-solver iterations the last step took in total.
public int IterationsLastStep { get; }
Property Value
JumpSpeed
Upward speed given by Jump().
public float JumpSpeed { get; set; }
Property Value
MaxSpeed
Top speed on the ground, metres per second.
public float MaxSpeed { get; set; }
Property Value
MinSpeed
Below this speed the mover stops dead rather than creeping.
public float MinSpeed { get; set; }
Property Value
Planes
The contact planes found in the last step, for drawing.
public ReadOnlySpan<B2CollisionPlane> Planes { get; }
Property Value
- ReadOnlySpan<B2CollisionPlane>
PogoDampingRatio
Damping ratio of that spring; under 1 gives a small bounce on landing.
public float PogoDampingRatio { get; set; }
Property Value
PogoEnd
Where the pogo cast ended in the last step: the hit point's height, or the full reach when it missed.
public Vector2 PogoEnd { get; }
Property Value
PogoFilter
What counts as ground for the pogo.
public B2QueryFilter PogoFilter { get; set; }
Property Value
- B2QueryFilter
PogoHertz
Stiffness of the spring that floats the mover above the ground.
public float PogoHertz { get; set; }
Property Value
PogoHit
Whether the pogo cast hit anything in the last step.
public bool PogoHit { get; }
Property Value
PogoOrigin
Where the pogo cast started in the last step: the lower capsule centre.
public Vector2 PogoOrigin { get; }
Property Value
PogoRestLength
How far above the ground the lower capsule centre rests. Three radii by default.
public float PogoRestLength { get; set; }
Property Value
PogoShape
The shape cast down to find the ground.
public PogoShape PogoShape { get; set; }
Property Value
Position
World-space centre of the capsule.
public Vector2 Position { get; }
Property Value
Radius
The capsule's radius.
public float Radius { get; }
Property Value
StopSpeed
Below this speed ground friction removes a fixed amount per second instead of a fraction, so stopping is crisp.
public float StopSpeed { get; set; }
Property Value
Throttle
Walk input, -1 to 1: the fraction of MaxSpeed wanted, negative for left.
public float Throttle { get; set; }
Property Value
Top
World-space upper centre of the capsule.
public Vector2 Top { get; }
Property Value
Velocity
Current velocity, metres per second.
public Vector2 Velocity { get; }
Property Value
Methods
AfterSimulationUpdate(Box2DSimulation, float)
Called after the simulation has advanced.
public void AfterSimulationUpdate(Box2DSimulation simulation, float deltaTime)
Parameters
simulationBox2DSimulationThe simulation that was stepped.
deltaTimefloatThe time step in seconds.
Jump()
Asks for a jump. Honoured at the next step if the mover is on the ground, then forgotten, so call it on the key press rather than while the key is held.
public void Jump()
SetResponse(B2ShapeId, float, bool)
Sets how shapeId pushes a mover, in the shape's user data.
public static void SetResponse(B2ShapeId shapeId, float maxPush, bool clipVelocity)
Parameters
shapeIdB2ShapeIdThe shape.
maxPushfloatSee MaxPush.
clipVelocityboolSee ClipVelocity.
SimulationUpdate(Box2DSimulation, float)
Called before the simulation advances.
public void SimulationUpdate(Box2DSimulation simulation, float deltaTime)
Parameters
simulationBox2DSimulationThe simulation being stepped.
deltaTimefloatThe time step in seconds.
Step(B2WorldId, float)
Advances the mover by one step of deltaTime seconds in
worldId. Called for you when registered with a simulation.
public void Step(B2WorldId worldId, float deltaTime)
Parameters
worldIdB2WorldIdThe world to collide with.
deltaTimefloatStep length in seconds.
Teleport(Vector2)
Moves the mover to position and stops it.
public void Teleport(Vector2 position)
Parameters
positionVector2New world-space centre.