TiledMapProcessor.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.IO.Compression;
  5. using System.Linq;
  6. using Microsoft.Xna.Framework.Content.Pipeline;
  7. using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
  8. using MonoGame.Extended.Content.Tiled;
  9. using MonoGame.Extended.Tiled;
  10. namespace MonoGame.Extended.Content.Pipeline.Tiled
  11. {
  12. public static class TiledMapContentHelper
  13. {
  14. public static void Process(TiledMapObjectContent obj, ContentProcessorContext context)
  15. {
  16. if (!string.IsNullOrWhiteSpace(obj.TemplateSource))
  17. {
  18. var externalReference = new ExternalReference<TiledMapObjectLayerContent>(obj.TemplateSource);
  19. var template = context.BuildAndLoadAsset<TiledMapObjectLayerContent, TiledMapObjectTemplateContent>(externalReference, "");
  20. // Nothing says a template can't reference another template.
  21. // Yay recusion!
  22. Process(template.Object, context);
  23. if (!obj._globalIdentifier.HasValue && template.Object._globalIdentifier.HasValue)
  24. obj.GlobalIdentifier = template.Object.GlobalIdentifier;
  25. if (!obj._height.HasValue && template.Object._height.HasValue)
  26. obj.Height = template.Object.Height;
  27. if (!obj._identifier.HasValue && template.Object._identifier.HasValue)
  28. obj.Identifier = template.Object.Identifier;
  29. if (!obj._rotation.HasValue && template.Object._rotation.HasValue)
  30. obj.Rotation = template.Object.Rotation;
  31. if (!obj._visible.HasValue && template.Object._visible.HasValue)
  32. obj.Visible = template.Object.Visible;
  33. if (!obj._width.HasValue && template.Object._width.HasValue)
  34. obj.Width = template.Object.Width;
  35. if (!obj._x.HasValue && template.Object._x.HasValue)
  36. obj.X = template.Object.X;
  37. if (!obj._y.HasValue && template.Object._y.HasValue)
  38. obj.Y = template.Object.Y;
  39. if (obj.Ellipse == null && template.Object.Ellipse != null)
  40. obj.Ellipse = template.Object.Ellipse;
  41. if (string.IsNullOrWhiteSpace(obj.Name) && !string.IsNullOrWhiteSpace(template.Object.Name))
  42. obj.Name = template.Object.Name;
  43. if (obj.Polygon == null && template.Object.Polygon != null)
  44. obj.Polygon = template.Object.Polygon;
  45. if (obj.Polyline == null && template.Object.Polyline != null)
  46. obj.Polyline = template.Object.Polyline;
  47. foreach (var tProperty in template.Object.Properties)
  48. {
  49. if (!obj.Properties.Exists(p => p.Name == tProperty.Name))
  50. obj.Properties.Add(tProperty);
  51. }
  52. if (string.IsNullOrWhiteSpace(obj.Type) && !string.IsNullOrWhiteSpace(template.Object.Type))
  53. obj.Type = template.Object.Type;
  54. if (string.IsNullOrWhiteSpace(obj.Class) && !string.IsNullOrWhiteSpace(template.Object.Class))
  55. obj.Class = template.Object.Class;
  56. }
  57. }
  58. }
  59. [ContentProcessor(DisplayName = "Tiled Map Processor - MonoGame.Extended")]
  60. public class TiledMapProcessor : ContentProcessor<TiledMapContentItem, TiledMapContentItem>
  61. {
  62. public override TiledMapContentItem Process(TiledMapContentItem contentItem, ContentProcessorContext context)
  63. {
  64. try
  65. {
  66. ContentLogger.Logger = context.Logger;
  67. var map = contentItem.Data;
  68. if (map.Orientation == TiledMapOrientationContent.Hexagonal || map.Orientation == TiledMapOrientationContent.Staggered)
  69. throw new NotSupportedException($"{map.Orientation} Tiled Maps are currently not implemented!");
  70. foreach (var tileset in map.Tilesets)
  71. {
  72. if (string.IsNullOrWhiteSpace(tileset.Source))
  73. {
  74. // Load the Texture2DContent for the tileset as it will be saved into the map content file.
  75. contentItem.BuildExternalReference<Texture2DContent>(context, tileset.Image);
  76. }
  77. else
  78. {
  79. // Link to the tileset for the content loader to load at runtime.
  80. //var externalReference = new ExternalReference<TiledMapTilesetContent>(tileset.Source);
  81. //tileset.Content = context.BuildAsset<TiledMapTilesetContent, TiledMapTilesetContent>(externalReference, "");
  82. contentItem.BuildExternalReference<TiledMapTilesetContent>(context, tileset.Source);
  83. }
  84. }
  85. ProcessLayers(contentItem, map, context, map.Layers);
  86. return contentItem;
  87. }
  88. catch (Exception ex)
  89. {
  90. context.Logger.LogImportantMessage(ex.Message);
  91. throw;
  92. }
  93. }
  94. private static void ProcessLayers(TiledMapContentItem contentItem, TiledMapContent map, ContentProcessorContext context, List<TiledMapLayerContent> layers)
  95. {
  96. foreach (var layer in layers)
  97. {
  98. switch (layer)
  99. {
  100. case TiledMapImageLayerContent imageLayer:
  101. ContentLogger.Log($"Processing image layer '{imageLayer.Name}'");
  102. contentItem.BuildExternalReference<Texture2DContent>(context, imageLayer.Image);
  103. ContentLogger.Log($"Processed image layer '{imageLayer.Name}'");
  104. break;
  105. case TiledMapTileLayerContent tileLayer when tileLayer.Data.Chunks.Count > 0:
  106. throw new NotSupportedException($"{map.FilePath} contains data chunks. These are currently not supported.");
  107. case TiledMapTileLayerContent tileLayer:
  108. var data = tileLayer.Data;
  109. var encodingType = data.Encoding ?? "xml";
  110. var compressionType = data.Compression ?? "xml";
  111. ContentLogger.Log($"Processing tile layer '{tileLayer.Name}': Encoding: '{encodingType}', Compression: '{compressionType}'");
  112. var tileData = DecodeTileLayerData(encodingType, tileLayer);
  113. var tiles = CreateTiles(map.RenderOrder, map.Width, map.Height, tileData);
  114. tileLayer.Tiles = tiles;
  115. ContentLogger.Log($"Processed tile layer '{tileLayer}': {tiles.Length} tiles");
  116. break;
  117. case TiledMapObjectLayerContent objectLayer:
  118. ContentLogger.Log($"Processing object layer '{objectLayer.Name}'");
  119. foreach (var obj in objectLayer.Objects)
  120. TiledMapContentHelper.Process(obj, context);
  121. ContentLogger.Log($"Processed object layer '{objectLayer.Name}'");
  122. break;
  123. case TiledMapGroupLayerContent groupLayer:
  124. ProcessLayers(contentItem, map, context, groupLayer.Layers);
  125. break;
  126. }
  127. }
  128. }
  129. private static List<TiledMapTileContent> DecodeTileLayerData(string encodingType, TiledMapTileLayerContent tileLayer)
  130. {
  131. List<TiledMapTileContent> tiles;
  132. switch (encodingType)
  133. {
  134. case "xml":
  135. tiles = tileLayer.Data.Tiles;
  136. break;
  137. case "csv":
  138. tiles = DecodeCommaSeperatedValuesData(tileLayer.Data);
  139. break;
  140. case "base64":
  141. tiles = DecodeBase64Data(tileLayer.Data, tileLayer.Width, tileLayer.Height);
  142. break;
  143. default:
  144. throw new NotSupportedException($"The tile layer encoding '{encodingType}' is not supported.");
  145. }
  146. return tiles;
  147. }
  148. private static TiledMapTile[] CreateTiles(TiledMapTileDrawOrderContent renderOrder, int mapWidth, int mapHeight, List<TiledMapTileContent> tileData)
  149. {
  150. TiledMapTile[] tiles;
  151. switch (renderOrder)
  152. {
  153. case TiledMapTileDrawOrderContent.LeftDown:
  154. tiles = CreateTilesInLeftDownOrder(tileData, mapWidth, mapHeight).ToArray();
  155. break;
  156. case TiledMapTileDrawOrderContent.LeftUp:
  157. tiles = CreateTilesInLeftUpOrder(tileData, mapWidth, mapHeight).ToArray();
  158. break;
  159. case TiledMapTileDrawOrderContent.RightDown:
  160. tiles = CreateTilesInRightDownOrder(tileData, mapWidth, mapHeight).ToArray();
  161. break;
  162. case TiledMapTileDrawOrderContent.RightUp:
  163. tiles = CreateTilesInRightUpOrder(tileData, mapWidth, mapHeight).ToArray();
  164. break;
  165. default:
  166. throw new NotSupportedException($"{renderOrder} is not supported.");
  167. }
  168. return tiles.ToArray();
  169. }
  170. private static IEnumerable<TiledMapTile> CreateTilesInLeftDownOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight)
  171. {
  172. for (var y = 0; y < mapHeight; y++)
  173. {
  174. for (var x = mapWidth - 1; x >= 0; x--)
  175. {
  176. var dataIndex = x + y * mapWidth;
  177. var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier;
  178. if (globalIdentifier == 0)
  179. continue;
  180. var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y);
  181. yield return tile;
  182. }
  183. }
  184. }
  185. private static IEnumerable<TiledMapTile> CreateTilesInLeftUpOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight)
  186. {
  187. for (var y = mapHeight - 1; y >= 0; y--)
  188. {
  189. for (var x = mapWidth - 1; x >= 0; x--)
  190. {
  191. var dataIndex = x + y * mapWidth;
  192. var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier;
  193. if (globalIdentifier == 0)
  194. continue;
  195. var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y);
  196. yield return tile;
  197. }
  198. }
  199. }
  200. private static IEnumerable<TiledMapTile> CreateTilesInRightDownOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight)
  201. {
  202. for (var y = 0; y < mapHeight; y++)
  203. {
  204. for (var x = 0; x < mapWidth; x++)
  205. {
  206. var dataIndex = x + y * mapWidth;
  207. var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier;
  208. if (globalIdentifier == 0)
  209. continue;
  210. var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y);
  211. yield return tile;
  212. }
  213. }
  214. }
  215. private static IEnumerable<TiledMapTile> CreateTilesInRightUpOrder(List<TiledMapTileContent> tileLayerData, int mapWidth, int mapHeight)
  216. {
  217. for (var y = mapHeight - 1; y >= 0; y--)
  218. {
  219. for (var x = mapWidth - 1; x >= 0; x--)
  220. {
  221. var dataIndex = x + y * mapWidth;
  222. var globalIdentifier = tileLayerData[dataIndex].GlobalIdentifier;
  223. if (globalIdentifier == 0)
  224. continue;
  225. var tile = new TiledMapTile(globalIdentifier, (ushort)x, (ushort)y);
  226. yield return tile;
  227. }
  228. }
  229. }
  230. private static List<TiledMapTileContent> DecodeBase64Data(TiledMapTileLayerDataContent data, int width, int height)
  231. {
  232. var tileList = new List<TiledMapTileContent>();
  233. var encodedData = data.Value.Trim();
  234. var decodedData = Convert.FromBase64String(encodedData);
  235. using (var stream = OpenStream(decodedData, data.Compression))
  236. {
  237. using (var reader = new BinaryReader(stream))
  238. {
  239. data.Tiles = new List<TiledMapTileContent>();
  240. for (var y = 0; y < width; y++)
  241. {
  242. for (var x = 0; x < height; x++)
  243. {
  244. var gid = reader.ReadUInt32();
  245. tileList.Add(new TiledMapTileContent
  246. {
  247. GlobalIdentifier = gid
  248. });
  249. }
  250. }
  251. }
  252. }
  253. return tileList;
  254. }
  255. private static List<TiledMapTileContent> DecodeCommaSeperatedValuesData(TiledMapTileLayerDataContent data)
  256. {
  257. return data.Value
  258. .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
  259. .Select(uint.Parse)
  260. .Select(x => new TiledMapTileContent { GlobalIdentifier = x })
  261. .ToList();
  262. }
  263. private static Stream OpenStream(byte[] decodedData, string compressionMode)
  264. {
  265. var memoryStream = new MemoryStream(decodedData, false);
  266. return compressionMode switch
  267. {
  268. "gzip" => new GZipStream(memoryStream, CompressionMode.Decompress),
  269. "zlib" => new ZLibStream(memoryStream, CompressionMode.Decompress),
  270. _ => memoryStream
  271. };
  272. }
  273. }
  274. }