> ## Documentation Index
> Fetch the complete documentation index at: https://v5.rpgjs.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Rpg Client Engine

> Reference for the `RpgClientEngine` class.

# Rpg Client Engine

Reference for the `RpgClientEngine` class.

## Members

* [addComponentAnimation](#addcomponentanimation)
* [addSound](#addsound)
* [addSpriteComponentBehind](#addspritecomponentbehind)
* [addSpriteComponentInFront](#addspritecomponentinfront)
* [cameraFollowTargetId](#camerafollowtargetid)
* [clear](#clear)
* [clearClientPredictionStates](#clearclientpredictionstates)
* [flash](#flash)
* [getComponentAnimation](#getcomponentanimation)
* [getSound](#getsound)
* [getSpriteSheet](#getspritesheet)
* [mapShakeTrigger](#mapshaketrigger)
* [playSound](#playsound)
* [processAction](#processaction)
* [processDash](#processdash)
* [pointer](#pointer)
* [setCameraFollow](#setcamerafollow)
* [setKeyboardControls](#setkeyboardcontrols)
* [setSoundResolver](#setsoundresolver)
* [setSpritesheetResolver](#setspritesheetresolver)
* [startTransition](#starttransition)
* [stopAllSounds](#stopallsounds)
* [stopSound](#stopsound)

## addComponentAnimation

Add a component animation to the engine

Component animations are temporary visual effects that can be displayed
on sprites or objects, such as hit indicators, spell effects, or status animations.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
addComponentAnimation(componentAnimation: {
    component: any,
    id: string
  })
```

### Parameters

* `componentAnimation`: `{
    component: any,
    id: string
  }`

### Returns

The added component animation configuration

### Examples

```ts theme={null}
// Add a hit animation component
engine.addComponentAnimation({
  id: 'hit',
  component: HitComponent
});

// Add an explosion effect component
engine.addComponentAnimation({
  id: 'explosion',
  component: ExplosionComponent
});
```

## addSound

Add a sound to the engine

Adds a sound to the engine's sound cache. The sound can be:

* A simple object with `id` and `src` properties
* A Howler instance
* An object with a `play()` method

If the sound has a `src` property, a Howler instance will be created automatically.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
addSound(sound: any, id?: string): any
```

### Parameters

* `sound`: `any`
* `id?`: `string`

### Returns

The added sound

### Examples

```ts theme={null}
// Simple sound object
engine.addSound({ id: 'click', src: 'click.mp3' });

// With explicit ID
engine.addSound({ src: 'music.mp3' }, 'background-music');
```

## addSpriteComponentBehind

Add a component to render behind sprites
Components added with this method will be displayed with a lower z-index than the sprite

Supports multiple formats:

1. Direct component: `ShadowComponent`
2. Configuration object: `{ component: LightHalo, props: {...} }`
3. With dynamic props: `{ component: LightHalo, props: (object) => {...} }`
4. With dependencies: `{ component: HealthBar, dependencies: (object) => [object.hp, object.param.maxHp] }`

Components with dependencies will only be displayed when all dependencies are resolved (!= undefined).
The object (sprite) is passed to the dependencies function to allow sprite-specific dependency resolution.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
addSpriteComponentBehind(component: any)
```

### Parameters

* `component`: `any`

### Returns

The added component or configuration

### Examples

```ts theme={null}
// Add a shadow component behind all sprites
engine.addSpriteComponentBehind(ShadowComponent);

// Add a component with static props
engine.addSpriteComponentBehind({ 
  component: LightHalo, 
  props: { radius: 30 } 
});

// Add a component with dynamic props and dependencies
engine.addSpriteComponentBehind({ 
  component: HealthBar, 
  props: (object) => ({ hp: object.hp(), maxHp: object.param.maxHp() }),
  dependencies: (object) => [object.hp, object.param.maxHp]
});
```

## addSpriteComponentInFront

Add a component to render in front of sprites
Components added with this method will be displayed with a higher z-index than the sprite

Supports multiple formats:

1. Direct component: `HealthBarComponent`
2. Configuration object: `{ component: StatusIndicator, props: {...} }`
3. With dynamic props: `{ component: HealthBar, props: (object) => {...} }`
4. With dependencies: `{ component: HealthBar, dependencies: (object) => [object.hp, object.param.maxHp] }`

Components with dependencies will only be displayed when all dependencies are resolved (!= undefined).
The object (sprite) is passed to the dependencies function to allow sprite-specific dependency resolution.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
addSpriteComponentInFront(component: any | { component: any, props: (object: any) => any, dependencies?: (object: any) => any[] })
```

### Parameters

* `component`: `any | { component: any, props: (object: any) => any, dependencies?: (object: any) => any[] }`

### Returns

The added component or configuration

### Examples

```ts theme={null}
// Add a health bar component in front of all sprites
engine.addSpriteComponentInFront(HealthBarComponent);

// Add a component with static props
engine.addSpriteComponentInFront({ 
  component: StatusIndicator, 
  props: { type: 'poison' } 
});

// Add a component with dynamic props and dependencies
engine.addSpriteComponentInFront({ 
  component: HealthBar, 
  props: (object) => ({ hp: object.hp(), maxHp: object.param.maxHp() }),
  dependencies: (object) => [object.hp, object.param.maxHp]
});
```

## cameraFollowTargetId

ID of the sprite that the camera should follow. null means follow the current player

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `property`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
cameraFollowTargetId
```

## clear

Clear all client resources and reset state

This method should be called to clean up all client-side resources when
shutting down or resetting the client engine. It:

* Destroys the PIXI renderer
* Stops all sounds
* Cleans up subscriptions and event listeners
* Resets scene map
* Stops ping/pong interval
* Clears prediction states

## Design

This method is used primarily in testing environments to ensure clean
state between tests. In production, the client engine typically persists
for the lifetime of the application.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
clear(): void
```

### Examples

```ts theme={null}
// In test cleanup
afterEach(() => {
  clientEngine.clear();
});
```

## clearClientPredictionStates

Clear client prediction states for cleanup

Removes old prediction states and input history to prevent memory leaks.
Should be called when changing maps or disconnecting.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
clearClientPredictionStates()
```

### Examples

```ts theme={null}
// Clear prediction states when changing maps
engine.clearClientPredictionStates();
```

## flash

Trigger a flash animation on a sprite

This method allows you to trigger a flash effect on any sprite from client-side code.
The flash can be configured with various options including type (alpha, tint, or both),
duration, cycles, and color.

## Design

The flash is applied directly to the sprite object using its flash trigger.
This is useful for client-side visual feedback, UI interactions, or local effects
that don't need to be synchronized with the server.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
flash(spriteId?: string, options?: {
      type?: 'alpha' | 'tint' | 'both';
      duration?: number;
      cycles?: number;
      alpha?: number;
      tint?: number | string;
    }): void
```

### Parameters

* `spriteId?`: `string`
* `options?`: `{
      type?: 'alpha' | 'tint' | 'both';
      duration?: number;
      cycles?: number;
      alpha?: number;
      tint?: number | string;
    }`

### Examples

```ts theme={null}
// Flash the current player with default settings
engine.flash();

// Flash a specific sprite with red tint
engine.flash('sprite-id', { type: 'tint', tint: 0xff0000 });

// Flash with both alpha and tint for dramatic effect
engine.flash(undefined, { 
  type: 'both', 
  alpha: 0.5, 
  tint: 0xff0000,
  duration: 200,
  cycles: 2
});

// Quick damage flash on current player
engine.flash(undefined, { 
  type: 'tint', 
  tint: 'red', 
  duration: 150,
  cycles: 1
});
```

## getComponentAnimation

Get a component animation by its ID

Retrieves the EffectManager instance for a specific component animation,
which can be used to display the animation on sprites or objects.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
getComponentAnimation(id: string): AnimationManager
```

### Parameters

* `id`: `string`

### Returns

The EffectManager instance for the animation

### Examples

```ts theme={null}
// Get the hit animation and display it
const hitAnimation = engine.getComponentAnimation('hit');
hitAnimation.displayEffect({ text: "Critical!" }, player);
```

## getSound

Get a sound by ID, using resolver if not found in cache

This method first checks if the sound exists in the cache.
If not found and a resolver is set, it calls the resolver to create the sound.
The resolved sound is automatically cached for future use.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
getSound(id: string): any | Promise<any>
```

### Parameters

* `id`: `string`

### Returns

The sound if found or created, or undefined if not found and no resolver

### Examples

```ts theme={null}
// Synchronous usage
const sound = engine.getSound('my-sound');

// Asynchronous usage (when resolver returns Promise)
const sound = await engine.getSound('dynamic-sound');
```

## getSpriteSheet

Get a spritesheet by ID, using resolver if not found in cache

This method first checks if the spritesheet exists in the cache.
If not found and a resolver is set, it calls the resolver to create the spritesheet.
The resolved spritesheet is automatically cached for future use.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
getSpriteSheet(id: string): any | Promise<any>
```

### Parameters

* `id`: `string`

### Returns

The spritesheet if found or created, or undefined if not found and no resolver

### Examples

```ts theme={null}
// Synchronous usage
const spritesheet = engine.getSpriteSheet('my-sprite');

// Asynchronous usage (when resolver returns Promise)
const spritesheet = await engine.getSpriteSheet('dynamic-sprite');
```

## mapShakeTrigger

Trigger for map shake animation

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `property`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
mapShakeTrigger
```

## playSound

Play a sound by its ID

This method retrieves a sound from the cache or resolver and plays it.
If the sound is not found, it will attempt to resolve it using the soundResolver.
Uses Howler.js for audio playback instead of native Audio elements.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
playSound(soundId: string, options?: { volume?: number; loop?: boolean }): Promise<void>
```

### Parameters

* `soundId`: `string`
* `options?`: `{ volume?: number; loop?: boolean }`

### Examples

```ts theme={null}
// Play a sound synchronously
engine.playSound('item-pickup');

// Play a sound with volume and loop
engine.playSound('background-music', { volume: 0.5, loop: true });

// Play a sound asynchronously (when resolver returns Promise)
await engine.playSound('dynamic-sound', { volume: 0.8 });
```

## processAction

Send an action input to the server. Use the optional `data` payload for context
such as a pointer position, selected target, or UI command details. The server
receives the normalized payload in `player.onInput()`.

For the full flow, including keyboard action bindings and the difference between
custom actions and event interactions, see
[`Custom action inputs`](../../guide/structure.md#custom-action-inputs).

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
processAction(action: string | number, data?: any): void
processAction(input: { action: string | number, data?: any }): void
```

### Parameters

* `action`: Action name or control value
* `data?`: Optional custom payload sent with the action

### Examples

```ts theme={null}
// Existing simple action
engine.processAction('action')

// Action with custom context
engine.processAction('projectile:shoot', {
  target: { x: 320, y: 180 },
  source: 'map-click'
})
```

On the server:

```ts theme={null}
const player = {
  onInput(player, input) {
    if (input.action === 'projectile:shoot') {
      const target = input.data?.target
    }
  }
}
```

## processDash

Start a predicted dash for the current player. The dash is sent through the
movement channel, so the client can simulate it immediately and the server can
validate the authoritative result.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
processDash(input?: {
  direction?: { x: number, y: number },
  additionalSpeed?: number,
  duration?: number,
  cooldown?: number
}): Promise<void>
```

### Examples

```ts theme={null}
// Dash in the current facing direction
await engine.processDash()

// Dash to the right with custom tuning
await engine.processDash({
  direction: { x: 1, y: 0 },
  additionalSpeed: 10,
  duration: 220,
  cooldown: 600
})
```

## pointer

Read the latest pointer position tracked by the client canvas. `world()` returns
coordinates in map/world space, suitable for action payloads such as projectile
targets. `screen()` returns the latest canvas/global pointer position.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `property`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
pointer.screen(): { x: number, y: number } | null
pointer.world(): { x: number, y: number } | null
pointer.updateFromEvent(event: any): { x: number, y: number } | null
```

### Examples

```ts theme={null}
const target = engine.pointer.world()

if (target) {
  engine.processAction('projectile:shoot', {
    target,
    source: 'keyboard'
  })
}
```

## interactions

Register client-only pointer behaviors for map sprites. Use interactions for
hover popovers, selection feedback, drag previews, cursor changes, and other
CanvasEngine overlays. Nothing is sent to the server unless a behavior calls
`ctx.action(...)`.

* Source: `packages/client/src/services/interactions.ts`
* Kind: `property`
* Defined in: `RpgClientEngine`

### Mental Model

`interactions` is a client-side layer attached to rendered map sprites. It does
not change gameplay state by itself.

Use it for:

* hover popovers
* selection states
* cursor changes
* local highlights and drag previews
* hitbox-based pointer filtering
* explicit pointer-driven actions

Do not use it as the authority for gameplay. When an interaction must change the
world, call `ctx.action(...)` and validate the request on the server.

### Registering A Behavior

```ts theme={null}
engine.interactions.use(target, behavior)
```

`target` can be:

* a sprite/event id
* an event name
* a sprite `_type`, such as `"event"` or `"player"`
* `"*"` for every sprite
* a function receiving `{ client, target, sprite }`

The returned function unregisters the behavior:

```ts theme={null}
const stop = engine.interactions.use('Guard', hoverPopover(GuardPopover))

stop()
```

### From A Client Module

Use `interactions.setup()` when registering behaviors from a module:

```ts theme={null}
import { defineModule, hoverPopover } from '@rpgjs/client'
import GuardPopover from './components/GuardPopover.ce'

export default defineModule({
  client: {
    interactions: {
      setup(engine) {
        engine.interactions.use('Guard', hoverPopover(GuardPopover))
      }
    }
  }
})
```

For simple lists, `interactions.use` is also accepted:

```ts theme={null}
export default defineModule({
  client: {
    interactions: {
      use: [
        ['Guard', hoverPopover(GuardPopover)]
      ]
    }
  }
})
```

### Hover Popover

Register a CanvasEngine component as an overlay for an existing event:

```ts theme={null}
import { hoverPopover } from '@rpgjs/client'
import GuardPopover from './components/GuardPopover.ce'

engine.interactions.use('Guard', hoverPopover(GuardPopover))
```

The component receives:

* `target` / `sprite`: the RPGJS client sprite
* `state`: `{ hovered, pressed, selected, dragging, data, overlays }`
* `bounds`: the default visual bounds
* `hitboxBounds`: the gameplay hitbox bounds
* `graphicBounds`: the rendered graphic bounds
* `pointer`: the client pointer helper
* `client`: the current `RpgClientEngine`

In CanvasEngine components, values returned by `defineProps()` are prop accessors.
Read the sprite with `sprite()` or `target()` before accessing its fields.
Component bounds are local to the sprite, so `bounds()` can be used directly to
draw overlays attached to that sprite.

Example:

```html theme={null}
<!-- GuardPopover.ce -->
<Container>
  @if (state().hovered) {
    <DOMContainer x={bounds().centerX} y={bounds().top - 32} zIndex={10000}>
      <div class="guard-popover">
        Parler a {target().name}
      </div>
    </DOMContainer>
  }
</Container>

<script>
  const { target, state, bounds } = defineProps()
</script>
```

The popover is local only. Hovering the guard does not call the server.

### Client-Only Selection

`selectable()` stores selection state locally. It does not send an action.

```ts theme={null}
import { selectable } from '@rpgjs/client'

engine.interactions.use('Chest', selectable())
```

The overlay component can read `state().selected`:

```html theme={null}
<Container>
  @if (state().selected) {
    <Graphics draw={drawRing} />
  }
</Container>

<script>
  const { state, bounds } = defineProps()

  const drawRing = (g) => {
    const box = bounds()
    g.ellipse(box.centerX, box.bottom - 4, box.width / 2, 6)
      .stroke({ color: 0xffd166, width: 2 })
  }
</script>
```

### Explicit Server Action

Call `ctx.action(...)` only when the pointer gesture is meant to perform
gameplay. This delegates to `engine.processAction(...)`.

```ts theme={null}
engine.interactions.use('Guard', {
  cursor: 'pointer',

  click(ctx) {
    ctx.action('guard:talk', {
      eventId: ctx.target.id
    })
  }
})
```

On the server, validate the request in the player input handler or a registered
action. The client-sent `eventId` should be treated as intent, not authority.

### Hitbox-Based Interactions

Use `hitTest()` to choose the clickable or draggable area. This is useful when a
sprite graphic is larger than its gameplay body.
Inside handlers and `hitTest()`, `ctx.bounds()` returns world-space bounds so it
can be compared directly with `ctx.pointer.world()`.

```ts theme={null}
engine.interactions.use('Tree', {
  cursor: 'pointer',

  hitTest(ctx) {
    return ctx.bounds('hitbox').contains(ctx.pointer.world())
  }
})
```

Available bounds:

* `ctx.bounds('hitbox')`: RPGJS gameplay hitbox
* `ctx.bounds('graphic')`: rendered graphic bounds
* `ctx.bounds()`: default bounds, currently graphic-first

You can also implement custom areas:

```ts theme={null}
engine.interactions.use('Tree', {
  hitTest(ctx) {
    const point = ctx.pointer.world()
    const box = ctx.bounds('graphic')

    if (!point) return false

    return (
      point.x >= box.left &&
      point.x <= box.right &&
      point.y >= box.bottom - 24 &&
      point.y <= box.bottom
    )
  },
})
```

### Drag And Drop To A Tile

`dragToTile()` starts a local drag state and sends an action on drop.

```ts theme={null}
import { dragToTile } from '@rpgjs/client'

engine.interactions.use('Crate', dragToTile({
  action: 'crate:move'
}))
```

The default payload is:

```ts theme={null}
{
  eventId: ctx.target.id,
  position: ctx.pointer.tile()
}
```

`ctx.pointer.tile()` returns:

```ts theme={null}
{
  x: number,
  y: number,
  worldX: number,
  worldY: number,
  width: number,
  height: number
}
```

Customize the payload with `data`:

```ts theme={null}
engine.interactions.use('Crate', dragToTile({
  action: 'crate:move',
  data(ctx) {
    return {
      crateId: ctx.target.id,
      tile: ctx.pointer.tile(),
      source: 'mouse'
    }
  }
}))
```

Or handle the drop yourself:

```ts theme={null}
engine.interactions.use('Crate', dragToTile({
  onDrop(ctx) {
    const tile = ctx.pointer.tile()

    if (!tile) {
      ctx.cancel()
      return
    }

    ctx.action('crate:move', {
      eventId: ctx.target.id,
      tile
    })
  }
}))
```

### Custom Drag Preview

For full control, use low-level handlers:

```ts theme={null}
engine.interactions.use('Crate', {
  cursor: 'grab',

  hitTest(ctx) {
    return ctx.bounds('hitbox').contains(ctx.pointer.world())
  },

  dragstart(ctx) {
    ctx.overlay.render(CrateGhost, {
      position: ctx.pointer.world()
    })
  },

  dragmove(ctx) {
    ctx.overlay.update({
      position: ctx.pointer.world(),
      tile: ctx.pointer.tile()
    })
  },

  drop(ctx) {
    ctx.overlay.clear()
    ctx.action('crate:move', {
      eventId: ctx.target.id,
      position: ctx.pointer.tile()
    })
  },

  cancel(ctx) {
    ctx.overlay.clear()
  }
})
```

The overlay component can use any CanvasEngine primitive, including
`DOMContainer`, `Graphics`, `Sprite`, or `Text`.

```html theme={null}
<!-- CrateGhost.ce -->
<Container>
  @if (position()) {
    <Graphics draw={drawPreview} zIndex={10000} />
  }
</Container>

<script>
  const { position, tile } = defineProps()

  const drawPreview = (g) => {
    const currentTile = tile()
    const currentPosition = position()

    if (currentTile) {
      g.rect(currentTile.worldX, currentTile.worldY, currentTile.width, currentTile.height)
        .stroke({ color: 0x66ff99, width: 2 })
    }
    if (currentPosition) {
      g.circle(currentPosition.x, currentPosition.y, 6)
        .fill({ color: 0xffffff, alpha: 0.6 })
    }
  }
</script>
```

### Low-Level Behavior API

A behavior can define these handlers:

```ts theme={null}
engine.interactions.use('Chest', {
  cursor: 'pointer',

  pointerenter(ctx) {
    ctx.overlay.render(ChestHint)
  },

  pointerleave(ctx) {
    ctx.overlay.clear()
  },

  pointerdown(ctx) {
    ctx.state.patch({ pressed: true })
  },

  pointerup(ctx) {
    ctx.state.patch({ pressed: false })
  },

  click(ctx) {
    ctx.select()
  }
})
```

Supported handler names:

* `pointerenter`
* `pointerleave`
* `pointerover`
* `pointerout`
* `pointerdown`
* `pointerup`
* `pointermove`
* `click`
* `dragstart`
* `dragmove`
* `drop`
* `cancel`

### Interaction Context

Every handler receives `ctx`:

```ts theme={null}
type ctx = {
  client: RpgClientEngine
  target: RpgClientObject
  sprite: RpgClientObject
  event?: unknown
  pointer: {
    screen(): { x: number, y: number } | null
    world(): { x: number, y: number } | null
    tile(): {
      x: number
      y: number
      worldX: number
      worldY: number
      width: number
      height: number
    } | null
  }
  bounds(kind?: 'bounds' | 'hitbox' | 'graphic' | string): Bounds
  state: {
    value(): InteractionState
    get(key: string): unknown
    set(key: string, value: unknown): void
    patch(patch: Partial<InteractionState>): void
  }
  overlay: {
    render(component: any, props?: Record<string, any>): void
    update(props?: Record<string, any>): void
    clear(): void
  }
  select(selected?: boolean): void
  action(action: string | number, data?: any): void
  cancel(): void
}
```

### Helpers

RPGJS exports small helpers for common cases:

```ts theme={null}
hoverPopover(component, props?)
selectable({ cursor?, onSelect? })
draggable({ cursor?, start?, move?, drop?, cancel? })
dragToTile({ action?, data?, onDrop?, cursor? })
```

These helpers are only shortcuts. For project-specific UX, pass a behavior
object directly to `engine.interactions.use(...)`.

### Network Rules

* Pointer movement, hover, overlays, selection, drag previews, and cursor changes
  are client-only.
* `ctx.overlay.*`, `ctx.state.*`, and `ctx.select()` do not send packets.
* `ctx.action(...)` is the only interaction helper that sends an action to the
  server.
* Server code must validate distance, permissions, target visibility, and map
  state before applying gameplay changes.

## setCameraFollow

Set the camera to follow a specific sprite

This method changes which sprite the camera viewport should follow.
The camera can smoothly animate to the target sprite before continuous follow starts.

## Design

The camera follow target is stored in a signal that is read by sprite components.
Each sprite checks if it should be followed by comparing its ID with the target ID.
When smoothMove options are provided, the viewport animation is handled by CanvasEngine's
viewport system. `time` and `ease` configure the transition to the target, while
`speed`, `acceleration`, and `radius` configure the continuous follow behavior after
the transition.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
setCameraFollow(targetId: string | null, smoothMove?: boolean | {
  enabled?: boolean;
  time?: number;
  ease?: CameraFollowEase;
  speed?: number;
  acceleration?: number | null;
  radius?: number | null;
}): void
```

### Parameters

* `targetId`: `string | null`
* `smoothMove?`: `boolean | { enabled?: boolean; time?: number; ease?: CameraFollowEase; speed?: number; acceleration?: number | null; radius?: number | null }`

### Examples

```ts theme={null}
// Follow another player with default smooth animation
engine.setCameraFollow(otherPlayerId, true);

// Follow an event with custom smooth animation
engine.setCameraFollow(eventId, {
  time: 1000,
  ease: "easeInOutQuad"
});

// Follow with a smooth transition and softer continuous follow
engine.setCameraFollow(eventId, {
  time: 1000,
  ease: "easeInOutQuad",
  speed: 12,
  acceleration: 0.2,
  radius: 80
});

// Follow without animation (instant)
engine.setCameraFollow(targetId, false);

// Return to following current player
engine.setCameraFollow(null);
```

## setKeyboardControls

Assigns a CanvasEngine KeyboardControls instance to the dependency injection context

This method registers a KeyboardControls instance from CanvasEngine into the DI container,
making it available for injection throughout the application. The particularity is that
this method is automatically called when a sprite is displayed on the map, allowing the
controls to be automatically associated with the active sprite.

## Design

* The instance is stored in the DI context under the `KeyboardControls` token

* It's automatically assigned when a sprite component mounts (in `character.ce`)

* The controls instance comes from the CanvasEngine component's directives

* Once registered, it can be retrieved using `inject(KeyboardControls)` from anywhere

* Source: `packages/client/src/RpgClientEngine.ts`

* Kind: `method`

* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
setKeyboardControls(controlInstance: any)
```

### Parameters

* `controlInstance`: `any`

### Examples

```ts theme={null}
// The method is automatically called when a sprite is displayed:
// client.setKeyboardControls(element.directives.controls)

// Later, retrieve and use the controls instance:
import { Input, inject, KeyboardControls } from '@rpgjs/client'

const controls = inject(KeyboardControls)
const control = controls.getControl(Input.Enter)

if (control) {
  console.log(control.actionName) // 'action'
}
```

## setSoundResolver

Set a resolver function for sounds

The resolver is called when a sound is requested but not found in the cache.
It can be synchronous (returns directly) or asynchronous (returns a Promise).
The resolved sound is automatically cached for future use.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
setSoundResolver(resolver: (id: string) => any | Promise<any>): void
```

### Parameters

* `resolver`: `(id: string) => any | Promise<any>`

### Examples

```ts theme={null}
// Synchronous resolver
engine.setSoundResolver((id) => {
  if (id === 'dynamic-sound') {
    return { id: 'dynamic-sound', src: 'path/to/sound.mp3' };
  }
  return undefined;
});

// Asynchronous resolver (loading from API)
engine.setSoundResolver(async (id) => {
  const response = await fetch(`/api/sounds/${id}`);
  const data = await response.json();
  return data;
});
```

## setSpritesheetResolver

Set a resolver function for spritesheets

The resolver is called when a spritesheet is requested but not found in the cache.
It can be synchronous (returns directly) or asynchronous (returns a Promise).
The resolved spritesheet is automatically cached for future use.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
setSpritesheetResolver(resolver: (id: string) => any | Promise<any>): void
```

### Parameters

* `resolver`: `(id: string) => any | Promise<any>`

### Examples

```ts theme={null}
// Synchronous resolver
engine.setSpritesheetResolver((id) => {
  if (id === 'dynamic-sprite') {
    return { id: 'dynamic-sprite', image: 'path/to/image.png', framesWidth: 32, framesHeight: 32 };
  }
  return undefined;
});

// Asynchronous resolver (loading from API)
engine.setSpritesheetResolver(async (id) => {
  const response = await fetch(`/api/spritesheets/${id}`);
  const data = await response.json();
  return data;
});
```

## startTransition

Start a transition

Convenience method to display a transition by its ID using the GUI system.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

The returned promise resolves when the transition component calls its
`onFinish` prop.

### Signature

```ts theme={null}
startTransition(id: string, props?: any): Promise<void>
```

### Parameters

* `id`: `string`
* `props?`: `any`

### Examples

```ts theme={null}
// Start a fade transition
engine.startTransition('fade', { duration: 1000, color: 'black' });

// Start with onFinish callback
engine.startTransition('fade', {
  duration: 1000,
  onFinish: () => console.log('Fade complete')
});

// Wait until the transition finishes
await engine.startTransition('fade', { duration: 1000 });
```

## stopAllSounds

Stop all currently playing sounds

This method stops all sounds that are currently playing.
Useful when changing maps to prevent sound overlap.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
stopAllSounds(): void
```

### Examples

```ts theme={null}
// Stop all sounds
engine.stopAllSounds();
```

## stopSound

Stop a sound that is currently playing

This method stops a sound that was previously started with `playSound()`.

* Source: `packages/client/src/RpgClientEngine.ts`
* Kind: `method`
* Defined in: `RpgClientEngine`

### Signature

```ts theme={null}
stopSound(soundId: string): void
```

### Parameters

* `soundId`: `string`

### Examples

```ts theme={null}
// Start a looping sound
engine.playSound('background-music', { loop: true });

// Later, stop it
engine.stopSound('background-music');
```
