flock-ldtk

LDtk level parser and renderer for the Flock engine

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, IntGrid and Entities, 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/Y scroll relative to the camera (apply_parallax, run automatically by install).
  • Culling: render only named levels (only:) — e.g. a level + its neighbours — and/or a world-space bounds rectangle.

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.

Repository

flock-ldtk

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

MIT License

Links
Synced at

Wed, 19 Aug 2026 12:18:18 GMT

Languages