tecs.gfx.sheet

A sprite sheet divides one image into frames, tags, and slices.

Aseprite supplies the model: each frame carries its own duration, tags play inclusive spans in a direction, and slices carry rectangles, nine-slice centers, and pivots that may move between frames. fromAseprite reads its JSON export into the same interface grid, rects, and build produce.

const hero = tecs.gfx.sheet.grid({
    name = "game.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"))

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

Frames count from one in sheet order. Tag zero is the whole sheet playing forward, which is what an animation naming no tag plays.

bind resolves the sheet's pixel rectangles against a tecs.gfx.images id, so sprite and uv then answer the region a Sprite samples. A sheet is usable before that: until it is bound, a frame's region is its plain fraction of the image.

A sheet name is a snapshot compatibility surface. Every entity playing the sheet shares its registered frame, tag, slice, and timing data.

Pivots#

pivotOf answers a slice's pivot as a fraction of its frame, which is what an attachment point on a hand, a muzzle, or a pair of feet is measured in. No component carries one: a frame packet places a quad by its center, so a pivot is a number a game reads rather than something the renderer applies. The render lane owns adding a quad origin, and a pivot becomes a component when it does.

Module contents

Types

TypeKindDescription
AsepriteOptionstypeConfigures a sheet read from an Aseprite JSON export.
BuilderrecordBuilds a sheet one frame, tag, and slice at a time.
DirectiontypeSelects how a tag walks its span.
GridOptionstypeConfigures a sheet cut into a uniform grid.
PivotValuestructA sprite's anchor as fractions of its frame.
RecttypeDefines one frame's pixel rectangle and duration.
RectsOptionstypeConfigures a sheet cut into explicitly listed rectangles.
SheetrecordRepresents an image divided into frames.
SlicetypeRepresents a named region that moves across frames.
SliceKeytypeDefines where a slice sits from one frame onward and which points it carries.
TagtypeDefines an inclusive frame span and its playback direction.

Functions

FunctionKindDescription
buildfunctionCreates a builder for a sheet the other constructors cannot describe.
byIdfunctionReturns the sheet a registration index names.
byNamefunctionReturns the sheet a name names.
fromAsepritefunctionCreates a sheet from an Aseprite JSON export.
gridfunctionCreates a sheet whose frames form a uniform grid.
rectsfunctionCreates a sheet from explicitly listed frame rectangles.
replacefunctionFolds a freshly built sheet into the one already registered under its name, keeping the old sheet's id.
revisionfunctionReturns how many times any sheet's frames have changed.

Values

ValueKindDescription
DEFAULT_DURATIONvariableThe milliseconds a frame is held when nothing says otherwise, which is what Aseprite writes for a frame nobody retimed.
PivotvariableAnchors a sprite directly, or follows a sheet slice.

Types#

AsepriteOptionstype#

type AsepriteOptions = {
    name: string?,
    json: any
}

Configures a sheet read from an Aseprite JSON export.

Builderrecord#

record Builder ...

Builds a sheet one frame, tag, and slice at a time.

What every constructor here goes through, and what an importer for a format nothing else reads writes into.

Members

NameKindDescription
sheetfield
tagsfield
slicesfield
framemethod
tagmethod
slicemethod
sliceKeysmethod
finishmethod

sheetfield#

sheet: Sheet
@private

tagsfield#

tags: {[string]: Tag}
@private

slicesfield#

slices: {Slice}
@private

framemethod#

frame: function(self: Builder, x: number, y: number, w: number, h: number, duration: number?): Builder
Arguments
NameTypeDescription
selfBuilder
xnumber
ynumber
wnumber
hnumber
durationnumber?
Returns
TypeDescription
Builder

tagmethod#

tag: function(self: Builder, name: string, from: integer, to: integer, direction: Direction?): Builder
Arguments
NameTypeDescription
selfBuilder
namestring
frominteger
tointeger
directionDirection?
Returns
TypeDescription
Builder

slicemethod#

