Recipes
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
- Create the player entity with
project_create_entity(project.entity.create). - Attach
TransformComponentwithproject_add_component(project.entity.component.set). Every component value MUST carry its discriminator:{ "type": "TransformComponent", "position": {...} }. - Attach
CameraComponenton the SAME entity:{ "type": "CameraComponent", "fov": 90, "projection": "perspective", "active": true, "priority": 20, "clearMode": "skybox" }. The highest-priority active camera wins in Play. LeavefollowEntityId/lookAtEntityIdunset for first person (the camera rides the player transform). - Attach
CharacterMovementComponent:{ "type": "CharacterMovementComponent", "enableKcc": true, "maxSpeed": 6, "gravity": 20, "jumpSpeed": 7 }.enableKcc: trueopts the entity into the predicted kinematic controller. - Attach a
ColliderComponentso 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 }. - Attach an
InputProfileComponentwith a look action ofvalueType: "vector2d",policy.effectSystem: "look", and apolicy.look{ sensitivityDeg, invertY, pitchMinDeg, pitchMaxDeg }block (bounds -89..89). Setprediction.mode: "client_only"and bindsources: [{ "device": "mouse", "control": "move", "event": "move" }]withtriggers: [{ "kind": "continuous_delta", "parameters": {} }]. This followsfirstPersonControllerBaseActionsinpackages/ecs/src/index.ts. Keep the defaultplayer.moveaction for WASD (see input-mapping-actions-bindings for the full shape). - Verify mouse look through the native input dispatcher. Its client-only look branch calls
stream.applyLookInputinweb_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
typeand the write is rejectedcomponent_state_invalid. This is the TransformComponent lesson and applies to every component. CharacterMovementComponentwithenableKcc: false(the default) will not move under prediction; set it true for a player.- Camera
fovis clamped 1..170 andprojectionmust beperspectivefor FPS. - Pointer lock needs a user gesture (
platform.v1guardplatform.user_gesture); request it from a click handler, not on spawn. - Do not look for a
PlayerControllerComponentorCameraTargetComponent; both are stripped patterns and no longer exist.
Verify
engine_get_component_schema(engine.component.schema.read) onCameraComponentandInputProfileComponentto 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, thenqa_capture_renderer_viewportto confirm the first-person view renders.