Skip to content

Repository files navigation

spriteanimation

A reusable, engine-independent pipeline for 2D pixel-art sprite animation — and, in card/, the game that consumes it.

The pipeline itself is infrastructure. It defines how frames become timed, validated, exportable animations — and gives you the tools to check and preview them — without committing to any engine. Nothing in the root of this repository knows what a card is.

card/ is the consumer: Vantage, a card battler in Phaser 3 where both players act every round and the defender chooses the blocks. It reads the resolved bundle and copies frames down; it never writes upward. That one-way boundary is what keeps the pipeline reusable, and it is enforced by card/tools/sync-assets.mjs being the only thing that crosses it.


Purpose

Sprite animation projects usually rot in the same way: frame files drift out of sync with the code that plays them, timing lives in three places, and every new engine target means hand-rewriting metadata. This system fixes that by making one declarative manifest the single source of truth, and by making every rule machine-checkable.

Three principles:

  1. One source of truth. A character's frames, timing, and events live in one character.json. Everything else is generated from it.
  2. Everything is validated. Naming, frame dimensions, missing frames, and sheet geometry are checked by a tool, not by eye.
  3. Engine-independent. The core format has no engine concepts in it. Engine specifics live only in export adapters, so adding a target never changes the source data.

Requirements

Python 3.9+ and a browser. That is all — the toolchain is standard library only, with no pip install step.


Quick start

python tools/spritetool.py info                      # what exists right now
python tools/spritetool.py validate                  # check everything
python tools/spritetool.py preview                   # browser preview at :8000

python tools/spritetool.py new-character my-hero     # scaffold a character
python tools/spritetool.py ingest my-hero --animation walk --from ../sprite/my-hero/walk

characters/example-hero/ ships with placeholder art so all of the above work immediately. Delete it once you have your own characters.


Directory structure

Directory Contents
animations/ Character-agnostic animation archetypes — what "walk" means here
characters/ One folder per character: manifest + frames. _template/ to copy
spritesheets/ Packed sheets and their atlas metadata
exports/ Generated engine bundles — web/, phaser/, godot/, unity/
previews/ The browser preview viewer and any generated preview output
configuration/ Project config and JSON Schemas
tools/ The spritetool CLI and its library
documentation/ The specifications
card/ Vantage — the card game built on this pipeline. Its own npm project

Documentation map

Read this For
documentation/ASEPRITE.md Drawing the art: canvas setup, tags, frame durations, export
documentation/ANIMATION_SPEC.md What an animation is; states, frames, timing, events
documentation/NAMING.md Frame and animation naming rules
documentation/CONFIGURATION.md Every config key, all three layers
documentation/SPRITE_INGEST.md How art from ../sprite/ gets in
documentation/VALIDATION.md Every validation code and its fix
EXPORT_GUIDE.md Getting animations out to web and engines

The animation workflow

   ../sprite/                    art is created or generated
        |
        |  spritetool ingest     copy + rename + dimension check
        v
   characters/<id>/frames/       canonically named frames
        |
        |  edit character.json   declare clips: frames, fps, loop, events
        v
   spritetool validate           naming, geometry, gaps, sheet consistency
        |
        |  spritetool preview    watch it move; tune fps and scale
        v
   exports/<target>/             engine-ready bundles

Each step is explicit and reversible. Nothing watches your filesystem and nothing renames files behind your back.


Sprite sheet workflow

Frames start as loose PNGs (frames.mode: "sequence") because that is what comes out of art tools and what is easiest to inspect, diff, and re-ingest.

Sheets are a distribution format, adopted when you care about HTTP requests or draw calls. Switching a character to frames.mode: "sheet" changes only the frames block in its manifest — clips, timing, and events are untouched, because frame indices mean the same thing in both modes.

The validator checks a sheet against its declared grid: a columns x rows grid of frameWidth x frameHeight cells, plus padding and margin, must equal the PNG's real dimensions. Off-by-one packing errors get caught before they reach an engine.