slice: function(
    self: Builder,
    name: string,
    x: number,
    y: number,
    w: number,
    h: number,
    pivotX: number?,
    pivotY: number?
): Builder
Arguments
NameTypeDescription
selfBuilder
namestring
xnumber
ynumber
wnumber
hnumber
pivotXnumber?
pivotYnumber?
Returns
TypeDescription
Builder

sliceKeysmethod#

sliceKeys: function(self: Builder, name: string, data: string?, keys: {SliceKey}): Builder
Arguments
NameTypeDescription
selfBuilder
namestring
datastring?
keys{SliceKey}
Returns
TypeDescription
Builder

finishmethod#

finish: function(self: Builder): Sheet
Arguments
NameTypeDescription
selfBuilder
Returns
TypeDescription
Sheet

Directiontype#

type Direction = "forward" | "reverse" | "pingpong"

Selects how a tag walks its span.

pingpong plays forward and then back without repeating either end, so a three-frame tag is 1, 2, 3, 2 and then round again.

These identifiers reach an Aseprite export and configuration a developer writes, so they are a compatibility surface.

GridOptionstype#

type GridOptions = {
    name: string,
    imageWidth: number,
    imageHeight: number,
    frameWidth: number,
    frameHeight: number,
    margin: number?,
    spacing: number?,
    columns: integer?,
    rows: integer?,
    count: integer?,
    duration: number?,
    tags: {[string]: Tag}?,
    slices: {Slice}?
}

Configures a sheet cut into a uniform grid.

PivotValuestruct#

struct PivotValue ...
@derive(nupp.derive.Debug)

A sprite's anchor as fractions of its frame. A bound slice follows its authored keys during GPU animation without updating the component.

Members

NameKindDescription
xfieldCaller-writable horizontal anchor; one half selects the center.
yfieldCaller-writable vertical anchor; one half selects the center.
sheetfieldCaller-writable sheet id, or zero for a direct anchor.
slicefieldCaller-writable slice id, or zero for a direct anchor.

xfield#

x: number

Caller-writable horizontal anchor; one half selects the center.

yfield#

y: number

Caller-writable vertical anchor; one half selects the center.

sheetfield#

sheet: integer

Caller-writable sheet id, or zero for a direct anchor.

slicefield#

slice: integer

Caller-writable slice id, or zero for a direct anchor.

Recttype#

type Rect = {
    x: number?,
    y: number?,
    w: number,
    h: number,
    duration: number?
}

Defines one frame's pixel rectangle and duration.

x and y default to zero, and duration to DEFAULT_DURATION milliseconds. These keys are the sheet format's own and are a compatibility surface.

RectsOptionstype#

type RectsOptions = {
    name: string,
    imageWidth: number,
    imageHeight: number,
    frames: {Rect},
    tags: {[string]: Tag}?,
    slices: {Slice}?
}

Configures a sheet cut into explicitly listed rectangles.

Sheetrecord#

record Sheet ...

Represents an image divided into frames.

Members

NameKindDescription
namefieldRead-only.
idfieldRead-only.
countfieldRead-only.
imageWidthfieldRead-only.
imageHeightfieldRead-only.
rectsfieldEngine-owned.
durationsfieldEngine-owned.
uvsfieldEngine-owned.
tagFromfield
tagTofield
tagDirectionfield
tagNamesfield
tagIdsfield
tagOrderfieldEngine-owned.
tagTimingfieldEngine-owned.
tagCyclefield
sliceListfield
sliceIdsfield
imagefield
originUfield
originVfield
scaleUfield
scaleVfield
rectmethod
durationmethod
uvmethod
hasTagmethod
tagmethod
tagIdmethod
tagNamemethod
tagCountmethod
cyclemethod
frameAtmethod
pivotmethod
slicemethod
sliceIdmethod
sliceNamemethod
sliceKeyAtmethod
pivotOfmethod
boundImagemethod
bindmethod
spritemethod

namefield#

name: string

Read-only. Reports the registered name a snapshot stores.

idfield#

id: integer

Read-only. Reports the registration index an Animation carries. Construction assigns an id once and never reuses it.

countfield#

count: integer

Read-only. Reports the number of frames, which is the largest index rect, uv, and sprite accept.

