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
Design
Pick dungeon or platformer, a theme, and size. Share the URL — it includes the seed.
Export
Download Tiled .tmx + PNG (best for Unity/Godot/Phaser), a Godot .tscn, or JSON.
Import
Unity: SuperTiled2Unity on the .tmx. Godot: Tiled plugin or drop in the .tscn. Or run the importer scripts on JSON.
02Manifest Anatomy
{
"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 IndexNumerical ID (0, 1, 2...) for easy array-based level indexing in engines.
- Deterministic IDSeed-based unique string for reliable cross-session level referencing.
- Embedded RegistryTile 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.
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