flock-aseprite
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
.asepriteparser (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
AsepriteAnimationcomponent +AsepritePluginplay tags each frame (forward / reverse / ping-pong, finiterepeat) by driving aSprite'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
.asepritepath 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 old0x0004/0x0011palettes 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