Pure-Nim library for loading and parsing LDtk (Level Designer Toolkit) project files. Targets LDtk JSON format v1.5.3; older files (v1.0.0+) load with graceful defaults.
- Parse
.ldtkproject files into typed Nim objects - Automatic external level (
.ldtkl) loading - Supports single-world and multi-world project layouts
- Zero external Nim dependencies
- Builds under
--mm:orc(ldtky's own default) or--mm:arc— consumers may compile ldtky under either
nimble install https://github.com/mattsp1290/ldtkyOr add to your .nimble file:
requires "ldtky >= 0.1.0"import ldtky/ldtky
let project = loadProject("my_game/world.ldtk")
# Collect all levels (handles both single-world and multi-world layouts)
var levels: seq[Level]
if project.worlds.len > 0:
for w in project.worlds: levels.add(w.levels)
else:
levels = project.levels
for level in levels:
echo "Level: ", level.identifier, " (", level.pxWid, "x", level.pxHei, "px)"
if level.layerInstances.isSome:
for layer in level.layerInstances.get:
echo " Layer: ", layer.identifier, " (", layer.layerType, ")"let project = loadProject("world.ldtk")
if project.worlds.len > 0:
# Multi-world layout
for world in project.worlds:
echo "World: ", world.identifier
for level in world.levels:
echo " Level: ", level.identifier
else:
# Single-world layout (levels at root)
for level in project.levels:
echo "Level: ", level.identifier# levels = collected as in Quick Start above
for level in levels:
if level.layerInstances.isNone: continue
for layer in level.layerInstances.get:
for entity in layer.entityInstances:
echo "Entity: ", entity.identifier, " at ", entity.px
for fi in entity.fieldInstances:
if fi.value.isSome:
let v = fi.value.get
echo " Field ", fi.identifier, " (", fi.fieldType, "): kind=", v.kindldtky parses tileset metadata but does no image loading — that is the rendering
backend's job. TilesetDef.relPath is stored verbatim (relative to the .ldtk
file's directory). Use resolveTilesetPath to turn it into an on-disk path:
import std/options
import ldtky/ldtky
let project = loadProject("maps/world.ldtk")
for ts in project.defs.tilesets:
let imgPath = resolveTilesetPath("maps/world.ldtk", ts)
if imgPath.isSome:
echo ts.identifier, " -> ", imgPath.get # load with your GFX library
else:
echo ts.identifier, " is an embedded atlas (no external image)"Edge cases the helper handles:
- Embedded-atlas tilesets have no
relPath;resolveTilesetPathreturnsnone. - The resolved path may point outside the project tree (LDtk allows
relPathto escape, e.g.../shared/atlas.png); callers should not assume containment.
Placed tiles (LayerInstance.gridTiles / autoLayerTiles — AutoLayer tiles are
the common flip-bit carriers) hold flip bits in Tile.f (bit 0 = X-flip,
bit 1 = Y-flip). Use the accessors instead of bit-twiddling:
for tile in layer.gridTiles & layer.autoLayerTiles:
let fx = tile.flipX
let fy = tile.flipY(An int-taking overload, flipX(tile.f) / flipY(tile.f), also exists for
when you only have the raw f value.)
| Proc | Description |
|---|---|
loadProject(path) |
Load a .ldtk file (auto-loads external levels) |
loadExternalLevels(project, dir) |
Manually inject .ldtkl sidecar data |
parseProject(node) |
Parse a JsonNode into LdtkJsonRoot |
| Type | Description |
|---|---|
LdtkJsonRoot |
Top-level project (defs, levels, worlds) |
Definitions |
All entity/layer/tileset/enum definitions |
Level |
A single level (ref object) |
LayerInstance |
Layer data (tiles, entities, intgrid) |
EntityInstance |
A placed entity with field instances |
FieldValue |
A typed field value (18 discriminated variants) |
TilesetDef |
Tileset metadata (no image loading) |
- Nim >= 2.0.0
- LDtk JSON format v1.0.0 to v1.5.3
- Files with
externalLevels: truerequire.ldtklsibling files
ldtky's own builds default to --mm:orc. The parsed object graph is a strict
tree — only Level and LayerInstance are ref types, and a layer links to
its level only via an integer levelId, not a back-reference — so it contains
no reference cycles, and arc (which lacks a cycle collector) does not leak it.
CI compiles and runs the full test suite under both (nimble test for orc,
nimble testArc for arc), and guards that no new ref type is introduced under
src/ (a new ref back-edge could form a cycle and silently leak under arc).
A consumer that pulls ldtky via --path and compiles --mm:arc (e.g. a
size-tuned handheld build) is supported.
MIT — see LICENSE