imageWidthfield#

imageWidth: number

Read-only. Reports the source image width in pixels. Frame rectangles are measured in it, so binding to an image of another size places frames wrongly.

imageHeightfield#

imageHeight: number

Read-only. Reports the source image height in pixels.

rectsfield#

rects: {number}
@private

Engine-owned. Stores each frame's pixel rectangle, four numbers a frame. Ordinary game code should call rect.

durationsfield#

durations: {number}
@private

Engine-owned. Stores each frame's duration in seconds. Construction converts the authored milliseconds once so playback never divides.

uvsfield#

uvs: {number}
@private

Engine-owned. Stores each frame's region, four numbers a frame.

tagFromfield#

tagFrom: {[integer]: integer}
@private

tagTofield#

tagTo: {[integer]: integer}
@private

tagDirectionfield#

tagDirection: {[integer]: Direction}
@private

tagNamesfield#

tagNames: {string}
@private

tagIdsfield#

tagIds: {[string]: integer}
@private

tagOrderfield#

tagOrder: {[integer]: {integer}}
@private

Engine-owned. Stores the frame shown at each step of a tag's cycle, which is what collapses the three directions into one lookup: the direction decides the order once, when the sheet is built.

tagTimingfield#

tagTiming: {[integer]: {number}}
@private

Engine-owned. Stores the elapsed seconds each step of a tag's cycle ends on.

tagCyclefield#

tagCycle: {[integer]: number}
@private

sliceListfield#

sliceList: {Slice}
@private

sliceIdsfield#

sliceIds: {[string]: integer}
@private

imagefield#

image: integer
@private

originUfield#

originU: number
@private

originVfield#

originV: number
@private

scaleUfield#

scaleU: number
@private

scaleVfield#

scaleV: number
@private

rectmethod#

rect: function(self: Sheet, frame: integer): (number, number, number, number)
Arguments
NameTypeDescription
selfSheet
frameinteger
Returns
TypeDescription
number
number
number
number

durationmethod#

duration: function(self: Sheet, frame: integer): number
Arguments
NameTypeDescription
selfSheet
frameinteger
Returns
TypeDescription
number

uvmethod#

uv: function(self: Sheet, frame: integer): (number, number, number, number)
Arguments
NameTypeDescription
selfSheet
frameinteger
Returns
TypeDescription
number
number
number
number

hasTagmethod#

hasTag: function(self: Sheet, name: string): boolean
Arguments
NameTypeDescription
selfSheet
namestring
Returns
TypeDescription
boolean

tagmethod#

tag: function(self: Sheet, name: string): (integer, integer, Direction)
Arguments
NameTypeDescription
selfSheet
namestring
Returns
TypeDescription
integer
integer
Direction

tagIdmethod#

tagId: function(self: Sheet, name: string?): integer
Arguments
NameTypeDescription
selfSheet
namestring?
Returns
TypeDescription
integer

tagNamemethod#

tagName: function(self: Sheet, id: integer): string
Arguments
NameTypeDescription
selfSheet
idinteger
Returns
TypeDescription
string

tagCountmethod#

tagCount: function(self: Sheet): integer
Arguments
NameTypeDescription
selfSheet
Returns
TypeDescription
integer

cyclemethod#

cycle: function(self: Sheet, id: integer): number
Arguments
NameTypeDescription
selfSheet
idinteger
Returns
TypeDescription
number

frameAtmethod#

frameAt: function(self: Sheet, id: integer, time: number): integer
Arguments
NameTypeDescription
selfSheet
idinteger
timenumber
Returns
TypeDescription
integer

pivotmethod#

pivot: function(
    self: Sheet,
    name: string,
    frame: integer?
): components.FFIInstance<PivotValue, components.FFIComponent<PivotValue>>
Arguments
NameTypeDescription
selfSheet
namestring
frameinteger?
Returns
TypeDescription
components.FFIInstance<PivotValue, components.FFIComponent<PivotValue>>

slicemethod#

slice: function(self: Sheet, name: string): Slice?
Arguments
NameTypeDescription
selfSheet
namestring
Returns
TypeDescription
Slice?

