diff --git a/LDtk.Codegen/Program.cs b/LDtk.Codegen/Program.cs index 6d9550bd..398b08e5 100644 --- a/LDtk.Codegen/Program.cs +++ b/LDtk.Codegen/Program.cs @@ -2,7 +2,6 @@ namespace LDtk.Codegen; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using CommandLine; diff --git a/LDtk.JsonSchema/Program.cs b/LDtk.JsonSchema/Program.cs index 5ed6e1ac..3b22d0c5 100644 --- a/LDtk.JsonSchema/Program.cs +++ b/LDtk.JsonSchema/Program.cs @@ -83,7 +83,7 @@ public static async Task Main() }; string[] ignoreFields = { - "__FORCED_REFS", + "_FORCED_REFS", "LevelFields" }; diff --git a/LDtk/JsonPartials/LDtkFile.cs b/LDtk/JsonPartials/LDtkFile.cs index cffd8a06..b6b5e866 100644 --- a/LDtk/JsonPartials/LDtkFile.cs +++ b/LDtk/JsonPartials/LDtkFile.cs @@ -11,6 +11,17 @@ namespace LDtk; [DebuggerDisplay("ExternalFiles: {ExternalLevels} Path: {FilePath}")] public partial class LDtkFile { + /// + public EntityDefinition[] Entities => Defs.Entities; + /// + public EnumDefinition[] Enums => Defs.Enums; + /// + public EnumDefinition[] ExternalEnums => Defs.ExternalEnums; + /// + public LayerDefinition[] Layers => Defs.Layers; + /// + public TilesetDefinition[] Tilesets => Defs.Tilesets; + /// Initializes a new instance of the class. Used by json deserializer not for use by user. public LDtkFile() { } @@ -25,6 +36,7 @@ public LDtkFile() { } /// Loads the ldtk world file from disk directly using json source generator. /// Path to the .ldtk file. /// Returns the file loaded from the path. + /// Failed to Deserialize ldtk file from public static LDtkFile FromFile(string filePath) { LDtkFile? file = JsonSerializer.Deserialize(File.ReadAllText(filePath), Constants.JsonSourceGenerator.LDtkFile); @@ -39,6 +51,7 @@ public static LDtkFile FromFile(string filePath) /// Loads the ldtk world file from disk directly. /// Path to the .ldtk file. /// Returns the file loaded from the path. + /// Thrown when the file failed to deserialize. public static LDtkFile FromFileReflection(string filePath) { LDtkFile? file = JsonSerializer.Deserialize(File.ReadAllText(filePath), Constants.SerializeOptions); @@ -65,7 +78,8 @@ public static LDtkFile FromFile(string filePath, ContentManager content) /// Loads the ldtkl world file from disk directly or from the embeded one depending on if the file uses externalworlds. /// The iid of the world to load. /// Returns the world from the iid. - public LDtkWorld? LoadWorld(Guid iid) + /// Throws if no world with the is found. + public LDtkWorld LoadWorld(Guid iid) { foreach (LDtkWorld world in Worlds) { @@ -89,13 +103,14 @@ public static LDtkFile FromFile(string filePath, ContentManager content) return world; } - return null; + throw new LDtkException($"No World({iid}) found in this project"); } /// Gets an entity from an converted to . /// The type to convert the entity to. /// The entityRef to convert. /// The converted entity. + /// Throws if no entity with the is found. public T GetEntityRef(EntityReference reference) where T : new() { @@ -111,4 +126,19 @@ public T GetEntityRef(EntityReference reference) throw new LDtkException($"No EntityRef of type {typeof(T).Name} found in this level"); } + + /// ToString + public override string ToString() + { + return $""" + LDtkFile: {JsonVersion} + BgColor: {BgColor} + Defs: {Defs} + ExternalLevels: {ExternalLevels} + Iid: {Iid} + Levels: {Levels} + Toc: {Toc} + Worlds: {Worlds} + """; + } } diff --git a/LDtk/JsonPartials/LDtkLevel.cs b/LDtk/JsonPartials/LDtkLevel.cs index b4c306e8..9ecb07fb 100644 --- a/LDtk/JsonPartials/LDtkLevel.cs +++ b/LDtk/JsonPartials/LDtkLevel.cs @@ -4,6 +4,7 @@ namespace LDtk; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; @@ -33,10 +34,12 @@ public partial class LDtkLevel [JsonIgnore] public bool Loaded { get; internal set; } - /// - /// Initializes a new instance of the class. Used by json deserializer not for use by user!. + /// Initializes a new instance of the class. Used by json deserializer not for use by user!. #pragma warning disable CS8618 - public LDtkLevel() { } + public LDtkLevel() + { + layersLookup = new(LayerInstances?.Length ?? 0); + } #pragma warning restore /// Loads the ldtk world file from disk directly using json source generator. @@ -76,6 +79,49 @@ public LDtkLevel() { } return file; } + /// Get the level with an iid. + /// Content pipeline + /// No level with identifier found in this world + public void Load(ContentManager? content = null) + { + if (ExternalRelPath != null) + { + LDtkLevel? level; + + if (content != null) + { + level = FromFile(FilePath, content); + } + else + { + level = FromFile(FilePath); + } + + if (level == null) + { + throw new LDtkException("External level not found"); + } + + _BgColor = level._BgColor; + _BgPos = level._BgPos; + BgRelPath = level.BgRelPath; + ExternalRelPath = level.ExternalRelPath; + FieldInstances = level.FieldInstances; + Identifier = level.Identifier; + Iid = level.Iid; + LayerInstances = level.LayerInstances; + _Neighbours = level._Neighbours; + PxHei = level.PxHei; + PxWid = level.PxWid; + Uid = level.Uid; + WorldDepth = level.WorldDepth; + WorldX = level.WorldX; + WorldY = level.WorldY; + + Loaded = true; + } + } + /// Gets an intgrid with the in a . /// /// Throws when identifier is not valid @@ -111,6 +157,7 @@ public LDtkIntGrid GetIntGrid(string identifier) } /// Gets the custom fields of the level. + /// public T GetCustomFields() where T : new() { T levelFields = new(); @@ -121,6 +168,7 @@ public LDtkIntGrid GetIntGrid(string identifier) } /// Gets one entity of type T in the current level best used with 1 per level constraint. + /// /// public T GetEntity() where T : new() @@ -140,6 +188,7 @@ public T GetEntity() } /// Gets an entity from an converted to . + /// /// /// public T GetEntityRef(EntityReference reference) @@ -169,6 +218,7 @@ public T GetEntityRef(EntityReference reference) } /// Gets an array of entities of type in the current level. + /// public T[] GetEntities() where T : new() { @@ -191,7 +241,7 @@ public T[] GetEntities() } } - return [.. entities]; + return entities.ToArray(); } T GetEntityFromInstance(EntityInstance entityInstance) @@ -218,4 +268,29 @@ public bool Contains(Point point) { return point.X >= Position.X && point.Y >= Position.Y && point.X <= Position.X + Size.X && point.Y <= Position.Y + Size.Y; } + + Dictionary layersLookup; + + /// Gets the layer instance with the given . + /// Layer Identifier + /// + public LayerInstance this[string layerIdentifier] + { + get + { + if (layersLookup.TryGetValue(layerIdentifier, out LayerInstance? layerLookup)) + { + return layerLookup; + } + + LayerInstance? layer = LayerInstances!.FirstOrDefault(x => x?._Identifier == layerIdentifier, null); + if (layer != null) + { + layersLookup.Add(layerIdentifier, layer); + return layer; + } + + throw new LDtkException($"Layer {layerIdentifier} not found in level {Identifier}"); + } + } } diff --git a/LDtk/LDtkJson.cs b/LDtk/LDtkJson.cs index 31b64ae1..202fe834 100644 --- a/LDtk/LDtkJson.cs +++ b/LDtk/LDtkJson.cs @@ -25,10 +25,6 @@ public partial class LDtkFile [JsonPropertyName("externalLevels")] public bool ExternalLevels { get; set; } - /// This object is not actually used by LDtk. It ONLY exists to force explicit references to all types, to make sure QuickType finds them and integrate all of them. Otherwise, Quicktype will drop types that are not explicitely used. - [JsonPropertyName("__FORCED_REFS")] - public object _FORCED_REFS { get; set; } - /// Unique project identifier [JsonPropertyName("iid")] public Guid Iid { get; set; } diff --git a/LDtk/Renderer/LDtkRenderer.cs b/LDtk/Renderer/LDtkRenderer.cs index f69cfea1..80658877 100644 --- a/LDtk/Renderer/LDtkRenderer.cs +++ b/LDtk/Renderer/LDtkRenderer.cs @@ -3,7 +3,6 @@ namespace LDtk.Renderer; using System; using System.Collections.Generic; using System.IO; -using System.Linq; using LDtk; @@ -16,7 +15,7 @@ namespace LDtk.Renderer; /// This can all be done in your own class if you want to reimplement it and customize it differently /// this one is mostly here to get you up and running quickly. /// -public class LDtkRenderer : IDisposable +public class LevelRenderer : IDisposable { /// Gets or sets the spritebatch used for rendering with this Renderer. public SpriteBatch SpriteBatch { get; set; } @@ -34,7 +33,7 @@ public class LDtkRenderer : IDisposable /// Initializes a new instance of the class. This is used to intizialize the renderer for use with direct file loading. /// Spritebatch - public LDtkRenderer(SpriteBatch spriteBatch) + public LevelRenderer(SpriteBatch spriteBatch) { SpriteBatch = spriteBatch; graphicsDevice = spriteBatch.GraphicsDevice; @@ -42,14 +41,17 @@ public LDtkRenderer(SpriteBatch spriteBatch) if (pixel == null) { pixel = new Texture2D(graphicsDevice, 1, 1); - pixel.SetData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }); + pixel.SetData(new byte[] + { + 0xFF, 0xFF, 0xFF, 0xFF + } + ); } if (error == null) { error = new Texture2D(graphicsDevice, 2, 2); - error.SetData( - new byte[] + error.SetData(new byte[] { 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, @@ -61,7 +63,7 @@ public LDtkRenderer(SpriteBatch spriteBatch) /// Initializes a new instance of the class. This is used to intizialize the renderer for use with content Pipeline. /// SpriteBatch /// Optional ContentManager - public LDtkRenderer(SpriteBatch spriteBatch, ContentManager content) + public LevelRenderer(SpriteBatch spriteBatch, ContentManager content) : this(spriteBatch) { this.content = content; @@ -69,7 +71,6 @@ public LDtkRenderer(SpriteBatch spriteBatch, ContentManager content) /// Prerender out the level to textures to optimize the rendering process. /// The level to prerender. - /// The level already has been prerendered. public void PrerenderLevel(LDtkLevel level) { if (PrerenderedLevels.ContainsKey(level.Identifier)) @@ -77,17 +78,12 @@ public void PrerenderLevel(LDtkLevel level) return; } - RenderedLevel renderLevel = new(); - - SpriteBatch.Begin(samplerState: SamplerState.PointClamp); + RenderedLevel renderLevel = new() { - renderLevel.Layers = RenderLayers(level); - } - - SpriteBatch.End(); + Layers = RenderLayers(level) + }; PrerenderedLevels.Add(level.Identifier, renderLevel); - graphicsDevice.SetRenderTarget(null); } Texture2D[] RenderLayers(LDtkLevel level) @@ -109,7 +105,7 @@ Texture2D[] RenderLayers(LDtkLevel level) { LayerInstance layer = level.LayerInstances[i]; - if (layer._TilesetRelPath == null) + if (string.IsNullOrEmpty(layer._TilesetRelPath)) { continue; } @@ -119,59 +115,86 @@ Texture2D[] RenderLayers(LDtkLevel level) continue; } - Texture2D texture = GetTexture(level, layer._TilesetRelPath); - - int width = layer._CWid * layer._GridSize; - int height = layer._CHei * layer._GridSize; - RenderTarget2D renderTarget = new(graphicsDevice, width, height, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + SpriteBatch.Begin(samplerState: SamplerState.PointClamp); + { + Texture2D texture = GetTexture(level, layer._TilesetRelPath); - graphicsDevice.SetRenderTarget(renderTarget); - layers.Add(renderTarget); + int width = layer._CWid * layer._GridSize; + int height = layer._CHei * layer._GridSize; - switch (layer._Type) - { - case LayerType.Tiles: - foreach (TileInstance tile in layer.GridTiles.Where(_ => layer._TilesetDefUid.HasValue)) + RenderTarget2D renderTarget = new(graphicsDevice, width, height) { - Vector2 position = new(tile.Px.X + layer._PxTotalOffsetX, tile.Px.Y + layer._PxTotalOffsetY); - Rectangle rect = new(tile.Src.X, tile.Src.Y, layer._GridSize, layer._GridSize); - SpriteEffects mirror = (SpriteEffects)tile.F; - SpriteBatch.Draw(texture, position, rect, Color.White, 0, Vector2.Zero, 1f, mirror, 0); - } - break; + Name = layer._Identifier + }; + + graphicsDevice.SetRenderTarget(renderTarget); + graphicsDevice.Clear(Color.Transparent); - case LayerType.AutoLayer: - case LayerType.IntGrid: - if (layer.AutoLayerTiles.Length > 0) + var tiles = layer._Type switch { - foreach (TileInstance tile in layer.AutoLayerTiles.Where(_ => layer._TilesetDefUid.HasValue)) - { - Vector2 position = new(tile.Px.X + layer._PxTotalOffsetX, tile.Px.Y + layer._PxTotalOffsetY); - Rectangle rect = new(tile.Src.X, tile.Src.Y, layer._GridSize, layer._GridSize); - SpriteEffects mirror = (SpriteEffects)tile.F; - SpriteBatch.Draw(texture, position, rect, Color.White, 0, Vector2.Zero, 1f, mirror, 0); - } + LayerType.Tiles => layer.GridTiles, + LayerType.AutoLayer => layer.AutoLayerTiles, + LayerType.IntGrid => layer.AutoLayerTiles, + LayerType.Entities => [], + _ => [], + }; + + foreach (TileInstance tile in tiles) + { + DrawTile(layer, texture, tile); } - break; + + layers.Add(renderTarget); } + SpriteBatch.End(); } + graphicsDevice.SetRenderTarget(null); return layers.ToArray(); } - Texture2D RenderBackgroundToLayer(LDtkLevel level) + void DrawTile(LayerInstance layer, Texture2D texture, TileInstance tile) + { + Vector2 position = new(tile.Px.X + layer._PxTotalOffsetX, tile.Px.Y + layer._PxTotalOffsetY); + Rectangle rect = new(tile.Src.X, tile.Src.Y, layer._GridSize, layer._GridSize); + SpriteEffects mirror = (SpriteEffects)tile.F; + SpriteBatch.Draw( + texture, + position, + rect, + Color.White, + 0, + Vector2.Zero, + 1f, + mirror, + 0.5f); + } + + RenderTarget2D RenderBackgroundToLayer(LDtkLevel level) { Texture2D texture = GetTexture(level, level.BgRelPath); - RenderTarget2D layer = new(graphicsDevice, level.PxWid, level.PxHei, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + RenderTarget2D layer = new(graphicsDevice, level.PxWid, level.PxHei) + { + Name = "Background" + }; graphicsDevice.SetRenderTarget(layer); { LevelBackgroundPosition? bg = level._BgPos; if (bg != null) { - Vector2 pos = bg.TopLeftPx.ToVector2(); - SpriteBatch.Draw(texture, pos, new Rectangle((int)bg.CropRect[0], (int)bg.CropRect[1], (int)bg.CropRect[2], (int)bg.CropRect[3]), Color.White, 0, Vector2.Zero, bg.Scale, SpriteEffects.None, 0); + Vector2 position = bg.TopLeftPx.ToVector2(); + SpriteBatch.Draw( + texture, + position, + new Rectangle((int)bg.CropRect[0], (int)bg.CropRect[1], (int)bg.CropRect[2], (int)bg.CropRect[3]), + Color.White, + 0, + Vector2.Zero, + bg.Scale, + SpriteEffects.None, + 0); } } graphicsDevice.SetRenderTarget(null); @@ -220,7 +243,21 @@ public void RenderPrerenderedLevel(LDtkLevel level) { for (int i = 0; i < prerenderedLevel.Layers.Length; i++) { - SpriteBatch.Draw(prerenderedLevel.Layers[i], level.Position.ToVector2(), Color.White); + Texture2D layer = prerenderedLevel.Layers[i]; + + float depth = 1f - (i * 0.05f); + // if (layer.Name == "Buildings") + // { + SpriteBatch.Draw( + layer, + new(level.Position.X, level.Position.Y, layer.Bounds.Width, layer.Bounds.Height), + layer.Bounds, + new(0xffffffff), + 0, + Vector2.Zero, + SpriteEffects.None, + depth); + // } } } else @@ -239,6 +276,7 @@ public void RenderLevel(LDtkLevel level) for (int i = 0; i < layers.Length; i++) { SpriteBatch.Draw(layers[i], level.Position.ToVector2(), Color.White); + layers[i].Dispose(); } } @@ -254,63 +292,28 @@ public void RenderIntGrid(LDtkIntGrid intGrid) if (cellValue != 0) { - // Color col = intGrid.GetColorFromValue(cellValue); int spriteX = intGrid.WorldPosition.X + (x * intGrid.TileSize); int spriteY = intGrid.WorldPosition.Y + (y * intGrid.TileSize); - SpriteBatch.Draw(pixel, new Vector2(spriteX, spriteY), null, Color.Pink /*col*/, 0, Vector2.Zero, new Vector2(intGrid.TileSize), SpriteEffects.None, 0); + SpriteBatch.Draw( + pixel, + new Vector2(spriteX, spriteY), + null, + Color.Pink, + 0, + Vector2.Zero, + new Vector2(intGrid.TileSize), + SpriteEffects.None, + 0); } } } } - /// Renders the entity with the tile it includes. - /// The entity you want to render. - /// The spritesheet/texture for rendering the entity. - public void RenderEntity(T entity, Texture2D texture) - where T : ILDtkEntity - { - SpriteBatch.Draw(texture, entity.Position, entity.Tile, Color.White, 0, entity.Pivot * entity.Size, 1, SpriteEffects.None, 0); - } - - /// Renders the entity with the tile it includes. - /// The entity you want to render. - /// The spritesheet/texture for rendering the entity. - /// The direction to flip the entity when rendering. - public void RenderEntity(T entity, Texture2D texture, SpriteEffects flipDirection) - where T : ILDtkEntity - { - SpriteBatch.Draw(texture, entity.Position, entity.Tile, Color.White, 0, entity.Pivot * entity.Size, 1, flipDirection, 0); - } - - /// Renders the entity with the tile it includes. - /// The entity you want to render. - /// The spritesheet/texture for rendering the entity. - /// The current frame of animation. Is a very basic entity animation frames must be to the right of them and be the same size. - public void RenderEntity(T entity, Texture2D texture, int animationFrame) - where T : ILDtkEntity - { - Rectangle animatedTile = entity.Tile; - animatedTile.Offset(animatedTile.Width * animationFrame, 0); - SpriteBatch.Draw(texture, entity.Position, animatedTile, Color.White, 0, entity.Pivot * entity.Size, 1, SpriteEffects.None, 0); - } - - /// Renders the entity with the tile it includes. - /// The entity you want to render. - /// The spritesheet/texture for rendering the entity. - /// The direction to flip the entity when rendering. - /// The current frame of animation. Is a very basic entity animation frames must be to the right of them and be the same size. - public void RenderEntity(T entity, Texture2D texture, SpriteEffects flipDirection, int animationFrame) - where T : ILDtkEntity - { - Rectangle animatedTile = entity.Tile; - animatedTile.Offset(animatedTile.Width * animationFrame, 0); - SpriteBatch.Draw(texture, entity.Position, animatedTile, Color.White, 0, entity.Pivot * entity.Size, 1, flipDirection, 0); - } - /// Dispose Renderer public void Dispose() { pixel.Dispose(); + error.Dispose(); GC.SuppressFinalize(this); } }