Gessa Docs
Recipes

Recipe

First-person camera movement and mouselook controls

The player sees through their own eyes, turns with the mouse, and walks with the keyboard. The engine has no FirstPersonController atomic (it was stripped, see component-types.md Recently Stripped Pattern Components). You assemble the feel from the existing components and native input dispatch.
engine v1.0.234since component-field-contract ecs.component-field-contract.v1, action-catalog.v1.0.232, camera.v1, input.v1Copy for LLM

Use this for

first-person shooter or explorer where the player looks with the mouse and walks with WASD; pointer-lock mouselook; FPS viewpoint

Not for

top-down or fixed-camera games; third-person follow (use third-person-and-platformer-movement); pure UI screens

Pairs with: Input mapping actions bindings keyboard mouse gamepad touch, Camera feel field of view smoothing tone mapping and post, Physics colliders rigid bodies triggers and joints, Spawn points checkpoints and respawn flow

The recipe

  1. Create the player entity with project_create_entity (project.entity.create).
  2. Attach TransformComponent with project_add_component (project.entity.component.set). Every component value MUST carry its discriminator: { "type": "TransformComponent", "position": {...} }.
  3. Attach CameraComponent on the SAME entity: { "type": "CameraComponent", "fov": 90, "projection": "perspective", "active": true, "priority": 20, "clearMode": "skybox" }. The highest-priority active camera wins in Play. Leave followEntityId/lookAtEntityId unset for first person (the camera rides the player transform).
  4. Attach CharacterMovementComponent: { "type": "CharacterMovementComponent", "enableKcc": true, "maxSpeed": 6, "gravity": 20, "jumpSpeed": 7 }. enableKcc: true opts the entity into the predicted kinematic controller.
  5. Attach a ColliderComponent so the mover is blocked by geometry: { "type": "ColliderComponent", "shape": "capsule", "collision": "solid", "size": {"x":0.5,"y":1.8,"z":0.5}, "layers":["default"], "friction":0.5, "restitution":0 }.
  6. Attach an InputProfileComponent with a look action of valueType: "vector2d", policy.effectSystem: "look", and a policy.look { sensitivityDeg, invertY, pitchMinDeg, pitchMaxDeg } block (bounds -89..89). Set prediction.mode: "client_only" and bind sources: [{ "device": "mouse", "control": "move", "event": "move" }] with triggers: [{ "kind": "continuous_delta", "parameters": {} }]. This follows firstPersonControllerBaseActions in packages/ecs/src/index.ts. Keep the default player.move action for WASD (see input-mapping-actions-bindings for the full shape).
  7. Verify mouse look through the native input dispatcher. Its client-only look branch calls stream.applyLookInput in web_client/spa/src/runtime/inputDispatcher/dispatcher.ts. Do not add a script that applies the same look input again. Author scripts only for additional game behavior; ordinary movement and look do not require them.

Pitfalls

  • Component values are discriminated unions: omit type and the write is rejected component_state_invalid. This is the TransformComponent lesson and applies to every component.
  • CharacterMovementComponent with enableKcc: false (the default) will not move under prediction; set it true for a player.
  • Camera fov is clamped 1..170 and projection must be perspective for FPS.
  • Pointer lock needs a user gesture (platform.v1 guard platform.user_gesture); request it from a click handler, not on spawn.
  • Do not look for a PlayerControllerComponent or CameraTargetComponent; both are stripped patterns and no longer exist.

Verify

  • engine_get_component_schema (engine.component.schema.read) on CameraComponent and InputProfileComponent to confirm field names before writing.
  • project_get_graph_snapshot (project.graph.snapshot.read) to confirm the player entity carries Camera, CharacterMovement, Collider and Input.
  • simulation_run (qa.run.start) to prove movement responds, then qa_capture_renderer_viewport to confirm the first-person view renders.
Was this helpful?Report an issueContact support

On this page