Skip to main content
Part of the character-movement skill. The skill links here when your agent needs the detail.
Both build on the minimal 3D walk in SKILL.md. The difference is entirely the camera rig and how look rotation is split across nodes.

First-person node layout (non-negotiable)

Yaw on the body, pitch on the Head. Never pitch the body (gimbal lock + physics tilt). Camera is a child of Head so it inherits both rotations.

Crouch

Keep two collision shapes (standing + crouching), toggle disabled, and a RayCast3D pointing up (CeilingCheck) so you cannot stand up under a ceiling. Lerp the Head y from 1.5 to ~0.9 for a smooth crouch, not a snap.

Headbob (optional polish)

Drive the Camera’s local y with sin(time * bob_freq) * bob_amp scaled by horizontal speed, only while is_on_floor(). Keep amp under 0.08 or it nauseates.

Third-person node layout

The SpringArm3D is the whole trick: it raycasts from the pivot to the camera and shortens when a wall is in the way, so the camera never clips into geometry. Set its collision_mask to your Level layer only (not the player). Third-person differences from FPS:
  • Mouse motion rotates the CameraController (orbit), not the body.
  • The visual wrapper rotates to face the MOVEMENT direction, decoupled from the camera. Because imported characters face the engine forward -Z, lerp CharacterRotationRoot.rotation.y toward atan2(-direction.x, -direction.z).
  • Rotate only CharacterRotationRoot.rotation.y. Do not yaw the CharacterBody3D or add a compensating 180-degree model rotation. The package already normalizes forward to -Z.
  • Keep the third-person camera behind the character on the camera rig’s local +Z side.
  • Camera direction drives movement basis: build direction from the spring-arm’s global basis, flattened to the XZ plane, so “forward” means “where the camera looks”.

Canonical humanoid animation states

Keep translation in the controller and use package animations only as in-place visuals. For a standard playable third-person character, read and copy default-third-person-controller.gd. It already loads exact semantic roles from character.json, selects state after move_and_slide(), gives airborne Jump highest priority, changes clips only when state changes, uses camera-relative movement, and turns only the normalized -Z visual root. Do not reassemble the controller from partial examples. Do not advance the CharacterBody3D from animation root translation. Rotate only the visual child when movement is non-zero, and keep the last facing direction while idle.

Legacy clip-name compatibility only

Use substring discovery only for old, non-package GLBs that have no manifest. Resolve Jump before Run and claim each real clip at most once. Never use this for a canonical package:
Validate every requested role after legacy resolution. Do not fall back from Jump to Walk or Run, and do not reuse one clip for multiple roles.

Shared traps

  • Capture/release the mouse as a pair or you trap the player (see SKILL.md).
  • Sprint reads an unbound sprint action = silent no-op; bind it first.
  • Spring arm with the wrong mask collides with the player and jams at length 0.

Source: library/skills/character-movement/references/movement-3d.md, rendered from summer-engine@4.3.0 on npm.