menu_bookDocumentation

PlayerController: Architecture, Ground Detection, Jump System, WishVelocity Sync, and Physics Step Integration

calendar_today May 4, 2026 schedule ~1 min read person patrickjr verified 50

PlayerController: Architecture, Ground Detection, and Jump System

PlayerController is the built-in s&box character movement component. It uses a Rigidbody internally and implements IScenePhysicsEvents to hook into the physics step.

Key Properties

CSHARP
// Body configuration
[Property] float BodyRadius = 16f;
[Property] float BodyHeight = 72f;
[Property] float BodyMass = 500f;

// Movement speeds
[Property] float WalkSpeed = 110f;
[Property] float RunSpeed = 320f;
[Property] float DuckedSpeed = 70f;
[Property] float JumpSpeed = 300f;
[Property] float DuckedHeight = 36f;

// Physics tuning
[Property] float BrakePower = 1f;   // friction when slowing down on ground
[Property] float AirFriction = 0.1f; // friction when airborne

// State
bool IsOnGround      // true if standing on something
bool IsAirborne      // !IsOnGround && !IsSwimming && !IsClimbing
bool IsClimbing
bool IsSwimming
bool IsDucking
Vector3 Velocity     // actual velocity minus ground velocity
Vector3 WishVelocity // [Sync] desired velocity
Vector3 GroundVelocity // velocity of the ground beneath us

Ground Detection

Ground detection runs in PostPhysicsStep() via CategorizeGround(). It traces a small capsule downward from the player's position. The trace uses a radius scale that shrinks from 1.0 to 0.7 to handle edge cases.

CSHARP
// Ground state
GameObject GroundObject    // the object we're standing on (null if airborne)
Component GroundComponent  // the collider/rigidbody we're standing on
Surface GroundSurface      // physics surface material
float GroundFriction       // friction of the ground surface
bool GroundIsDynamic       // is the ground a dynamic physics object?
TimeSince TimeSinceGrounded   // time since last on ground
TimeSince TimeSinceUngrounded // time since last off ground

Jump System

CSHARP
// Built-in jump (called from InputJump)
controller.Jump( Vector3.Up * JumpSpeed );
Jump() is smart — it subtracts any opposing velocity before adding the jump velocity, then clamps the result. This prevents double-jumping from giving extra height when running up a slope. PreventGrounding( seconds ) prevents the player from being grounded for a duration — used after jumping to avoid immediately re-grounding.

Physics Step Integration

CSHARP
void IScenePhysicsEvents.PrePhysicsStep()
{
    UpdateBody();
    if ( !IsProxy )
    {
        Mode.AddVelocity();
        Mode.PrePhysicsStep();
    }
}

void IScenePhysicsEvents.PostPhysicsStep()
{
    Velocity = Body.Velocity - GroundVelocity;
    UpdateGroundVelocity();
    RestoreStep();
    Mode?.PostPhysicsStep();
    CategorizeGround();
    ChooseBestMoveMode();
}

WishVelocity is Synced

WishVelocity is marked [Sync], so it's automatically replicated from the owner to all clients. This allows proxies to see the player's intended movement direction for animation purposes.

OnJumped RPC

CSHARP
[Rpc.Broadcast( NetFlags.OwnerOnly | NetFlags.Unreliable )]
public void OnJumped()
{
    // Triggers jump animation on all clients
    // OwnerOnly = only the owner can call this
    // Unreliable = fire-and-forget, may drop
}
Was this helpful?