tecs.gfx.animation

Fixed-step sprite-sheet playback.

A Sheet divides one image into frames and names frame spans with tags. An Animation selects a sheet and a tag and carries the speed, the loop flag, and where the cycle has got to. Playback advances on the fixed clock. The GPU resolves the shared frame table from the entity's Sprite playback encoding, so clean animated crowds perform no per-entity CPU work or instance uploads as frames change.

const hero = tecs.gfx.sheet.grid({
    name = "hero",
    imageWidth = 256,
    imageHeight = 32,
    frameWidth = 32,
    frameHeight = 32,
    tags = {idle = {from = 1, to = 4}, run = {from = 5, to = 8}},
})

hero:bind(tecs.gfx.images.id("sprites/hero"))
tecs.gfx.animation.plugin(world)

world:spawn(
    tecs.ecs.Transform2D(64, 64, 0, 1, 0, 32, 32),
    tecs.gfx.Tint(1, 1, 1, 1),
    hero:sprite(),
    tecs.gfx.animation.of(hero, "run"),
    tecs.gfx.Renderable2D
)

An entity needs a Sprite as well, because that is what playback writes and what extraction reads. An Animation on its own draws nothing.

Events#

Completed fires once when a one-shot passes its last frame, and Looped fires when a looping tag wraps. Both reach only entities carrying AnimationEvents, so a game watches a handful of animations without walking the rest of an animated crowd. Completed also clears playing on watched entities. Unwatched one-shots clamp to the last frame entirely on the GPU.

Reloads#

Re-exporting a sheet under the same name and folding it in with tecs.gfx.sheet.replace keeps every entity's sheet id, tag, and phase, so playback carries on and shows the new frames from its next step.

Module contents

Types

TypeKindDescription
AnimationstructStores playback state for one entity.
Completedrecord
Loopedrecord
PlayOptionstypeConfigures how an animation plays.

Functions

FunctionKindDescription
frameOffunctionReturns exactly the sheet frame selected by the shared GPU table.
offunctionCreates an animation that plays a named sheet tag, ready to spawn.
playfunctionPoints a live entity at a tag and restarts it there.
pluginfunctionInstalls dirty-gated GPU playback and events for AnimationEvents entities.
restartfunctionPlays an entity's animation again from the start of its tag.
timeOffunctionReturns elapsed seconds within an entity's animation cycle.

Values

ValueKindDescription
AnimationComponentvariableThe process-wide Animation component definition.
AnimationEventsvariable
DEFAULT_SPEEDvariableThe rate an animation runs at when nothing says otherwise, which is the timing the sheet's frames were authored with.

Types#

Animationstruct#

struct Animation
    sheet: integer
    tag: integer
    speed: number
    time: number
    frame: integer
    loop: boolean
    playing: boolean
    crossed: integer
end
@derive(nupp.derive.Debug, nupp.derive.Serde)

Stores playback state for one entity.

time is where the cycle has got to, in seconds, and stays inside the cycle, so it neither grows without bound nor loses precision to its own age. The cycle's duration is the sum of the durations of the frames the tag visits, which tecs.gfx.sheet.Sheet.cycle reports.

Fields

sheet#
sheet: integer

Caller-writable. Selects a sheet by its registration index. Zero plays nothing.

tag#
tag: integer

Caller-writable. Selects a tag by its Sheet.tagId index. Zero plays the whole sheet in order.

speed#
speed: number

Caller-writable. Multiplies the timing the sheet carries. One is the timing as authored and two is twice as fast. Zero or less holds the current frame and stops time advancing, which is a pause that leaves playing alone.

How long each frame is held is the sheet's answer rather than an entity's, because that is where an artist sets it: a hold frame is a frame with a long duration, which no single rate can express.

time#
time: number

Caller-writable. Supplies the phase used when frame is zero. To seek, write time and reset frame to zero. Read live phase with timeOf.

frame#
frame: integer

Engine-owned. Holds -1 for encoded GPU playback, or zero to request re-encoding from time. Ordinary game code should call frameOf, which answers for an entity carrying no Animation too. Writing zero asks playback to rewrite the Sprite, which is what play, restart, of, and a restored snapshot do.

loop#
loop: boolean

Caller-writable. Controls whether the tag restarts after its last frame.

playing#
playing: boolean

Caller-writable. Controls whether time advances.

crossed#
crossed: integer

Engine-owned. Retains the serialized component layout; GPU playback leaves this legacy event scratch field at zero. Ordinary game code should observe Completed and Looped instead. The reporting system clears it, and a snapshot drops it.

Completedrecord#

