Skip to content

Add a locomotion state

Goal

A sixth state in the hero's state machine — a crouch on Ctrl that halves the walk speed — reported to the animator through character.setState and shown on the HUD, with the state machine still allocating nothing.

Files you will edit

  • src/systems/locomotion.ts
  • src/game.ts

Steps

  1. Name it. The states are exported string constants, because they cross the wasm boundary as the state field of set-character-state and a rig will match on them. In src/systems/locomotion.ts:

    ts
    /** Moving low and slow. */
    export const CROUCH = 'crouch';
  2. Read the key and pick the speed. Inside locomotionSystem, beside the existing running:

    ts
    const crouching = !frozen && ctx.input.isDown('Control');
    const running = !frozen && !crouching && ctx.input.isDown('Shift');
    const speed = crouching ? Number(ctx.rules.crouchSpeed ?? 1.5) : running ? runSpeed : walkSpeed;

    Nothing here allocates: isDown is a shift and a mask over a preallocated bitset, and the speed is a number.

  3. Tell the character controller. moveCharacter already takes a crouch request; the host character controller is what decides what a crouch does to the capsule:

    ts
    ctx.physics.moveCharacter(hero, vx, 0, vz, wantsJump, crouching);
  4. Put it in the state machine. The ternary chain is ordered by priority — in the air beats on the ground, and crouching beats running:

    ts
    locomotionState.state = grounded
      ? planar < STILL_SPEED
        ? IDLE
        : crouching
          ? CROUCH
          : running
            ? RUN
            : WALK
      : wantsJump || vy > 0
        ? JUMP
        : FALL;

    character.setState(hero, locomotionState.state, vx, vy, vz, grounded) at the bottom of the system already sends it, and src/hud.ts already draws locomotionState.state in the state row. Neither needs a change.

  5. Add the number. One line in the rules block of src/game.ts, so the speed is tunable without opening a system:

    ts
    rules: {
      walkSpeed: 3.2,
      runSpeed: 6,
      crouchSpeed: 1.5,
    },

Verify

sh
npm test -w templates/third-person

Add a case beside walks at the walk speed and runs at the run speed: hold Control, step five frames, and assert locomotionState.state is 'crouch' and locomotionState.speed is 1.5. In the browser, the HUD's state row follows what you are doing, and the end-to-end suite reads the same value back out of the host with adapter.animationOf(hero).state.

See also