Skip to content

Backpedal with a held facing ​

Goal ​

Move a fighter away from an opponent while its drawn body keeps facing them. CharacterPawn is a character bundle on the characterMovement kinematic capsule, with walking, jumping and pawn blocking. A held controller yaw is independent of travel direction.

Files you will edit ​

  • src/game.ts

Steps ​

  1. Start with a game whose manifest already contains a character bundle named fighter. Keep its host bootstrap. Place two fighters over the existing floor:
ts
import {
  CharacterPawn,
  addMovementInput,
  defineGame,
  setDesiredRotation,
  spawnActor,
  yawToward,
} from 'gameable';
import type { ActorHandle } from 'gameable';

let fighter: ActorHandle;
let opponent: ActorHandle;
const away = { x: 0, y: 0, z: 0 };

export default defineGame({
  init(ctx) {
    fighter = spawnActor(ctx, CharacterPawn, {
      position: { x: -2, y: 0.93, z: 0 },
      props: { character: 'fighter', maxWalkSpeed: 2, orientRotationToMovement: false },
    });
    opponent = spawnActor(ctx, CharacterPawn, {
      position: { x: 2, y: 0.93, z: 0 },
      props: { character: 'fighter', orientRotationToMovement: false },
    });
  },
  systems: [
    (ctx) => {
      const from = fighter.position;
      const to = opponent.position;
      setDesiredRotation(fighter.entity, yawToward(from, to));
      setDesiredRotation(opponent.entity, yawToward(to, from));
      away.x = from.x - to.x;
      away.z = from.z - to.z;
      const length = Math.hypot(away.x, away.z);
      if (length > 0) {
        away.x /= length;
        away.z /= length;
      }
      if (ctx.input.isDown('A')) addMovementInput(fighter.entity, away);
    },
  ],
});
  1. Hold A to retreat. Movement input is consumed on the next actor step. rotationRate limits yaw in degrees/second; jumpZVelocity sets jump speed in metres/second (jump(entity) requests it). The actor disables animation carry so main's movement solver owns travel and the take follows its velocity.
  2. The motion layer loops the set's jog_back take when available. Otherwise it plays a forward stride at a negative rate while holding the body yaw. setDesiredRotation(entity, undefined) releases the persistent hold. With orientRotationToMovement: true, travel controls rotation and the held yaw is ignored. Calls to character.setState without facing keep ordinary motion.

Verify ​

Run the game's usual preview. Hold A: separation increases while both bodies face each other. Release A: the fighter brakes to a stand. Test both a set with jog_back and a forward-only set, then check direct and compiled WASM builds. The optional facing must also survive room replication and a late join.

See also ​