Jaconir
Documentation
SCHEMA v1.0.4

Import maps into Unity, Godot, or Phaser

Prefer Tiled .tmx plus a tileset PNG for SuperTiled2Unity (Unity) or Godot’s Tiled importer. Use a Godot .tscn for a native scene, or JSON plus the C# / GDScript scripts below.

01The 3-step workflow

Step 1

Design

Pick dungeon or platformer, a theme, and size. Share the URL — it includes the seed.

Step 2

Export

Download Tiled .tmx + PNG (best for Unity/Godot/Phaser), a Godot .tscn, or JSON.

Step 3

Import

Unity: SuperTiled2Unity on the .tmx. Godot: Tiled plugin or drop in the .tscn. Or run the importer scripts on JSON.

02Manifest Anatomy

SCHEMA v1.0.4+ (Standardized)
{
  "name": "Ancient Nexus",
  "id": "biome_x1y2z3",
  "index": 0,
  "metadata": {
    "name": "Ancient Nexus",
    "id": "biome_x1y2z3",
    "index": 0,
    "seed": "alpha74x",
    "mode": "dungeon",
    "tileRegistry": [
      { "id": 1, "name": "WALL", "color": "#5d4057" },
      { "id": 3, "name": "ITEM", "color": "#f39c12" }
    ]
  },
  "layers": [
    { "name": "geometry", "tiles": [...] }
  ],
  "entities": [...]
}

High-Fidelity Mapping

  • Sequential Index
    Numerical ID (0, 1, 2...) for easy array-based level indexing in engines.
  • Deterministic ID
    Seed-based unique string for reliable cross-session level referencing.
  • Embedded Registry
    Tile IDs and color mappings are baked into the JSON for automatic engine styling.

Architect Tip

"Leverage the Tile Registry to map specific Biome IDs to your engine's Prefabs or TileSets. No more hardcoding offsets!"

03World Connectivity & Progression

Jaconir Architect utilizing Seed-Only Persistence. This ensures infinitely scalable production manifests while maintaining a footprint under 100KB.

Synthetic Worlds

Use the Sequential Auto-Batch to generate 50 randomized biomes in seconds. Ideal for rogue-likes or testing large-scale engine performance.

Curated Worlds

Hand-pick specific biomes from your history and add them to the Export Suite. This allows you to design a meticulously balanced level curve for your campaign.

Engine Boilerplates

Use these scripts as a starting point. They handle the heavy lifting of JSON parsing and coordinate mapping.

Both scripts map tiles by numeric id and entities by type, and warn with counts about anything in the export that has no tile, prefab or scene assigned.

Upgrading from an earlier version? The single tile and prefab fields (wallTile, playerPrefab, exitPrefab, itemPrefab in Unity; player_scene, exit_scene, item_scene in Godot) are replaced by per-id and per-type mapping lists. The Unity data classes moved into the Jaconir.LevelImport namespace and were renamed (TileData is now JaconirTileDto). Re-assign your tiles, prefabs and scenes after replacing the script.