record Completed
    entity: integer
    sheet: sheet.Sheet
    tag: string
end
@derive(events.Event)@event(name="Completed")

Fields

entity#
entity: integer

Read-only. Identifies the entity whose animation ended a cycle.

sheet#
sheet: sheet.Sheet

Read-only. Reports the sheet that was playing.

tag#
tag: string

Read-only. Reports the tag that ended, or the empty string for a whole sheet.

Loopedrecord#

record Looped
    entity: integer
    sheet: sheet.Sheet
    tag: string
end
@derive(events.Event)@event(name="Looped")

Fields

entity#
entity: integer

Read-only. Identifies the entity whose animation ended a cycle.

sheet#
sheet: sheet.Sheet

Read-only. Reports the sheet that was playing.

tag#
tag: string

Read-only. Reports the tag that ended, or the empty string for a whole sheet.

PlayOptionstype#

type PlayOptions = {
    speed: number?,
    loop: boolean?,
    playing: boolean?
}

Configures how an animation plays.

speed defaults to one, and loop and playing both default to true.

Functions#

frameOffunction#

function frameOf(borrows world: ecs.World, entity: integer): integer

Returns exactly the sheet frame selected by the shared GPU table.

Arguments

NameTypeDescription
borrows worldecs.World

the entity's world

entityinteger

a live entity

Returns

TypeDescription
integer

the one-based sheet frame, or zero without a known sheet

offunction#

function of(source: sheet.Sheet, tag: string?, options: PlayOptions?): components.FFIInstance<Animation, components.FFIComponent<Animation>>

Creates an animation that plays a named sheet tag, ready to spawn.

Omit the tag to play the whole sheet in order.

Arguments

NameTypeDescription
sourcesheet.Sheet

the sheet to play

tagstring?

a tag the sheet names, or nil for the whole sheet

optionsPlayOptions?

the speed, loop, and playing overrides, or nil for the sheet's own timing, looping, and playing

Returns

TypeDescription
components.FFIInstance<Animation, components.FFIComponent<Animation>>

a component value ready for world:spawn or world:set, not an entity

Raises

  • when the sheet does not carry the named tag, because an author naming a tag here has one in mind and the sheet is already in hand to check

playfunction#

function play(exclusive world: ecs.World, entity: integer, source: sheet.Sheet, tag: string?, options: PlayOptions?): nil

Points a live entity at a tag and restarts it there.

Restarting is the point: the time and the frame both reset, so the next fixed step writes the tag's first frame whatever the entity was showing. An entity carrying no Animation is given one.

Arguments

NameTypeDescription
exclusive worldecs.World

the world the entity lives in

entityinteger

a live entity carrying a Sprite

sourcesheet.Sheet

the sheet to play

tagstring?

a tag the sheet names, or nil for the whole sheet

optionsPlayOptions?

the speed, loop, and playing overrides, or nil for the defaults

Returns

TypeDescription
nil

Raises

  • when the sheet does not carry the named tag

pluginfunction#

function plugin(exclusive world: ecs.World): nil

Installs dirty-gated GPU playback and events for AnimationEvents entities. One-shots clamp on the GPU without a CPU visit; add AnimationEvents when game code needs Completed or the playing flag cleared. Installing twice is harmless. Snapshots save live phases and restore against a fresh clock.

Arguments

NameTypeDescription
exclusive worldecs.World

the world receiving the animation systems

Returns

TypeDescription
nil

restartfunction#

function restart(exclusive world: ecs.World, entity: integer): boolean

Plays an entity's animation again from the start of its tag.

The sheet, tag, speed, and loop flag are left alone; what resets is where in the cycle playback has got to and whether it is running. For replaying a one-shot that has finished, and for rewinding one that has not.

Arguments

NameTypeDescription
exclusive worldecs.World

the world the entity lives in

entityinteger

a live entity

Returns

TypeDescription
boolean

whether there was an animation to restart; false leaves the entity untouched, since nothing there says what it would play

timeOffunction#

function timeOf(borrows world: ecs.World, entity: integer): number

Returns elapsed seconds within an entity's animation cycle. The answer is derived from its start and the fixed clock; no component needs advancing.

Arguments

NameTypeDescription
borrows worldecs.World

the entity's world

entityinteger

a live entity

Returns

TypeDescription
number

cycle seconds, or zero without an animation

Values#

AnimationComponentvariable#

The process-wide Animation component definition.

AnimationEventsvariable#

DEFAULT_SPEEDvariable#

const DEFAULT_SPEED: number

The rate an animation runs at when nothing says otherwise, which is the timing the sheet's frames were authored with.