flock-aseprite

Aseprite (.ase binary + JSON export) loader and animation for the Flock engine

flock-aseprite

Load Aseprite pixel-art assets into the Flock ECS engine — both the native .aseprite/.ase binary and the JSON + PNG sprite-sheet export.

What it does

  • Native .aseprite parser (pure Crystal): reads the binary format and composites every frame on the CPU — layers, raw and zlib-compressed cels, RGBA / grayscale / indexed color, linked cels, per-layer and per-cel opacity (Normal blend, src-over), the palette, animation tags (loop direction + repeat) and slices (pivot + 9-slice). Frames are packed into a single strip atlas.
  • JSON export parser (pure Crystal): reads File ▸ Export Sprite Sheet ▸ JSON Data (Array or Hash layout) — frame regions, durations, frameTags, slices — pointing at the exported PNG.
  • Animation layer (Flock): an AsepriteAnimation component + AsepritePlugin play tags each frame (forward / reverse / ping-pong, finite repeat) by driving a Sprite's atlas UVs.

The parsers are headless (no GPU); only the render layer pulls in Flock's GPU stack.

Prerequisites

Crystal >= 1.16. The parsers are headless; the render layer needs Flock's native stack — see the Flock prerequisites (brew install sdl3 sdl3_image sdl3_ttf, wgpu-native via ../wgpu-cr). SDL3_image decodes the JSON export's PNG. Resolved by relative path, so no shards install.

Usage

require "flock-aseprite"

app.add_plugin(Flock::Aseprite::AsepritePlugin.new) # advances animations each frame

app.add_startup do |world, _cmd|
  gpu = world.resource(Flock::GpuContext)

  clip = Flock::Aseprite.load(gpu, "assets/hero.aseprite")     # native binary
  # clip = Flock::Aseprite.load_sheet(gpu, "assets/hero.json") # or the JSON+PNG export

  Flock::Aseprite.spawn(clip, world, Flock::Vec2.new(0, 0), tag: "walk", scale: 4.0)
end

Switch tags at runtime:

world.query(Flock::Aseprite::AsepriteAnimation) do |_e, anim|
  anim.value = anim.value.with_tag("jump")
end

Read Aseprite data headless (tooling, tests) — no GPU:

require "flock/aseprite/ase"   # or "flock/aseprite/sheet"
doc = Flock::Aseprite.parse_file("hero.aseprite")
doc.frame_count            # => 8
doc.tag("walk")            # => Tag(from: 0, to: 3, direction: Forward, ...)
doc.slice("hitbox").keys   # => [SliceKey(x,y,w,h, pivot, 9-slice center), ...]
doc.atlas_pixels           # RGBA8 strip of every composited frame

Limitations

  • JSON export: use Trim disabled. Trimmed frames pack to their non-empty bounds, so the atlas rect no longer matches the canvas the sprite quad is sized to; the render layer does not reposition trimmed frames (it warns on load). Export with Trim off for correct placement. The native .aseprite path is unaffected.
  • Per-cel z-index is unsupported. The native parser skips the cel's 2 z-index bytes and composites cels strictly in layer order. Sprites that rely on per-cel z-index to reorder cels within a frame will composite in a different order than Aseprite shows.
  • Blend modes other than Normal are treated as Normal (covers most pixel art).
  • Group-layer visibility is not propagated to children (individual layer visibility is honored).
  • Tilemap layers (cel type 3) are skipped.
  • Native parser reads the modern palette chunk (0x2019); very old 0x0004/0x0011 palettes are not read (only affects indexed sprites from pre-2015 files).

Run & test

crystal spec                       # headless parser tests (.ase binary + JSON export)
crystal run examples/show_ase.cr   # windowed animation demo

License

MIT — see LICENSE.

Repository

flock-aseprite

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 1
  • about 6 hours ago
  • August 19, 2026
License

MIT License

Links
Synced at

Wed, 19 Aug 2026 12:18:15 GMT

Languages