Unity (C#)

Tilemap & Prefab Based

// Jaconir Multi-Layer Level Importer for Unity 2021.3+ (requires 2D Tilemap package).
//
// Automatically builds a complete 5-layer atmospheric Metroidvania environment:
//   - Tilemap_BG_Far (SortingLayer: Background, order -20, dark atmospheric tint)
//   - Tilemap_BG_Near (SortingLayer: Background, order -10, mid-cavern cutout tint)
//   - Tilemap_BG_Pillars (SortingLayer: Background, order -5)
//   - Tilemap_Collision (SortingLayer: Default, order 0, with TilemapCollider2D & CompositeCollider2D)
//   - Props_Container (instantiates multi-tile props with bottom-center anchoring & flipX)
//   - Tilemap_FG_Vines (SortingLayer: Foreground, order 10)
//   - Entities_Container (spawns gameplay gates, keys, doors, enemies)
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Tilemaps;

namespace Jaconir.LevelImport {

    [Serializable]
    public class JaconirLevelMetadata {
        public string name;
        public string seed;
        public int width;
        public int height;
        public string theme;
        public string mode;
        public string decorPreset;
    }

    [Serializable]
    public class JaconirTileDto {
        public int x;
        public int y;
        public string type;
        public int id;
    }

    [Serializable]
    public class JaconirLayerDto {
        public string name;
        public List<JaconirTileDto> tiles;
    }

    [Serializable]
    public class JaconirPropDto {
        public string type;
        public int x;
        public int y;
        public int w;
        public int h;
        public float bottomCenterWorldX;
        public float bottomWorldY;
        public string layer;
        public bool flipX;
    }

    [Serializable]
    public class JaconirEntityDto {
        public string id;
        public string type;
        public float x;
        public float y;
        public string pairId; // Links a 'key' to its 'door'
    }

    [Serializable]
    public class JaconirLevelManifest {
        public JaconirLevelMetadata metadata;
        public List<JaconirLayerDto> layers;
        public List<JaconirPropDto> props;
        public List<JaconirEntityDto> entities;
    }

    [Serializable]
    public class JaconirWorldManifest {
        public List<JaconirLevelManifest> worldManifest;
        public int count;
    }

    [Serializable]
    public class JaconirTileMapping {
        public int id;
        public string label;
        public TileBase tile;
    }

    [Serializable]
    public class JaconirEntityMapping {
        public string type;
        public GameObject prefab;
    }

    [Serializable]
    public class JaconirPropMapping {
        public string type;
        public GameObject prefab;
    }

    public class JaconirImportReport {
        public readonly Dictionary<int, int> placedTiles = new Dictionary<int, int>();
        public readonly Dictionary<int, int> unmappedTiles = new Dictionary<int, int>();
        public readonly Dictionary<string, int> spawnedEntities = new Dictionary<string, int>();
        public readonly Dictionary<string, int> spawnedProps = new Dictionary<string, int>();
        public readonly Dictionary<string, int> unmappedEntities = new Dictionary<string, int>();
        public readonly Dictionary<string, int> unmappedProps = new Dictionary<string, int>();
        readonly Dictionary<int, string> tileLabels = new Dictionary<int, string>();

        public bool IsComplete => unmappedTiles.Count == 0 && unmappedEntities.Count == 0 && unmappedProps.Count == 0;

        internal void CountTile(JaconirTileDto tile, bool placed) {
            Increment(placed ? placedTiles : unmappedTiles, tile.id);
            if (!tileLabels.ContainsKey(tile.id)) tileLabels[tile.id] = tile.type;
        }

        internal void CountEntity(string type, bool spawned) {
            Increment(spawned ? spawnedEntities : unmappedEntities, type);
        }

        internal void CountProp(string type, bool spawned) {
            Increment(spawned ? spawnedProps : unmappedProps, type);
        }

        static void Increment<T>(Dictionary<T, int> counts, T key) {
            counts.TryGetValue(key, out int n);
            counts[key] = n + 1;
        }

        public override string ToString() {
            var sb = new StringBuilder();
            foreach (var kv in placedTiles) sb.AppendLine($"  placed tile {kv.Key} ({tileLabels[kv.Key]}): {kv.Value}");
            foreach (var kv in spawnedEntities) sb.AppendLine($"  spawned {kv.Key}: {kv.Value}");
            foreach (var kv in spawnedProps) sb.AppendLine($"  spawned prop {kv.Key}: {kv.Value}");
            foreach (var kv in unmappedTiles) sb.AppendLine($"  UNMAPPED tile {kv.Key} ({tileLabels[kv.Key]}): {kv.Value} cells skipped.");
            foreach (var kv in unmappedEntities) sb.AppendLine($"  UNMAPPED entity '{kv.Key}': {kv.Value} skipped.");
            foreach (var kv in unmappedProps) sb.AppendLine($"  UNMAPPED prop '{kv.Key}': {kv.Value} skipped.");
            return sb.ToString();
        }
    }

    public class JaconirLevelImporter : MonoBehaviour {
        [Header("Target Grid Container")]
        public Grid targetGrid;

        [Header("Tile Mappings (by numeric tile id)")]
        public List<JaconirTileMapping> tileMappings = DefaultTileMappings();

        [Header("Entity Mappings (by entity type)")]
        public List<JaconirEntityMapping> entityMappings = DefaultEntityMappings();

        [Header("Prop Mappings (by prop type)")]
        public List<JaconirPropMapping> propMappings = DefaultPropMappings();

        [Tooltip("Log unmapped content as an error instead of a warning.")]
        public bool treatUnmappedAsError = false;

        public event Action<GameObject, JaconirEntityDto> EntitySpawned;
        public event Action<GameObject, JaconirPropDto> PropSpawned;

        static List<JaconirTileMapping> DefaultTileMappings() {
            return new List<JaconirTileMapping> {
            new JaconirTileMapping { id = 1, label = "wall" },
            new JaconirTileMapping { id = 2, label = "hazard_spike" },
            new JaconirTileMapping { id = 3, label = "item" },
            new JaconirTileMapping { id = 4, label = "portal" },
            new JaconirTileMapping { id = 5, label = "hazard_crumble" },
            };
        }

        static List<JaconirEntityMapping> DefaultEntityMappings() {
            return new List<JaconirEntityMapping> {
            new JaconirEntityMapping { type = "spawn_gate" },
            new JaconirEntityMapping { type = "transition_gate" },
            new JaconirEntityMapping { type = "key" },
            new JaconirEntityMapping { type = "door" },
            new JaconirEntityMapping { type = "key_red" },
            new JaconirEntityMapping { type = "door_red" },
            new JaconirEntityMapping { type = "guardian" },
            new JaconirEntityMapping { type = "exit" },
            new JaconirEntityMapping { type = "item" },
            new JaconirEntityMapping { type = "portal" },
            new JaconirEntityMapping { type = "wall" },
            };
        }

        static List<JaconirPropMapping> DefaultPropMappings() {
            return new List<JaconirPropMapping> {
                new JaconirPropMapping { type = "ruined_arch_3x3" },
                new JaconirPropMapping { type = "hollow_statue_3x3" },
                new JaconirPropMapping { type = "ancient_shrine_3x3" },
                new JaconirPropMapping { type = "street_lantern_3x3" },
                new JaconirPropMapping { type = "cave_grass_1x1" },
                new JaconirPropMapping { type = "glowing_mushrooms_1x1" },
                new JaconirPropMapping { type = "rubble_pile_1x1" },
                new JaconirPropMapping { type = "skull_totem_1x1" },
                new JaconirPropMapping { type = "corner_overgrowth" }
            };
        }

        public JaconirImportReport Import(string jsonContent) {
            JaconirLevelManifest manifest = JsonUtility.FromJson<JaconirLevelManifest>(jsonContent);
            return ImportFromManifest(manifest);
        }

        public JaconirImportReport ImportFromManifest(JaconirLevelManifest manifest) {
            var report = new JaconirImportReport();
            string label = $"{manifest.metadata.name} (seed {manifest.metadata.seed})";

            if (targetGrid == null) {
                targetGrid = GetComponent<Grid>() ?? gameObject.AddComponent<Grid>();
            }

            var tilesById = new Dictionary<int, TileBase>();
            foreach (var m in tileMappings) if (m.tile != null) tilesById[m.id] = m.tile;
            var prefabsByType = new Dictionary<string, GameObject>();
            foreach (var m in entityMappings) if (m.prefab != null && !string.IsNullOrEmpty(m.type)) prefabsByType[m.type] = m.prefab;
            var propsByType = new Dictionary<string, GameObject>();
            foreach (var m in propMappings) if (m.prefab != null && !string.IsNullOrEmpty(m.type)) propsByType[m.type] = m.prefab;

            // 1. Setup Layer Tilemaps
            Tilemap tmCollision = GetOrCreateTilemap("Tilemap_Collision", "Default", 0, true);
            Tilemap tmBgNear = GetOrCreateTilemap("Tilemap_BG_Near", "Background", -10, false, new Color(0.45f, 0.50f, 0.60f, 1f));
            Tilemap tmBgPillars = GetOrCreateTilemap("Tilemap_BG_Pillars", "Background", -5, false);
            Tilemap tmFgVines = GetOrCreateTilemap("Tilemap_FG_Vines", "Foreground", 10, false);

            tmCollision.ClearAllTiles();
            tmBgNear.ClearAllTiles();
            tmBgPillars.ClearAllTiles();
            tmFgVines.ClearAllTiles();

            // 2. Clear old spawned entities and props
            Transform entityRoot = GetOrCreateRoot("Entities_Container");
            Transform propRoot = GetOrCreateRoot("Props_Container");
            ClearChildren(entityRoot);
            ClearChildren(propRoot);

            // 3. Populate Tilemaps
            if (manifest.layers != null) {
                foreach (var layer in manifest.layers) {
                    Tilemap target = null;
                    if (layer.name == "Tilemap_Collision" || layer.name == "geometry") target = tmCollision;
                    else if (layer.name == "Tilemap_BG_Near") target = tmBgNear;
                    else if (layer.name == "Tilemap_BG_Pillars") target = tmBgPillars;
                    else if (layer.name == "Tilemap_FG_Vines") target = tmFgVines;

                    if (target == null || layer.tiles == null) continue;

                    var positions = new List<Vector3Int>();
                    var tiles = new List<TileBase>();
                    foreach (var tile in layer.tiles) {
                        bool mapped = tilesById.TryGetValue(tile.id, out TileBase tileAsset);
                        report.CountTile(tile, mapped);
                        if (!mapped) continue;
                        positions.Add(new Vector3Int(tile.x, -tile.y, 0));
                        tiles.Add(tileAsset);
                    }
                    target.SetTiles(positions.ToArray(), tiles.ToArray());
                }
            }

            // 4. Instantiate Multi-Tile Props
            if (manifest.props != null) {
                foreach (var prop in manifest.props) {
                    bool mapped = propsByType.TryGetValue(prop.type, out GameObject prefab);
                    report.CountProp(prop.type, mapped);
                    if (!mapped) continue;

                    // Align to bottom-center of prop footprint
                    float worldX = prop.bottomCenterWorldX > 0 ? prop.bottomCenterWorldX : (prop.x + prop.w * 0.5f);
                    float worldY = prop.bottomWorldY > 0 ? -prop.bottomWorldY : -(prop.y + prop.h);
                    Vector3 pos = targetGrid.CellToWorld(new Vector3Int(Mathf.FloorToInt(worldX), Mathf.FloorToInt(worldY), 0));

                    GameObject instance = Instantiate(prefab, pos, Quaternion.identity, propRoot);
                    instance.name = $"{prop.type} ({prop.x}, {prop.y})";

                    var sr = instance.GetComponentInChildren<SpriteRenderer>();
                    if (sr != null && prop.flipX) sr.flipX = true;

                    PropSpawned?.Invoke(instance, prop);
                }
            }

            // 5. Instantiate Gameplay Entities
            if (manifest.entities != null) {
                foreach (var entity in manifest.entities) {
                    bool mapped = prefabsByType.TryGetValue(entity.type, out GameObject prefab);
                    report.CountEntity(entity.type, mapped);
                    if (!mapped) continue;

                    var cell = new Vector3Int(Mathf.FloorToInt(entity.x), -Mathf.FloorToInt(entity.y), 0);
                    GameObject instance = Instantiate(prefab, targetGrid.GetCellCenterWorld(cell), Quaternion.identity, entityRoot);
                    instance.name = $"{entity.type} ({entity.id})";

                    EntitySpawned?.Invoke(instance, entity);
                }
            }

            Debug.Log($"Jaconir import of {label} complete:
{report}");
            return report;
        }

        Tilemap GetOrCreateTilemap(string childName, string sortingLayer, int order, bool addColliders, Color? tint = null) {
            Transform t = targetGrid.transform.Find(childName);
            GameObject go = t ? t.gameObject : new GameObject(childName);
            go.transform.SetParent(targetGrid.transform, false);

            Tilemap tm = go.GetComponent<Tilemap>() ?? go.AddComponent<Tilemap>();
            TilemapRenderer tr = go.GetComponent<TilemapRenderer>() ?? go.AddComponent<TilemapRenderer>();
            tr.sortingLayerName = sortingLayer;
            tr.sortingOrder = order;

            if (tint.HasValue) tm.color = tint.Value;

            if (addColliders) {
                if (!go.GetComponent<TilemapCollider2D>()) go.AddComponent<TilemapCollider2D>();
                var rb = go.GetComponent<Rigidbody2D>() ?? go.AddComponent<Rigidbody2D>();
                rb.bodyType = RigidbodyType2D.Static;
                var comp = go.GetComponent<CompositeCollider2D>() ?? go.AddComponent<CompositeCollider2D>();
                var col = go.GetComponent<TilemapCollider2D>();
                if (col) col.usedByComposite = true;
            }

            return tm;
        }

        Transform GetOrCreateRoot(string name) {
            Transform t = targetGrid.transform.Find(name);
            if (!t) {
                var go = new GameObject(name);
                go.transform.SetParent(targetGrid.transform, false);
                return go.transform;
            }
            return t;
        }

        void ClearChildren(Transform parent) {
            for (int i = parent.childCount - 1; i >= 0; i--) {
                DestroyObject(parent.GetChild(i).gameObject);
            }
        }

        static void DestroyObject(GameObject go) {
            if (Application.isPlaying) Destroy(go);
            else DestroyImmediate(go);
        }
    }

    public class JaconirWorldManager : MonoBehaviour {
        public JaconirLevelImporter importer;
        private JaconirWorldManifest currentWorld;
        private int currentLevelIndex = 0;

        public void LoadWorld(string json) {
            currentWorld = JsonUtility.FromJson<JaconirWorldManifest>(json);
            currentLevelIndex = 0;
            LoadCurrentLevel();
        }

        public void LoadNextLevel() {
            if (currentWorld != null && currentLevelIndex + 1 < currentWorld.worldManifest.Count) {
                currentLevelIndex++;
                LoadCurrentLevel();
            } else {
                Debug.Log("World Complete!");
            }
        }

        private void LoadCurrentLevel() {
            importer.ImportFromManifest(currentWorld.worldManifest[currentLevelIndex]);
        }
    }
}

Godot (GDScript)

TileMap & PackedScene

extends Node2D
## Jaconir level importer sample for Godot 4.x.

signal entity_spawned(node: Node2D, entity: Dictionary)

@export var tilemap: TileMap
@export var tile_source_id: int = 0
@export var tile_atlas_coords: Dictionary = {
	1: Vector2i(0, 0), # wall
	2: Vector2i(1, 0), # hazard_spike
	3: Vector2i(2, 0), # item
	4: Vector2i(3, 0), # portal
	5: Vector2i(0, 1), # hazard_crumble
}
@export var entity_scenes: Dictionary = {
	"spawn_gate": null,
	"transition_gate": null,
	"key": null,
	"door": null,
	"key_red": null,
	"door_red": null,
	"guardian": null,
	"exit": null,
	"item": null,
	"portal": null,
	"wall": null,
}

var world_data = null
var current_index = 0
var _entity_root: Node2D

func import_single_level(json_path: String) -> Dictionary:
	var file = FileAccess.open(json_path, FileAccess.READ)
	var data = JSON.parse_string(file.get_as_text())
	return _build_level(data)

func _build_level(data) -> Dictionary:
	var report = {"placed_tiles": {}, "unmapped_tiles": {}, "spawned": {}, "unmapped_entities": {}}
	tilemap.clear()
	if _entity_root == null:
		_entity_root = get_node_or_null("JaconirEntities")
	if _entity_root == null:
		_entity_root = Node2D.new()
		_entity_root.name = "JaconirEntities"
		add_child(_entity_root)
	for child in _entity_root.get_children():
		child.queue_free()

	# 1. Build geometry
	for tile in _tiles_of(data):
		var id = int(tile["id"])
		if tile_atlas_coords.has(id):
			tilemap.set_cell(0, Vector2i(int(tile["x"]), int(tile["y"])), tile_source_id, tile_atlas_coords[id])
			_count(report["placed_tiles"], id)
		else:
			_count(report["unmapped_tiles"], id)

	# 2. Spawn entities
	for entity in data.get("entities", []):
		var type: String = entity["type"]
		var scene: PackedScene = entity_scenes.get(type)
		if scene == null:
			_count(report["unmapped_entities"], type)
			continue
		var instance = scene.instantiate()
		instance.position = tilemap.map_to_local(Vector2i(int(entity["x"]), int(entity["y"])))
		_entity_root.add_child(instance)
		_count(report["spawned"], type)
		entity_spawned.emit(instance, entity)

	return report

func _tiles_of(data) -> Array:
	var out = []
	if data.has("layers"):
		for layer in data["layers"]:
			if layer["name"] == "geometry" or layer["name"] == "Tilemap_Collision":
				out.append_array(layer["tiles"])
	elif data.has("tiles"):
		for key in data["tiles"]:
			var xy = key.split(",")
			out.append({"x": int(xy[0]), "y": int(xy[1]), "id": data["tiles"][key]})
	return out

func _count(counts: Dictionary, key):
	counts[key] = counts.get(key, 0) + 1

Integration Support

The manifest schema is intentionally platform-agnostic. While we provide Unity and Godot boilerplates, the structure is designed to be easily parsed by any engine or framework.