196 lines
7.0 KiB
C#
196 lines
7.0 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using CSharpLdtkImporter.Models;
|
|
using Godot;
|
|
|
|
namespace CSharpLdtkImporter;
|
|
|
|
public class LdtkSceneBuilder
|
|
{
|
|
private readonly LdtkData _ldtkData;
|
|
private readonly string _basePath;
|
|
private readonly LdtkEntityMap _entityMap;
|
|
private readonly Godot.Collections.Dictionary<int, TileSet> _tileSetCache = new();
|
|
|
|
public LdtkSceneBuilder(LdtkData ldtkData, string sourceFile, LdtkEntityMap entityMap)
|
|
{
|
|
_ldtkData = ldtkData;
|
|
_basePath = sourceFile.GetBaseDir();
|
|
_entityMap = entityMap;
|
|
}
|
|
|
|
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)
|
|
{
|
|
Node2D instance = null;
|
|
|
|
GD.Print($"EntityMap keys: {string.Join(", ", _entityMap?.EntitySceneMap.Keys ?? Array.Empty<string>())}");
|
|
GD.Print($"Entity Identifier: {entity.Identifier}");
|
|
GD.Print($"Is Entity Mapped: {_entityMap?.EntitySceneMap.ContainsKey(entity.Identifier)}");
|
|
|
|
if (_entityMap?.EntitySceneMap.TryGetValue(entity.Identifier, out var packedScene) == true &&
|
|
packedScene != null)
|
|
{
|
|
GD.Print($"Instantiating entity '{entity.Identifier}' from mapped scene.");
|
|
instance = packedScene.Instantiate<Node2D>();
|
|
}
|
|
else
|
|
{
|
|
GD.Print($"Defaulting to Marker2D for unmapped entity '{entity.Identifier}'.");
|
|
instance = new Marker2D();
|
|
}
|
|
|
|
instance.Name = entity.Identifier;
|
|
instance.Position = new Vector2(entity.Px[0], entity.Px[1]);
|
|
|
|
foreach (var field in entity.FieldInstances)
|
|
{
|
|
instance.SetMeta(field.Identifier, ConvertJsonElementToVariant(field.Value));
|
|
}
|
|
|
|
entityLayerRoot.AddChild(instance);
|
|
}
|
|
|
|
return entityLayerRoot;
|
|
}
|
|
|
|
private static Variant ConvertJsonElementToVariant(JsonElement element)
|
|
{
|
|
return element.ValueKind switch
|
|
{
|
|
JsonValueKind.String => element.GetString(),
|
|
JsonValueKind.Number => element.GetDouble(),
|
|
JsonValueKind.True => true,
|
|
JsonValueKind.False => false,
|
|
JsonValueKind.Null => default,
|
|
// For LDTK points (which are objects) or arrays, we can serialize them back to a JSON string.
|
|
// A more advanced implementation could convert these to Vector2 or Array types.
|
|
JsonValueKind.Object => Json.Stringify(Json.ParseString(element.GetRawText())),
|
|
JsonValueKind.Array => Json.Stringify(Json.ParseString(element.GetRawText())),
|
|
_ => default
|
|
};
|
|
}
|
|
|
|
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<Texture2D>(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;
|
|
}
|
|
} |