sliceIdmethod#

sliceId: function(self: Sheet, name: string?): integer
Arguments
NameTypeDescription
selfSheet
namestring?
Returns
TypeDescription
integer

sliceNamemethod#

sliceName: function(self: Sheet, id: integer): string
Arguments
NameTypeDescription
selfSheet
idinteger
Returns
TypeDescription
string

sliceKeyAtmethod#

sliceKeyAt: function(self: Sheet, id: integer, frame: integer): SliceKey?
Arguments
NameTypeDescription
selfSheet
idinteger
frameinteger
Returns
TypeDescription
SliceKey?

pivotOfmethod#

pivotOf: function(self: Sheet, id: integer, frame: integer): (number, number)
Arguments
NameTypeDescription
selfSheet
idinteger
frameinteger
Returns
TypeDescription
number
number

boundImagemethod#

boundImage: function(self: Sheet): integer
Arguments
NameTypeDescription
selfSheet
Returns
TypeDescription
integer

bindmethod#

bind: function(self: Sheet, image: integer, u0: number?, v0: number?, u1: number?, v1: number?): Sheet
Arguments
NameTypeDescription
selfSheet
imageinteger
u0number?
v0number?
u1number?
v1number?
Returns
TypeDescription
Sheet

spritemethod#

sprite: function(
    self: Sheet,
    frame: integer?
): components.FFIInstance<rendercomponents.Sprite, components.FFIComponent<rendercomponents.Sprite>>
Arguments
NameTypeDescription
selfSheet
frameinteger?
Returns
TypeDescription
components.FFIInstance<rendercomponents.Sprite, components.FFIComponent<rendercomponents.Sprite>>

Slicetype#

type Slice = {
    name: string,
    data: string?,
    keys: {SliceKey}
}

Represents a named region that moves across frames.

Aseprite's slices carry hitboxes, attachment points, and nine-patch borders. Tecs reads pivots out of them; everything else is the game's to use. These keys are the sheet format's own and are a compatibility surface.

SliceKeytype#

type SliceKey = {
    frame: integer?,
    x: number?,
    y: number?,
    w: number?,
    h: number?,
    centerX: number?,
    centerY: number?,
    centerW: number?,
    centerH: number?,
    pivotX: number?,
    pivotY: number?
}

Defines where a slice sits from one frame onward and which points it carries.

A slice holds a key until the next one, so a slice that never moves is one key at frame one. A nil centerX means the slice names no nine-slice center, and a nil pivotX means it names no pivot. These keys are the sheet format's own and are a compatibility surface.

Tagtype#

type Tag = {
    from: integer,
    to: integer,
    direction: Direction?
}

Defines an inclusive frame span and its playback direction.

Aseprite calls this a frame tag, and a tag's name is what an animation asks for. direction defaults to "forward". These keys are the sheet format's own and are a compatibility surface.

Functions#

buildfunction#

function build(name: string, imageWidth: number, imageHeight: number): Builder

Creates a builder for a sheet the other constructors cannot describe.

The model is what the builder writes, so an atlas from any tool reaches the same sheet an Aseprite export does. Frames, tags, and slices are added in any order, and finish registers the result.

Arguments

NameTypeDescription
namestring

the name to register the finished sheet under

imageWidthnumber

the width in pixels of the image the frames are cut from

imageHeightnumber

the height in pixels of that image

Returns

TypeDescription
Builder

a builder whose methods chain

Raises

TypeCondition
string

when the name is empty or either dimension is not positive

byIdfunction#

function byId(id: integer): Sheet?

Returns the sheet a registration index names.

Arguments

NameTypeDescription
idinteger

an id from a sheet this process built; ids are assigned in build order, so one is meaningful only within a run

Returns

TypeDescription
Sheet?

the sheet, or nil when nothing was built under the id

byNamefunction#

function byName(name: string): Sheet?

Returns the sheet a name names.

Building a second sheet under a taken name replaces what this returns, so a reload points new entities at the new sheet. Entities already carrying the old id keep drawing the old one, which is what stops a reload pulling a frame out from under them.

