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, _waitnot5|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 forSetState()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” (useSetState("Fall")), but uses “Jump” sprite category. Shows frame 2 for 1 tick, then waits.Running[Run]: State name is “Running” (useSetState("Running")), but uses “Run” sprite categoryWalk[Run]: State name is “Walk” (useSetState("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
- Start simple: Use duration-only frames (
5, 5, 5) until you get the timing right - Common durations:
- Fast actions (attacks): 3-5 ticks per frame
- Normal movement: 5-10 ticks per frame
- Slow/idle: 10-20 ticks per frame
- Always end with explicit behavior:
_loop,_wait,_pingpong, or a state transition - this is required, not optional - Test incrementally: Add one animation at a time and test before adding more
- 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, _waitor_wait|sfxSound - ❌ Not allowed:
5|2|_wait(parser will throw error)
- ✅ Allowed:
- Custom events can be inline:
5|2|myEventis 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