Skip to content
birbpartyPublic

About

ldtk..y? because i gotta.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ldtky

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.

Features

  • Parse .ldtk project 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

Installation

nimble install https://github.com/mattsp1290/ldtky

Or add to your .nimble file:

requires "ldtky >= 0.1.0"

Quick Start

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, ")"

Multi-World Projects

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

Entity Instances

# 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.kind

Tilesets

ldtky 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; resolveTilesetPath returns none.
  • The resolved path may point outside the project tree (LDtk allows relPath to escape, e.g. ../shared/atlas.png); callers should not assume containment.

Tile flip bits

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.)

API Overview

Loading

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

Key Types

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)

Compatibility

  • Nim >= 2.0.0
  • LDtk JSON format v1.0.0 to v1.5.3
  • Files with externalLevels: true require .ldtkl sibling files

Memory management (orc / arc)

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.

License

MIT — see LICENSE

About

ldtk..y? because i gotta.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages