flock-ldtk
flock-ldtk
Parse LDtk (Level Designer Toolkit) projects and render them with the Flock ECS engine.
The parser (Flock::Ldtk::Project) is pure Crystal — no native/GPU dependency — so projects can be loaded and inspected headless (tests, tooling, servers). Rendering turns placed tiles into Flock::Sprites and bridges LDtk entities and IntGrid cells to game logic.
See flock-tilemap for the Tiled/TMX equivalent.
Prerequisites
Crystal >= 1.16. The parser is headless; rendering needs Flock's native stack — see the Flock prerequisites (brew install sdl3 sdl3_image sdl3_ttf, wgpu-native via ../wgpu-cr). Resolved by relative path, so no shards install.
Features
- Projects & worlds: multiple worlds (
worlds[], legacy single-world too), levels positioned in each, per-level background color and image (bgRelPath), neighbours (for streaming), typed custom fields, and external levels (.ldtkl). - Layers:
Tiles,AutoLayer,IntGridandEntities, kept in LDtk's top-first order, with per-layer opacity, visibility and pixel offset. - Tiles: explicit placement (
px+src+ flip X/Y + per-tile alpha), from the layer's tileset (by relative image path). - Tilesets: per-tile enum tags and custom data — the standard channel to drive gameplay from tiles (e.g. which tiles are solid). Enum defs (values, colors, icons).
- IntGrid: integer grids with per-value name + color; render as a debug/collision overlay or iterate the cells to build collision.
- Entities: grid + pixel position, size, pivot, tags, editor tile thumbnail rendered per
tileRenderMode(Stretch / FitInside / Cover / Repeat / FullSizeCropped / FullSizeUncropped / NineSlice, else a colored box from the entity's smart/def color), and rich typed fields — Int/Float/Bool/String/Color/Enum/Point/EntityRef/Array. A hook turns them into game entities. - Parallax: layers with
parallaxFactorX/Yscroll relative to the camera (apply_parallax, run automatically byinstall). - Culling: render only named levels (
only:) — e.g. a level + its neighbours — and/or a world-spaceboundsrectangle.
Not covered: parallaxScaling (the depth-zoom variant of parallax; the offset is applied), re-running auto-layer rules (the resulting autoLayerTiles render correctly regardless), and the toc index (spawn_entities already iterates all entities).
Usage
require "flock-ldtk"
# One-liner: load + render + own textures.
app.add_plugin(Flock::WindowPlugin.new("Game", 800, 600))
app.add_plugin(Flock::RenderPlugin.new)
Flock::Ldtk.install(app, "assets/world.ldtk")
Or drive it yourself:
project = Flock::Ldtk::Project.load("assets/world.ldtk") # pure parse (also headless)
textures = Flock::Ldtk.render(project, world, gpu) # spawn tile + entity sprites
# LDtk entities -> game entities:
Flock::Ldtk.spawn_entities(project, world) do |ent, world_pos|
next unless ent.identifier == "Enemy"
e = world.spawn
world.add(e, Flock::Transform2D.new(world_pos))
world.add(e, Enemy.new(hp: ent.fields.int("hp", 10)))
end
# IntGrid -> collision:
Flock::Ldtk.each_int_cell(project) do |layer, cx, cy, value, center|
add_solid(center, layer.grid_size) if value == 1
end
# Collision from tile enum-tags (which tiles are "Solid"):
ts = project.tileset(uid)
solid = ts.tagged?(tile.tile_id, "Solid")
# Stream a level and its neighbours only:
here = project.level("Cave_3").not_nil!
Flock::Ldtk.render(project, world, gpu, only: [here.identifier] + project.neighbours(here).map(&.identifier))
pos = Flock::Ldtk.cell_center(level, cx, cy, 16) # grid -> world
lvl = Flock::Ldtk.level_at(project, mouse_world) # which level a point is in
# textures.each(&.release) when done (install does this for you).
origin sets the world position of LDtk's (0,0). LDtk's Y axis points down, Flock's up, so center a Camera2D on origin + (level.px_wid/2, -level.px_hei/2). Pass textures: (keyed by a tileset's uid) to override an image, or intgrid: true to draw IntGrid layers.
Run
crystal spec # parser tests (headless)
crystal run examples/show_ldtk.cr # one-call install() demo (BMP tileset on disk)
crystal run examples/ldtk_render_test.cr # offscreen readback (tile placement)
crystal run examples/ldtk_entity_test.cr # entity thumbnail + spawn_entities
crystal run examples/ldtk_entity_box_test.cr # tile-less entity -> colored box
crystal run examples/ldtk_intgrid_test.cr # IntGrid overlay + each_int_cell
crystal run examples/ldtk_bg_test.cr # level background image
crystal run examples/ldtk_cull_test.cr # only / bounds culling
crystal run examples/ldtk_parallax_test.cr # parallax layers
crystal run examples/ldtk_nineslice_test.cr # nine-slice entity render mode
crystal run examples/ldtk_rendermode_test.cr # entity tileRenderMode variants
crystal run examples/ldtk_coords_test.cr # coordinate helpers
WGPU_FRAMES=5 crystal run examples/show_ldtk.cr # headless smoke
Like Flock, this shard resolves its dependency by relative path (../flock), so no shards install is required to build the examples or specs.
License
MIT — see LICENSE.
flock-ldtk
- 0
- 0
- 0
- 0
- 1
- about 7 hours ago
- August 19, 2026
MIT License
Wed, 19 Aug 2026 12:18:18 GMT