CharacterController
A capsule-based character controller (Jolt CharacterVirtual) for player and NPC movement. Created via Scene.createCharacterController. The controller handles ground detection, stair-stepping, slope limits, and state tracking automatically.
Creating a controller
Section titled “Creating a controller”scene.createCharacterController(height, radius, x, y, z)
Section titled “scene.createCharacterController(height, radius, x, y, z)”Returns: CharacterController — A new controller placed at the given position.
Parameters:
height(Num) — Capsule standing height in metres.radius(Num) — Capsule radius in metres.x,y,z(Num) — Initial world position.
_controller = _scene.createCharacterController(1.8, 0.3, 0, 2, 0)Movement input
Section titled “Movement input”setDesiredVelocity(vx, vy, vz)
Section titled “setDesiredVelocity(vx, vy, vz)”Set the velocity the controller should try to reach this frame. Call every frame from update(dt). The physics system handles ground detection and slope response; do not set a Y velocity for jumps — use jump() instead.
var speed = 5var vx = 0var vz = 0if (Input.key("a")) vx = -speedif (Input.key("d")) vx = speedif (Input.key("w")) vz = -speedif (Input.key("s")) vz = speed_controller.setDesiredVelocity(vx, 0, vz)jump()
Section titled “jump()”Apply an upward impulse. Only effective when the controller is grounded (isGrounded is true).
if (Input.keyJustPressed("space") && _controller.isGrounded) { _controller.jump()}running=(value) / crouching=(value) / swimming=(value)
Section titled “running=(value) / crouching=(value) / swimming=(value)”Set locomotion flags. These switch the controller into the corresponding state for speed selection and animation.
facingYaw / facingYaw=(value)
Section titled “facingYaw / facingYaw=(value)”Get or set the yaw angle (radians) the controller is facing. Used for directional movement and animation.
State queries
Section titled “State queries”Returns: Num — Current CharacterState constant.
groundState
Section titled “groundState”Returns: Num — Ground contact state:
| Value | Meaning |
|---|---|
0 | On ground (flat/walkable) |
1 | On steep ground (too steep to walk) |
2 | Not supported (hanging, ledge) |
3 | In air |
isGrounded
Section titled “isGrounded”Returns: Bool — true when the controller is on walkable ground (groundState == 0).
isSupported
Section titled “isSupported”Returns: Bool — true when the controller has any ground contact (including steep slopes).
getGroundNormal()
Section titled “getGroundNormal()”Returns: List — [nx, ny, nz] ground surface normal.
getGroundVelocity()
Section titled “getGroundVelocity()”Returns: List — [vx, vy, vz] velocity of the surface the controller is standing on (for moving platforms).
getLinearVelocity()
Section titled “getLinearVelocity()”Returns: List — [vx, vy, vz] current controller velocity.
if (_controller.state == CharacterState.JUMPING) { // play jump animation}if (_controller.groundState == 1) { // on steep slope — slide down _controller.setDesiredVelocity(0, -5, 0)}Position & transform
Section titled “Position & transform”getPosition() / setPosition(x, y, z)
Section titled “getPosition() / setPosition(x, y, z)”Get or set the controller world position. Returns [x, y, z].
getRotation() / setRotation(rx, ry, rz)
Section titled “getRotation() / setRotation(rx, ry, rz)”Get or set the rotation (Euler radians). Returns [rx, ry, rz].
node / node=(value)
Section titled “node / node=(value)”Get or bind the controller to a Node. When bound, the node’s world position is updated each frame to follow the controller. Bind after creating the controller to drive a scene node.
_controllerNode = _scene.addNode("Player")_controller.node = _controllerNodesetShape(height, radius)
Section titled “setShape(height, radius)”Returns: Bool — true if the shape change succeeded (i.e. the new capsule fits in the current position without overlap).
Change the capsule dimensions at runtime (e.g. stand up from crouch). Returns false if the new shape would overlap existing geometry.
height / radius
Section titled “height / radius”Read-only current capsule dimensions.
Physics-query identity
Section titled “Physics-query identity”Characters carry an inner kinematic body (Jolt mInnerBodyShape, a capsule at 90 % of
the outer dimensions), so they exist in the physics broadphase: Physics raycasts, shape
sweeps and overlaps can hit them, and dynamic bodies and CCD collide with them instead of
passing through. The inner body follows the character automatically (including setShape
crouches) and never collides with its own character.
Characters register on their own gameplay collision layer (bit 1, value 2), so a query
layer mask can include or exclude them deliberately. The default match-anything mask finds
them; the built-in camera occlusion collider (CamRig.collider) masks them out so
characters never yank the camera.
bodyId
Section titled “bodyId”Returns: Num — the body id that Physics query hits carry when they strike this
character, or null when the inner body is disabled.
A hit on a character has no scene node (hit.getNodeId() is 0) — attribute it by body
id instead:
var hit = Physics.raycastMaxDist(scene, ox, oy, oz, dx, dy, dz, 100)if (hit != null && hit.getBodyId() == _controller.bodyId) { // the ray struck this character}Physics.overlapSphere resolves characters to their bound node (controller.node),
so AI-perception style queries return characters like any other body-backed node.
destroy()
Section titled “destroy()”Remove the character from the physics world: raycasts stop hitting it, dynamic bodies stop colliding with it, and this object is invalidated — every later call is a safe no-op. Idempotent. The bound node (your visuals) is untouched — detach it separately. Use this when despawning a character; without it, the character’s physics presence lives until the scene is destroyed.
_controller.destroy()_controllerNode.detach()To disable inner bodies game-wide (restoring the old query-invisible, non-solid
characters), set character_inner_body = false in the game.toml [Physics] section — see
Configuration.
Speed settings
Section titled “Speed settings”| Property | Default | Description |
|---|---|---|
walkSpeed / walkSpeed=(value) | — | Walk speed in m/s |
runSpeed / runSpeed=(value) | — | Run speed in m/s |
crouchSpeed / crouchSpeed=(value) | — | Crouch speed in m/s |
swimSpeed / swimSpeed=(value) | — | Swim speed in m/s |
jumpVelocity / jumpVelocity=(value) | — | Upward velocity applied on jump() |
_controller.walkSpeed = 3_controller.runSpeed = 7_controller.jumpVelocity = 6CharacterState
Section titled “CharacterState”Constant class for state values returned by controller.state:
| Constant | Value | Description |
|---|---|---|
CharacterState.IDLE | 0 | Standing still |
CharacterState.WALKING | 1 | Moving at walk speed |
CharacterState.RUNNING | 2 | Moving at run speed |
CharacterState.JUMPING | 3 | In a jump (rising) |
CharacterState.FALLING | 4 | Falling (no ground contact) |
CharacterState.SWIMMING | 5 | Swimming |
CharacterState.CROUCHING | 6 | Crouching, stationary |
CharacterState.CROUCH_WALKING | 7 | Moving while crouching |
CharacterState.INTERACTING | 8 | Custom interaction state |
CharacterState.CUSTOM_TOGGLE_ON | 9 | Custom toggle on |
CharacterState.CUSTOM_TOGGLE_OFF | 10 | Custom toggle off |
if (_controller.state == CharacterState.RUNNING) { // Play run animation}See Physics for rigid body operations and contact events. See Scene for createCharacterController.