diff options
author | chai <215380520@qq.com> | 2024-06-03 10:15:45 +0800 |
---|---|---|
committer | chai <215380520@qq.com> | 2024-06-03 10:15:45 +0800 |
commit | acea7b2e728787a0d83bbf83c8c1f042d2c32e7e (patch) | |
tree | 0bfec05c1ca2d71be2c337bcd110a0421f19318b /Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled | |
parent | 88febcb02bf127d961c6471d9e846c0e1315f5c3 (diff) |
+ plugins project
Diffstat (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled')
11 files changed, 1036 insertions, 0 deletions
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/ContentWriterExtensions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/ContentWriterExtensions.cs new file mode 100644 index 0000000..28ded17 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/ContentWriterExtensions.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + public static class ContentWriterExtensions + { + // ReSharper disable once SuggestBaseTypeForParameter + public static void WriteTiledMapProperties(this ContentWriter writer, IReadOnlyCollection<TiledMapPropertyContent> value) + { + if (value == null) + { + writer.Write(0); + return; + } + writer.Write(value.Count); + foreach (var property in value) + { + writer.Write(property.Name); + writer.Write(property.Value ?? string.Empty); + WriteTiledMapProperties(writer, property.Properties); + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledContentItem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledContentItem.cs new file mode 100644 index 0000000..b376252 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledContentItem.cs @@ -0,0 +1,22 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled; + +public class TiledContentItem<T>: ContentItem<T> +{ + public TiledContentItem(T data) : base(data) + { + } + + public void BuildExternalReference<T>(ContentProcessorContext context, TiledMapImageContent image) + { + var parameters = new OpaqueDataDictionary + { + { "ColorKeyColor", image.TransparentColor }, + { "ColorKeyEnabled", true } + }; + BuildExternalReference<Texture2DContent>(context, image.Source, parameters); + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapContentItem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapContentItem.cs new file mode 100644 index 0000000..b0b7b5d --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapContentItem.cs @@ -0,0 +1,12 @@ +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + public class TiledMapContentItem : TiledContentItem<TiledMapContent> + { + public TiledMapContentItem(TiledMapContent data) + : base(data) + { + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapImporter.cs new file mode 100644 index 0000000..70e2bee --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapImporter.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Serialization; +using Microsoft.Xna.Framework.Content.Pipeline; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentImporter(".tmx", DefaultProcessor = "TiledMapProcessor", DisplayName = "Tiled Map Importer - MonoGame.Extended")] + public class TiledMapImporter : ContentImporter<TiledMapContentItem> + { + public override TiledMapContentItem Import(string filePath, ContentImporterContext context) + { + try + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + + ContentLogger.Logger = context.Logger; + ContentLogger.Log($"Importing '{filePath}'"); + + var map = DeserializeTiledMapContent(filePath, context); + + if (map.Width > ushort.MaxValue || map.Height > ushort.MaxValue) + throw new InvalidContentException($"The map '{filePath} is much too large. The maximum supported width and height for a Tiled map is {ushort.MaxValue}."); + + ContentLogger.Log($"Imported '{filePath}'"); + + return new TiledMapContentItem(map); + + } + catch (Exception e) + { + context.Logger.LogImportantMessage(e.StackTrace); + throw; + } + } + + private static TiledMapContent DeserializeTiledMapContent(string mapFilePath, ContentImporterContext context) + { + using (var reader = new StreamReader(mapFilePath)) + { + var mapSerializer = new XmlSerializer(typeof(TiledMapContent)); + var map = (TiledMapContent)mapSerializer.Deserialize(reader); + + map.FilePath = mapFilePath; + + for (var i = 0; i < map.Tilesets.Count; i++) + { + var tileset = map.Tilesets[i]; + + string getTilesetSource(string source) + => Path.GetFullPath(Path.Combine(Path.GetDirectoryName(mapFilePath), source)); + + if (!string.IsNullOrWhiteSpace(tileset.Source)) + { + tileset.Source = getTilesetSource(tileset.Source); + ContentLogger.Log($"Adding dependency for {tileset.Source}"); + // We depend on the tileset. If the tileset changes, the map also needs to rebuild. + context.AddDependency(tileset.Source); + } + else + { + tileset.Image.Source = getTilesetSource(tileset.Image.Source); + ContentLogger.Log($"Adding dependency for {tileset.Image.Source}"); + context.AddDependency(tileset.Image.Source); + } + } + + ImportLayers(context, map.Layers, Path.GetDirectoryName(mapFilePath)); + + map.Name = mapFilePath; + return map; + } + } + + private static void ImportLayers(ContentImporterContext context, List<TiledMapLayerContent> layers, string path) + { + for (var i = 0; i < layers.Count; i++) + { + if (layers[i] is TiledMapImageLayerContent imageLayer) + { + imageLayer.Image.Source = Path.Combine(path, imageLayer.Image.Source); + ContentLogger.Log($"Adding dependency for '{imageLayer.Image.Source}'"); + + // Tell the pipeline that we depend on this image and need to rebuild the map if the image changes. + // (Maybe the image is a different size) + context.AddDependency(imageLayer.Image.Source); + } + if (layers[i] is TiledMapObjectLayerContent objectLayer) + foreach (var obj in objectLayer.Objects) + if (!String.IsNullOrWhiteSpace(obj.TemplateSource)) + { + obj.TemplateSource = Path.Combine(path, obj.TemplateSource); + ContentLogger.Log($"Adding dependency for '{obj.TemplateSource}'"); + // Tell the pipeline that we depend on this template and need to rebuild the map if the template changes. + // (Templates are loaded into objects on process, so all objects which depend on the template file + // need the change to the template) + context.AddDependency(obj.TemplateSource); + } + if (layers[i] is TiledMapGroupLayerContent groupLayer) + // Yay recursion! + ImportLayers(context, groupLayer.Layers, path); + } + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapObjectTemplateImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapObjectTemplateImporter.cs new file mode 100644 index 0000000..eeddcaa --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapObjectTemplateImporter.cs @@ -0,0 +1,54 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using System; +using System.IO; +using System.Xml.Serialization; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentImporter(".tx", DefaultProcessor = "TiledMapObjectTemplateProcessor", DisplayName = "Tiled Map Object Template Importer - MonoGame.Extended")] + public class TiledMapObjectTemplateImporter : ContentImporter<TiledMapObjectTemplateContent> + { + public override TiledMapObjectTemplateContent Import(string filePath, ContentImporterContext context) + { + try + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + + ContentLogger.Logger = context.Logger; + ContentLogger.Log($"Importing '{filePath}'"); + + var template = DeserializeTileMapObjectTemplateContent(filePath, context); + + ContentLogger.Log($"Imported '{filePath}'"); + + return template; + } + catch (Exception e) + { + context.Logger.LogImportantMessage(e.StackTrace); + return null; + } + } + + private static TiledMapObjectTemplateContent DeserializeTileMapObjectTemplateContent(string filePath, ContentImporterContext context) + { + using (var reader = new StreamReader(filePath)) + { + var templateSerializer = new XmlSerializer(typeof(TiledMapObjectTemplateContent)); + var template = (TiledMapObjectTemplateContent)templateSerializer.Deserialize(reader); + + if (!string.IsNullOrWhiteSpace(template.Tileset?.Source)) + { + template.Tileset.Source = Path.Combine(Path.GetDirectoryName(filePath), template.Tileset.Source); + ContentLogger.Log($"Adding dependency '{template.Tileset.Source}'"); + // We depend on this tileset. + context.AddDependency(template.Tileset.Source); + } + + return template; + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapProcessor.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapProcessor.cs new file mode 100644 index 0000000..9668a72 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapProcessor.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using MonoGame.Extended.Tiled; +using MonoGame.Extended.Tiled.Serialization; +using MonoGame.Framework.Utilities.Deflate; +using CompressionMode = System.IO.Compression.CompressionMode; +using GZipStream = System.IO.Compression.GZipStream; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + public static class TiledMapContentHelper + { + public static void Process(TiledMapObjectContent obj, ContentProcessorContext context) + { + if (!string.IsNullOrWhiteSpace(obj.TemplateSource)) + { + var externalReference = new ExternalReference<TiledMapObjectLayerContent>(obj.TemplateSource); + var template = context.BuildAndLoadAsset<TiledMapObjectLayerContent, TiledMapObjectTemplateContent>(externalReference, ""); + + // Nothing says a template can't reference another template. + // Yay recusion! + Process(template.Object, context); + + if (!obj._globalIdentifier.HasValue && template.Object._globalIdentifier.HasValue) + obj.GlobalIdentifier = template.Object.GlobalIdentifier; + + if (!obj._height.HasValue && template.Object._height.HasValue) + obj.Height = template.Object.Height; + + if (!obj._identifier.HasValue && template.Object._identifier.HasValue) + obj.Identifier = template.Object.Identifier; + + if (!obj._rotation.HasValue && template.Object._rotation.HasValue) + obj.Rotation = template.Object.Rotation; + + if (!obj._visible.HasValue && template.Object._visible.HasValue) + obj.Visible = template.Object.Visible; + + if (!obj._width.HasValue && template.Object._width.HasValue) + obj.Width = template.Object.Width; + + if (!obj._x.HasValue && template.Object._x.HasValue) + obj.X = template.Object.X; + + if (!obj._y.HasValue && template.Object._y.HasValue) + obj.Y = template.Object.Y; + + if (obj.Ellipse == null && template.Object.Ellipse != null) + obj.Ellipse = template.Object.Ellipse; + + if (string.IsNullOrWhiteSpace(obj.Name) && !string.IsNullOrWhiteSpace(template.Object.Name)) + obj.Name = template.Object.Name; + + if (obj.Polygon == null && template.Object.Polygon != null) + obj.Polygon = template.Object.Polygon; + + if (obj.Polyline == null && template.Object.Polyline != null) + obj.Polyline = template.Object.Polyline; + + foreach (var tProperty in template.Object.Properties) + { + if (!obj.Properties.Exists(p => p.Name == tProperty.Name)) + obj.Properties.Add(tProperty); + } + + if (string.IsNullOrWhiteSpace(obj.Type) && !string.IsNullOrWhiteSpace(template.Object.Type)) + obj.Type = template.Object.Type; + + if (string.IsNullOrWhiteSpace(obj.Class) && !string.IsNullOrWhiteSpace(template.Object.Class)) + obj.Class = template.Object.Class; + } + } + } + + + [ContentProcessor(DisplayName = "Tiled Map Processor - MonoGame.Extended")] + public class TiledMapProcessor : ContentProcessor<TiledMapContentItem, TiledMapContentItem> + { + public override TiledMapContentItem Process(TiledMapContentItem contentItem, ContentProcessorContext context) + { + try + { + ContentLogger.Logger = context.Logger; + var map = contentItem.Data; + + if (map.Orientation == TiledMapOrientationContent.Hexagonal || map.Orientation == TiledMapOrientationContent.Staggered) + throw new NotSupportedException($"{map.Orientation} Tiled Maps are currently not implemented!"); + + foreach (var tileset in map.Tilesets) + { + if (string.IsNullOrWhiteSpace(tileset.Source)) + { + // Load the Texture2DContent for the tileset as it will be saved into the map content file. + contentItem.BuildExternalReference<Texture2DContent>(context, tileset.Image); + } + else + { + // Link to the tileset for the content loader to load at runtime. + //var externalReference = new ExternalReference<TiledMapTilesetContent>(tileset.Source); + //tileset.Content = context.BuildAsset<TiledMapTilesetContent, TiledMapTilesetContent>(externalReference, ""); + contentItem.BuildExternalReference<TiledMapTilesetContent>(context, tileset.Source); + } + } + + ProcessLayers(contentItem, map, context, map.Layers); + + return contentItem; + } + catch (Exception ex) + { + context.Logger.LogImportantMessage(ex.Message); + throw; + } + } + + private static void ProcessLayers(TiledMapContentItem contentItem, TiledMapContent map, ContentProcessorContext context, List<TiledMapLayerContent> layers) + { + foreach (var layer in layers) + { + switch (layer) + { + case TiledMapImageLayerContent imageLayer: + ContentLogger.Log($"Processing image layer '{imageLayer.Name}'"); + contentItem.BuildExternalReference<Texture2DContent>(context, imageLayer.Image); + ContentLogger.Log($"Processed image layer '{imageLayer.Name}'"); + break; + + case TiledMapTileLayerContent tileLayer when tileLayer.Data.Chunks.Count > 0: + throw new NotSupportedException($"{map.FilePath} contains data chunks. These are currently not supported."); + + case TiledMapTileLayerContent tileLayer: + var data = tileLayer.Data; + var encodingType = data.Encoding ?? "xml"; + var compressionType = data.Compression ?? "xml"; + + ContentLogger.Log($"Processing tile layer '{tileLayer.Name}': Encoding: '{encodingType}', Compression: '{compressionType}'"); + var tileData = DecodeTileLayerData(encodingType, tileLayer); + var tiles = CreateTiles(map.RenderOrder, map.Width, map.Height, tileData); + tileLayer.Tiles = tiles; + ContentLogger.Log($"Processed tile layer '{tileLayer}': {tiles.Length} tiles"); + break; + + case TiledMapObjectLayerContent objectLayer: + ContentLogger.Log($"Processing object layer '{objectLayer.Name}'"); + + foreach (var obj in objectLayer.Objects) + TiledMapContentHelper.Process(obj, context); + + ContentLogger.Log($"Processed object layer '{objectLayer.Name}'"); + break; + + case TiledMapGroupLayerContent groupLayer: + ProcessLayers(contentItem, map, context, groupLayer.Layers); + break; + } + } + } + + private static List<TiledMapTileContent> DecodeTileLayerData(string encodingType, TiledMapTileLayerContent tileLayer) + { + List<TiledMapTileContent> tiles; + + switch (encodingType) + { + case "xml": + tiles = tileLayer.Data.Tiles; + break; + case "csv": + tiles = DecodeCommaSeperatedValuesData(tileLayer.Data); + break; + case "base64": + tiles = DecodeBase64Data(tileLayer.Data, tileLayer.Width, tileLayer.Height); + break; + default: + throw new NotSupportedException($"The tile layer encoding '{encodingType}' is not supported."); + } + + return tiles; + } + + private static TiledMapTile[] CreateTiles(TiledMapTileDrawOrderContent renderOrder, int mapWidth, int mapHeight, List<TiledMapTileContent> tileData) + { + TiledMapTile[] tiles; + + switch (renderOrder) + { + case TiledMapTileDrawOrderContent.LeftDown: + tiles = CreateTilesInLeftDownOrder(tileData, mapWidth, mapHeight).ToArray(); + break; + case TiledMapTileDrawOrderContent.LeftUp: + tiles = CreateTilesInLeftUpOrder(tileData, mapWidth, mapHeight).ToArray(); + break; + case TiledMapTileDrawOrderContent.RightDown: + tiles = CreateTilesInRightDownOrder(tileData, mapWidth, mapHeight).ToArray(); + break; + case TiledMapTileDrawOrderContent.RightUp: + tiles = CreateTilesInRightUpOrder(tileData, mapWidth, mapHeight).ToArray(); + break; + default: + throw new NotSupportedException($"{renderOrder} is not supported."); + } + + return tiles.ToArray(); + } + + private static IEnumerable<TiledMapTile> CreateTilesInLeftDownOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight) + { + for (var y = 0; y < mapHeight; y++) + { + for (var x = mapWidth - 1; x >= 0; x--) + { + var dataIndex = x + y * mapWidth; + var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier; + if (globalIdentifier == 0) + continue; + var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y); + yield return tile; + } + } + } + + private static IEnumerable<TiledMapTile> CreateTilesInLeftUpOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight) + { + for (var y = mapHeight - 1; y >= 0; y--) + { + for (var x = mapWidth - 1; x >= 0; x--) + { + var dataIndex = x + y * mapWidth; + var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier; + if (globalIdentifier == 0) + continue; + var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y); + yield return tile; + } + } + } + + private static IEnumerable<TiledMapTile> CreateTilesInRightDownOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight) + { + for (var y = 0; y < mapHeight; y++) + { + for (var x = 0; x < mapWidth; x++) + { + var dataIndex = x + y * mapWidth; + var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier; + if (globalIdentifier == 0) + continue; + var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y); + yield return tile; + } + } + } + + private static IEnumerable<TiledMapTile> CreateTilesInRightUpOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight) + { + for (var y = mapHeight - 1; y >= 0; y--) + { + for (var x = mapWidth - 1; x >= 0; x--) + { + var dataIndex = x + y * mapWidth; + var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier; + if (globalIdentifier == 0) + continue; + var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y); + yield return tile; + } + } + } + + private static List<TiledMapTileContent> DecodeBase64Data(TiledMapTileLayerDataContent data, int width, int height) + { + var tileList = new List<TiledMapTileContent>(); + var encodedData = data.Value.Trim(); + var decodedData = Convert.FromBase64String(encodedData); + + using (var stream = OpenStream(decodedData, data.Compression)) + { + using (var reader = new BinaryReader(stream)) + { + data.Tiles = new List<TiledMapTileContent>(); + + for (var y = 0; y < width; y++) + { + for (var x = 0; x < height; x++) + { + var gid = reader.ReadUInt32(); + tileList.Add(new TiledMapTileContent + { + GlobalIdentifier = gid + }); + } + } + } + } + + return tileList; + } + + private static List<TiledMapTileContent> DecodeCommaSeperatedValuesData(TiledMapTileLayerDataContent data) + { + return data.Value + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(uint.Parse) + .Select(x => new TiledMapTileContent { GlobalIdentifier = x }) + .ToList(); + } + + private static Stream OpenStream(byte[] decodedData, string compressionMode) + { + var memoryStream = new MemoryStream(decodedData, false); + + return compressionMode switch + { + "gzip" => new GZipStream(memoryStream, CompressionMode.Decompress), + "zlib" => new ZlibStream(memoryStream, Framework.Utilities.Deflate.CompressionMode.Decompress), + _ => memoryStream + }; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetContentItem.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetContentItem.cs new file mode 100644 index 0000000..d4b2221 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetContentItem.cs @@ -0,0 +1,14 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + public class TiledMapTilesetContentItem : TiledContentItem<TiledMapTilesetContent> + { + public TiledMapTilesetContentItem(TiledMapTilesetContent data) + : base(data) + { + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetImporter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetImporter.cs new file mode 100644 index 0000000..c848bde --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetImporter.cs @@ -0,0 +1,60 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using System; +using System.IO; +using System.Xml.Serialization; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentImporter(".tsx", DefaultProcessor = "TiledMapTilesetProcessor", DisplayName = "Tiled Map Tileset Importer - MonoGame.Extended")] + public class TiledMapTilesetImporter : ContentImporter<TiledMapTilesetContentItem> + { + public override TiledMapTilesetContentItem Import(string filePath, ContentImporterContext context) + { + try + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + + ContentLogger.Logger = context.Logger; + ContentLogger.Log($"Importing '{filePath}'"); + + var tileset = DeserializeTiledMapTilesetContent(filePath, context); + + ContentLogger.Log($"Imported '{filePath}'"); + + return new TiledMapTilesetContentItem(tileset); + } + catch (Exception e) + { + context.Logger.LogImportantMessage(e.StackTrace); + throw; + } + } + + private TiledMapTilesetContent DeserializeTiledMapTilesetContent(string filePath, ContentImporterContext context) + { + using (var reader = new StreamReader(filePath)) + { + var tilesetSerializer = new XmlSerializer(typeof(TiledMapTilesetContent)); + var tileset = (TiledMapTilesetContent)tilesetSerializer.Deserialize(reader); + + if (tileset.Image is not null) + tileset.Image.Source = context.AddDependencyWithLogging(filePath, tileset.Image.Source); + + foreach (var tile in tileset.Tiles) + { + foreach (var obj in tile.Objects) + { + if (!string.IsNullOrWhiteSpace(obj.TemplateSource)) + obj.TemplateSource = context.AddDependencyWithLogging(filePath, obj.TemplateSource); + } + if (tile.Image is not null) + tile.Image.Source = context.AddDependencyWithLogging(filePath, tile.Image.Source); + } + + return tileset; + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetProcessor.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetProcessor.cs new file mode 100644 index 0000000..5682602 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetProcessor.cs @@ -0,0 +1,44 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using System; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentProcessor(DisplayName = "Tiled Map Tileset Processor - MonoGame.Extended")] + public class TiledMapTilesetProcessor : ContentProcessor<TiledMapTilesetContentItem, TiledMapTilesetContentItem> + { + public override TiledMapTilesetContentItem Process(TiledMapTilesetContentItem contentItem, ContentProcessorContext context) + { + try + { + var tileset = contentItem.Data; + + ContentLogger.Logger = context.Logger; + ContentLogger.Log($"Processing tileset '{tileset.Name}'"); + + // Build the Texture2D asset and load it as it will be saved as part of this tileset file. + if (tileset.Image is not null) + contentItem.BuildExternalReference<Texture2DContent>(context, tileset.Image); + + foreach (var tile in tileset.Tiles) + { + foreach (var obj in tile.Objects) + { + TiledMapContentHelper.Process(obj, context); + } + if (tile.Image is not null) + contentItem.BuildExternalReference<Texture2DContent>(context, tile.Image); + } + + ContentLogger.Log($"Processed tileset '{tileset.Name}'"); + + return contentItem; + } + catch (Exception ex) + { + context.Logger.LogImportantMessage(ex.Message); + throw ex; + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetWriter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetWriter.cs new file mode 100644 index 0000000..48690ad --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapTilesetWriter.cs @@ -0,0 +1,145 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; +using MonoGame.Extended.Tiled; +using System; +using System.Globalization; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentTypeWriter] + public class TiledMapTilesetWriter : ContentTypeWriter<TiledMapTilesetContentItem> + { + public override string GetRuntimeReader(TargetPlatform targetPlatform) => "MonoGame.Extended.Tiled.TiledMapTilesetReader, MonoGame.Extended.Tiled"; + + public override string GetRuntimeType(TargetPlatform targetPlatform) => "MonoGame.Extended.Tiled.TiledMapTileset, MonoGame.Extended.Tiled"; + + protected override void Write(ContentWriter writer, TiledMapTilesetContentItem contentItem) + { + try + { + WriteTileset(writer, contentItem.Data, contentItem); + } + catch (Exception ex) + { + ContentLogger.Logger.LogImportantMessage(ex.StackTrace); + throw; + } + } + + public static void WriteTileset(ContentWriter writer, TiledMapTilesetContent tileset, IExternalReferenceRepository externalReferenceRepository) + { + var externalReference = externalReferenceRepository.GetExternalReference<Texture2DContent>(tileset.Image?.Source); + writer.WriteExternalReference(externalReference); + writer.Write(tileset.Class ?? tileset.Type ?? string.Empty); + writer.Write(tileset.TileWidth); + writer.Write(tileset.TileHeight); + writer.Write(tileset.TileCount); + writer.Write(tileset.Spacing); + writer.Write(tileset.Margin); + writer.Write(tileset.Columns); + writer.Write(tileset.Tiles.Count); + + foreach (var tilesetTile in tileset.Tiles) + WriteTilesetTile(writer, tilesetTile, externalReferenceRepository); + + writer.WriteTiledMapProperties(tileset.Properties); + } + + private static void WriteTilesetTile(ContentWriter writer, TiledMapTilesetTileContent tilesetTile, + IExternalReferenceRepository externalReferenceRepository) + { + var externalReference = externalReferenceRepository.GetExternalReference<Texture2DContent>(tilesetTile.Image?.Source); + writer.WriteExternalReference(externalReference); + + writer.Write(tilesetTile.LocalIdentifier); + writer.Write(tilesetTile.Type); + writer.Write(tilesetTile.Frames.Count); + writer.Write(tilesetTile.Objects.Count); + + foreach (var @object in tilesetTile.Objects) + WriteObject(writer, @object); + + foreach (var frame in tilesetTile.Frames) + { + writer.Write(frame.TileIdentifier); + writer.Write(frame.Duration); + } + + writer.WriteTiledMapProperties(tilesetTile.Properties); + } + + private static void WriteObject(ContentWriter writer, TiledMapObjectContent @object) + { + var type = GetObjectType(@object); + + writer.Write((byte)type); + + writer.Write(@object.Identifier); + writer.Write(@object.Name ?? string.Empty); + writer.Write(@object.Class ?? @object.Type ?? string.Empty); + writer.Write(@object.X); + writer.Write(@object.Y); + writer.Write(@object.Width); + writer.Write(@object.Height); + writer.Write(@object.Rotation); + writer.Write(@object.Visible); + + writer.WriteTiledMapProperties(@object.Properties); + + switch (type) + { + case TiledMapObjectType.Rectangle: + case TiledMapObjectType.Ellipse: + break; + case TiledMapObjectType.Tile: + writer.Write(@object.GlobalIdentifier); + break; + case TiledMapObjectType.Polygon: + WritePolyPoints(writer, @object.Polygon.Points); + break; + case TiledMapObjectType.Polyline: + WritePolyPoints(writer, @object.Polyline.Points); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + // ReSharper disable once SuggestBaseTypeForParameter + private static void WritePolyPoints(ContentWriter writer, string @string) + { + var stringPoints = @string.Split(' '); + + writer.Write(stringPoints.Length); + + foreach (var stringPoint in stringPoints) + { + var xy = stringPoint.Split(','); + var x = float.Parse(xy[0], CultureInfo.InvariantCulture.NumberFormat); + writer.Write(x); + var y = float.Parse(xy[1], CultureInfo.InvariantCulture.NumberFormat); + writer.Write(y); + } + } + + public static TiledMapObjectType GetObjectType(TiledMapObjectContent content) + { + if (content.GlobalIdentifier > 0) + return TiledMapObjectType.Tile; + + if (content.Ellipse != null) + return TiledMapObjectType.Ellipse; + + if (content.Polygon != null) + return TiledMapObjectType.Polygon; + + // ReSharper disable once ConvertIfStatementToReturnStatement + if (content.Polyline != null) + return TiledMapObjectType.Polyline; + + return TiledMapObjectType.Rectangle; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapWriter.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapWriter.cs new file mode 100644 index 0000000..126debb --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Content.Pipeline/Tiled/TiledMapWriter.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Graphics; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; +using MonoGame.Extended.Tiled; +using MonoGame.Extended.Tiled.Serialization; + +namespace MonoGame.Extended.Content.Pipeline.Tiled +{ + [ContentTypeWriter] + public class TiledMapWriter : ContentTypeWriter<TiledMapContentItem> + { + private TiledMapContentItem _contentItem; + + protected override void Write(ContentWriter writer, TiledMapContentItem contentItem) + { + _contentItem = contentItem; + + var map = contentItem.Data; + + try + { + WriteMetaData(writer, map); + WriteTilesets(writer, map.Tilesets); + WriteLayers(writer, map.Layers); + } + catch (Exception ex) + { + ContentLogger.Logger.LogImportantMessage(ex.StackTrace); + throw; + } + } + + private static void WriteMetaData(ContentWriter writer, TiledMapContent map) + { + writer.Write(map.Class ?? map.Type ?? string.Empty); + writer.Write(map.Width); + writer.Write(map.Height); + writer.Write(map.TileWidth); + writer.Write(map.TileHeight); + writer.Write(ColorHelper.FromHex(map.BackgroundColor)); + writer.Write((byte)map.RenderOrder); + writer.Write((byte)map.Orientation); + writer.WriteTiledMapProperties(map.Properties); + } + + private void WriteTilesets(ContentWriter writer, IReadOnlyCollection<TiledMapTilesetContent> tilesets) + { + writer.Write(tilesets.Count); + + foreach (var tileset in tilesets) + WriteTileset(writer, tileset); + } + + private void WriteTileset(ContentWriter writer, TiledMapTilesetContent tileset) + { + writer.Write(tileset.FirstGlobalIdentifier); + + if (!string.IsNullOrWhiteSpace(tileset.Source)) + { + writer.Write(true); + writer.WriteExternalReference(_contentItem.GetExternalReference<TiledMapTilesetContent>(tileset.Source)); + } + else + { + writer.Write(false); + TiledMapTilesetWriter.WriteTileset(writer, tileset, _contentItem); + } + } + + private void WriteLayers(ContentWriter writer, IReadOnlyCollection<TiledMapLayerContent> layers) + { + writer.Write(layers.Count); + + foreach (var layer in layers) + WriteLayer(writer, layer); + } + + private void WriteLayer(ContentWriter writer, TiledMapLayerContent layer) + { + writer.Write((byte)layer.LayerType); + + writer.Write(layer.Name ?? string.Empty); + writer.Write(layer.Class ?? layer.Type ?? string.Empty); + writer.Write(layer.Visible); + writer.Write(layer.Opacity); + writer.Write(layer.OffsetX); + writer.Write(layer.OffsetY); + writer.Write(layer.ParallaxX); + writer.Write(layer.ParallaxY); + + writer.WriteTiledMapProperties(layer.Properties); + + switch (layer.LayerType) + { + case TiledMapLayerType.ImageLayer: + WriteImageLayer(writer, (TiledMapImageLayerContent)layer); + break; + case TiledMapLayerType.TileLayer: + WriteTileLayer(writer, (TiledMapTileLayerContent)layer); + break; + case TiledMapLayerType.ObjectLayer: + WriteObjectLayer(writer, (TiledMapObjectLayerContent)layer); + break; + case TiledMapLayerType.GroupLayer: + WriteLayers(writer, ((TiledMapGroupLayerContent)layer).Layers); + break; + default: + throw new ArgumentOutOfRangeException(nameof(layer.LayerType)); + } + } + + private void WriteImageLayer(ContentWriter writer, TiledMapImageLayerContent imageLayer) + { + var externalReference = _contentItem.GetExternalReference<Texture2DContent>(imageLayer.Image.Source); + writer.WriteExternalReference(externalReference); + writer.Write(new Vector2(imageLayer.X, imageLayer.Y)); + } + + // ReSharper disable once SuggestBaseTypeForParameter + private static void WriteTileLayer(ContentWriter writer, TiledMapTileLayerContent tileLayer) + { + writer.Write(tileLayer.Width); + writer.Write(tileLayer.Height); + + writer.Write(tileLayer.Tiles.Length); + + foreach (var tile in tileLayer.Tiles) + { + writer.Write(tile.GlobalTileIdentifierWithFlags); + writer.Write(tile.X); + writer.Write(tile.Y); + } + } + + private static void WriteObjectLayer(ContentWriter writer, TiledMapObjectLayerContent layer) + { + writer.Write(ColorHelper.FromHex(layer.Color)); + writer.Write((byte)layer.DrawOrder); + + writer.Write(layer.Objects.Count); + + foreach (var @object in layer.Objects) + WriteObject(writer, @object); + } + + + private static void WriteObject(ContentWriter writer, TiledMapObjectContent @object) + { + var type = GetObjectType(@object); + + writer.Write((byte)type); + + writer.Write(@object.Identifier); + writer.Write(@object.Name ?? string.Empty); + writer.Write(@object.Class ?? @object.Type ?? string.Empty); + writer.Write(@object.X); + writer.Write(@object.Y); + writer.Write(@object.Width); + writer.Write(@object.Height); + writer.Write(@object.Rotation); + writer.Write(@object.Visible); + + writer.WriteTiledMapProperties(@object.Properties); + + switch (type) + { + case TiledMapObjectType.Rectangle: + case TiledMapObjectType.Ellipse: + break; + case TiledMapObjectType.Tile: + writer.Write(@object.GlobalIdentifier); + break; + case TiledMapObjectType.Polygon: + WritePolyPoints(writer, @object.Polygon.Points); + break; + case TiledMapObjectType.Polyline: + WritePolyPoints(writer, @object.Polyline.Points); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + // ReSharper disable once SuggestBaseTypeForParameter + private static void WritePolyPoints(ContentWriter writer, string @string) + { + var stringPoints = @string.Split(' '); + + writer.Write(stringPoints.Length); + + foreach (var stringPoint in stringPoints) + { + var xy = stringPoint.Split(','); + var x = float.Parse(xy[0], CultureInfo.InvariantCulture.NumberFormat); + writer.Write(x); + var y = float.Parse(xy[1], CultureInfo.InvariantCulture.NumberFormat); + writer.Write(y); + } + } + + public static TiledMapObjectType GetObjectType(TiledMapObjectContent content) + { + if (content.GlobalIdentifier > 0) + return TiledMapObjectType.Tile; + + if (content.Ellipse != null) + return TiledMapObjectType.Ellipse; + + if (content.Polygon != null) + return TiledMapObjectType.Polygon; + + // ReSharper disable once ConvertIfStatementToReturnStatement + if (content.Polyline != null) + return TiledMapObjectType.Polyline; + + return TiledMapObjectType.Rectangle; + } + + public override string GetRuntimeType(TargetPlatform targetPlatform) => "MonoGame.Extended.Tiled.TiledMap, MonoGame.Extended.Tiled"; + + public override string GetRuntimeReader(TargetPlatform targetPlatform) => "MonoGame.Extended.Tiled.TiledMapReader, MonoGame.Extended.Tiled"; + } +} |