diff --git a/.editorconfig b/.editorconfig index 3d3ffd2..8373e60 100755 --- a/.editorconfig +++ b/.editorconfig @@ -7,9 +7,9 @@ root = true #### Core EditorConfig Options #### # Indentation and spacing -indent_size = 2 +indent_size = 4 indent_style = space -tab_width = 2 +tab_width = 4 # New line preferences end_of_line = crlf diff --git a/TiledCSPlus.Example/Program.cs b/TiledCSPlus.Example/Program.cs index f817640..0a780e8 100644 --- a/TiledCSPlus.Example/Program.cs +++ b/TiledCSPlus.Example/Program.cs @@ -3,36 +3,36 @@ class Program { - public static void Main() - { - //load a tilemap - TiledMap TiledMap = new("assets/tilemap.tmx"); - - //load a tileset by loading all used tilesets from a directory - Dictionary Tilesets = TiledMap.GetTiledTilesets("assets/"); - //you can load them manually of course, like this - /* foreach (TiledMapTileset tiledMapTileset in TiledMap.Tilesets) + public static void Main() { - if tileset is not embedded into tilemap - if (!tiledMapTileset.IsTilesetEmbedded) + //load a tilemap + TiledMap TiledMap = new("assets/tilemap.tmx"); + + //load a tileset by loading all used tilesets from a directory + Dictionary Tilesets = TiledMap.GetTiledTilesets("assets/"); + //you can load them manually of course, like this + /* foreach (TiledMapTileset tiledMapTileset in TiledMap.Tilesets) { - load a tileset file using tiledMapTileset.Source as a filename... - ...using new TiledTileset() - TiledTileset tiledTileset = new TiledTileset(tiledMapTileset.Source); - ..or other method that works for you, into a MemoryStream - MemoryStream ms = new MemoryStream(); //file has to be loaded into a byte array - TiledTileset tiledTileset = new TiledTileset(ms); + if tileset is not embedded into tilemap + if (!tiledMapTileset.IsTilesetEmbedded) + { + load a tileset file using tiledMapTileset.Source as a filename... + ...using new TiledTileset() + TiledTileset tiledTileset = new TiledTileset(tiledMapTileset.Source); + ..or other method that works for you, into a MemoryStream + MemoryStream ms = new MemoryStream(); //file has to be loaded into a byte array + TiledTileset tiledTileset = new TiledTileset(ms); - //then, add into loaded tilesets as usual - Tilesets.Add(tiledMapTileset.FirstGid, tiledTileset); - } - } */ + //then, add into loaded tilesets as usual + Tilesets.Add(tiledMapTileset.FirstGid, tiledTileset); + } + } */ - //get map tileset which some tile belongs to - TiledMapTileset tileMapTileset = TiledMap.GetTiledMapTileset(TiledMap.Layers[0].Data[0]); - //now that you have tileset laded into Tilesets dictionary, you can get the tileset by - TiledTileset tileTileset = Tilesets[tileMapTileset.FirstGid]; - //and now, get the rect for tile's position in the tileset image - _ = TiledMap.GetSourceRect(tileMapTileset, tileTileset, TiledMap.Layers[0].Data[0]); - } + //get map tileset which some tile belongs to + TiledMapTileset tileMapTileset = TiledMap.GetTiledMapTileset(TiledMap.Layers[0].Data[0]); + //now that you have tileset laded into Tilesets dictionary, you can get the tileset by + TiledTileset tileTileset = Tilesets[tileMapTileset.FirstGid]; + //and now, get the rect for tile's position in the tileset image + _ = TiledMap.GetSourceRect(tileMapTileset, tileTileset, TiledMap.Layers[0].Data[0]); + } } \ No newline at end of file diff --git a/TiledCSPlus.Test/TiledCSPlus.Test.csproj b/TiledCSPlus.Test/TiledCSPlus.Test.csproj index 400577d..d99350d 100644 --- a/TiledCSPlus.Test/TiledCSPlus.Test.csproj +++ b/TiledCSPlus.Test/TiledCSPlus.Test.csproj @@ -1,36 +1,39 @@ - - - - net6.0 - enable - enable - - false - - - - - - - - - - - - - - Always - - - Always - - - Always - - - - - - - - + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + + + + + + diff --git a/TiledCSPlus.Test/TiledMap.cs b/TiledCSPlus.Test/TiledMap.cs index 1bc01d7..ee8d67a 100644 --- a/TiledCSPlus.Test/TiledMap.cs +++ b/TiledCSPlus.Test/TiledMap.cs @@ -2,46 +2,58 @@ namespace TiledCSPlus.Test; public class TiledMapTest { - private TiledMap? TiledMap; - private Dictionary? Tilesets; - [SetUp] - public void Setup() - { - TiledMap = new TiledMap("assets/tilemap.tmx"); - Tilesets = TiledMap.GetTiledTilesets("assets/"); - } + private TiledMap? TiledMap; + private TiledMap? TiledMap19; + private Dictionary? Tilesets; - [Test] - public void MapMetadata() - { - TiledMap?.TiledVersion.ShouldBe("1.10.2"); - TiledMap?.Width.ShouldBe(10); - TiledMap?.Height.ShouldBe(10); - TiledMap?.TileHeight.ShouldBe(16); - TiledMap?.TileWidth.ShouldBe(16); - TiledMap?.Orientation.ShouldBe("orthogonal"); - TiledMap?.BackgroundColor.ShouldBe(new Color(1, 2, 3, 4)); - } + [SetUp] + public void Setup() + { + TiledMap = new TiledMap("assets/tilemap1.10.tmx"); + TiledMap19 = new("assets/tilemap1.9.tmx"); + Tilesets = TiledMap.GetTiledTilesets("assets/"); + } - [Test] - public void Tileset() - { - TiledMap?.Tilesets[0].FirstGid.ShouldBe(1); - TiledMap?.Tilesets[0].Source.ShouldBe("tileset.tsx"); - TiledMap?.Tilesets[0].IsTilesetEmbedded.ShouldBe(false); - TiledMap?.Tilesets[1].FirstGid.ShouldBe(2); - TiledMap?.Tilesets[1].Source.ShouldBe(null); - TiledMap?.Tilesets[1].IsTilesetEmbedded.ShouldBe(true); + [Test] + public void MapMetadata() + { + TiledMap?.TiledVersion.ShouldBe("1.10.2"); + TiledMap?.MapVersion.ShouldBe("1.10"); + TiledMap?.Width.ShouldBe(10); + TiledMap?.Height.ShouldBe(10); + TiledMap?.TileHeight.ShouldBe(16); + TiledMap?.TileWidth.ShouldBe(16); + TiledMap?.Orientation.ShouldBe("orthogonal"); + TiledMap?.BackgroundColor.ShouldBe(new Color(1, 2, 3, 4)); + } - Tilesets?[1].Image.Source.ShouldBe("tileset.png"); - TiledMap?.EmbeddedTilesets[2].Name.ShouldBe("tileset-embedded"); + [Test] + public void Tileset() + { + TiledMap?.Tilesets[0].FirstGid.ShouldBe(1); + TiledMap?.Tilesets[0].Source.ShouldBe("tileset.tsx"); + TiledMap?.Tilesets[0].IsTilesetEmbedded.ShouldBe(false); + TiledMap?.Tilesets[1].FirstGid.ShouldBe(2); + TiledMap?.Tilesets[1].Source.ShouldBe(null); + TiledMap?.Tilesets[1].IsTilesetEmbedded.ShouldBe(true); - } + Tilesets?[1].Image.Source.ShouldBe("tileset.png"); + Tilesets?[1].TilesetVersion.ShouldBe("1.8"); + TiledMap?.EmbeddedTilesets[2].Name.ShouldBe("tileset-embedded110"); + } - [Test] - public void Layers() - { - TiledMap?.Layers[0].TintColor.ShouldBe(new Color(255, 254, 253, 252)); - TiledMap?.Layers[2].Name.ShouldBe("Image Layer 1"); - } + [Test] + public void Layers() + { + TiledMap?.Layers[0].TintColor.ShouldBe(new Color(255, 254, 253, 252)); + TiledMap?.Layers[2].Name.ShouldBe("Image Layer 1"); + TiledMap?.Layers[1].Objects[0].Class.ShouldBe("test110"); + } + + [Test] + public void CompabilityWith19() + { + TiledMap19?.MapVersion.ShouldBe("1.9"); + TiledMap19?.Layers[1].Objects[0].Class.ShouldBe("test19"); + } } \ No newline at end of file diff --git a/TiledCSPlus.Test/assets/TiledCSPlus_test.tiled-project b/TiledCSPlus.Test/assets/TiledCSPlus_test.tiled-project index 713c275..953ae34 100644 --- a/TiledCSPlus.Test/assets/TiledCSPlus_test.tiled-project +++ b/TiledCSPlus.Test/assets/TiledCSPlus_test.tiled-project @@ -1,14 +1,14 @@ -{ - "automappingRulesFile": "", - "commands": [ - ], - "compatibilityVersion": 1080, - "extensionsPath": "extensions", - "folders": [ - "." - ], - "properties": [ - ], - "propertyTypes": [ - ] -} +{ + "automappingRulesFile": "", + "commands": [ + ], + "compatibilityVersion": 1100, + "extensionsPath": "extensions", + "folders": [ + "." + ], + "properties": [ + ], + "propertyTypes": [ + ] +} diff --git a/TiledCSPlus.Test/assets/TiledCSPlus_test.tiled-session b/TiledCSPlus.Test/assets/TiledCSPlus_test.tiled-session index fd674f3..a99818e 100644 --- a/TiledCSPlus.Test/assets/TiledCSPlus_test.tiled-session +++ b/TiledCSPlus.Test/assets/TiledCSPlus_test.tiled-session @@ -3,7 +3,7 @@ "height": 4300, "width": 2 }, - "activeFile": "D:/Projects/csharp/TiledCSPlus/TiledCSPlus.Example/assets/tilemap.tmx", + "activeFile": "tileset.tsx", "expandedProjectPaths": [ "." ], @@ -12,12 +12,23 @@ "": { "scaleInDock": 1 }, + "D:/Projects/csharp/MelonEngine/MelonEngine.Common/data/levels/map01.tmx": { + "scale": 1, + "selectedLayer": 0, + "viewCenter": { + "x": 480, + "y": -0.5 + } + }, + "D:/Projects/csharp/MelonEngine/MelonEngine.Common/data/sprites/overworld.tsx": { + "scaleInDock": 1 + }, "D:/Projects/csharp/TiledCSPlus/TiledCSPlus.Example/assets/tilemap.tmx": { "scale": 7.43375, "selectedLayer": 0, "viewCenter": { - "x": 84.54683033462251, - "y": 79.50227005212713 + "x": 79.90583487472676, + "y": 79.83857407096014 } }, "D:/Projects/csharp/TiledCSPlus/TiledCSPlus.Example/assets/tilemap.tmx#tileset-embedded": { @@ -30,8 +41,8 @@ "scale": 4, "selectedLayer": 0, "viewCenter": { - "x": 79.875, - "y": 80 + "x": 225.375, + "y": 134 } }, "tilemap.tmx#tileset": { @@ -40,6 +51,21 @@ "tilemap.tmx#tileset-embedded": { "scaleInDock": 1 }, + "tilemap1.9.tmx": { + "scale": 3.645625, + "selectedLayer": 1, + "viewCenter": { + "x": 80.09600548602779, + "y": 80.23315618035316 + } + }, + "tilemap1.9.tmx#tileset-embedded": { + "scaleInDock": 1 + }, + "tilemap1.9.tmx#tileset-embedded19": { + "scaleInDock": 1, + "scaleInEditor": 1 + }, "tileset.tsx": { "scaleInDock": 1, "scaleInEditor": 1 @@ -53,15 +79,17 @@ "map.tileWidth": 16, "map.width": 10, "openFiles": [ - "tilemap.tmx", - "D:/Projects/csharp/TiledCSPlus/TiledCSPlus.Example/assets/tilemap.tmx", + "D:/Projects/csharp/MelonEngine/MelonEngine.Common/data/levels/map01.tmx", + "tilemap1.9.tmx", "tileset.tsx" ], "project": "TiledCSPlus_test.tiled-project", "recentFiles": [ - "tilemap.tmx", + "D:/Projects/csharp/MelonEngine/MelonEngine.Common/data/levels/map01.tmx", + "tilemap1.9.tmx", "tileset.tsx", - "D:/Projects/csharp/TiledCSPlus/TiledCSPlus.Example/assets/tilemap.tmx" + "D:/Projects/csharp/TiledCSPlus/TiledCSPlus.Example/assets/tilemap.tmx", + "tilemap.tmx" ], "tileset.embedInMap": true, "tileset.lastUsedFilter": "Tiled tileset files (*.tsx *.xml)", diff --git a/TiledCSPlus.Test/assets/tilemap1.10.tmx b/TiledCSPlus.Test/assets/tilemap1.10.tmx new file mode 100644 index 0000000..092f4f7 --- /dev/null +++ b/TiledCSPlus.Test/assets/tilemap1.10.tmx @@ -0,0 +1,17 @@ + + + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAABAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAQAAAAEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + + + + + + + + diff --git a/TiledCSPlus.Test/assets/tilemap.tmx b/TiledCSPlus.Test/assets/tilemap1.9.tmx similarity index 73% rename from TiledCSPlus.Test/assets/tilemap.tmx rename to TiledCSPlus.Test/assets/tilemap1.9.tmx index 9a5aa44..421f487 100644 --- a/TiledCSPlus.Test/assets/tilemap.tmx +++ b/TiledCSPlus.Test/assets/tilemap1.9.tmx @@ -1,15 +1,17 @@ - - - - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAABAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAQAAAAEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - - - - - - + + + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAABAAAAAQAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAQAAAAEAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + + + + + + + + diff --git a/TiledCSPlus.Test/assets/tileset.tsx b/TiledCSPlus.Test/assets/tileset.tsx index 9fc1b9b..2d3b690 100644 --- a/TiledCSPlus.Test/assets/tileset.tsx +++ b/TiledCSPlus.Test/assets/tileset.tsx @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/TiledCSPlus/Enums.cs b/TiledCSPlus/Enums.cs index 61446e4..3cea2c6 100644 --- a/TiledCSPlus/Enums.cs +++ b/TiledCSPlus/Enums.cs @@ -1,64 +1,64 @@ namespace TiledCSPlus { - /// - /// Represents the layer type - /// - public enum TiledLayerType - { /// - /// Indicates that the layer is an object layer + /// Represents the layer type /// - ObjectLayer, + public enum TiledLayerType + { + /// + /// Indicates that the layer is an object layer + /// + ObjectLayer, - /// - /// Indicates that the layer is a tile layer - /// - TileLayer, + /// + /// Indicates that the layer is a tile layer + /// + TileLayer, - /// - /// Indicates that the layer is an image layer - /// - ImageLayer - } + /// + /// Indicates that the layer is an image layer + /// + ImageLayer + } - /// - /// Represents property's value data type - /// - public enum TiledPropertyType - { /// - /// A string value + /// Represents property's value data type /// - String, + public enum TiledPropertyType + { + /// + /// A string value + /// + String, - /// - /// A bool value - /// - Bool, + /// + /// A bool value + /// + Bool, - /// - /// A color value in hex format - /// - Color, + /// + /// A color value in hex format + /// + Color, - /// - /// A file path as string - /// - File, + /// + /// A file path as string + /// + File, - /// - /// A float value - /// - Float, + /// + /// A float value + /// + Float, - /// - /// An int value - /// - Int, + /// + /// An int value + /// + Int, - /// - /// An object value which is the id of an object in the map - /// - Object - } + /// + /// An object value which is the id of an object in the map + /// + Object + } } \ No newline at end of file diff --git a/TiledCSPlus/Exceptions.cs b/TiledCSPlus/Exceptions.cs index 79c6649..f325070 100644 --- a/TiledCSPlus/Exceptions.cs +++ b/TiledCSPlus/Exceptions.cs @@ -2,27 +2,26 @@ namespace TiledCSPlus { - /// - /// Represents an exception only thrown by TiledCS - /// - public class TiledException : Exception - { /// - /// Returns an instance of TiledException + /// Represents an exception only thrown by TiledCS /// - /// The exception message - public TiledException(string message) : base(message) + public class TiledException : Exception { + /// + /// Returns an instance of TiledException + /// + /// The exception message + public TiledException(string message) : base(message) + { + } + /// + /// Returns an instance of TiledException + /// + /// The exception message + /// The inner exception + public TiledException(string message, Exception inner) : base(message, inner) + { + } } - /// - /// Returns an instance of TiledException - /// - /// The exception message - /// The inner exception - public TiledException(string message, Exception inner) : base(message, inner) - { - - } - } } \ No newline at end of file diff --git a/TiledCSPlus/Extensions.cs b/TiledCSPlus/Extensions.cs index 9dcc0e3..91a9718 100644 --- a/TiledCSPlus/Extensions.cs +++ b/TiledCSPlus/Extensions.cs @@ -2,28 +2,29 @@ namespace TiledCSPlus { - /// - /// Defines all extension methods used within TiledCS - /// - public static class Extensions - { /// - /// Converts a comma separated string to an int array + /// Defines all extension methods used within TiledCS /// - /// The comma separated string source - /// The parsed int array - public static int[] AsIntArray(this string src) + public static class Extensions { - return src.Select(x => int.Parse(x.ToString().Length == 0 ? "-1" : x.ToString())).ToArray(); - } - /// - /// Converts a string array whose values are actually all numbers to an int array - /// - /// The string array - /// The parsed int array - public static int[] AsIntArray(this string[] src) - { - return src.Select(x => int.Parse(x.Length == 0 ? "-1" : x)).ToArray(); + /// + /// Converts a comma separated string to an int array + /// + /// The comma separated string source + /// The parsed int array + public static int[] AsIntArray(this string src) + { + return src.Select(x => int.Parse(x.ToString().Length == 0 ? "-1" : x.ToString())).ToArray(); + } + + /// + /// Converts a string array whose values are actually all numbers to an int array + /// + /// The string array + /// The parsed int array + public static int[] AsIntArray(this string[] src) + { + return src.Select(x => int.Parse(x.Length == 0 ? "-1" : x)).ToArray(); + } } - } } \ No newline at end of file diff --git a/TiledCSPlus/TiledMap.cs b/TiledCSPlus/TiledMap.cs index dcf5008..9702565 100644 --- a/TiledCSPlus/TiledMap.cs +++ b/TiledCSPlus/TiledMap.cs @@ -8,909 +8,933 @@ namespace TiledCSPlus { - /// - /// Represents a Tiled map - /// - public class TiledMap - { - const uint FLIPPED_HORIZONTALLY_FLAG = 0b10000000000000000000000000000000; - const uint FLIPPED_VERTICALLY_FLAG = 0b01000000000000000000000000000000; - const uint FLIPPED_DIAGONALLY_FLAG = 0b00100000000000000000000000000000; - - /// - /// How many times we shift the FLIPPED flags to the right in order to store it in a byte. - /// For example: 0b10100000000000000000000000000000 >> SHIFT_FLIP_FLAG_TO_BYTE = 0b00000101 - /// - const int SHIFT_FLIP_FLAG_TO_BYTE = 29; - - /// - /// Returns the Tiled version used to create this map - /// - public string TiledVersion { get; internal set; } - /// - /// Returns an array of properties defined in the map + /// Represents a Tiled map /// - public TiledProperty[] Properties { get; internal set; } - - /// - /// Returns an array of tileset definitions in the map - /// - public TiledMapTileset[] Tilesets { get; internal set; } - - /// - /// Returns an array of layers or null if none were defined - /// - public TiledLayer[] Layers { get; internal set; } - - /// - /// Returns an array of groups or null if none were defined - /// - public TiledGroup[] Groups { get; internal set; } - - /// - /// Returns the defined map orientation as a string - /// - public string Orientation { get; internal set; } - - /// - /// Returns the render order as a string - /// - public string RenderOrder { get; internal set; } - - /// - /// The amount of horizontal tiles - /// - public int Width { get; internal set; } - - /// - /// The amount of vertical tiles - /// - public int Height { get; internal set; } - - /// - /// The tile width in pixels - /// - public int TileWidth { get; internal set; } - - /// - /// The tile height in pixels - /// - public int TileHeight { get; internal set; } - - /// - /// The parallax origin x - /// - public float ParallaxOriginX { get; internal set; } - - /// - /// The parallax origin y - /// - public float ParallaxOriginY { get; internal set; } - - /// - /// Returns true if the map is configured as infinite - /// - public bool Infinite { get; internal set; } - - /// - /// Returns the defined map background color as a TiledColor - /// - public Color BackgroundColor { get; internal set; } - - /// - /// Returns tilesets embedded in the map - /// - public Dictionary EmbeddedTilesets { get; internal set; } - - /// - /// Returns an empty instance of TiledMap - /// - public TiledMap() + public class TiledMap { - } - - /// - /// Loads a Tiled map in TMX format and parses it - /// - /// The path to the tmx file - /// Thrown when the map could not be loaded or is not in a correct format - public TiledMap(string path) - { - // Check the file - if (!File.Exists(path)) - { - throw new TiledException($"{path} not found"); - } - - var content = File.ReadAllText(path); - - if (path.EndsWith(".tmx")) - { - ParseXml(content); - } - else - { - throw new TiledException("Unsupported file format"); - } - } - - /// - /// Loads a Tiled map in TMX format and parses it - /// - /// Stream of opened tmx file - /// Thrown when the map could not be loaded - public TiledMap(Stream stream) - { - var streamReader = new StreamReader(stream); - var content = streamReader.ReadToEnd(); - ParseXml(content); - } - - /// - /// Can be used to parse the content of a TMX map manually instead of loading it using the constructor - /// - /// The tmx file content as string - /// - public void ParseXml(string xml) - { - try - { - // Load the xml document - var document = new XmlDocument(); - document.LoadXml(xml); - - var nodeMap = document.SelectSingleNode("map"); - var nodesProperty = nodeMap.SelectNodes("properties/property"); - var nodesLayer = nodeMap.SelectNodes("layer | objectgroup | imagelayer"); - var nodesTileset = nodeMap.SelectNodes("tileset"); - var nodesGroup = nodeMap.SelectNodes("group"); - var attrParallaxOriginX = nodeMap.Attributes["parallaxoriginx"]; - var attrParallaxOriginY = nodeMap.Attributes["parallaxoriginy"]; - var attrBackgroundColor = nodeMap.Attributes["backgroundcolor"]; - - TiledVersion = nodeMap.Attributes["tiledversion"].Value; - Orientation = nodeMap.Attributes["orientation"].Value; - RenderOrder = nodeMap.Attributes["renderorder"].Value; - Infinite = nodeMap.Attributes["infinite"].Value == "1"; - EmbeddedTilesets = new Dictionary(); - - Width = int.Parse(nodeMap.Attributes["width"].Value); - Height = int.Parse(nodeMap.Attributes["height"].Value); - TileWidth = int.Parse(nodeMap.Attributes["tilewidth"].Value); - TileHeight = int.Parse(nodeMap.Attributes["tileheight"].Value); - - - if (nodesProperty != null) Properties = ParseProperties(nodesProperty); - if (nodesTileset != null) Tilesets = ParseTilesets(nodesTileset); - if (nodesLayer != null) Layers = ParseLayers(nodesLayer); - if (nodesGroup != null) Groups = ParseGroups(nodesGroup); - if (attrParallaxOriginX != null) ParallaxOriginX = float.Parse(attrParallaxOriginX.Value, CultureInfo.InvariantCulture); - if (attrParallaxOriginY != null) ParallaxOriginY = float.Parse(attrParallaxOriginY.Value, CultureInfo.InvariantCulture); - if (attrBackgroundColor != null) BackgroundColor = ParseColor(attrBackgroundColor.Value); - } - catch (Exception ex) - { - throw new TiledException("An error occurred while trying to parse the Tiled map file", ex); - } - } - - private TiledProperty[] ParseProperties(XmlNodeList nodeList) - { - var result = new List(); - - foreach (XmlNode node in nodeList) - { - var attrType = node.Attributes["type"]; - - var property = new TiledProperty + const uint FLIPPED_HORIZONTALLY_FLAG = 0b10000000000000000000000000000000; + const uint FLIPPED_VERTICALLY_FLAG = 0b01000000000000000000000000000000; + const uint FLIPPED_DIAGONALLY_FLAG = 0b00100000000000000000000000000000; + + /// + /// How many times we shift the FLIPPED flags to the right in order to store it in a byte. + /// For example: 0b10100000000000000000000000000000 >> SHIFT_FLIP_FLAG_TO_BYTE = 0b00000101 + /// + const int SHIFT_FLIP_FLAG_TO_BYTE = 29; + + /// + /// Returns the Tiled version used to create this map + /// + public string TiledVersion { get; internal set; } + + /// + /// Returns map compability mode this map is saved in + /// + public string MapVersion { get; internal set; } + + /// + /// Returns an array of properties defined in the map + /// + public TiledProperty[] Properties { get; internal set; } + + /// + /// Returns an array of tileset definitions in the map + /// + public TiledMapTileset[] Tilesets { get; internal set; } + + /// + /// Returns an array of layers or null if none were defined + /// + public TiledLayer[] Layers { get; internal set; } + + /// + /// Returns an array of groups or null if none were defined + /// + public TiledGroup[] Groups { get; internal set; } + + /// + /// Returns the defined map orientation as a string + /// + public string Orientation { get; internal set; } + + /// + /// Returns the render order as a string + /// + public string RenderOrder { get; internal set; } + + /// + /// The amount of horizontal tiles + /// + public int Width { get; internal set; } + + /// + /// The amount of vertical tiles + /// + public int Height { get; internal set; } + + /// + /// The tile width in pixels + /// + public int TileWidth { get; internal set; } + + /// + /// The tile height in pixels + /// + public int TileHeight { get; internal set; } + + /// + /// The parallax origin x + /// + public float ParallaxOriginX { get; internal set; } + + /// + /// The parallax origin y + /// + public float ParallaxOriginY { get; internal set; } + + /// + /// Returns true if the map is configured as infinite + /// + public bool Infinite { get; internal set; } + + /// + /// Returns the defined map background color as a TiledColor + /// + public Color BackgroundColor { get; internal set; } + + /// + /// Returns tilesets embedded in the map + /// + public Dictionary EmbeddedTilesets { get; internal set; } + + /// + /// Returns an empty instance of TiledMap + /// + public TiledMap() { - Name = node.Attributes["name"].Value, - Value = node.Attributes["value"]?.Value, - Type = TiledPropertyType.String - }; + } - if (attrType != null) + /// + /// Loads a Tiled map in TMX format and parses it + /// + /// The path to the tmx file + /// Thrown when the map could not be loaded or is not in a correct format + public TiledMap(string path) { - if (attrType.Value == "bool") property.Type = TiledPropertyType.Bool; - if (attrType.Value == "color") property.Type = TiledPropertyType.Color; - if (attrType.Value == "file") property.Type = TiledPropertyType.File; - if (attrType.Value == "float") property.Type = TiledPropertyType.Float; - if (attrType.Value == "int") property.Type = TiledPropertyType.Int; - if (attrType.Value == "object") property.Type = TiledPropertyType.Object; + // Check the file + if(!File.Exists(path)) + { + throw new TiledException($"{path} not found"); + } + + var content = File.ReadAllText(path); + + if(path.EndsWith(".tmx")) + { + ParseXml(content); + } + else + { + throw new TiledException("Unsupported file format"); + } } - property.Value ??= node.InnerText; - - result.Add(property); - } - - return result.ToArray(); - } - - private TiledMapTileset[] ParseTilesets(XmlNodeList nodeList) - { - var result = new List(); - - foreach (XmlNode node in nodeList) - { - if (node.Attributes["source"] == null) + /// + /// Loads a Tiled map in TMX format and parses it + /// + /// Stream of opened tmx file + /// Thrown when the map could not be loaded + public TiledMap(Stream stream) { - //tilemap is an embedded tilemap - TiledTileset tileset = new(); - tileset.ParseXml(node.OuterXml); - int firstgid = int.Parse(node.Attributes["firstgid"].Value); - EmbeddedTilesets.Add(firstgid, tileset); - var maptileset = new TiledMapTileset - { - FirstGid = int.Parse(node.Attributes["firstgid"].Value), - IsTilesetEmbedded = true - }; - result.Add(maptileset); + var streamReader = new StreamReader(stream); + var content = streamReader.ReadToEnd(); + ParseXml(content); } - else + + /// + /// Can be used to parse the content of a TMX map manually instead of loading it using the constructor + /// + /// The tmx file content as string + /// + public void ParseXml(string xml) { - var tileset = new TiledMapTileset - { - FirstGid = int.Parse(node.Attributes["firstgid"].Value), - Source = node.Attributes["source"]?.Value, - IsTilesetEmbedded = false - }; - result.Add(tileset); + try + { + // Load the xml document + var document = new XmlDocument(); + document.LoadXml(xml); + + var nodeMap = document.SelectSingleNode("map"); + var nodesProperty = nodeMap.SelectNodes("properties/property"); + var nodesLayer = nodeMap.SelectNodes("layer | objectgroup | imagelayer"); + var nodesTileset = nodeMap.SelectNodes("tileset"); + var nodesGroup = nodeMap.SelectNodes("group"); + var attrParallaxOriginX = nodeMap.Attributes["parallaxoriginx"]; + var attrParallaxOriginY = nodeMap.Attributes["parallaxoriginy"]; + var attrBackgroundColor = nodeMap.Attributes["backgroundcolor"]; + + TiledVersion = nodeMap.Attributes["tiledversion"].Value; + MapVersion = nodeMap.Attributes["version"].Value; + Orientation = nodeMap.Attributes["orientation"].Value; + RenderOrder = nodeMap.Attributes["renderorder"].Value; + Infinite = nodeMap.Attributes["infinite"].Value == "1"; + EmbeddedTilesets = new Dictionary(); + + Width = int.Parse(nodeMap.Attributes["width"].Value); + Height = int.Parse(nodeMap.Attributes["height"].Value); + TileWidth = int.Parse(nodeMap.Attributes["tilewidth"].Value); + TileHeight = int.Parse(nodeMap.Attributes["tileheight"].Value); + + + if(nodesProperty != null) Properties = ParseProperties(nodesProperty); + if(nodesTileset != null) Tilesets = ParseTilesets(nodesTileset); + if(nodesLayer != null) Layers = ParseLayers(nodesLayer); + if(nodesGroup != null) Groups = ParseGroups(nodesGroup); + if(attrParallaxOriginX != null) + ParallaxOriginX = float.Parse(attrParallaxOriginX.Value, CultureInfo.InvariantCulture); + if(attrParallaxOriginY != null) + ParallaxOriginY = float.Parse(attrParallaxOriginY.Value, CultureInfo.InvariantCulture); + if(attrBackgroundColor != null) BackgroundColor = ParseColor(attrBackgroundColor.Value); + } + catch(Exception ex) + { + throw new TiledException("An error occurred while trying to parse the Tiled map file", ex); + } } - } - - return result.ToArray(); - } - private TiledGroup[] ParseGroups(XmlNodeList nodeListGroups) - { - var result = new List(); - - foreach (XmlNode node in nodeListGroups) - { - var nodesProperty = node.SelectNodes("properties/property"); - var nodesGroup = node.SelectNodes("group"); - var nodesLayer = node.SelectNodes("layer | objectgroup | imagelayer"); - var attrVisible = node.Attributes["visible"]; - var attrLocked = node.Attributes["locked"]; - - var tiledGroup = new TiledGroup + private TiledProperty[] ParseProperties(XmlNodeList nodeList) { - Id = int.Parse(node.Attributes["id"].Value), - Name = node.Attributes["name"].Value - }; - - if (attrVisible != null) tiledGroup.Visible = attrVisible.Value == "1"; - if (attrLocked != null) tiledGroup.Locked = attrLocked.Value == "1"; - if (nodesProperty != null) tiledGroup.Properties = ParseProperties(nodesProperty); - if (nodesGroup != null) tiledGroup.Groups = ParseGroups(nodesGroup); - if (nodesLayer != null) tiledGroup.Layers = ParseLayers(nodesLayer); - - result.Add(tiledGroup); - } - - return result.ToArray(); - } + var result = new List(); + + foreach(XmlNode node in nodeList) + { + var attrType = node.Attributes["type"]; + + var property = new TiledProperty + { + Name = node.Attributes["name"].Value, + Value = node.Attributes["value"]?.Value, + Type = TiledPropertyType.String + }; + + if(attrType != null) + { + if(attrType.Value == "bool") property.Type = TiledPropertyType.Bool; + if(attrType.Value == "color") property.Type = TiledPropertyType.Color; + if(attrType.Value == "file") property.Type = TiledPropertyType.File; + if(attrType.Value == "float") property.Type = TiledPropertyType.Float; + if(attrType.Value == "int") property.Type = TiledPropertyType.Int; + if(attrType.Value == "object") property.Type = TiledPropertyType.Object; + } + + property.Value ??= node.InnerText; + + result.Add(property); + } + + return result.ToArray(); + } - private TiledLayer[] ParseLayers(XmlNodeList nodesLayer) - { - var result = new List(); - foreach (XmlNode node in nodesLayer) - { - switch (node.Name) + private TiledMapTileset[] ParseTilesets(XmlNodeList nodeList) { - case "layer": - result.Add(ParseLayer(node, TiledLayerType.TileLayer)); - break; - case "objectgroup": - result.Add(ParseLayer(node, TiledLayerType.ObjectLayer)); - break; - case "imagelayer": - result.Add(ParseLayer(node, TiledLayerType.ImageLayer)); - break; - default: - throw new TiledException($"Unknown layer type: {node.Name}"); + var result = new List(); + + foreach(XmlNode node in nodeList) + { + if(node.Attributes["source"] == null) + { + //tilemap is an embedded tilemap + TiledTileset tileset = new(); + tileset.ParseXml(node.OuterXml); + int firstgid = int.Parse(node.Attributes["firstgid"].Value); + EmbeddedTilesets.Add(firstgid, tileset); + var maptileset = new TiledMapTileset + { + FirstGid = int.Parse(node.Attributes["firstgid"].Value), IsTilesetEmbedded = true + }; + result.Add(maptileset); + } + else + { + var tileset = new TiledMapTileset + { + FirstGid = int.Parse(node.Attributes["firstgid"].Value), + Source = node.Attributes["source"]?.Value, + IsTilesetEmbedded = false + }; + result.Add(tileset); + } + } + + return result.ToArray(); } - } - - return result.ToArray(); - } - - private TiledLayer ParseLayer(XmlNode node, TiledLayerType type) - { - var nodesProperty = node.SelectNodes("properties/property"); - var attrVisible = node.Attributes["visible"]; - var attrLocked = node.Attributes["locked"]; - var attrTint = node.Attributes["tintcolor"]; - var attrOffsetX = node.Attributes["offsetx"]; - var attrOffsetY = node.Attributes["offsety"]; - var attrParallaxX = node.Attributes["parallaxx"]; - var attrParallaxY = node.Attributes["parallaxy"]; - var attrOpacity = node.Attributes["opacity"]; - var attrClass = node.Attributes["class"]; - var attrWidth = node.Attributes["width"]; - var attrHeight = node.Attributes["height"]; - - var tiledLayer = new TiledLayer - { - Id = int.Parse(node.Attributes["id"].Value), - Type = type, - Name = node.Attributes["name"].Value, - Visible = true, - Opacity = 1.0f, - Parrallax = new Vector2(1.0f, 1.0f) - }; - - if (attrWidth != null) tiledLayer.Width = int.Parse(attrWidth.Value); - if (attrHeight != null) tiledLayer.Height = int.Parse(attrHeight.Value); - if (attrVisible != null) tiledLayer.Visible = attrVisible.Value == "1"; - if (attrLocked != null) tiledLayer.Locked = attrLocked.Value == "1"; - if (attrTint != null) tiledLayer.TintColor = ParseColor(attrTint.Value); - if (attrClass != null) tiledLayer.Class = attrClass.Value; - if (attrOpacity != null) tiledLayer.Opacity = float.Parse(attrOpacity.Value, CultureInfo.InvariantCulture); - if (attrOffsetX != null || attrOffsetY != null) tiledLayer.Offset = new Vector2(0, 0); - if (attrOffsetX != null) tiledLayer.Offset.X = float.Parse(attrOffsetX.Value, CultureInfo.InvariantCulture); - if (attrOffsetY != null) tiledLayer.Offset.Y = float.Parse(attrOffsetY.Value, CultureInfo.InvariantCulture); - if (attrParallaxX != null) tiledLayer.Parrallax.X = float.Parse(attrParallaxX.Value, CultureInfo.InvariantCulture); - if (attrParallaxY != null) tiledLayer.Parrallax.Y = float.Parse(attrParallaxY.Value, CultureInfo.InvariantCulture); - if (nodesProperty != null) tiledLayer.Properties = ParseProperties(nodesProperty); - - if (type == TiledLayerType.TileLayer) - { - var nodeData = node.SelectSingleNode("data"); - - ParseTileLayerData(nodeData, ref tiledLayer); - } - - if (type == TiledLayerType.ObjectLayer) - { - var nodesObject = node.SelectNodes("object"); - - tiledLayer.Objects = ParseObjects(nodesObject); - } - - if (type == TiledLayerType.ImageLayer) - { - var nodeImage = node.SelectSingleNode("image"); - - if (nodeImage != null) tiledLayer.Image = ParseImage(nodeImage); - } - - return tiledLayer; - } - - private void ParseTileLayerData(XmlNode nodeData, ref TiledLayer tiledLayer) - { - var encoding = nodeData.Attributes["encoding"].Value; - var compression = nodeData.Attributes["compression"]?.Value; - - if (encoding != "csv" && encoding != "base64") - { - throw new TiledException("Only CSV and Base64 encodings are currently supported"); - } - if (Infinite) - { - var nodesChunk = nodeData.SelectNodes("chunk"); - var chunks = new List(); - - foreach (XmlNode nodeChunk in nodesChunk) + private TiledGroup[] ParseGroups(XmlNodeList nodeListGroups) { - var chunk = new TiledChunk - { - X = int.Parse(nodeChunk.Attributes["x"].Value), - Y = int.Parse(nodeChunk.Attributes["y"].Value), - Width = int.Parse(nodeChunk.Attributes["width"].Value), - Height = int.Parse(nodeChunk.Attributes["height"].Value) - }; - - int[] chunkData = new int[] { }; - byte[] chunkDataRotationFlags = new byte[] { }; - if (encoding == "csv") ParseTileLayerDataAsCSV(nodeChunk.InnerText, out chunkData, out chunkDataRotationFlags); - if (encoding == "base64") ParseTileLayerDataAsBase64(nodeChunk.InnerText, compression, out chunkData, out chunkDataRotationFlags); - chunk.Data = chunkData; - chunk.DataRotationFlags = chunkDataRotationFlags; - - chunks.Add(chunk); + var result = new List(); + + foreach(XmlNode node in nodeListGroups) + { + var nodesProperty = node.SelectNodes("properties/property"); + var nodesGroup = node.SelectNodes("group"); + var nodesLayer = node.SelectNodes("layer | objectgroup | imagelayer"); + var attrVisible = node.Attributes["visible"]; + var attrLocked = node.Attributes["locked"]; + + var tiledGroup = new TiledGroup + { + Id = int.Parse(node.Attributes["id"].Value), Name = node.Attributes["name"].Value + }; + + if(attrVisible != null) tiledGroup.Visible = attrVisible.Value == "1"; + if(attrLocked != null) tiledGroup.Locked = attrLocked.Value == "1"; + if(nodesProperty != null) tiledGroup.Properties = ParseProperties(nodesProperty); + if(nodesGroup != null) tiledGroup.Groups = ParseGroups(nodesGroup); + if(nodesLayer != null) tiledGroup.Layers = ParseLayers(nodesLayer); + + result.Add(tiledGroup); + } + + return result.ToArray(); } - tiledLayer.Chunks = chunks.ToArray(); - } - else - { - int[] chunkData = new int[] { }; - byte[] chunkDataRotationFlags = new byte[] { }; - if (encoding == "csv") ParseTileLayerDataAsCSV(nodeData.InnerText, out chunkData, out chunkDataRotationFlags); - if (encoding == "base64") ParseTileLayerDataAsBase64(nodeData.InnerText, compression, out chunkData, out chunkDataRotationFlags); - tiledLayer.Data = chunkData; - tiledLayer.DataRotationFlags = chunkDataRotationFlags; - } - } - - private void ParseTileLayerDataAsBase64(string input, string compression, out int[] data, out byte[] dataRotationFlags) - { - using var base64DataStream = new MemoryStream(Convert.FromBase64String(input)); - if (compression == null) - { - // Parse the decoded bytes and update the inner data as well as the data rotation flags - var rawBytes = new byte[4]; - data = new int[base64DataStream.Length]; - dataRotationFlags = new byte[base64DataStream.Length]; - - for (var i = 0; i < base64DataStream.Length; i++) + private TiledLayer[] ParseLayers(XmlNodeList nodesLayer) { - base64DataStream.Read(rawBytes, 0, rawBytes.Length); - var rawID = BitConverter.ToUInt32(rawBytes, 0); - var hor = ((rawID & FLIPPED_HORIZONTALLY_FLAG)); - var ver = ((rawID & FLIPPED_VERTICALLY_FLAG)); - var dia = ((rawID & FLIPPED_DIAGONALLY_FLAG)); - dataRotationFlags[i] = (byte)((hor | ver | dia) >> SHIFT_FLIP_FLAG_TO_BYTE); - - // assign data to rawID with the rotation flags cleared - data[i] = (int)(rawID & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)); + var result = new List(); + foreach(XmlNode node in nodesLayer) + { + switch(node.Name) + { + case "layer": + result.Add(ParseLayer(node, TiledLayerType.TileLayer)); + break; + case "objectgroup": + result.Add(ParseLayer(node, TiledLayerType.ObjectLayer)); + break; + case "imagelayer": + result.Add(ParseLayer(node, TiledLayerType.ImageLayer)); + break; + default: + throw new TiledException($"Unknown layer type: {node.Name}"); + } + } + + return result.ToArray(); } - } - else if (compression == "zlib") - { - // .NET doesn't play well with the headered zlib data that Tiled produces, - // so we have to manually skip the 2-byte header to get what DeflateStream's looking for - // Should an external library be used instead of this hack? - base64DataStream.ReadByte(); - base64DataStream.ReadByte(); - - using var decompressionStream = new DeflateStream(base64DataStream, CompressionMode.Decompress); - // Parse the raw decompressed bytes and update the inner data as well as the data rotation flags - var decompressedDataBuffer = new byte[4]; // size of each tile - var dataRotationFlagsList = new List(); - var layerDataList = new List(); - - while (decompressionStream.Read(decompressedDataBuffer, 0, decompressedDataBuffer.Length) == decompressedDataBuffer.Length) + + private TiledLayer ParseLayer(XmlNode node, TiledLayerType type) { - var rawID = BitConverter.ToUInt32(decompressedDataBuffer, 0); - var hor = ((rawID & FLIPPED_HORIZONTALLY_FLAG)); - var ver = ((rawID & FLIPPED_VERTICALLY_FLAG)); - var dia = ((rawID & FLIPPED_DIAGONALLY_FLAG)); - dataRotationFlagsList.Add((byte)((hor | ver | dia) >> SHIFT_FLIP_FLAG_TO_BYTE)); - - // assign data to rawID with the rotation flags cleared - layerDataList.Add((int)(rawID & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG))); + var nodesProperty = node.SelectNodes("properties/property"); + var attrVisible = node.Attributes["visible"]; + var attrLocked = node.Attributes["locked"]; + var attrTint = node.Attributes["tintcolor"]; + var attrOffsetX = node.Attributes["offsetx"]; + var attrOffsetY = node.Attributes["offsety"]; + var attrParallaxX = node.Attributes["parallaxx"]; + var attrParallaxY = node.Attributes["parallaxy"]; + var attrOpacity = node.Attributes["opacity"]; + var attrClass = node.Attributes["class"]; + var attrWidth = node.Attributes["width"]; + var attrHeight = node.Attributes["height"]; + + var tiledLayer = new TiledLayer + { + Id = int.Parse(node.Attributes["id"].Value), + Type = type, + Name = node.Attributes["name"].Value, + Visible = true, + Opacity = 1.0f, + Parrallax = new Vector2(1.0f, 1.0f) + }; + + if(attrWidth != null) tiledLayer.Width = int.Parse(attrWidth.Value); + if(attrHeight != null) tiledLayer.Height = int.Parse(attrHeight.Value); + if(attrVisible != null) tiledLayer.Visible = attrVisible.Value == "1"; + if(attrLocked != null) tiledLayer.Locked = attrLocked.Value == "1"; + if(attrTint != null) tiledLayer.TintColor = ParseColor(attrTint.Value); + if(attrClass != null) tiledLayer.Class = attrClass.Value; + if(attrOpacity != null) tiledLayer.Opacity = float.Parse(attrOpacity.Value, CultureInfo.InvariantCulture); + if(attrOffsetX != null || attrOffsetY != null) tiledLayer.Offset = new Vector2(0, 0); + if(attrOffsetX != null) tiledLayer.Offset.X = float.Parse(attrOffsetX.Value, CultureInfo.InvariantCulture); + if(attrOffsetY != null) tiledLayer.Offset.Y = float.Parse(attrOffsetY.Value, CultureInfo.InvariantCulture); + if(attrParallaxX != null) + tiledLayer.Parrallax.X = float.Parse(attrParallaxX.Value, CultureInfo.InvariantCulture); + if(attrParallaxY != null) + tiledLayer.Parrallax.Y = float.Parse(attrParallaxY.Value, CultureInfo.InvariantCulture); + if(nodesProperty != null) tiledLayer.Properties = ParseProperties(nodesProperty); + + if(type == TiledLayerType.TileLayer) + { + var nodeData = node.SelectSingleNode("data"); + + ParseTileLayerData(nodeData, ref tiledLayer); + } + + if(type == TiledLayerType.ObjectLayer) + { + var nodesObject = node.SelectNodes("object"); + + tiledLayer.Objects = ParseObjects(nodesObject); + } + + if(type == TiledLayerType.ImageLayer) + { + var nodeImage = node.SelectSingleNode("image"); + + if(nodeImage != null) tiledLayer.Image = ParseImage(nodeImage); + } + + return tiledLayer; } - data = layerDataList.ToArray(); - dataRotationFlags = dataRotationFlagsList.ToArray(); - } - else if (compression == "gzip") - { - using var decompressionStream = new GZipStream(base64DataStream, CompressionMode.Decompress); - // Parse the raw decompressed bytes and update the inner data as well as the data rotation flags - var decompressedDataBuffer = new byte[4]; // size of each tile - var dataRotationFlagsList = new List(); - var layerDataList = new List(); - - while (decompressionStream.Read(decompressedDataBuffer, 0, decompressedDataBuffer.Length) == decompressedDataBuffer.Length) + private void ParseTileLayerData(XmlNode nodeData, ref TiledLayer tiledLayer) { - var rawID = BitConverter.ToUInt32(decompressedDataBuffer, 0); - var hor = ((rawID & FLIPPED_HORIZONTALLY_FLAG)); - var ver = ((rawID & FLIPPED_VERTICALLY_FLAG)); - var dia = ((rawID & FLIPPED_DIAGONALLY_FLAG)); - - dataRotationFlagsList.Add((byte)((hor | ver | dia) >> SHIFT_FLIP_FLAG_TO_BYTE)); - - // assign data to rawID with the rotation flags cleared - layerDataList.Add((int)(rawID & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG))); + var encoding = nodeData.Attributes["encoding"].Value; + var compression = nodeData.Attributes["compression"]?.Value; + + if(encoding != "csv" && encoding != "base64") + { + throw new TiledException("Only CSV and Base64 encodings are currently supported"); + } + + if(Infinite) + { + var nodesChunk = nodeData.SelectNodes("chunk"); + var chunks = new List(); + + foreach(XmlNode nodeChunk in nodesChunk) + { + var chunk = new TiledChunk + { + X = int.Parse(nodeChunk.Attributes["x"].Value), + Y = int.Parse(nodeChunk.Attributes["y"].Value), + Width = int.Parse(nodeChunk.Attributes["width"].Value), + Height = int.Parse(nodeChunk.Attributes["height"].Value) + }; + + int[] chunkData = new int[] { }; + byte[] chunkDataRotationFlags = new byte[] { }; + if(encoding == "csv") + ParseTileLayerDataAsCSV(nodeChunk.InnerText, out chunkData, out chunkDataRotationFlags); + if(encoding == "base64") + ParseTileLayerDataAsBase64(nodeChunk.InnerText, compression, out chunkData, + out chunkDataRotationFlags); + chunk.Data = chunkData; + chunk.DataRotationFlags = chunkDataRotationFlags; + + chunks.Add(chunk); + } + + tiledLayer.Chunks = chunks.ToArray(); + } + else + { + int[] chunkData = new int[] { }; + byte[] chunkDataRotationFlags = new byte[] { }; + if(encoding == "csv") + ParseTileLayerDataAsCSV(nodeData.InnerText, out chunkData, out chunkDataRotationFlags); + if(encoding == "base64") + ParseTileLayerDataAsBase64(nodeData.InnerText, compression, out chunkData, + out chunkDataRotationFlags); + tiledLayer.Data = chunkData; + tiledLayer.DataRotationFlags = chunkDataRotationFlags; + } } - data = layerDataList.ToArray(); - dataRotationFlags = dataRotationFlagsList.ToArray(); - } - else - { - throw new TiledException("Zstandard compression is currently not supported"); - } - } - - private void ParseTileLayerDataAsCSV(string input, out int[] data, out byte[] dataRotationFlags) - { - var csvs = input.Split(','); - - data = new int[csvs.Length]; - dataRotationFlags = new byte[csvs.Length]; - - // Parse the comma separated csv string and update the inner data as well as the data rotation flags - for (var i = 0; i < csvs.Length; i++) - { - var rawID = uint.Parse(csvs[i]); - var hor = ((rawID & FLIPPED_HORIZONTALLY_FLAG)); - var ver = ((rawID & FLIPPED_VERTICALLY_FLAG)); - var dia = ((rawID & FLIPPED_DIAGONALLY_FLAG)); - dataRotationFlags[i] = (byte)((hor | ver | dia) >> SHIFT_FLIP_FLAG_TO_BYTE); - - // assign data to rawID with the rotation flags cleared - data[i] = (int)(rawID & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)); - } - } - - private TiledImage ParseImage(XmlNode node) - { - var tiledImage = new TiledImage - { - Source = node.Attributes["source"].Value, - Width = int.Parse(node.Attributes["width"].Value), - Height = int.Parse(node.Attributes["height"].Value) - }; - - return tiledImage; - } - - private TiledObject[] ParseObjects(XmlNodeList nodeList) - { - var result = new List(); - - foreach (XmlNode node in nodeList) - { - var nodesProperty = node.SelectNodes("properties/property"); - var nodePolygon = node.SelectSingleNode("polygon"); - var nodePoint = node.SelectSingleNode("point"); - var nodeEllipse = node.SelectSingleNode("ellipse"); - var attrGid = node.Attributes["gid"]; - - var obj = new TiledObject - { - Id = int.Parse(node.Attributes["id"].Value), - Name = node.Attributes["name"]?.Value, - Class = node.Attributes["class"]?.Value, - Type = node.Attributes["type"]?.Value, - Position = new Vector2(float.Parse(node.Attributes["x"].Value, CultureInfo.InvariantCulture), - float.Parse(node.Attributes["y"].Value, CultureInfo.InvariantCulture)) - }; - - if (attrGid != null) + private void ParseTileLayerDataAsBase64(string input, string compression, out int[] data, + out byte[] dataRotationFlags) { - ParseObjectGid(ref obj, attrGid.Value); + using var base64DataStream = new MemoryStream(Convert.FromBase64String(input)); + if(compression == null) + { + // Parse the decoded bytes and update the inner data as well as the data rotation flags + var rawBytes = new byte[4]; + data = new int[base64DataStream.Length]; + dataRotationFlags = new byte[base64DataStream.Length]; + + for(var i = 0; i < base64DataStream.Length; i++) + { + base64DataStream.Read(rawBytes, 0, rawBytes.Length); + var rawID = BitConverter.ToUInt32(rawBytes, 0); + var hor = ((rawID & FLIPPED_HORIZONTALLY_FLAG)); + var ver = ((rawID & FLIPPED_VERTICALLY_FLAG)); + var dia = ((rawID & FLIPPED_DIAGONALLY_FLAG)); + dataRotationFlags[i] = (byte)((hor | ver | dia) >> SHIFT_FLIP_FLAG_TO_BYTE); + + // assign data to rawID with the rotation flags cleared + data[i] = (int)(rawID & + ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)); + } + } + else if(compression == "zlib") + { + // .NET doesn't play well with the headered zlib data that Tiled produces, + // so we have to manually skip the 2-byte header to get what DeflateStream's looking for + // Should an external library be used instead of this hack? + base64DataStream.ReadByte(); + base64DataStream.ReadByte(); + + using var decompressionStream = new DeflateStream(base64DataStream, CompressionMode.Decompress); + // Parse the raw decompressed bytes and update the inner data as well as the data rotation flags + var decompressedDataBuffer = new byte[4]; // size of each tile + var dataRotationFlagsList = new List(); + var layerDataList = new List(); + + while(decompressionStream.Read(decompressedDataBuffer, 0, decompressedDataBuffer.Length) == + decompressedDataBuffer.Length) + { + var rawID = BitConverter.ToUInt32(decompressedDataBuffer, 0); + var hor = ((rawID & FLIPPED_HORIZONTALLY_FLAG)); + var ver = ((rawID & FLIPPED_VERTICALLY_FLAG)); + var dia = ((rawID & FLIPPED_DIAGONALLY_FLAG)); + dataRotationFlagsList.Add((byte)((hor | ver | dia) >> SHIFT_FLIP_FLAG_TO_BYTE)); + + // assign data to rawID with the rotation flags cleared + layerDataList.Add((int)(rawID & + ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | + FLIPPED_DIAGONALLY_FLAG))); + } + + data = layerDataList.ToArray(); + dataRotationFlags = dataRotationFlagsList.ToArray(); + } + else if(compression == "gzip") + { + using var decompressionStream = new GZipStream(base64DataStream, CompressionMode.Decompress); + // Parse the raw decompressed bytes and update the inner data as well as the data rotation flags + var decompressedDataBuffer = new byte[4]; // size of each tile + var dataRotationFlagsList = new List(); + var layerDataList = new List(); + + while(decompressionStream.Read(decompressedDataBuffer, 0, decompressedDataBuffer.Length) == + decompressedDataBuffer.Length) + { + var rawID = BitConverter.ToUInt32(decompressedDataBuffer, 0); + var hor = ((rawID & FLIPPED_HORIZONTALLY_FLAG)); + var ver = ((rawID & FLIPPED_VERTICALLY_FLAG)); + var dia = ((rawID & FLIPPED_DIAGONALLY_FLAG)); + + dataRotationFlagsList.Add((byte)((hor | ver | dia) >> SHIFT_FLIP_FLAG_TO_BYTE)); + + // assign data to rawID with the rotation flags cleared + layerDataList.Add((int)(rawID & + ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | + FLIPPED_DIAGONALLY_FLAG))); + } + + data = layerDataList.ToArray(); + dataRotationFlags = dataRotationFlagsList.ToArray(); + } + else + { + throw new TiledException("Zstandard compression is currently not supported"); + } } - if (nodesProperty != null) + private void ParseTileLayerDataAsCSV(string input, out int[] data, out byte[] dataRotationFlags) { - obj.Properties = ParseProperties(nodesProperty); + var csvs = input.Split(','); + + data = new int[csvs.Length]; + dataRotationFlags = new byte[csvs.Length]; + + // Parse the comma separated csv string and update the inner data as well as the data rotation flags + for(var i = 0; i < csvs.Length; i++) + { + var rawID = uint.Parse(csvs[i]); + var hor = ((rawID & FLIPPED_HORIZONTALLY_FLAG)); + var ver = ((rawID & FLIPPED_VERTICALLY_FLAG)); + var dia = ((rawID & FLIPPED_DIAGONALLY_FLAG)); + dataRotationFlags[i] = (byte)((hor | ver | dia) >> SHIFT_FLIP_FLAG_TO_BYTE); + + // assign data to rawID with the rotation flags cleared + data[i] = + (int)(rawID & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)); + } } - if (nodePolygon != null) + private TiledImage ParseImage(XmlNode node) { - var points = nodePolygon.Attributes["points"].Value; - var vertices = points.Split(' '); - - var polygon = new TiledPolygon - { - Points = new Vector2[vertices.Length] - }; - - for (var i = 0; i < vertices.Length; i++) - { - polygon.Points[i] = - new Vector2(float.Parse(vertices[i].Split(',')[0], CultureInfo.InvariantCulture), - float.Parse(vertices[i].Split(',')[1], CultureInfo.InvariantCulture)); - } - - obj.Polygon = polygon; + var tiledImage = new TiledImage + { + Source = node.Attributes["source"].Value, + Width = int.Parse(node.Attributes["width"].Value), + Height = int.Parse(node.Attributes["height"].Value) + }; + + return tiledImage; } - if (nodeEllipse != null) + private TiledObject[] ParseObjects(XmlNodeList nodeList) { - obj.Ellipse = new TiledEllipse(); + var result = new List(); + + foreach(XmlNode node in nodeList) + { + var nodesProperty = node.SelectNodes("properties/property"); + var nodePolygon = node.SelectSingleNode("polygon"); + var nodePoint = node.SelectSingleNode("point"); + var nodeEllipse = node.SelectSingleNode("ellipse"); + var attrGid = node.Attributes["gid"]; + + var obj = new TiledObject + { + Id = int.Parse(node.Attributes["id"].Value), + Name = node.Attributes["name"]?.Value, + Class = MapVersion == "1.9" ? node.Attributes["class"]?.Value : node.Attributes["type"]?.Value, + Position = new Vector2(float.Parse(node.Attributes["x"].Value, CultureInfo.InvariantCulture), + float.Parse(node.Attributes["y"].Value, CultureInfo.InvariantCulture)) + }; + + if(attrGid != null) + { + ParseObjectGid(ref obj, attrGid.Value); + } + + if(nodesProperty != null) + { + obj.Properties = ParseProperties(nodesProperty); + } + + if(nodePolygon != null) + { + var points = nodePolygon.Attributes["points"].Value; + var vertices = points.Split(' '); + + var polygon = new TiledPolygon { Points = new Vector2[vertices.Length] }; + + for(var i = 0; i < vertices.Length; i++) + { + polygon.Points[i] = + new Vector2(float.Parse(vertices[i].Split(',')[0], CultureInfo.InvariantCulture), + float.Parse(vertices[i].Split(',')[1], CultureInfo.InvariantCulture)); + } + + obj.Polygon = polygon; + } + + if(nodeEllipse != null) + { + obj.Ellipse = new TiledEllipse(); + } + + if(nodePoint != null) + { + obj.Point = new TiledPoint(); + } + + if(node.Attributes["width"] != null || node.Attributes["height"] != null) obj.Size = new Size(0, 0); + if(node.Attributes["width"] != null) + { + obj.Size.Width = float.Parse(node.Attributes["width"].Value, CultureInfo.InvariantCulture); + } + + if(node.Attributes["height"] != null) + { + obj.Size.Height = float.Parse(node.Attributes["height"].Value, CultureInfo.InvariantCulture); + } + + if(node.Attributes["rotation"] != null) + { + obj.Rotation = float.Parse(node.Attributes["rotation"].Value, CultureInfo.InvariantCulture); + } + + result.Add(obj); + } + + return result.ToArray(); } - if (nodePoint != null) + private void ParseObjectGid(ref TiledObject tiledObject, String gid) { - obj.Point = new TiledPoint(); + var rawID = uint.Parse(gid); + var hor = ((rawID & FLIPPED_HORIZONTALLY_FLAG)); + var ver = ((rawID & FLIPPED_VERTICALLY_FLAG)); + var dia = ((rawID & FLIPPED_DIAGONALLY_FLAG)); + + tiledObject.DataRotationFlag = (byte)((hor | ver | dia) >> SHIFT_FLIP_FLAG_TO_BYTE); + tiledObject.Gid = + (int)(rawID & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)); } - if (node.Attributes["width"] != null || node.Attributes["height"] != null) obj.Size = new Size(0, 0); - if (node.Attributes["width"] != null) + private Color ParseColor(string hexColor) { - obj.Size.Width = float.Parse(node.Attributes["width"].Value, CultureInfo.InvariantCulture); + hexColor = hexColor[1..]; + List color = new(); + for(int i = 0; i < hexColor.Length - 1; i += 2) + { + color.Add(Convert.ToByte(hexColor.Substring(i, 2), 16)); + } + + return color.Count == 3 + ? new Color(color[0], color[1], color[2]) + : new Color(color[1], color[2], color[3], color[0]); + ; } - if (node.Attributes["height"] != null) + /* HELPER METHODS */ + /// + /// Locates the right TiledMapTileset object for you within the Tilesets array + /// + /// A value from the TiledLayer.data array + /// An element within the Tilesets array or null if no match was found + public TiledMapTileset GetTiledMapTileset(int gid) { - obj.Size.Height = float.Parse(node.Attributes["height"].Value, CultureInfo.InvariantCulture); + if(Tilesets == null) + { + return null; + } + + for(var i = 0; i < Tilesets.Length; i++) + { + if(i < Tilesets.Length - 1) + { + int gid1 = Tilesets[i + 0].FirstGid; + int gid2 = Tilesets[i + 1].FirstGid; + + if(gid >= gid1 && gid < gid2) + { + return Tilesets[i]; + } + } + else + { + return Tilesets[i]; + } + } + + return new TiledMapTileset(); } - if (node.Attributes["rotation"] != null) + /// + /// Loads external tilesets and matches them to firstGids from elements within the Tilesets array + /// + /// The folder where the TiledMap file is located + /// A dictionary where the key represents the firstGid of the associated TiledMapTileset and the value the TiledTileset object + public Dictionary GetTiledTilesets(string src) { - obj.Rotation = float.Parse(node.Attributes["rotation"].Value, CultureInfo.InvariantCulture); + var tilesets = new Dictionary(); + var info = new FileInfo(src); + var srcFolder = info.Directory; + + if(Tilesets == null) + { + return tilesets; + } + + foreach(var mapTileset in Tilesets) + { + var path = $"{srcFolder}/{mapTileset.Source}"; + + if(mapTileset.Source == null) + { + continue; + } + + if(File.Exists(path)) + { + tilesets.Add(mapTileset.FirstGid, new TiledTileset(path)); + } + else + { + throw new TiledException("Cannot locate tileset '" + path + + "'. Please make sure the source folder is correct and it ends with a slash."); + } + } + + return tilesets; } - result.Add(obj); - } - - return result.ToArray(); - } - - private void ParseObjectGid(ref TiledObject tiledObject, String gid) - { - var rawID = uint.Parse(gid); - var hor = ((rawID & FLIPPED_HORIZONTALLY_FLAG)); - var ver = ((rawID & FLIPPED_VERTICALLY_FLAG)); - var dia = ((rawID & FLIPPED_DIAGONALLY_FLAG)); - - tiledObject.DataRotationFlag = (byte)((hor | ver | dia) >> SHIFT_FLIP_FLAG_TO_BYTE); - tiledObject.Gid = (int)(rawID & ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG)); - } - - private Color ParseColor(string hexColor) - { - hexColor = hexColor[1..]; - List color = new(); - for (int i = 0; i < hexColor.Length - 1; i += 2) - { - color.Add(Convert.ToByte(hexColor.Substring(i, 2), 16)); - } - - return color.Count == 3 ? new Color(color[0], color[1], color[2]) : new Color(color[1], color[2], color[3], color[0]); ; - } - - /* HELPER METHODS */ - /// - /// Locates the right TiledMapTileset object for you within the Tilesets array - /// - /// A value from the TiledLayer.data array - /// An element within the Tilesets array or null if no match was found - public TiledMapTileset GetTiledMapTileset(int gid) - { - if (Tilesets == null) - { - return null; - } - - for (var i = 0; i < Tilesets.Length; i++) - { - if (i < Tilesets.Length - 1) + /// + /// Locates a specific TiledTile object + /// + /// An element within the Tilesets array + /// An instance of the TiledTileset class + /// An element from within a TiledLayer.data array + /// An entry of the TiledTileset.tiles array or null if none of the tile id's matches the gid + /// Tip: Use the GetTiledMapTileset and GetTiledTilesets methods for retrieving the correct TiledMapTileset and TiledTileset objects + public TiledTile GetTiledTile(TiledMapTileset mapTileset, TiledTileset tileset, int gid) { - int gid1 = Tilesets[i + 0].FirstGid; - int gid2 = Tilesets[i + 1].FirstGid; - - if (gid >= gid1 && gid < gid2) - { - return Tilesets[i]; - } + foreach(var tile in tileset.Tiles) + { + if(tile.Id == gid - mapTileset.FirstGid) + { + return tile; + } + } + + return null; } - else + + /// + /// This method can be used to figure out the source rect on a Tileset image for rendering tiles. + /// + /// + /// + /// + /// An instance of the class TiledSourceRect that represents a rectangle. Returns null if the provided gid was not found within the tileset. + public TiledSourceRect GetSourceRect(TiledMapTileset mapTileset, TiledTileset tileset, int gid) { - return Tilesets[i]; + var tileHor = 0; + var tileVert = 0; + + for(var i = 0; i < tileset.TileCount; i++) + { + if(i == gid - mapTileset.FirstGid) + { + var result = new TiledSourceRect + { + X = tileHor * tileset.TileWidth, + Y = tileVert * tileset.TileHeight, + Width = tileset.TileWidth, + Height = tileset.TileHeight + }; + + return result; + } + + // Update x and y position + tileHor++; + + if(tileHor == tileset.Image.Width / tileset.TileWidth) + { + tileHor = 0; + tileVert++; + } + } + + return null; } - } - - return new TiledMapTileset(); - } - /// - /// Loads external tilesets and matches them to firstGids from elements within the Tilesets array - /// - /// The folder where the TiledMap file is located - /// A dictionary where the key represents the firstGid of the associated TiledMapTileset and the value the TiledTileset object - public Dictionary GetTiledTilesets(string src) - { - var tilesets = new Dictionary(); - var info = new FileInfo(src); - var srcFolder = info.Directory; - - if (Tilesets == null) - { - return tilesets; - } + /// + /// Checks is a tile is flipped horizontally + /// + /// An entry of the TiledMap.layers array + /// The tile's horizontal position + /// The tile's vertical position + /// True if the tile was flipped horizontally or False if not + public bool IsTileFlippedHorizontal(TiledLayer layer, int tileHor, int tileVert) + { + if(layer.Type != TiledLayerType.TileLayer) + { + throw new TiledException("Retrieving tile flipped state for a tile does not work for non-tile layers"); + } - foreach (var mapTileset in Tilesets) - { - var path = $"{srcFolder}/{mapTileset.Source}"; + return IsTileFlippedHorizontal(layer, tileHor + (tileVert * (int)layer.Width)); + } - if (mapTileset.Source == null) + /// + /// Checks is a tile is flipped horizontally + /// + /// An entry of the TiledMap.layers array + /// An index of the TiledLayer.data array + /// True if the tile was flipped horizontally or False if not + public bool IsTileFlippedHorizontal(TiledLayer layer, int dataIndex) { - continue; + return (layer.DataRotationFlags[dataIndex] & (FLIPPED_HORIZONTALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; } - if (File.Exists(path)) + /// + /// Checks is a tile linked to an object is flipped horizontally + /// + /// The tiled object + /// True if the tile was flipped horizontally or False if not + public bool IsTileFlippedHorizontal(TiledObject tiledObject) { - tilesets.Add(mapTileset.FirstGid, new TiledTileset(path)); + if(tiledObject.Gid == 0) + { + throw new TiledException("Tiled object not linked to a tile"); + } + + return (tiledObject.DataRotationFlag & (FLIPPED_HORIZONTALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; } - else + + /// + /// Checks is a tile is flipped vertically + /// + /// An entry of the TiledMap.layers array + /// The tile's horizontal position + /// The tile's vertical position + /// True if the tile was flipped vertically or False if not + public bool IsTileFlippedVertical(TiledLayer layer, int tileHor, int tileVert) { - throw new TiledException("Cannot locate tileset '" + path + "'. Please make sure the source folder is correct and it ends with a slash."); - } - } + if(layer.Type != TiledLayerType.TileLayer) + { + throw new TiledException("Retrieving tile flipped state for a tile does not work for non-tile layers"); + } - return tilesets; - } + return IsTileFlippedVertical(layer, tileHor + (tileVert * (int)layer.Width)); + } - /// - /// Locates a specific TiledTile object - /// - /// An element within the Tilesets array - /// An instance of the TiledTileset class - /// An element from within a TiledLayer.data array - /// An entry of the TiledTileset.tiles array or null if none of the tile id's matches the gid - /// Tip: Use the GetTiledMapTileset and GetTiledTilesets methods for retrieving the correct TiledMapTileset and TiledTileset objects - public TiledTile GetTiledTile(TiledMapTileset mapTileset, TiledTileset tileset, int gid) - { - foreach (var tile in tileset.Tiles) - { - if (tile.Id == gid - mapTileset.FirstGid) + /// + /// Checks is a tile is flipped vertically + /// + /// An entry of the TiledMap.layers array + /// An index of the TiledLayer.data array + /// True if the tile was flipped vertically or False if not + public bool IsTileFlippedVertical(TiledLayer layer, int dataIndex) { - return tile; + return (layer.DataRotationFlags[dataIndex] & (FLIPPED_VERTICALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; } - } - return null; - } + /// + /// Checks is a tile linked to an object is flipped vertically + /// + /// An entry of the TiledMap.layers array + /// The tiled object + /// True if the tile was flipped horizontally or False if not + public bool IsTileFlippedVertical(TiledObject tiledObject) + { + if(tiledObject.Gid == 0) + { + throw new TiledException("Tiled object not linked to a tile"); + } - /// - /// This method can be used to figure out the source rect on a Tileset image for rendering tiles. - /// - /// - /// - /// - /// An instance of the class TiledSourceRect that represents a rectangle. Returns null if the provided gid was not found within the tileset. - public TiledSourceRect GetSourceRect(TiledMapTileset mapTileset, TiledTileset tileset, int gid) - { - var tileHor = 0; - var tileVert = 0; + return (tiledObject.DataRotationFlag & (FLIPPED_VERTICALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; + } - for (var i = 0; i < tileset.TileCount; i++) - { - if (i == gid - mapTileset.FirstGid) + /// + /// Checks is a tile is flipped diagonally + /// + /// An entry of the TiledMap.layers array + /// The tile's horizontal position + /// The tile's vertical position + /// True if the tile was flipped diagonally or False if not + public bool IsTileFlippedDiagonal(TiledLayer layer, int tileHor, int tileVert) { - var result = new TiledSourceRect - { - X = tileHor * tileset.TileWidth, - Y = tileVert * tileset.TileHeight, - Width = tileset.TileWidth, - Height = tileset.TileHeight - }; - - return result; - } + if(layer.Type != TiledLayerType.TileLayer) + { + throw new TiledException("Retrieving tile flipped state for a tile does not work for non-tile layers"); + } - // Update x and y position - tileHor++; + return IsTileFlippedDiagonal(layer, tileHor + (tileVert * (int)layer.Width)); + } - if (tileHor == tileset.Image.Width / tileset.TileWidth) + /// + /// Checks is a tile is flipped diagonally + /// + /// An entry of the TiledMap.layers array + /// An index of the TiledLayer.data array + /// True if the tile was flipped diagonally or False if not + public bool IsTileFlippedDiagonal(TiledLayer layer, int dataIndex) { - tileHor = 0; - tileVert++; + return (layer.DataRotationFlags[dataIndex] & (FLIPPED_DIAGONALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; } - } - return null; - } - - /// - /// Checks is a tile is flipped horizontally - /// - /// An entry of the TiledMap.layers array - /// The tile's horizontal position - /// The tile's vertical position - /// True if the tile was flipped horizontally or False if not - public bool IsTileFlippedHorizontal(TiledLayer layer, int tileHor, int tileVert) - { - if (layer.Type != TiledLayerType.TileLayer) - { - throw new TiledException("Retrieving tile flipped state for a tile does not work for non-tile layers"); - } - - return IsTileFlippedHorizontal(layer, tileHor + (tileVert * (int)layer.Width)); - } - - /// - /// Checks is a tile is flipped horizontally - /// - /// An entry of the TiledMap.layers array - /// An index of the TiledLayer.data array - /// True if the tile was flipped horizontally or False if not - public bool IsTileFlippedHorizontal(TiledLayer layer, int dataIndex) - { - return (layer.DataRotationFlags[dataIndex] & (FLIPPED_HORIZONTALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; - } - - /// - /// Checks is a tile linked to an object is flipped horizontally - /// - /// The tiled object - /// True if the tile was flipped horizontally or False if not - public bool IsTileFlippedHorizontal(TiledObject tiledObject) - { - if (tiledObject.Gid == 0) - { - throw new TiledException("Tiled object not linked to a tile"); - } - - return (tiledObject.DataRotationFlag & (FLIPPED_HORIZONTALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; - } - - /// - /// Checks is a tile is flipped vertically - /// - /// An entry of the TiledMap.layers array - /// The tile's horizontal position - /// The tile's vertical position - /// True if the tile was flipped vertically or False if not - public bool IsTileFlippedVertical(TiledLayer layer, int tileHor, int tileVert) - { - if (layer.Type != TiledLayerType.TileLayer) - { - throw new TiledException("Retrieving tile flipped state for a tile does not work for non-tile layers"); - } - - return IsTileFlippedVertical(layer, tileHor + (tileVert * (int)layer.Width)); - } - - /// - /// Checks is a tile is flipped vertically - /// - /// An entry of the TiledMap.layers array - /// An index of the TiledLayer.data array - /// True if the tile was flipped vertically or False if not - public bool IsTileFlippedVertical(TiledLayer layer, int dataIndex) - { - return (layer.DataRotationFlags[dataIndex] & (FLIPPED_VERTICALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; - } - - /// - /// Checks is a tile linked to an object is flipped vertically - /// - /// An entry of the TiledMap.layers array - /// The tiled object - /// True if the tile was flipped horizontally or False if not - public bool IsTileFlippedVertical(TiledObject tiledObject) - { - if (tiledObject.Gid == 0) - { - throw new TiledException("Tiled object not linked to a tile"); - } - - return (tiledObject.DataRotationFlag & (FLIPPED_VERTICALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; - } - - /// - /// Checks is a tile is flipped diagonally - /// - /// An entry of the TiledMap.layers array - /// The tile's horizontal position - /// The tile's vertical position - /// True if the tile was flipped diagonally or False if not - public bool IsTileFlippedDiagonal(TiledLayer layer, int tileHor, int tileVert) - { - if (layer.Type != TiledLayerType.TileLayer) - { - throw new TiledException("Retrieving tile flipped state for a tile does not work for non-tile layers"); - } - - return IsTileFlippedDiagonal(layer, tileHor + (tileVert * (int)layer.Width)); - } - - /// - /// Checks is a tile is flipped diagonally - /// - /// An entry of the TiledMap.layers array - /// An index of the TiledLayer.data array - /// True if the tile was flipped diagonally or False if not - public bool IsTileFlippedDiagonal(TiledLayer layer, int dataIndex) - { - return (layer.DataRotationFlags[dataIndex] & (FLIPPED_DIAGONALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; - } - - /// - /// Checks is a tile linked to an object is flipped diagonally - /// - /// The tiled object - /// True if the tile was flipped horizontally or False if not - public bool IsTileFlippedDiagonal(TiledObject tiledObject) - { - if (tiledObject.Gid == 0) - { - throw new TiledException("Tiled object not linked to a tile"); - } + /// + /// Checks is a tile linked to an object is flipped diagonally + /// + /// The tiled object + /// True if the tile was flipped horizontally or False if not + public bool IsTileFlippedDiagonal(TiledObject tiledObject) + { + if(tiledObject.Gid == 0) + { + throw new TiledException("Tiled object not linked to a tile"); + } - return (tiledObject.DataRotationFlag & (FLIPPED_DIAGONALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; + return (tiledObject.DataRotationFlag & (FLIPPED_DIAGONALLY_FLAG >> SHIFT_FLIP_FLAG_TO_BYTE)) > 0; + } } - } -} +} \ No newline at end of file diff --git a/TiledCSPlus/TiledModels.cs b/TiledCSPlus/TiledModels.cs index 1fe5b42..aeb2037 100644 --- a/TiledCSPlus/TiledModels.cs +++ b/TiledCSPlus/TiledModels.cs @@ -3,497 +3,487 @@ namespace TiledCSPlus { - /// - /// Represents a size - /// - public class Size : IEquatable - { - public float Width { get; set; } - public float Height { get; set; } - - public Size(float width = 0, float height = 0) - { - this.Width = width; - this.Height = height; - } - - public bool Equals(Size other) - { - return Width.Equals(other.Width) && Height.Equals(other.Height); - } - } - - /// - /// Represents a color in RGBA format - /// - public class Color : IEquatable - { - /// - /// Red channel of the color - /// - public byte R { get; set; } - - /// - /// Green channel of the color - /// - public byte G { get; set; } - - /// - /// Blue channel of the color - /// - public byte B { get; set; } - /// - /// Alpha channel of the color + /// Represents a size /// - public byte A { get; set; } - - public Color(byte r, byte g, byte b, byte a = 255) + public class Size : IEquatable { - this.R = r; - this.G = g; - this.B = b; - this.A = a; + public float Width { get; set; } + public float Height { get; set; } + + public Size(float width = 0, float height = 0) + { + this.Width = width; + this.Height = height; + } + + public bool Equals(Size other) + { + return Width.Equals(other.Width) && Height.Equals(other.Height); + } } - public bool Equals(Color other) - { - return R.Equals(other.R) && G.Equals(other.G) && B.Equals(other.B) && A.Equals(other.A); - } - } - - /// - /// Represents an element within the Tilesets array of a TiledMap object - /// - public class TiledMapTileset - { - /// - /// The first gid defines which gid matches the tile with source vector 0,0. Is used to determine which tileset belongs to which gid - /// - public int FirstGid { get; internal set; } - - /// - /// Returns true if tileset is embedded in map - /// - public bool IsTilesetEmbedded { get; internal set; } - - /// - /// The tsx file path as defined in the map file itself - /// - public string Source { get; internal set; } - } - - /// - /// Represents a property object in both tilesets, maps, layers and objects. Values are all in string but you can use the 'type' property for conversions - /// - public class TiledProperty - { - /// - /// The property name or key in string format - /// - public string Name { get; internal set; } - - /// - /// The property type as used in Tiled. Can be bool, number, string, ... - /// - public TiledPropertyType Type { get; internal set; } - - /// - /// The value in string format - /// - public string Value { get; internal set; } - } - - /// - /// Represents a tile layer as well as an object layer within a tile map - /// - public class TiledLayer - { - /// - /// The layer id - /// - public int Id { get; internal set; } - - /// - /// The layer name - /// - public string Name { get; internal set; } - - /// - /// Total horizontal tiles - /// - public int Width { get; internal set; } - - /// - /// Total vertical tiles - /// - public int Height { get; internal set; } - - /// - /// The layer type. - /// - public TiledLayerType Type { get; internal set; } - - /// - /// The tint color set by the user in Color class - /// - public Color TintColor { get; internal set; } - - /// - /// Defines if the layer is visible in the editor - /// - public bool Visible { get; internal set; } - - /// - /// Is true when the layer is locked - /// - public bool Locked { get; internal set; } - - /// - /// Layer offset - /// - public Vector2 Offset; - - /// - /// Parallax position - /// - public Vector2 Parrallax; - - /// - /// The layer opacity - /// - public float Opacity { get; internal set; } - - /// - /// The layer class - /// - public string Class { get; internal set; } - - /// - /// An int array of gid numbers which define which tile is being used where. The length of the array equals the layer width * the layer height. Is null when the layer is not a tilelayer. - /// - public int[] Data { get; internal set; } - - /// - /// A parallel array to data which stores the rotation flags of the tile. - /// Bit 3 is horizontal flip, - /// bit 2 is vertical flip, and - /// bit 1 is (anti) diagonal flip. - /// Is null when the layer is not a tilelayer. - /// - public byte[] DataRotationFlags { get; internal set; } - - /// - /// The list of objects in case of an objectgroup layer. Is null when the layer has no objects. - /// - public TiledObject[] Objects { get; internal set; } - - /// - /// The layer properties if set - /// - public TiledProperty[] Properties { get; internal set; } - - /// - /// The image the layer represents when the layer is an image layer - /// - public TiledImage Image { get; internal set; } - - /// - /// The chunks of data when the map is infinite - /// - public TiledChunk[] Chunks { get; internal set; } - } - - /// - /// Represents an tiled object defined in object layers and tiles - /// - public class TiledObject - { - /// - /// The object id - /// - public int Id { get; internal set; } - - /// - /// The object's name - /// - public string Name { get; internal set; } - - /// - /// The object type if defined. Null if none was set. - /// - public string Type { get; internal set; } - - /// - /// The object's class - /// - public string Class { get; internal set; } - - /// - /// Object's position in pixels - /// - public Vector2 Position { get; internal set; } - - /// - /// The object's rotation - /// - public float Rotation { get; internal set; } - - /// - /// Object's size in pixels - /// - public Size Size { get; internal set; } - - /// - /// The tileset gid when the object is linked to a tile - /// - public int Gid { get; internal set; } - - /// - /// A byte which stores the rotation flags of the tile linked to the object's gid. - /// Bit 3 is horizontal flip, - /// bit 2 is vertical flip, and - /// bit 1 is (anti) diagonal flip. - /// Is null when the layer is not a tilelayer. - /// - public byte DataRotationFlag { get; internal set; } - - /// - /// An array of properties. Is null if none were defined. - /// - public TiledProperty[] Properties { get; internal set; } - - /// - /// If an object was set to a polygon shape, this property will be set and can be used to access the polygon's data - /// - public TiledPolygon Polygon { get; internal set; } - /// - /// If an object was set to a point shape, this property will be set + /// Represents a color in RGBA format /// - public TiledPoint Point { get; internal set; } - - /// - /// If an object was set to an ellipse shape, this property will be set - /// - public TiledEllipse Ellipse { get; internal set; } - } - - /// - /// Represents a polygon shape - /// - public class TiledPolygon - { - /// - /// The array of vertices represented in Vector2 format. - /// - public Vector2[] Points { get; internal set; } - } - - /// - /// Represents a point shape - /// - public class TiledPoint - { - } - - /// - /// Represents an ellipse shape - /// - public class TiledEllipse - { - } - - /// - /// Represents a tile within a tileset - /// - /// These are not defined for all tiles within a tileset, only the ones with properties, terrains and animations. - public class TiledTile - { - /// - /// The tile id - /// - public int Id { get; internal set; } - - /// - /// The custom tile type, set by the user - /// - public string Type { get; internal set; } - - /// - /// The custom tile class, set by the user - /// - public string Class { get; internal set; } - - /// - /// The terrain definitions as int array. These are indices indicating what part of a terrain and which terrain this tile represents. - /// - /// In the map file empty space is used to indicate null or no value. However, since it is an int array I needed something so I decided to replace empty values with -1. - public int[] Terrain { get; internal set; } - - /// - /// An array of properties. Is null if none were defined. - /// - public TiledProperty[] Properties { get; internal set; } - - /// - /// An array of tile animations. Is null if none were defined. - /// - public TiledTileAnimation[] Animations { get; internal set; } - - /// - /// An array of tile objects created using the tile collision editor - /// - public TiledObject[] Objects { get; internal set; } - - /// - /// The individual tile image - /// - public TiledImage Image { get; internal set; } - } - - /// - /// Represents an image - /// - public class TiledImage - { - /// - /// The image width - /// - public int Width { get; internal set; } - - /// - /// The image height - /// - public int Height { get; internal set; } - - /// - /// The image source path - /// - public string Source; - } - - /// - /// Represents a tile animation. Tile animations are a group of tiles which act as frames for an animation. - /// - public class TiledTileAnimation - { - /// - /// The tile id within a tileset - /// - public int TileId { get; internal set; } - - /// - /// The duration in miliseconds - /// - public int Duration { get; internal set; } - } - - /// - /// Used as data type for the GetSourceRect method. Represents basically a rectangle. - /// - public class TiledSourceRect - { - /// - /// The x position in pixels from the tile location in the source image - /// - public int X { get; internal set; } - - /// - /// The y position in pixels from the tile location in the source image - /// - public int Y { get; internal set; } - - /// - /// The width in pixels from the tile in the source image - /// - public int Width { get; internal set; } + public class Color : IEquatable + { + /// + /// Red channel of the color + /// + public byte R { get; set; } + + /// + /// Green channel of the color + /// + public byte G { get; set; } + + /// + /// Blue channel of the color + /// + public byte B { get; set; } + + /// + /// Alpha channel of the color + /// + public byte A { get; set; } + + public Color(byte r, byte g, byte b, byte a = 255) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + public bool Equals(Color other) + { + return R.Equals(other.R) && G.Equals(other.G) && B.Equals(other.B) && A.Equals(other.A); + } + } /// - /// The height in pixels from the tile in the source image - /// - public int Height { get; internal set; } - } - - /// - /// Represents a layer or object group - /// - public class TiledGroup - { - /// - /// The group's id + /// Represents an element within the Tilesets array of a TiledMap object /// - public int Id { get; internal set; } + public class TiledMapTileset + { + /// + /// The first gid defines which gid matches the tile with source vector 0,0. Is used to determine which tileset belongs to which gid + /// + public int FirstGid { get; internal set; } + + /// + /// Returns true if tileset is embedded in map + /// + public bool IsTilesetEmbedded { get; internal set; } + + /// + /// The tsx file path as defined in the map file itself + /// + public string Source { get; internal set; } + } /// - /// The group's name + /// Represents a property object in both tilesets, maps, layers and objects. Values are all in string but you can use the 'type' property for conversions /// - public string Name { get; internal set; } + public class TiledProperty + { + /// + /// The property name or key in string format + /// + public string Name { get; internal set; } + + /// + /// The property type as used in Tiled. Can be bool, number, string, ... + /// + public TiledPropertyType Type { get; internal set; } + + /// + /// The value in string format + /// + public string Value { get; internal set; } + } /// - /// The group's visibility + /// Represents a tile layer as well as an object layer within a tile map /// - public bool Visible { get; internal set; } + public class TiledLayer + { + /// + /// The layer id + /// + public int Id { get; internal set; } + + /// + /// The layer name + /// + public string Name { get; internal set; } + + /// + /// Total horizontal tiles + /// + public int Width { get; internal set; } + + /// + /// Total vertical tiles + /// + public int Height { get; internal set; } + + /// + /// The layer type. + /// + public TiledLayerType Type { get; internal set; } + + /// + /// The tint color set by the user in Color class + /// + public Color TintColor { get; internal set; } + + /// + /// Defines if the layer is visible in the editor + /// + public bool Visible { get; internal set; } + + /// + /// Is true when the layer is locked + /// + public bool Locked { get; internal set; } + + /// + /// Layer offset + /// + public Vector2 Offset; + + /// + /// Parallax position + /// + public Vector2 Parrallax; + + /// + /// The layer opacity + /// + public float Opacity { get; internal set; } + + /// + /// The layer class + /// + public string Class { get; internal set; } + + /// + /// An int array of gid numbers which define which tile is being used where. The length of the array equals the layer width * the layer height. Is null when the layer is not a tilelayer. + /// + public int[] Data { get; internal set; } + + /// + /// A parallel array to data which stores the rotation flags of the tile. + /// Bit 3 is horizontal flip, + /// bit 2 is vertical flip, and + /// bit 1 is (anti) diagonal flip. + /// Is null when the layer is not a tilelayer. + /// + public byte[] DataRotationFlags { get; internal set; } + + /// + /// The list of objects in case of an objectgroup layer. Is null when the layer has no objects. + /// + public TiledObject[] Objects { get; internal set; } + + /// + /// The layer properties if set + /// + public TiledProperty[] Properties { get; internal set; } + + /// + /// The image the layer represents when the layer is an image layer + /// + public TiledImage Image { get; internal set; } + + /// + /// The chunks of data when the map is infinite + /// + public TiledChunk[] Chunks { get; internal set; } + } /// - /// The group's locked state + /// Represents an tiled object defined in object layers and tiles /// - public bool Locked { get; internal set; } + public class TiledObject + { + /// + /// The object id + /// + public int Id { get; internal set; } + + /// + /// The object's name + /// + public string Name { get; internal set; } + + /// + /// The object's class + /// + public string Class { get; internal set; } + + /// + /// Object's position in pixels + /// + public Vector2 Position { get; internal set; } + + /// + /// The object's rotation + /// + public float Rotation { get; internal set; } + + /// + /// Object's size in pixels + /// + public Size Size { get; internal set; } + + /// + /// The tileset gid when the object is linked to a tile + /// + public int Gid { get; internal set; } + + /// + /// A byte which stores the rotation flags of the tile linked to the object's gid. + /// Bit 3 is horizontal flip, + /// bit 2 is vertical flip, and + /// bit 1 is (anti) diagonal flip. + /// Is null when the layer is not a tilelayer. + /// + public byte DataRotationFlag { get; internal set; } + + /// + /// An array of properties. Is null if none were defined. + /// + public TiledProperty[] Properties { get; internal set; } + + /// + /// If an object was set to a polygon shape, this property will be set and can be used to access the polygon's data + /// + public TiledPolygon Polygon { get; internal set; } + + /// + /// If an object was set to a point shape, this property will be set + /// + public TiledPoint Point { get; internal set; } + + /// + /// If an object was set to an ellipse shape, this property will be set + /// + public TiledEllipse Ellipse { get; internal set; } + } /// - /// The group's user properties + /// Represents a polygon shape /// - public TiledProperty[] Properties { get; internal set; } + public class TiledPolygon + { + /// + /// The array of vertices represented in Vector2 format. + /// + public Vector2[] Points { get; internal set; } + } /// - /// The group's layers + /// Represents a point shape /// - public TiledLayer[] Layers { get; internal set; } + public class TiledPoint + { + } /// - /// The group's objects + /// Represents an ellipse shape /// - public TiledObject[] Objects { get; internal set; } + public class TiledEllipse + { + } /// - /// The group's subgroups - /// - public TiledGroup[] Groups { get; internal set; } - } - - /// - /// Represents a tile layer chunk when the map is infinite - /// - public class TiledChunk - { - /// - /// The chunk's x position + /// Represents a tile within a tileset /// - public int X { get; internal set; } + /// These are not defined for all tiles within a tileset, only the ones with properties, terrains and animations. + public class TiledTile + { + /// + /// The tile id + /// + public int Id { get; internal set; } + + /// + /// The custom tile class, set by the user + /// + public string Class { get; internal set; } + + /// + /// The terrain definitions as int array. These are indices indicating what part of a terrain and which terrain this tile represents. + /// + /// In the map file empty space is used to indicate null or no value. However, since it is an int array I needed something so I decided to replace empty values with -1. + public int[] Terrain { get; internal set; } + + /// + /// An array of properties. Is null if none were defined. + /// + public TiledProperty[] Properties { get; internal set; } + + /// + /// An array of tile animations. Is null if none were defined. + /// + public TiledTileAnimation[] Animations { get; internal set; } + + /// + /// An array of tile objects created using the tile collision editor + /// + public TiledObject[] Objects { get; internal set; } + + /// + /// The individual tile image + /// + public TiledImage Image { get; internal set; } + } /// - /// The chunk's y position + /// Represents an image /// - public int Y { get; internal set; } + public class TiledImage + { + /// + /// The image width + /// + public int Width { get; internal set; } + + /// + /// The image height + /// + public int Height { get; internal set; } + + /// + /// The image source path + /// + public string Source; + } /// - /// The chunk's width + /// Represents a tile animation. Tile animations are a group of tiles which act as frames for an animation. /// - public int Width { get; internal set; } + public class TiledTileAnimation + { + /// + /// The tile id within a tileset + /// + public int TileId { get; internal set; } + + /// + /// The duration in miliseconds + /// + public int Duration { get; internal set; } + } /// - /// The chunk's height + /// Used as data type for the GetSourceRect method. Represents basically a rectangle. /// - public int Height { get; internal set; } + public class TiledSourceRect + { + /// + /// The x position in pixels from the tile location in the source image + /// + public int X { get; internal set; } + + /// + /// The y position in pixels from the tile location in the source image + /// + public int Y { get; internal set; } + + /// + /// The width in pixels from the tile in the source image + /// + public int Width { get; internal set; } + + /// + /// The height in pixels from the tile in the source image + /// + public int Height { get; internal set; } + } /// - /// The chunk's data is similar to the data array in the TiledLayer class + /// Represents a layer or object group /// - public int[] Data { get; internal set; } + public class TiledGroup + { + /// + /// The group's id + /// + public int Id { get; internal set; } + + /// + /// The group's name + /// + public string Name { get; internal set; } + + /// + /// The group's visibility + /// + public bool Visible { get; internal set; } + + /// + /// The group's locked state + /// + public bool Locked { get; internal set; } + + /// + /// The group's user properties + /// + public TiledProperty[] Properties { get; internal set; } + + /// + /// The group's layers + /// + public TiledLayer[] Layers { get; internal set; } + + /// + /// The group's objects + /// + public TiledObject[] Objects { get; internal set; } + + /// + /// The group's subgroups + /// + public TiledGroup[] Groups { get; internal set; } + } /// - /// The chunk's data rotation flags are similar to the data rotation flags array in the TiledLayer class + /// Represents a tile layer chunk when the map is infinite /// - public byte[] DataRotationFlags { get; internal set; } - } + public class TiledChunk + { + /// + /// The chunk's x position + /// + public int X { get; internal set; } + + /// + /// The chunk's y position + /// + public int Y { get; internal set; } + + /// + /// The chunk's width + /// + public int Width { get; internal set; } + + /// + /// The chunk's height + /// + public int Height { get; internal set; } + + /// + /// The chunk's data is similar to the data array in the TiledLayer class + /// + public int[] Data { get; internal set; } + + /// + /// The chunk's data rotation flags are similar to the data rotation flags array in the TiledLayer class + /// + public byte[] DataRotationFlags { get; internal set; } + } } \ No newline at end of file diff --git a/TiledCSPlus/TiledTileset.cs b/TiledCSPlus/TiledTileset.cs index 82ee13e..c04fb41 100644 --- a/TiledCSPlus/TiledTileset.cs +++ b/TiledCSPlus/TiledTileset.cs @@ -7,348 +7,356 @@ namespace TiledCSPlus { - /// - /// Represents a Tiled tileset - /// - public class TiledTileset - { /// - /// The Tiled version used to create this tileset + /// Represents a Tiled tileset /// - public string TiledVersion { get; set; } - /// - /// The tileset name - /// - public string Name { get; set; } - /// - /// The tileset class - /// - public string Class { get; set; } - /// - /// The tile width in pixels - /// - public int TileWidth { get; set; } - /// - /// The tile height in pixels - /// - public int TileHeight { get; set; } - /// - /// The total amount of tiles - /// - public int TileCount { get; set; } - /// - /// The amount of horizontal tiles - /// - public int Columns { get; set; } - /// - /// The image definition used by the tileset - /// - public TiledImage Image { get; set; } - /// - /// The amount of spacing between the tiles in pixels - /// - public int Spacing { get; set; } - /// - /// The amount of margin between the tiles in pixels - /// - public int Margin { get; set; } - /// - /// An array of tile definitions - /// - /// Not all tiles within a tileset have definitions. Only those with properties, animations, terrains, ... - public TiledTile[] Tiles { get; set; } - /// - /// An array of tileset properties - /// - public TiledProperty[] Properties { get; set; } - - /// - /// The tile offset in pixels - /// - public Vector2 Offset { get; set; } - - /// - /// Returns an empty instance of TiledTileset - /// - public TiledTileset() + public class TiledTileset { - - } - - /// - /// Loads a tileset in TSX format and parses it - /// - /// The file path of the TSX file - /// Thrown when the file could not be found or parsed - public TiledTileset(string path) - { - // Check the file - if (!File.Exists(path)) - { - throw new TiledException($"{path} not found"); - } - - var content = File.ReadAllText(path); - - if (path.EndsWith(".tsx")) - { - ParseXml(content); - } - else - { - throw new TiledException("Unsupported file format"); - } - } - - /// - /// Loads a tileset in TSX format and parses it - /// - /// The file stream of the TSX file - /// Thrown when the file could not be parsed - public TiledTileset(Stream stream) - { - var streamReader = new StreamReader(stream); - var content = streamReader.ReadToEnd(); - ParseXml(content); - } - - /// - /// Can be used to parse the content of a TSX tileset manually instead of loading it using the constructor - /// - /// The tmx file content as string - /// - public void ParseXml(string xml) - { - try - { - var document = new XmlDocument(); - document.LoadXml(xml); - - var nodeTileset = document.SelectSingleNode("tileset"); - var nodeImage = nodeTileset.SelectSingleNode("image"); - var nodeOffset = nodeTileset.SelectSingleNode("tileoffset"); - var nodesTile = nodeTileset.SelectNodes("tile"); - var nodesProperty = nodeTileset.SelectNodes("properties/property"); - - var attrMargin = nodeTileset.Attributes["margin"]; - var attrSpacing = nodeTileset.Attributes["spacing"]; - var attrClass = nodeTileset.Attributes["class"]; - - TiledVersion = nodeTileset.Attributes["tiledversion"] != null - ? nodeTileset.Attributes["tiledversion"].Value - : ""; - Name = nodeTileset.Attributes["name"]?.Value; - TileWidth = int.Parse(nodeTileset.Attributes["tilewidth"].Value); - TileHeight = int.Parse(nodeTileset.Attributes["tileheight"].Value); - TileCount = int.Parse(nodeTileset.Attributes["tilecount"].Value); - Columns = int.Parse(nodeTileset.Attributes["columns"].Value); - - if (attrMargin != null) Margin = int.Parse(nodeTileset.Attributes["margin"].Value); - if (attrSpacing != null) Spacing = int.Parse(nodeTileset.Attributes["spacing"].Value); - if (attrClass != null) Class = attrClass.Value; - if (nodeImage != null) Image = ParseImage(nodeImage); - if (nodeOffset != null) Offset = ParseOffset(nodeOffset); - - Tiles = ParseTiles(nodesTile); - Properties = ParseProperties(nodesProperty); - } - catch (Exception ex) - { - throw new TiledException("An error occurred while trying to parse the Tiled tileset file", ex); - } - } - - private Vector2 ParseOffset(XmlNode node) - { - var tiledOffset = new Vector2 - { - X = int.Parse(node.Attributes["x"].Value), - Y = int.Parse(node.Attributes["y"].Value) - }; - - return tiledOffset; - } - - private TiledImage ParseImage(XmlNode node) - { - var tiledImage = new TiledImage - { - Source = node.Attributes["source"].Value, - Width = int.Parse(node.Attributes["width"].Value), - Height = int.Parse(node.Attributes["height"].Value) - }; - - return tiledImage; - } - - private TiledTileAnimation[] ParseAnimations(XmlNodeList nodeList) - { - var result = new List(); - - foreach (XmlNode node in nodeList) - { - var animation = new TiledTileAnimation - { - TileId = int.Parse(node.Attributes["tileid"].Value), - Duration = int.Parse(node.Attributes["duration"].Value) - }; - - result.Add(animation); - } - - return result.ToArray(); - } - - private TiledProperty[] ParseProperties(XmlNodeList nodeList) - { - var result = new List(); - - foreach (XmlNode node in nodeList) - { - var attrType = node.Attributes["type"]; - - var property = new TiledProperty + /// + /// The Tiled version used to create this tileset + /// + public string TiledVersion { get; set; } + + /// + /// The Tiled version this tileset is compatible with + /// + public string TilesetVersion { get; set; } + + /// + /// The tileset name + /// + public string Name { get; set; } + + /// + /// The tileset class + /// + public string Class { get; set; } + + /// + /// The tile width in pixels + /// + public int TileWidth { get; set; } + + /// + /// The tile height in pixels + /// + public int TileHeight { get; set; } + + /// + /// The total amount of tiles + /// + public int TileCount { get; set; } + + /// + /// The amount of horizontal tiles + /// + public int Columns { get; set; } + + /// + /// The image definition used by the tileset + /// + public TiledImage Image { get; set; } + + /// + /// The amount of spacing between the tiles in pixels + /// + public int Spacing { get; set; } + + /// + /// The amount of margin between the tiles in pixels + /// + public int Margin { get; set; } + + /// + /// An array of tile definitions + /// + /// Not all tiles within a tileset have definitions. Only those with properties, animations, terrains, ... + public TiledTile[] Tiles { get; set; } + + /// + /// An array of tileset properties + /// + public TiledProperty[] Properties { get; set; } + + /// + /// The tile offset in pixels + /// + public Vector2 Offset { get; set; } + + /// + /// Returns an empty instance of TiledTileset + /// + public TiledTileset() { - Name = node.Attributes["name"].Value, - Value = node.Attributes["value"]?.Value, - Type = TiledPropertyType.String - }; + } - if (attrType != null) + /// + /// Loads a tileset in TSX format and parses it + /// + /// The file path of the TSX file + /// Thrown when the file could not be found or parsed + public TiledTileset(string path) { - if (attrType.Value == "bool") property.Type = TiledPropertyType.Bool; - if (attrType.Value == "color") property.Type = TiledPropertyType.Color; - if (attrType.Value == "file") property.Type = TiledPropertyType.File; - if (attrType.Value == "float") property.Type = TiledPropertyType.Float; - if (attrType.Value == "int") property.Type = TiledPropertyType.Int; - if (attrType.Value == "object") property.Type = TiledPropertyType.Object; + // Check the file + if(!File.Exists(path)) + { + throw new TiledException($"{path} not found"); + } + + var content = File.ReadAllText(path); + + if(path.EndsWith(".tsx")) + { + ParseXml(content); + } + else + { + throw new TiledException("Unsupported file format"); + } } - property.Value ??= node.InnerText; - - result.Add(property); - } - - return result.ToArray(); - } - - private TiledTile[] ParseTiles(XmlNodeList nodeList) - { - var result = new List(); - - foreach (XmlNode node in nodeList) - { - var nodesProperty = node.SelectNodes("properties/property"); - var nodesObject = node.SelectNodes("objectgroup/object"); - var nodesAnimation = node.SelectNodes("animation/frame"); - var nodeImage = node.SelectSingleNode("image"); - - var tile = new TiledTile - { - Id = int.Parse(node.Attributes["id"].Value), - Class = node.Attributes["class"]?.Value, - Type = node.Attributes["type"]?.Value, - Terrain = node.Attributes["terrain"]?.Value.Split(',').AsIntArray(), - Properties = ParseProperties(nodesProperty), - Animations = ParseAnimations(nodesAnimation), - Objects = ParseObjects(nodesObject) - }; - - if (nodeImage != null) + /// + /// Loads a tileset in TSX format and parses it + /// + /// The file stream of the TSX file + /// Thrown when the file could not be parsed + public TiledTileset(Stream stream) { - var tileImage = new TiledImage - { - Width = int.Parse(nodeImage.Attributes["width"].Value), - Height = int.Parse(nodeImage.Attributes["height"].Value), - Source = nodeImage.Attributes["source"].Value - }; - - tile.Image = tileImage; + var streamReader = new StreamReader(stream); + var content = streamReader.ReadToEnd(); + ParseXml(content); } - result.Add(tile); - } - - return result.ToArray(); - } - - private TiledObject[] ParseObjects(XmlNodeList nodeList) - { - var result = new List(); - - foreach (XmlNode node in nodeList) - { - var nodesProperty = node.SelectNodes("properties/property"); - var nodePolygon = node.SelectSingleNode("polygon"); - var nodePoint = node.SelectSingleNode("point"); - var nodeEllipse = node.SelectSingleNode("ellipse"); - - var obj = new TiledObject + /// + /// Can be used to parse the content of a TSX tileset manually instead of loading it using the constructor + /// + /// The tmx file content as string + /// + public void ParseXml(string xml) { - Id = int.Parse(node.Attributes["id"].Value), - Name = node.Attributes["name"]?.Value, - Class = node.Attributes["class"]?.Value, - Type = node.Attributes["type"]?.Value, - Gid = int.Parse(node.Attributes["gid"]?.Value ?? "0"), - Position = new Vector2(float.Parse(node.Attributes["x"].Value, CultureInfo.InvariantCulture), - float.Parse(node.Attributes["y"].Value, CultureInfo.InvariantCulture)) - }; - - if (nodesProperty != null) - { - obj.Properties = ParseProperties(nodesProperty); + try + { + var document = new XmlDocument(); + document.LoadXml(xml); + + var nodeTileset = document.SelectSingleNode("tileset"); + var nodeImage = nodeTileset.SelectSingleNode("image"); + var nodeOffset = nodeTileset.SelectSingleNode("tileoffset"); + var nodesTile = nodeTileset.SelectNodes("tile"); + var nodesProperty = nodeTileset.SelectNodes("properties/property"); + + var attrMargin = nodeTileset.Attributes["margin"]; + var attrSpacing = nodeTileset.Attributes["spacing"]; + var attrClass = nodeTileset.Attributes["class"]; + + TiledVersion = nodeTileset.Attributes["tiledversion"]?.Value; + TilesetVersion = nodeTileset.Attributes["version"]?.Value; + Name = nodeTileset.Attributes["name"]?.Value; + TileWidth = int.Parse(nodeTileset.Attributes["tilewidth"].Value); + TileHeight = int.Parse(nodeTileset.Attributes["tileheight"].Value); + TileCount = int.Parse(nodeTileset.Attributes["tilecount"].Value); + Columns = int.Parse(nodeTileset.Attributes["columns"].Value); + + if(attrMargin != null) Margin = int.Parse(nodeTileset.Attributes["margin"].Value); + if(attrSpacing != null) Spacing = int.Parse(nodeTileset.Attributes["spacing"].Value); + if(attrClass != null) Class = attrClass.Value; + if(nodeImage != null) Image = ParseImage(nodeImage); + if(nodeOffset != null) Offset = ParseOffset(nodeOffset); + + Tiles = ParseTiles(nodesTile); + Properties = ParseProperties(nodesProperty); + } + catch(Exception ex) + { + throw new TiledException("An error occurred while trying to parse the Tiled tileset file", ex); + } } - if (nodePolygon != null) + private Vector2 ParseOffset(XmlNode node) { - var points = nodePolygon.Attributes["points"].Value; - var vertices = points.Split(' '); - - var polygon = new TiledPolygon - { - Points = new Vector2[vertices.Length] - }; - - for (var i = 0; i < vertices.Length; i++) - { - polygon.Points[i] = - new Vector2(float.Parse(vertices[i].Split(',')[0], CultureInfo.InvariantCulture), - float.Parse(vertices[i].Split(',')[1], CultureInfo.InvariantCulture)); - } - - obj.Polygon = polygon; + var tiledOffset = new Vector2 + { + X = int.Parse(node.Attributes["x"].Value), Y = int.Parse(node.Attributes["y"].Value) + }; + + return tiledOffset; } - if (nodeEllipse != null) + private TiledImage ParseImage(XmlNode node) { - obj.Ellipse = new TiledEllipse(); + var tiledImage = new TiledImage + { + Source = node.Attributes["source"].Value, + Width = int.Parse(node.Attributes["width"].Value), + Height = int.Parse(node.Attributes["height"].Value) + }; + + return tiledImage; } - if (nodePoint != null) + private TiledTileAnimation[] ParseAnimations(XmlNodeList nodeList) { - obj.Point = new TiledPoint(); + var result = new List(); + + foreach(XmlNode node in nodeList) + { + var animation = new TiledTileAnimation + { + TileId = int.Parse(node.Attributes["tileid"].Value), + Duration = int.Parse(node.Attributes["duration"].Value) + }; + + result.Add(animation); + } + + return result.ToArray(); } - if (node.Attributes["width"] != null || node.Attributes["height"] != null) obj.Size = new Size(); - if (node.Attributes["width"] != null) + private TiledProperty[] ParseProperties(XmlNodeList nodeList) { - obj.Size.Width = float.Parse(node.Attributes["width"].Value, CultureInfo.InvariantCulture); + var result = new List(); + + foreach(XmlNode node in nodeList) + { + var attrType = node.Attributes["type"]; + + var property = new TiledProperty + { + Name = node.Attributes["name"].Value, + Value = node.Attributes["value"]?.Value, + Type = TiledPropertyType.String + }; + + if(attrType != null) + { + if(attrType.Value == "bool") property.Type = TiledPropertyType.Bool; + if(attrType.Value == "color") property.Type = TiledPropertyType.Color; + if(attrType.Value == "file") property.Type = TiledPropertyType.File; + if(attrType.Value == "float") property.Type = TiledPropertyType.Float; + if(attrType.Value == "int") property.Type = TiledPropertyType.Int; + if(attrType.Value == "object") property.Type = TiledPropertyType.Object; + } + + property.Value ??= node.InnerText; + + result.Add(property); + } + + return result.ToArray(); } - if (node.Attributes["height"] != null) + private TiledTile[] ParseTiles(XmlNodeList nodeList) { - obj.Size.Height = float.Parse(node.Attributes["height"].Value, CultureInfo.InvariantCulture); + var result = new List(); + + foreach(XmlNode node in nodeList) + { + var nodesProperty = node.SelectNodes("properties/property"); + var nodesObject = node.SelectNodes("objectgroup/object"); + var nodesAnimation = node.SelectNodes("animation/frame"); + var nodeImage = node.SelectSingleNode("image"); + + var tile = new TiledTile + { + Id = int.Parse(node.Attributes["id"].Value), + Class = TilesetVersion == "1.9" ? node.Attributes["class"]?.Value : node.Attributes["type"]?.Value, + Terrain = node.Attributes["terrain"]?.Value.Split(',').AsIntArray(), + Properties = ParseProperties(nodesProperty), + Animations = ParseAnimations(nodesAnimation), + Objects = ParseObjects(nodesObject) + }; + + if(nodeImage != null) + { + var tileImage = new TiledImage + { + Width = int.Parse(nodeImage.Attributes["width"].Value), + Height = int.Parse(nodeImage.Attributes["height"].Value), + Source = nodeImage.Attributes["source"].Value + }; + + tile.Image = tileImage; + } + + result.Add(tile); + } + + return result.ToArray(); } - if (node.Attributes["rotation"] != null) + private TiledObject[] ParseObjects(XmlNodeList nodeList) { - obj.Rotation = float.Parse(node.Attributes["rotation"].Value, CultureInfo.InvariantCulture); + var result = new List(); + + foreach(XmlNode node in nodeList) + { + var nodesProperty = node.SelectNodes("properties/property"); + var nodePolygon = node.SelectSingleNode("polygon"); + var nodePoint = node.SelectSingleNode("point"); + var nodeEllipse = node.SelectSingleNode("ellipse"); + + var obj = new TiledObject + { + Id = int.Parse(node.Attributes["id"].Value), + Name = node.Attributes["name"]?.Value, + Class = TilesetVersion == "1.9" ? node.Attributes["class"]?.Value : node.Attributes["type"]?.Value, + Gid = int.Parse(node.Attributes["gid"]?.Value ?? "0"), + Position = new Vector2(float.Parse(node.Attributes["x"].Value, CultureInfo.InvariantCulture), + float.Parse(node.Attributes["y"].Value, CultureInfo.InvariantCulture)) + }; + + if(nodesProperty != null) + { + obj.Properties = ParseProperties(nodesProperty); + } + + if(nodePolygon != null) + { + var points = nodePolygon.Attributes["points"].Value; + var vertices = points.Split(' '); + + var polygon = new TiledPolygon { Points = new Vector2[vertices.Length] }; + + for(var i = 0; i < vertices.Length; i++) + { + polygon.Points[i] = + new Vector2(float.Parse(vertices[i].Split(',')[0], CultureInfo.InvariantCulture), + float.Parse(vertices[i].Split(',')[1], CultureInfo.InvariantCulture)); + } + + obj.Polygon = polygon; + } + + if(nodeEllipse != null) + { + obj.Ellipse = new TiledEllipse(); + } + + if(nodePoint != null) + { + obj.Point = new TiledPoint(); + } + + if(node.Attributes["width"] != null || node.Attributes["height"] != null) obj.Size = new Size(); + if(node.Attributes["width"] != null) + { + obj.Size.Width = float.Parse(node.Attributes["width"].Value, CultureInfo.InvariantCulture); + } + + if(node.Attributes["height"] != null) + { + obj.Size.Height = float.Parse(node.Attributes["height"].Value, CultureInfo.InvariantCulture); + } + + if(node.Attributes["rotation"] != null) + { + obj.Rotation = float.Parse(node.Attributes["rotation"].Value, CultureInfo.InvariantCulture); + } + + result.Add(obj); + } + + return result.ToArray(); } - - result.Add(obj); - } - - return result.ToArray(); } - } } \ No newline at end of file