Kinematic Character Controller¶
KinematicCharacterBody adds character-controller behaviour to a Kinematic physics body. Set Velocity each fixed-update frame and the system slides it along contact surfaces, detects ground/wall/ceiling contacts, and integrates position. It is the right choice for platform characters and top-down actors that need precise, physics-aware movement without full dynamic simulation.
Setup¶
Register physics with AddPhysics() — the kinematic character systems are included automatically:
services.AddPhysics(opts => { opts.Gravity = new Vector2(0, 980); });
Add both kinematic systems to your scene:
protected override void OnLoadAsync(IEntityWorld world)
{
world.AddSystem<Box2DPhysicsSystem>();
world.AddSystem<PrePhysicsKinematicCharacterSystem>();
world.AddSystem<PostPhysicsKinematicCharacterSystem>();
}
Create a character entity with both PhysicsBodyComponent (Kinematic) and KinematicCharacterBody:
var player = world.CreateEntity("Player");
player.AddComponent<TransformComponent>(t => t.Position = new Vector2(200, 300));
player.AddComponent<PhysicsBodyComponent>(b =>
{
b.Shape = new CapsuleShape(new Vector2(0, -12), new Vector2(0, 12), 10);
b.BodyType = PhysicsBodyType.Kinematic;
b.FixedRotation = true;
});
player.AddComponent<KinematicCharacterBody>(c =>
{
c.SnapDistance = 8f; // Snap to floor on stairs/slopes
});
Moving the Character¶
Set Velocity each fixed-update frame. The pre-physics step slides the velocity along contact surfaces and integrates it into TransformComponent.Position before Box2D runs.
MoveAndSlide¶
The character moves by Velocity * deltaTime, deflecting off surfaces up to MaxSlides times. Use for standard platform or top-down movement.
protected override void OnFixedUpdate(GameTime fixedTime)
{
var character = _playerEntity.GetComponent<KinematicCharacterBody>();
var velocity = Vector2.Zero;
if (Input.IsKeyDown(Key.A)) velocity.X -= _speed;
if (Input.IsKeyDown(Key.D)) velocity.X += _speed;
if (character.IsGrounded && Input.IsKeyPressed(Key.Space))
velocity.Y = -_jumpForce;
else
velocity.Y = character.Velocity.Y + _gravity * (float)fixedTime.DeltaTime;
character.MoveAndSlide(velocity);
}
MoveAndSlide is a fluent method that sets Velocity and returns this.
MoveAndCollide¶
The character moves exactly by the given vector without sliding. If a solid surface is hit it stops at the contact point and LastMoveAndCollideHit is set. Use for projectile-style movement or step-based tools.
character.MoveAndCollide(new Vector2(0, 8)); // Probe downward 8 px
if (character.LastMoveAndCollideHit.HasValue)
Logger.LogDebug("Hit at {Point}", character.LastMoveAndCollideHit.Value.Point);
MoveAndCollide is mutually exclusive with MoveAndSlide per tick -- velocity integration is skipped for any tick in which a MoveAndCollide motion is queued.
Ground, Wall, and Ceiling Detection¶
These are updated each tick by the post-physics step.
if (character.IsGrounded)
{
Logger.LogDebug("Floor normal: {Normal}", character.FloorNormal);
}
if (character.IsOnWall)
{
Logger.LogDebug("Wall normal: {Normal}", character.WallNormal);
}
if (character.IsOnCeiling)
{
Logger.LogDebug("Ceiling normal: {Normal}", character.CeilingNormal);
}
// Touching a wall but not floor or ceiling
if (character.IsOnWallOnly) WallSlide();
Angle Limits¶
character.FloorAngleLimit = 0.8f; // Radians (~46 deg) from "up"
character.CeilingAngleLimit = 0.8f; // Radians from "down"
character.WallAngleLimit = float.PositiveInfinity; // All non-floor/ceiling contacts
Landing and Airborne Events¶
character.OnLanded += c => PlayLandSound();
character.OnAirborne += c => StartFallAnimation();
Moving Platforms¶
KinematicCharacterBody automatically detects and rides moving platforms (both Kinematic and Dynamic bodies). The platform's velocity is carried to the character each tick.
// Read the current platform velocity
var platformVelocity = character.PlatformVelocity;
Snap to Floor¶
Prevent the character from going airborne on small steps and slopes:
character.SnapDistance = 8f; // Probe this many pixels downward to find the floor
Slope Locking¶
Prevent sliding down slopes when the character is not actively pushing into them:
character.StopOnSlope = true;
Pushing Dynamic Objects¶
character.PushForce = 500f; // pixels/s^2 applied to dynamic bodies on contact
Speed Cap¶
character.MaxSpeed = 600f; // Clamp velocity magnitude before slide integration
Step Climbing¶
character.StepHeight = 12f; // Auto-step onto ledges up to this many pixels tall
Custom Up Direction¶
For wall-walking or per-character gravity:
character.UpDirection = new Vector2(0, -1); // Default: derived from world gravity each tick
character.UpDirection = new Vector2(0, 1); // Ceiling walker
Reading Slide Collisions¶
GetSlideCollisions() returns all contacts resolved in the last post-physics step. The list is cleared every tick -- do not hold a reference across frames.
foreach (var pair in character.GetSlideCollisions())
{
Logger.LogDebug("Sliding against {Entity}, normal {Normal}",
pair.Other?.Entity?.Name, pair.Contact.Normal);
}
EffectiveVelocity¶
The slide-corrected velocity actually applied this tick, after surface deflection. Useful for animation blending:
var speed = character.EffectiveVelocity.Length();
animator.SetFloat("Speed", speed);
Debugging¶
character.EnableDebugLogging = true; // Emits per-tick trace output -- disable before shipping
Summary¶
| Property | Description |
|---|---|
Velocity |
Desired velocity in pixels/s. Set every fixed-update frame. |
MoveAndSlide(vel) |
Set velocity and slide along surfaces. |
MoveAndCollide(motion) |
Discrete cast -- stops at first hit. |
IsGrounded |
Resting on a floor surface this tick |
IsOnWall |
Touching a wall this tick |
IsOnCeiling |
Touching a ceiling this tick |
FloorNormal |
Averaged floor contact normal |
WallNormal |
Wall contact normal |
CeilingNormal |
Ceiling contact normal |
EffectiveVelocity |
Post-deflection velocity applied this tick |
PlatformVelocity |
Velocity of the moving platform underfoot |
SnapDistance |
Floor-snap probe distance in pixels |
StopOnSlope |
Prevent sliding down passive slopes |
PushForce |
Impulse applied to dynamic bodies on contact |
MaxSpeed |
Velocity magnitude cap before integration |
StepHeight |
Auto step-up height in pixels |
MaxSlides |
Max deflection iterations per pre-step (default 3) |
GetSlideCollisions() |
All contacts resolved last tick |
LastMoveAndCollideHit |
Result of the last MoveAndCollide call |
MotionRemainder |
Unused motion remainder from MoveAndCollide |