Simple Sprite Animator DSL

Animation DSL Documentation

This guide explains how to write animation definitions using the Animation DSL (Domain-Specific Language). The DSL is a text-based format for defining sprite animation states.

Basic Syntax

Each animation state follows this format:

StateName: frame1, frame2, frame3, ...

Or with an optional sprite category alias:

StateName[SpriteCategory]: frame1, frame2, frame3, ...

IMPORTANT: Every state MUST end with explicit end behavior: _wait, _loop, _pingpong, or a state transition. The parser will throw an error if you forget this.

Frame Data Format

Each frame can be specified in several ways:

1. Duration Only (Simplest)

5
  • Shows frame for 5 fixed update ticks
  • Frame index is automatically assigned (0, 1, 2, 3…)
  • Duration must be >= 0 (negative durations are not allowed)

Example:

Walk: 5, 5, 5, 5, _loop

This creates a 4-frame walk animation where each frame displays for 5 ticks. Frame indices are 0, 1, 2, 3. Note: Must end with _loop (or _wait/transition).

2. Duration with Events

5|eventName
  • Shows frame for 5 ticks
  • Triggers an event when entering this frame
  • Multiple events: 5|event1|event2|event3

Example:

Attack: 5|swingSound, 5|hitSound, 5, Idle

Frame 0 plays for 5 ticks and triggers swingSound. Frame 1 plays for 5 ticks and triggers hitSound. Frame 2 plays for 5 ticks with no events, then transitions to Idle.

3. Explicit Duration + Frame Index

5|2
  • Displays for 5 ticks
  • Uses sprite frame index 2
  • Useful when sprites aren’t sequential (e.g., using frame 0, then frame 5, then frame 2)
  • Note: Duration always comes first, frame index second
  • Duration must be >= 0, frame index must be >= 0 (negative values are not allowed)

Example:

Idle: 10|0, 10|1, 10|2, 10|1, _loop

10 ticks showing sprite 0, then 10 ticks sprite 1, then 10 ticks sprite 2, then 10 ticks sprite 1, then loops back to sprite 0.

4. Duration + Frame Index + Events

5|2|eventName
  • Displays for 5 ticks
  • Uses sprite frame index 2
  • Triggers custom event(s)
  • Note: Duration always comes first, frame index second
  • Duration must be >= 0, frame index must be >= 0 (negative values are not allowed)
  • Important: Special events (_wait, _loop, _pingpong) cannot be inline with duration/frameIndex. They must be on separate frame entries (e.g., 5|2, _wait not 5|2|_wait)

Example:

Jump: 3|0|jumpSound, 3|1, 3|2|landSound, _wait

5. Events Only (Control Frames)

_loop
_wait
_pingpong
StateName
_loop|customEvent
_wait|sfxSound
  • These are control-only frames that don’t display new sprites
  • Duration is automatically 0 (instant, no display time)
  • Reuses the last displayed sprite frame - does not advance the sprite counter
  • Special events (_wait, _loop, _pingpong) must be on events-only frames (cannot be inline with duration/frameIndex)
  • Special events can be combined with custom events: _wait|sfxSound
  • Custom events can be inline: 5|2|myEvent (but special events cannot)
  • Must be on the last frame (or the parser will error)

Important: Events-only frames keep displaying the same sprite as the previous frame. For example, fall: 8, _wait will display sprite 0 for 8 ticks, then continue displaying sprite 0 during the wait (not sprite 1). You could use fall: 8, 0|1, _wait if you wanted to display sprite 1 during the wait.

Special Events

_loop

Restarts the animation from the beginning. Use this for animations that should repeat indefinitely. Must be on an events-only frame (cannot be inline with duration/frameIndex).

Example:

Idle: 10, 10, 10, 10, _loop
Run: 5, 5, 5, 5, _loop|vfxRun

First example: Plays frames 0-3, then loops back to frame 0 indefinitely. Second example: Loops and triggers a visual effect.

_wait

Pauses the animation on the last displayed frame. The animation won’t advance until manually changed via SetState(). Must be on an events-only frame (cannot be inline with duration/frameIndex). The sprite displayed during wait is the same as the last frame that had duration > 0.

Example:

Death: 5, 5, 5, 5, _wait
Fall: 1|2, _wait|sfxLand

