Skip to content

Animation

Sprites support frame-based animation using Aseprite frame tags. Each tag defines a named sequence of frames with timing and playback direction.

Frame Tags

Frame tags are named animation sequences defined in Aseprite (e.g., "idle", "walk", "attack").

Defining Tags in Aseprite

  1. Select frames in the timeline
  2. Right-click and choose New Tag
  3. Name the tag and set properties:
    • Direction: Forward, Reverse, or Ping-pong
    • Repeat: Number of times to loop (0 = infinite)

Using Tags in Code

teal
local gfx = require("tecs2d.gfx")

-- Create sprite with initial tag
world:spawn(
    tecs.builtins.Transform(100, 100),
    gfx.Sprite.fromAseprite("player.png", "idle")
)

-- Change tag at runtime
local sprite = world:get(entityId, gfx.Sprite)
sprite:setTag("walk")

Empty Tag

Passing an empty string ("") plays all frames in order:

teal
gfx.Sprite.fromAseprite("player.png", "")  -- All frames

Playback Directions

Tags support three playback directions:

DirectionConstantBehavior
Forward0Frames 1 → N, loop
Reverse1Frames N → 1, loop
Ping-pong2Frames 1 → N → 1, loop
teal
local sheet = gfx.SpriteSheet.fromFile("player.png")
local tag = sheet:getFrameTag("bounce")
if tag.direction == 2 then  -- Ping-pong
    print("This animation bounces back and forth")
end

Playback Control

Pause and Resume

teal
local sprite = world:get(entityId, gfx.Sprite)

-- Pause at current frame
sprite:pause()

-- Resume from paused frame
sprite:resume()

-- Synchronized batch pause (all sprites pause at same visual frame)
local currentTime = love.timer.getTime()
for entity in entities do
    local spr = world:get(entity, gfx.Sprite)
    spr:pause(currentTime)
end

Seek to Frame

teal
-- Jump to first frame and pause
sprite:pauseAtStart()

-- Jump to last frame and pause
sprite:pauseAtEnd()

-- Jump to specific frame (0-indexed within current tag)
sprite:gotoFrame(3)

Query Current Frame

teal
-- Frame index within current tag (0-indexed)
local frame = sprite:getFrame()

-- Absolute frame number in sheet (1-indexed)
local absFrame = sprite:getAbsoluteFrame()

-- Current tag name
local tag = sprite:getTag()

Speed Control

At Creation

teal
gfx.Sprite.fromAseprite("player.png", "walk", {
    speed = 0.5   -- Half speed (slow motion)
})

gfx.Sprite.fromAseprite("player.png", "run", {
    speed = 2.0   -- Double speed
})

Speed Values

ValueEffect
1.0Normal speed (default)
0.5Half speed (slow motion)
2.0Double speed
0.0Effectively paused

Note: Speed is set at sprite creation and cached in the instance. To change speed, create a new sprite or use different speed values.

Frame Timing

Each frame can have its own duration, set in Aseprite's timeline.

Variable Frame Timing

In Aseprite:

  1. Select a frame in the timeline
  2. Right-click and choose Frame Properties
  3. Set the duration in milliseconds

Tecs respects per-frame durations, enabling effects like:

  • Hold poses at the end of an attack
  • Quick anticipation frames before an action
  • Slower impact frames for emphasis

Accessing Timing

teal
local sheet = gfx.SpriteSheet.fromFile("player.png")

-- Single frame duration
local frame = sheet:getFrame(1)
print(frame.duration)  -- Duration in seconds

-- Total tag duration
local tag = sheet:getFrameTag("attack")
print(tag.duration)  -- Total duration in seconds

Staggered Start Times

To prevent synchronized animations looking unnatural, use startTime:

teal
-- Spawn crowd with staggered animations
for i = 1, 20 do
    world:spawn(
        tecs.builtins.Transform(i * 30, 100),
        gfx.Sprite.fromAseprite("npc.png", "idle", {
            startTime = math.random() * 2.0  -- Random 0-2 second offset
        })
    )
end

Animation Events

For callbacks when animation tags change, see Events.

Common Patterns

One-Shot Animation

Play an animation once and freeze on the last frame:

teal
local sprite = world:get(entityId, gfx.Sprite)
sprite:playOnce("attack")

Hold Last Frame

Play animation once, then switch to idle:

teal
local sprite = world:get(entityId, gfx.Sprite)
sprite:playOnce("death", function(anim)
    anim:setTag("idle")
end)

playOnce uses the same CPU timing data as sprite playback, so the callback runs when the animation reaches its terminal frame without needing GPU readback.

Tiled Animated Tiles

Tiles with animation defined in Tiled are automatically spawned as Sprite entities with globally-synced animation. Each animated tile includes a TileSource component that provides access to the original tile's metadata (properties, class, tileset info).

See TileSource Component for querying and working with animated tiles from Tiled maps.