# Sprite

> Talos 0.7 · Since 0.6

Present on `ctx.sprite` when the entity has a `sprite` component: a 2D picture in the world that turns to the camera, shows the view of its frame nearest the camera's direction, and plays the named animations the component declares.

`ts // An enemy: walk while chasing, flinch when hurt, then walk again; die and stay down. ctx.sprite?.play("walk") ctx.sprite?.play("pain", { then: "walk" }) ctx.sprite?.play("death") // loop: false in the scene: holds its last frame ctx.events.on("animation-end", (e) => { ... }) // e.data: SpriteAnimationEndEvent ```

## Signature

```ts
export interface Sprite {
  /**
   * Plays an animation the component declares, from its first frame. The animation already
   * playing runs on (so calling this every tick is fine) unless `opts.restart`.
   *
   * A non-looping animation (`loop: false`) stops on its last frame when it ends, raises the bus
   * event `opts.event` (default `"animation-end"`) with {@link SpriteAnimationEndEvent} as its
   * data, and hands over to `opts.then` when that is given. Throws an `Error` whose `name` is
   * `SpritePlayError` (message opening `sprite-play-unknown`) for an animation or a `then` the
   * sprite does not have. Returns true.
   */
  play(name: string, opts?: SpritePlayOptions): boolean
  /** Holds the frame showing now; `play` starts again. */
  stop(): void
  /** The animation playing, or stopped on; null while a still frame shows. */
  readonly animation: string | null
  /**
   * The frame showing, by name. Setting it shows that frame still and stops the animation; a
   * name the sprite does not have throws an `Error` whose `name` is `SpriteFrameError`.
   */
  frame: string
  /** The frame's entry in the playing animation's `frames`, 0 up. */
  readonly frameIndex: number
  /** False once {@link stop} ran or a non-looping animation reached its end. */
  readonly playing: boolean
  /**
   * The view the last drawn frame showed: 0 is the front (the camera on the side the entity
   * faces), counting counter-clockwise seen from above, so of 8, 2 is its left and 4 its back.
   */
  readonly view: number
  /** Playback multiplier: 1 as authored, 0 frozen. `RangeError` below 0 or not finite. */
  speed: number
  /** Whether it is drawn. A picked-up item can hide rather than despawn. */
  visible: boolean
  /** 0..1; below 1 the sprite is drawn see-through. `RangeError` outside 0..1. */
  opacity: number
  /** The tint multiplied into its pictures, `#rrggbb`; a room's light level is a grey. A string that is not `#RRGGBB` or `#RGB` throws a `TypeError`. */
  color: string
  /**
   * Drawn flipped left to right about its pivot, on top of a view's `mirror`: a 2D character
   * facing the other way (`ctx.sprite.flipX = velocity[0] < 0`). Starts as the component's `flipX`.
   */
  flipX: boolean
  /** Drawn flipped top to bottom about its pivot. Starts as the component's `flipY`. */
  flipY: boolean
  /**
   * A `flat` sprite's draw layer, -32..32: higher is drawn over lower. Starts as the component's
   * `sortLayer` (default 0). `RangeError` outside -32..32 or not finite.
   */
  sortLayer: number
  /**
   * A `flat` sprite's order within its layer, -500..500, need not be whole: higher is drawn over
   * lower, so a top-down game can set it from -y each tick. Starts as the component's `order`
   * (default 0). `RangeError` outside -500..500 or not finite.
   */
  order: number
}
```

Import: `import type { Sprite } from "@argus/api"`

## Members

| Member | Signature | Description |
|---|---|---|
| `play` | `play(name: string, opts?: SpritePlayOptions): boolean` | Plays an animation the component declares, from its first frame. The animation already playing runs on (so calling this every tick is fine) unless `opts.restart`. A non-looping animation (`loop: false`) stops on its last frame when it ends, raises the bus event `opts.event` (default `"animation-end"`) with {@link SpriteAnimationEndEvent} as its data, and hands over to `opts.then` when that is given. Throws an `Error` whose `name` is `SpritePlayError` (message opening `sprite-play-unknown`) for an animation or a `then` the sprite does not have. Returns true. |
| `stop` | `stop(): void` | Holds the frame showing now; `play` starts again. |
| `animation` | `readonly animation: string \| null` | The animation playing, or stopped on; null while a still frame shows. |
| `frame` | `frame: string` | The frame showing, by name. Setting it shows that frame still and stops the animation; a name the sprite does not have throws an `Error` whose `name` is `SpriteFrameError`. |
| `frameIndex` | `readonly frameIndex: number` | The frame's entry in the playing animation's `frames`, 0 up. |
| `playing` | `readonly playing: boolean` | False once {@link stop} ran or a non-looping animation reached its end. |
| `view` | `readonly view: number` | The view the last drawn frame showed: 0 is the front (the camera on the side the entity faces), counting counter-clockwise seen from above, so of 8, 2 is its left and 4 its back. |
| `speed` | `speed: number` | Playback multiplier: 1 as authored, 0 frozen. `RangeError` below 0 or not finite. |
| `visible` | `visible: boolean` | Whether it is drawn. A picked-up item can hide rather than despawn. |
| `opacity` | `opacity: number` | 0..1; below 1 the sprite is drawn see-through. `RangeError` outside 0..1. |
| `color` | `color: string` | The tint multiplied into its pictures, `#rrggbb`; a room's light level is a grey. A string that is not `#RRGGBB` or `#RGB` throws a `TypeError`. |
| `flipX` | `flipX: boolean` | Drawn flipped left to right about its pivot, on top of a view's `mirror`: a 2D character facing the other way (`ctx.sprite.flipX = velocity[0] < 0`). Starts as the component's `flipX`. |
| `flipY` | `flipY: boolean` | Drawn flipped top to bottom about its pivot. Starts as the component's `flipY`. |
| `sortLayer` | `sortLayer: number` | A `flat` sprite's draw layer, -32..32: higher is drawn over lower. Starts as the component's `sortLayer` (default 0). `RangeError` outside -32..32 or not finite. |
| `order` | `order: number` | A `flat` sprite's order within its layer, -500..500, need not be whole: higher is drawn over lower, so a top-down game can set it from -y each tick. Starts as the component's `order` (default 0). `RangeError` outside -500..500 or not finite. |

## Example

The tested fixture `fixtures/host/door.ts`.

```ts
import type { Behavior } from "@argus/api"
// docs/API.md, "Joints": a door on a hinge that swings open while the player is near and closes
// behind them. Typechecked by packages/api's tests.
export default {
  update(ctx) {
    const player = ctx.world.find("player")
    const near = player !== null && ctx.entity.distanceTo(player) < 2
    ctx.joint?.setMotor(near ? Math.PI / 2 : 0, { mode: "position", maxForce: 200 })
  },
} satisfies Behavior
```