First example: Plays all 4 frames (sprites 0, 1, 2, 3), then waits and continues displaying sprite 3. Second example: Shows frame 2 for 1 tick, then waits and continues displaying sprite 2 (not sprite 3) while playing sound effect.

_pingpong

Reverses the animation direction, playing frames backwards. The animation will ping-pong back and forth indefinitely. When the ping-pong event fires, it skips back 2 frames to avoid displaying the last frame twice. Must be on an events-only frame (cannot be inline with duration/frameIndex). The ping-pong frame itself uses the last displayed sprite index (doesn’t advance the sprite counter).

Example:

Breathe: 5, 5, 5, 5, _pingpong
BreatheVfx: 5, 5, 5, 5, _pingpong|vfxBreathe

Plays frames 0→1→2→3, then reverses to 2→1→0, then forward again 1→2→3, then reverses 2→1→0, repeating indefinitely.

Sequence: 0, 1, 2, 3, 2, 1, 0, 1, 2, 3, 2, 1, 0, ...

Note that frame 3 is not displayed twice in a row - when ping-pong fires after frame 3, it goes back to frame 2, then continues backwards. The _pingpong frame itself displays sprite 3 (the last displayed sprite before ping-pong).

State Transition (Using State Name as Event)

Transitions to another animation state when reaching this frame. The state name must match an existing state (case-sensitive).

Example:

Attack: 5, 5, 5, Idle

Plays 3 frames of attack animation, then automatically transitions to the Idle state.

State Aliases (Sprite Category Reuse)

You can specify a sprite category alias using brackets. This allows multiple states to reuse the same sprite set while having different state names.

Format:

StateName[SpriteCategory]: frames...

Important Notes:

  • The part before the brackets (StateName) is used for SetState() lookups - this is the “real” state name
  • The part inside the brackets (SpriteCategory) is used internally for sprite resolution only
  • You cannot use the alias to look up states - only the state name works
  • This is purely for sprite asset reuse, not for alternative naming

Example:

Fall[Jump]: 1|2, _wait
Running[Run]: 10, 10, 10, 10, _loop
Walk[Run]: 20, 20, 20, 20, _loop
  • Fall[Jump]: State name is “Fall” (use SetState("Fall")), but uses “Jump” sprite category. Shows frame 2 for 1 tick, then waits.
  • Running[Run]: State name is “Running” (use SetState("Running")), but uses “Run” sprite category
  • Walk[Run]: State name is “Walk” (use SetState("Walk")), but uses “Run” sprite category

You CANNOT do: SetState("Run") to access Running[Run] or Walk[Run] - you must use the actual state names.

Explicit End Behavior Required

CRITICAL: Every state MUST have explicit end behavior on its last frame. The parser will throw an InvalidOperationException if you forget this.

Valid end behaviors:

  • _wait - stops and waits
  • _loop - loops indefinitely
  • _pingpong - ping-pongs back and forth
  • A valid state name - transitions to that state

❌ Invalid (will throw error):

Fire: 5, 5

✅ Valid:

Fire: 5, 5, _wait
Fire: 5, 5, _loop
Fire: 5, 5, Idle

Complete Examples

Example 1: Simple Idle Animation (4 frames, looping)

Idle: 10, 10, 10, 10, _loop

Example 2: Walk Animation (4 frames, looping, with sound effect)

Walk: 5|stepSound, 5, 5|stepSound, 5, _loop

Example 3: Attack Animation (3 frames, returns to idle)

Attack: 8, 8, 8, Idle

Example 4: Jump Animation (3 frames, waits at end)

Jump: 5, 5, 5, _wait

Example 5: Death Animation (5 frames, stops at end)

Death: 6, 6, 6, 6, 6, _wait

Example 6: Complex Attack with Events (5 frames)

Slash: 3|swingStart, 3|swingMid, 3|hitFrame, 3|recover, 3, Idle

Example 7: Ping-Pong Breathing (4 frames)

Breathe: 8, 8, 8, 8, _pingpong

Plays frames 0→1→2→3, then reverses 2→1→0, then forward 1→2→3, repeating. Sequence: 0, 1, 2, 3, 2, 1, 0, 1, 2, 3, 2, 1, 0, ...

