using System.Linq; using CSharpLdtkImporter.Models; using Godot; namespace CSharpLdtkImporter; public class LdtkSceneBuilder { private readonly LdtkData _ldtkData; private readonly string _basePath; private readonly Godot.Collections.Dictionary _tileSetCache = new(); public LdtkSceneBuilder(LdtkData ldtkData, string sourceFile) { _ldtkData = ldtkData; _basePath = sourceFile.GetBaseDir(); } public Node2D BuildLdtkProjectRoot() { var root = new Node2D { Name = "LDTKProject" }; // Step 1: Build the entire node hierarchy in memory. foreach (var level in _ldtkData.Levels) { var levelNode = BuildLevel(level); root.AddChild(levelNode); } // Step 2: After the tree is built, set the owner for all descendants. // This is the crucial step that fixes the "Invalid owner" error. SetOwnerRecursive(root, root); return root; } private void SetOwnerRecursive(Node node, Node owner) { foreach (var child in node.GetChildren()) { child.Owner = owner; SetOwnerRecursive(child, owner); } } private Node2D BuildLevel(LdtkLevel level) { var levelRoot = new Node2D { Name = level.Identifier }; levelRoot.Position = new Vector2(level.WorldX, level.WorldY); foreach (var layer in level.LayerInstances.Reverse()) { var layerNode = layer.Type switch { "Tiles" => BuildTileMapLayer(layer), "AutoLayer" => BuildTileMapLayer(layer), "Entities" => BuildEntityLayer(layer), _ => new Node2D { Name = $"{layer.Identifier}_Unsupported" } }; levelRoot.AddChild(layerNode); } return levelRoot; } private TileMapLayer BuildTileMapLayer(LdtkLayerInstance layer) { var tileMapLayer = new TileMapLayer { Name = layer.Identifier }; if (!layer.TilesetDefUid.HasValue) return tileMapLayer; var tileSet = GetOrCreateTileSet(layer.TilesetDefUid.Value); if (tileSet == null) return tileMapLayer; tileMapLayer.TileSet = tileSet; var allTiles = layer.GridTiles.Concat(layer.AutoLayerTiles); if (tileMapLayer.TileSet.GetSource(0) is not TileSetAtlasSource atlasSource) return tileMapLayer; int atlasWidthInTiles = atlasSource.GetAtlasGridSize().X; if (atlasWidthInTiles == 0) return tileMapLayer; foreach (var tile in allTiles) { var gridCoords = new Vector2I(tile.Px[0] / layer.GridSize, tile.Px[1] / layer.GridSize); var atlasCoords = new Vector2I(tile.TileId % atlasWidthInTiles, tile.TileId / atlasWidthInTiles); long alternativeId = 0; if ((tile.FlipBits & 1) == 1) alternativeId |= TileSetAtlasSource.TransformFlipH; if ((tile.FlipBits & 2) == 2) alternativeId |= TileSetAtlasSource.TransformFlipV; tileMapLayer.SetCell(gridCoords, 0, atlasCoords, (int)alternativeId); } return tileMapLayer; } private Node2D BuildEntityLayer(LdtkLayerInstance layer) { var entityLayerRoot = new Node2D { Name = layer.Identifier }; foreach (var entity in layer.EntityInstances) { var marker = new Marker2D { Name = entity.Identifier, Position = new Vector2(entity.Px[0], entity.Px[1]) }; entityLayerRoot.AddChild(marker); } return entityLayerRoot; } private TileSet GetOrCreateTileSet(int tilesetDefUid) { if (_tileSetCache.TryGetValue(tilesetDefUid, out var cachedTileSet)) return cachedTileSet; var tilesetDef = _ldtkData.Defs.Tilesets.FirstOrDefault(t => t.Uid == tilesetDefUid); if (tilesetDef?.RelPath == null) return null; var texturePath = _basePath.PathJoin(tilesetDef.RelPath); var texture = ResourceLoader.Load(texturePath); if (texture == null) { GD.PushError($"LDTK Importer: Could not load texture at path: {texturePath}"); return null; } var newTileSet = new TileSet { TileShape = TileSet.TileShapeEnum.Square, TileLayout = TileSet.TileLayoutEnum.Stacked, TileSize = new Vector2I(tilesetDef.TileGridSize, tilesetDef.TileGridSize) }; var atlasSource = new TileSetAtlasSource { Texture = texture, TextureRegionSize = new Vector2I(tilesetDef.TileGridSize, tilesetDef.TileGridSize) }; newTileSet.AddSource(atlasSource); var (widthInTiles, heightInTiles) = (texture.GetWidth() / tilesetDef.TileGridSize, texture.GetHeight() / tilesetDef.TileGridSize); for (int x = 0; x < widthInTiles; x++) { for (int y = 0; y < heightInTiles; y++) { atlasSource.CreateTile(new Vector2I(x, y)); } } _tileSetCache[tilesetDefUid] = newTileSet; return newTileSet; } }