See documentation/ANIMATION_SPEC.md §3.


Frame naming

<character>_<animation>_<index>.png

example-hero_idle_000.png
example-hero_attack-heavy_003.png

All identifiers are kebab-case, which makes _ an unambiguous field separator. Indices are zero-padded to 3 digits, start at 0, restart for each animation, and must be contiguous. Full rules in documentation/NAMING.md.


Animation naming

Core vocabulary — idle, walk, run, attack, hurt, jump, death, interact.

Variants extend a base state with - and inherit its archetype automatically: attack-heavy, idle-blink. A state outside the vocabulary produces a warning, not an error, so experiments are possible but never silent.

Archetype files currently exist for idle, walk, and attack. The rest are valid states awaiting shared defaults — adding one is a ~15-line JSON file.


Export process

Exports are generated, never authored. Everything in exports/ is disposable and rebuildable from characters/.

The pipeline emits an intermediate resolved bundle — all three config layers flattened into one explicit structure — and each engine adapter is a small translation of that bundle. Adding a target means writing one adapter, not touching any source data.

You can see the resolved shape today: spritetool preview --build-only writes it to previews/viewer/index.json. That is deliberate — the preview viewer consumes the same data an exporter will.

See EXPORT_GUIDE.md for per-target formats and integration snippets.


How sprites from ../sprite/ are used

../sprite/ is the upstream source of truth for pixels. This project is the source of truth for timing and structure. Ingest is a one-way copy — nothing here ever writes to ../sprite/.

# dry run (default) — see the rename plan and dimension check
python tools/spritetool.py ingest my-hero --animation walk --from ../sprite/my-hero/walk

# commit it
python tools/spritetool.py ingest my-hero --animation walk --from ../sprite/my-hero/walk --apply

Ingest sorts naturally (frame_2 before frame_10), verifies every file against the character's declared geometry, renames on copy, and refuses to overwrite without --force. Your source art keeps whatever naming it already has.

Details in documentation/SPRITE_INGEST.md.


Adding a new character

python tools/spritetool.py new-character knight --name "Knight"
python tools/spritetool.py ingest knight --animation idle --from ../sprite/knight/idle --apply
# edit characters/knight/character.json to match the frame counts ingest reports
python tools/spritetool.py validate knight
python tools/spritetool.py preview

No code changes, no registry to update. The character is discovered by its folder.


Adding a new animation state

  1. Add the state name to animationStates.core in configuration/project.config.json.
  2. Optionally create animations/<state>.anim.json to record shared defaults.
  3. Add the clip to any character's animations block.

Steps 1 and 2 are optional — a character can use a new state immediately and take a warning until you make it official.



The game

cd card
npm install
npm run sync-assets     # pull frames + timing out of this pipeline
npm run dev             # http://localhost:5180
npm test                # 69 rules tests, no browser

card/README.md covers how it plays; card/DESIGN.md is the rulebook. It draws six champions from six characters in this roster, and each one's keyword comes from that character's upstream brief — Derrick never leaves the ground, so he is Tough; Kite climbs, so he is Elusive.

The art it uses is still this pipeline's placeholder art. ../sprite/tools/aseprite/ scaffolds .aseprite working files from these manifests — correct canvas, palette, Indexed mode, tags and per-frame durations already set — so replacing it is drawing, not setup.


Current status

Foundation complete: configuration, specification, metadata format, validation, ingest, and preview all work end to end against example-hero.

Not yet built: the sheet packer and the export adapters. Both are specified (EXPORT_GUIDE.md) and the resolved-bundle shape they consume already exists. See CHANGELOG.md for what is planned.


Credits

Created by Spandan Roy. See CONTRIBUTORS.md.

About

Vantage - a pixel-art card battler where both players act every round, built on a reusable sprite animation pipeline

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages