# Animation

> Sprite animation in FlatRedBall2. Use for AnimationChain, AnimationChainList, AnimationPlayer, AchxLoader, .achx/.achj files, Aseprite/.ase loading, Sprite.PlayAnimation, frame-based texture flipping, looping/non-looping animations, AnimationFinished events, and per-frame collision shapes (hitboxes/hurtboxes, static object collision).

- Skill: `vchelaru/animation` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add vchelaru/animation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vchelaru/animation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: vchelaru (https://skillmd.com/u/vchelaru)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vchelaru/animation

---


# Sprite Animation in FlatRedBall2

Sprites animate via `AnimationChain` / `AnimationChainList`, driven automatically by `Screen.Update` — no per-frame call needed in game code.

## Quick setup: adding animation content

1. Create a folder in your `*.Common` project's `Content` directory (for example `Content/Animations`).
2. Copy your source spritesheet PNG into that folder.
3. Open Animation Editor and save a new `.achx` in the same folder as the PNG.
4. Ensure your project copies these files to output (for example `Content/Animations/**` in `.csproj`; see `references/platformer-template.md`).

`FileRelativeTextures` in `.achx` expects relative texture paths, so keeping `.achx` and texture content together avoids broken lookups.

## Runtime Types

- `AnimationFrame` — texture + source rectangle (pixel coords) + flip flags + `FrameLength` (TimeSpan) + per-frame `RelativeX/Y` offsets + optional `Shapes` collection
- `AnimationChain : List<AnimationFrame>` — named sequence; `TotalLength` = sum of `FrameLength`s
- `AnimationChainList : List<AnimationChain>` — string indexer for lookup by name; the unit of "ownership" for per-frame shapes (see Topics)

## Standalone AnimationChain.MonoGame/.Common Package

The types above live in `FlatRedBall2.Animation` and back `Sprite.PlayAnimation`. A separate
generic package (namespaces `FlatRedBall.AnimationChain` / `FlatRedBall2.AnimationEditorCommon`) —
`AnimationChainList<TFrame>`, `AnimationPlayer<TFrame>`, `AchxLoader`
(`src/AnimationChain.MonoGame/AchxLoader.cs`) — is for raw MonoGame projects with no FRB2
`Screen`/`Sprite`. Same names, different (generic) shape — don't conflate the two; see
`samples/AnimationChainSample`.

## Sprite Playback API

| Member | Default | Notes |
|---|---|---|
| `AnimationChains` | `null` | Assign before `PlayAnimation`; without a parent entity, `RelativeX/Y` and per-frame shapes don't behave usefully |
| `PlayAnimation(string name)` | — | Looks up by name; no-op if not found |
| `PlayAnimation(AnimationChain chain)` | — | Play a specific chain directly |
| `Animate` | `false` | Auto-managed. **Do not write to express idle state** — see Gotchas |
| `IsLooping` | `true` | `false` for one-shot |
| `AnimationSpeed` | `1f` | Multiplier |
| `CurrentAnimation` | — | Read-only; returns the active chain |
| `AnimationFinished` | — | Fires when a non-looping animation ends |

`PlayAnimation` resets time to frame 0 and sets `Animate = true`. Calling it every frame with the same chain restarts on frame 0 every tick — guard with `CurrentAnimation?.Name != "Run"`.

## Building Chains in Code

There is no builder API; it's plain object init. Construct `AnimationFrame` instances with `Texture`, `SourceRectangle`, and `FrameLength`, add them to an `AnimationChain` (with a `Name`), add chains to an `AnimationChainList`, assign the list to `Sprite.AnimationChains`, then `PlayAnimation`. For hitboxes/hurtboxes per frame, see Topics.

## Prompt handoff template

When you want an AI assistant to wire animation selection to gameplay state, describe the mapping explicitly:

"Use `Player.achx` for `PlayerSprite`. Play `Idle` when speed is 0, `Run` when grounded and moving, `Jump` when vertical velocity is positive, and `Fall` when vertical velocity is negative. Do not restart the same chain every frame; only switch when the target chain name changes."

## Hot-Reload

`AnimationChainList.TryReloadFrom(path, content)` patches a list in place by chain-name match. Live `Sprite.CurrentAnimation` references keep playing with new frames. **Every sprite must share one list instance** — re-parsing per spawn defeats hot-reload. Wire via `WatchContentDirectory` — see `content-hot-reload`.

## Topics (load on demand)

| When you need to… | Read |
|---|---|
| Load Aseprite `.ase`/`.aseprite` files | `references/aseprite.md` |
| Load `.achx`/`.achj` or author one by hand | `references/achx-authoring.md` |
| Load Adobe Animate atlas XML | `references/adobe-animate.md` |
| Add per-frame shapes (hitboxes/hurtboxes, or a static object's fixed collision box) | `references/per-frame-shapes.md` |
| Drop in the bundled platformer animation template | `references/platformer-template.md` |
| Pick animation state in a platformer (state → chain mapping) | `platformer-movement` skill |

## Gotchas

- **Never pause animation to express "still" state.** Animation runs continuously. If a state should look motionless (idle, hanging on a ladder, holding a charge), the **content author** authors a chain that looks still — a 1-frame chain, or a multi-frame chain with subtle motion (breath, blinking). Game code that flips `_sprite.Animate = false` bakes a content decision into engine-driving code and forecloses author choices the artist may want later. Only `PlayAnimation` (sets true) and the non-looping end-of-chain hook (sets false) should write `Animate`. If you reach for `_sprite.Animate = …`, you want a different chain.
- **`AnimationChains` must be set before `PlayAnimation`** — otherwise silent no-op.
- **Standalone package: `TryReloadFrom` and `TryReload` are different methods, not synonyms.** `AnimationChainList<AnimationFrame>.TryReloadFrom` (instance) wants a frame-factory `Func<AnimationFrameSave, AnimationFrame>`; the `TryReload` extension in `AnimationChainListSaveExtensions` wants a texture-loader `Func<string, Texture2D?>`. Passing a texture-loader lambda to `TryReloadFrom` won't compile — use `TryReload`.
- **Non-looping animation stops on the last frame** — `Animate` flips false; call `PlayAnimation` again to restart.
- **Animation is paused when the screen is paused** — `AnimateSelf` runs inside the `!IsPaused` block in `Screen.Update`.
- **`Sprite.X` and `Sprite.Y` are overwritten on every frame switch.** Each `AnimationFrame` carries `RelativeX`/`RelativeY` (default `0`), and advancing assigns those unconditionally. Code like `_booster.Y = -10` in `CustomInitialize` works for exactly one frame, then snaps to `0`. To offset an animated sprite relative to its parent entity, bake the offset into each frame's `RelativeX`/`RelativeY` (in the `.achx` or in code), or attach the sprite to a child entity whose own `X`/`Y` carries the offset.
- **`TextureScale` recalculates `Width`/`Height` per frame** — when `TextureScale` is non-null, frame switches recompute dimensions from the source rect. Don't manually assign `Width`/`Height` if `TextureScale` is in play.
- **`.achx` isn't only for motion.** A chain can have a single frame and still be the right tool — it pairs a sprite's texture/offset with its collision box for a static object (a tree, a sign) that never animates. Going from that single frame to a real multi-frame animation later needs no code change; `PlayAnimation` and shape reconciliation behave the same either way.