Example 8: Using Explicit Frame Indices (non-sequential sprites)

Special: 5|0, 5|5, 5|2, 5|7, _loop

Uses sprite frames 0, 5, 2, 7 in that order. Each frame displays for 5 ticks. The _loop frame reuses sprite 7 (the last displayed sprite).

Example 9: State with Sprite Category Alias

Fall[Jump]: 1|2, _wait
Fall[Jump]: 1|2, _wait|sfxLand

State name is “Fall” (use SetState("Fall")), but uses “Jump” sprite category for sprite resolution. Shows frame 2 for 1 tick, then waits. The _wait must be on a separate frame entry (not inline). Can combine with custom events: _wait|sfxLand. The _wait frame reuses sprite 2 (the last displayed sprite).

Example 10: Duration 0 with Explicit Frame Index (instant sprite change)

Jump: 5, 0|2, 5, _wait

Displays sprite 0 for 5 ticks, then instantly sets sprite to 2 (duration 0), then displays sprite 3 for 5 ticks, then waits on sprite 3. The 0|2 frame sets the sprite immediately even though duration is 0.

Example 11: Duration 0 with Auto Frame Index (control-only)

Attack: 5, 0|sfxHit, 5, Idle

Displays sprite 0 for 5 ticks, then instantly triggers sfxHit event without changing sprite (reuses sprite 0), then displays sprite 1 for 5 ticks, then transitions to Idle. The 0|sfxHit frame is control-only and doesn’t advance the sprite counter.

Tips for Setting Up 3-5 Animations

  1. Start simple: Use duration-only frames (5, 5, 5) until you get the timing right
  2. Common durations:
    • Fast actions (attacks): 3-5 ticks per frame
    • Normal movement: 5-10 ticks per frame
    • Slow/idle: 10-20 ticks per frame
  3. Always end with explicit behavior: _loop, _wait, _pingpong, or a state transition - this is required, not optional
  4. Test incrementally: Add one animation at a time and test before adding more
  5. Use aliases for sprite reuse: Only use StateName[Alias] when you want multiple states to share the same sprite set

Common Patterns

Looping idle:

Idle: 15, 15, 15, 15, _loop

One-shot attack returning to idle:

Punch: 4, 4, 4, Idle

Movement with looping:

Run: 5, 5, 5, 5, _loop

Death sequence:

Death: 6, 6, 6, 6, 6, _wait

Action with sound effects:

Shoot: 3|gunSound, 3, 3, 3, Idle

State reusing sprites from another state:

Fall[Jump]: 1|2, _wait

Use SetState("Fall") to activate, but it uses “Jump” sprites. Shows frame 2 for 1 tick, then waits.

Notes

  • Frame indices start at 0
  • If you don’t specify a frame index, it auto-increments (0, 1, 2, 3…)
  • Duration is measured in FixedUpdate ticks (typically 50 per second)
  • Duration must be >= 0 - negative durations are not allowed (parser will throw error)
  • Frame index must be >= 0 - negative frame indices are not allowed (parser will throw error)
  • Duration 0 with auto frameIndex = control-only frame (reuses last displayed sprite, doesn’t advance sprite counter)
  • Duration 0 with explicit frameIndex = sets sprite immediately (explicit frameIndex always sets sprite, even with duration 0)
  • Events-only frames (_wait, _loop, _pingpong, state transitions) have duration 0 and reuse the last displayed sprite
  • Special events (_wait, _loop, _pingpong) must be on events-only frames, not inline with duration/frameIndex
    • ✅ Allowed: 5|2, _wait or _wait|sfxSound
    • ❌ Not allowed: 5|2|_wait (parser will throw error)
  • Custom events can be inline: 5|2|myEvent is valid
  • To wait on a specific frame: Use 1|frameIndex, _wait (separate frame entries)
  • Control frames (duration=0 or events-only) don’t advance the sprite counter - they reuse the last displayed sprite
  • Empty lines are ignored
  • State names are case-sensitive
  • You can have multiple states in one DSL file, one per line
  • Every state must have explicit end behavior - the parser validates this and will throw an error if missing
  • State aliases (brackets) are for sprite category reuse only - you cannot use them to look up states
  • Events fire when entering a frame (at the start), not after the duration completes