Arguments

NameTypeDescription
namestring

the name a sheet was built under

Returns

TypeDescription
Sheet?

the sheet most recently registered under the name, or nil

fromAsepritefunction#

function fromAseprite(options: AsepriteOptions): Sheet

Creates a sheet from an Aseprite JSON export.

One reader in front of the model rather than a second model: frames, their durations, frame tags with their directions, and slices with their keys all land in the sheet the builder writes.

The reader accepts Aseprite's array layout and its object layout, sorting the latter by frame name. It ignores spriteSourceSize, so export with trimming off.

Arguments

NameTypeDescription
optionsAsepriteOptions

the export as JSON text or an already-decoded table, and the name to register it under; the name defaults to the export's image name

Returns

TypeDescription
Sheet

the finished sheet, already registered

Raises

TypeCondition
string

when the export is not readable, when it names no image and the caller supplied no name, or when a frame carries no rectangle

gridfunction#

function grid(options: GridOptions): Sheet

Creates a sheet whose frames form a uniform grid.

Margin surrounds the grid and spacing separates the cells, so a cell's left edge is margin + column * (frameWidth + spacing). Both default to zero, which is an image cut with nothing between its cells. Frames come out in row-major order.

Arguments

NameTypeDescription
optionsGridOptions

the image size, the cell size, and the optional grid shape, tags, and slices

Returns

TypeDescription
Sheet

the finished sheet, already registered under its name and carrying an id

Raises

TypeCondition
string

on a missing name, a non-positive image or frame size, a grid that fits no cells, or a count past what the grid holds

rectsfunction#

function rects(options: RectsOptions): Sheet

Creates a sheet from explicitly listed frame rectangles.

For an image no grid describes: frames of differing sizes, or an atlas whose cells a packing tool placed. Rectangles are not checked against the image, so one running off the edge samples whatever the image holds there.

Arguments

NameTypeDescription
optionsRectsOptions

the image size, the frame list, and the optional tags and slices

Returns

TypeDescription
Sheet

the finished sheet, already registered under its name and carrying an id

Raises

TypeCondition
string

on a missing name, a non-positive image size, an empty frame list, or a frame with no positive size

replacefunction#

function replace(built: Sheet): Sheet?, string?

Folds a freshly built sheet into the one already registered under its name, keeping the old sheet's id.

The reverse of what building under a taken name does, and it exists because a reload wants the reverse. Building again answers new entities with the new sheet and leaves every entity already playing on the old one, which is right for two sheets that happen to share a name and wrong for one file that was re-exported. This overwrites in place instead, so an Animation holding the id continues and shows the new frames on its next step without the world changing.

The refusals exist because a component holds an index into this sheet, so anything this refuses is a change that would renumber one. Tag ids follow the tag names in sorted order and slice ids follow the slices, so adding, removing, or renaming either is a restart. Frames are free to change: an entity carries a tag and a time, and each step resolves its frame from the cycle.

Replacement keeps the live sheet's bind and rescales the new frames against it, so a caller need not bind again.

The sheet handed in is spent. Its id resolves to the live sheet rather than to itself, because the two share their frame tables once the fold is done and only one of them may be bound.

Arguments

NameTypeDescription
builtSheet

a sheet from any constructor here, registered moments ago under a name something else already held

Returns

TypeDescription
Sheet?

the live sheet, or nil and the refusal reason

string?

revisionfunction#

function revision(): integer

Returns how many times any sheet's frames have changed.

Bumped by registration and by bind, both of which move where a frame's region points. Anything holding a copy of those regions compares this against what it copied at rather than being told, which keeps the dependency running one way: a sheet knows nothing about who read it.

Returns

TypeDescription
integer

a count that only ever rises

Values#

DEFAULT_DURATIONvariable#

const DEFAULT_DURATION: number

The milliseconds a frame is held when nothing says otherwise, which is what Aseprite writes for a frame nobody retimed.

Pivotvariable#

const Pivot

Anchors a sprite directly, or follows a sheet slice. Snapshots store names.