diff options
Diffstat (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled')
67 files changed, 2814 insertions, 0 deletions
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/ContentReaderExtensions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/ContentReaderExtensions.cs new file mode 100644 index 0000000..86f1b6b --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/ContentReaderExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Xna.Framework.Content; + +namespace MonoGame.Extended.Tiled +{ + public static class ContentReaderExtensions + { + public static void ReadTiledMapProperties(this ContentReader reader, TiledMapProperties properties) + { + var count = reader.ReadInt32(); + + for (var i = 0; i < count; i++) + { + var key = reader.ReadString(); + var value = new TiledMapPropertyValue(reader.ReadString()); + ReadTiledMapProperties(reader, value.Properties); + properties[key] = value; + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/MonoGame.Extended.Tiled.csproj b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/MonoGame.Extended.Tiled.csproj new file mode 100644 index 0000000..a52cc65 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/MonoGame.Extended.Tiled.csproj @@ -0,0 +1,13 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <Description>Support for Tiled maps to make MonoGame more awesome. See http://www.mapeditor.org</Description> + <PackageTags>monogame tiled maps orthographic isometric</PackageTags> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\MonoGame.Extended.Graphics\MonoGame.Extended.Graphics.csproj" /> + <ProjectReference Include="..\MonoGame.Extended\MonoGame.Extended.csproj" /> + </ItemGroup> + +</Project> diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapAnimatedLayerModel.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapAnimatedLayerModel.cs new file mode 100644 index 0000000..791819a --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapAnimatedLayerModel.cs @@ -0,0 +1,32 @@ +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace MonoGame.Extended.Tiled.Renderers +{ + public sealed class TiledMapAnimatedLayerModel : TiledMapLayerModel + { + public TiledMapAnimatedLayerModel(GraphicsDevice graphicsDevice, Texture2D texture, VertexPositionTexture[] vertices, ushort[] indices, TiledMapTilesetAnimatedTile[] animatedTilesetTiles, TiledMapTileFlipFlags[] animatedTilesetTileFlipFlags) + : base(graphicsDevice, texture, vertices, indices) + { + Vertices = vertices; + AnimatedTilesetTiles = animatedTilesetTiles; + _animatedTilesetFlipFlags = animatedTilesetTileFlipFlags; + } + + public VertexPositionTexture[] Vertices { get; } + public TiledMapTilesetAnimatedTile[] AnimatedTilesetTiles { get; } + private readonly TiledMapTileFlipFlags[] _animatedTilesetFlipFlags; + + public ReadOnlySpan<TiledMapTileFlipFlags> AnimatedTilesetFlipFlags => _animatedTilesetFlipFlags; + + protected override VertexBuffer CreateVertexBuffer(GraphicsDevice graphicsDevice, int vertexCount) + { + return new DynamicVertexBuffer(graphicsDevice, VertexPositionTexture.VertexDeclaration, vertexCount, BufferUsage.WriteOnly); + } + + protected override IndexBuffer CreateIndexBuffer(GraphicsDevice graphicsDevice, int indexCount) + { + return new DynamicIndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, indexCount, BufferUsage.WriteOnly); ; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapAnimatedLayerModelBuilder.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapAnimatedLayerModelBuilder.cs new file mode 100644 index 0000000..7afcc21 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapAnimatedLayerModelBuilder.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled.Renderers +{ + public class TiledMapAnimatedLayerModelBuilder : TiledMapLayerModelBuilder<TiledMapAnimatedLayerModel> + { + public TiledMapAnimatedLayerModelBuilder() + { + AnimatedTilesetTiles = new List<TiledMapTilesetAnimatedTile>(); + AnimatedTilesetFlipFlags = new List<TiledMapTileFlipFlags>(); + } + + public List<TiledMapTilesetAnimatedTile> AnimatedTilesetTiles { get; } + public List<TiledMapTileFlipFlags> AnimatedTilesetFlipFlags { get; } + + protected override void ClearBuffers() + { + AnimatedTilesetTiles.Clear(); + AnimatedTilesetFlipFlags.Clear(); + } + + protected override TiledMapAnimatedLayerModel CreateModel(GraphicsDevice graphicsDevice, Texture2D texture) + { + return new TiledMapAnimatedLayerModel(graphicsDevice, texture, Vertices.ToArray(), Indices.ToArray(), AnimatedTilesetTiles.ToArray(), AnimatedTilesetFlipFlags.ToArray()); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapEffect.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapEffect.cs new file mode 100644 index 0000000..b24e2e9 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapEffect.cs @@ -0,0 +1,37 @@ +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.Graphics.Effects; + +namespace MonoGame.Extended.Tiled.Renderers +{ + public interface ITiledMapEffect : IEffectMatrices, ITextureEffect + { + float Alpha { get; set; } + } + + public class TiledMapEffect : DefaultEffect, ITiledMapEffect + { + public TiledMapEffect(GraphicsDevice graphicsDevice) + : base(graphicsDevice) + { + Initialize(); + } + + public TiledMapEffect(GraphicsDevice graphicsDevice, byte[] byteCode) + : base(graphicsDevice, byteCode) + { + Initialize(); + } + + public TiledMapEffect(Effect cloneSource) + : base(cloneSource) + { + Initialize(); + } + + private void Initialize() + { + VertexColorEnabled = false; + TextureEnabled = true; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapLayerModel.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapLayerModel.cs new file mode 100644 index 0000000..f837e1a --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapLayerModel.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled.Renderers +{ + public abstract class TiledMapLayerModel : IDisposable + { + protected TiledMapLayerModel(GraphicsDevice graphicsDevice, Texture2D texture, VertexPositionTexture[] vertices, ushort[] indices) + { + Texture = texture; + + // ReSharper disable once VirtualMemberCallInConstructor + VertexBuffer = CreateVertexBuffer(graphicsDevice, vertices.Length); + VertexBuffer.SetData(vertices, 0, vertices.Length); + + // ReSharper disable once VirtualMemberCallInConstructor + IndexBuffer = CreateIndexBuffer(graphicsDevice, indices.Length); + IndexBuffer.SetData(indices, 0, indices.Length); + + TriangleCount = indices.Length / 3; + } + + public void Dispose() + { + IndexBuffer.Dispose(); + VertexBuffer.Dispose(); + } + + public Texture2D Texture { get; } + public VertexBuffer VertexBuffer { get; } + public IndexBuffer IndexBuffer { get; } + public int TriangleCount { get; } + + protected abstract VertexBuffer CreateVertexBuffer(GraphicsDevice graphicsDevice, int vertexCount); + protected abstract IndexBuffer CreateIndexBuffer(GraphicsDevice graphicsDevice, int indexCount); + + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapLayerModelBuilder.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapLayerModelBuilder.cs new file mode 100644 index 0000000..fbc99d6 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapLayerModelBuilder.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled.Renderers +{ + public abstract class TiledMapLayerModelBuilder<T> + { + protected TiledMapLayerModelBuilder() + { + Indices = new List<ushort>(); + Vertices = new List<VertexPositionTexture>(); + } + + public List<ushort> Indices { get; } + public List<VertexPositionTexture> Vertices { get; } + public bool IsFull => Vertices.Count + TiledMapHelper.VerticesPerTile >= TiledMapHelper.MaximumVerticesPerModel; + public bool IsBuildable => Vertices.Any(); + + protected abstract void ClearBuffers(); + protected abstract T CreateModel(GraphicsDevice graphicsDevice, Texture2D texture); + + public T Build(GraphicsDevice graphicsDevice, Texture2D texture) + { + var model = CreateModel(graphicsDevice, texture); + Vertices.Clear(); + Indices.Clear(); + ClearBuffers(); + return model; + } + + public void AddSprite(Texture2D texture, Point2 position, Rectangle sourceRectangle, TiledMapTileFlipFlags flipFlags) + { + Indices.AddRange(CreateTileIndices(Vertices.Count)); + Debug.Assert(Indices.Count <= TiledMapHelper.MaximumIndicesPerModel); + + Vertices.AddRange(CreateVertices(texture, position, sourceRectangle, flipFlags)); + Debug.Assert(Vertices.Count <= TiledMapHelper.MaximumVerticesPerModel); + } + + private static IEnumerable<VertexPositionTexture> CreateVertices(Texture2D texture, Vector2 position, Rectangle sourceRectangle, TiledMapTileFlipFlags flags = TiledMapTileFlipFlags.None) + { + var reciprocalWidth = 1f / texture.Width; + var reciprocalHeight = 1f / texture.Height; + var texelLeft = sourceRectangle.X * reciprocalWidth; + var texelTop = sourceRectangle.Y * reciprocalHeight; + var texelRight = (sourceRectangle.X + sourceRectangle.Width) * reciprocalWidth; + var texelBottom = (sourceRectangle.Y + sourceRectangle.Height) * reciprocalHeight; + + VertexPositionTexture vertexTopLeft, vertexTopRight, vertexBottomLeft, vertexBottomRight; + + vertexTopLeft.Position = new Vector3(position, 0); + vertexTopRight.Position = new Vector3(position + new Vector2(sourceRectangle.Width, 0), 0); + vertexBottomLeft.Position = new Vector3(position + new Vector2(0, sourceRectangle.Height), 0); + vertexBottomRight.Position = new Vector3(position + new Vector2(sourceRectangle.Width, sourceRectangle.Height), 0); + + vertexTopLeft.TextureCoordinate.Y = texelTop; + vertexTopLeft.TextureCoordinate.X = texelLeft; + + vertexTopRight.TextureCoordinate.Y = texelTop; + vertexTopRight.TextureCoordinate.X = texelRight; + + vertexBottomLeft.TextureCoordinate.Y = texelBottom; + vertexBottomLeft.TextureCoordinate.X = texelLeft; + + vertexBottomRight.TextureCoordinate.Y = texelBottom; + vertexBottomRight.TextureCoordinate.X = texelRight; + + var flipDiagonally = (flags & TiledMapTileFlipFlags.FlipDiagonally) != 0; + var flipHorizontally = (flags & TiledMapTileFlipFlags.FlipHorizontally) != 0; + var flipVertically = (flags & TiledMapTileFlipFlags.FlipVertically) != 0; + + if (flipDiagonally) + { + FloatHelper.Swap(ref vertexTopRight.TextureCoordinate.X, ref vertexBottomLeft.TextureCoordinate.X); + FloatHelper.Swap(ref vertexTopRight.TextureCoordinate.Y, ref vertexBottomLeft.TextureCoordinate.Y); + } + + if (flipHorizontally) + { + if (flipDiagonally) + { + FloatHelper.Swap(ref vertexTopLeft.TextureCoordinate.Y, ref vertexTopRight.TextureCoordinate.Y); + FloatHelper.Swap(ref vertexBottomLeft.TextureCoordinate.Y, ref vertexBottomRight.TextureCoordinate.Y); + } + else + { + FloatHelper.Swap(ref vertexTopLeft.TextureCoordinate.X, ref vertexTopRight.TextureCoordinate.X); + FloatHelper.Swap(ref vertexBottomLeft.TextureCoordinate.X, ref vertexBottomRight.TextureCoordinate.X); + } + } + + if (flipVertically) + { + if (flipDiagonally) + { + FloatHelper.Swap(ref vertexTopLeft.TextureCoordinate.X, ref vertexBottomLeft.TextureCoordinate.X); + FloatHelper.Swap(ref vertexTopRight.TextureCoordinate.X, ref vertexBottomRight.TextureCoordinate.X); + } + else + { + FloatHelper.Swap(ref vertexTopLeft.TextureCoordinate.Y, ref vertexBottomLeft.TextureCoordinate.Y); + FloatHelper.Swap(ref vertexTopRight.TextureCoordinate.Y, ref vertexBottomRight.TextureCoordinate.Y); + } + } + + yield return vertexTopLeft; + yield return vertexTopRight; + yield return vertexBottomLeft; + yield return vertexBottomRight; + } + + private static IEnumerable<ushort> CreateTileIndices(int indexOffset) + { + yield return (ushort)(0 + indexOffset); + yield return (ushort)(1 + indexOffset); + yield return (ushort)(2 + indexOffset); + yield return (ushort)(1 + indexOffset); + yield return (ushort)(3 + indexOffset); + yield return (ushort)(2 + indexOffset); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapModel.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapModel.cs new file mode 100644 index 0000000..52e56bc --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapModel.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace MonoGame.Extended.Tiled.Renderers +{ + public class TiledMapModel : IDisposable + { + private readonly TiledMap _map; + private readonly Dictionary<TiledMapTileset, List<TiledMapTilesetAnimatedTile>> _animatedTilesByTileset; + + public TiledMapModel(TiledMap map, Dictionary<TiledMapLayer, TiledMapLayerModel[]> layersOfLayerModels) + { + _map = map; + LayersOfLayerModels = layersOfLayerModels; + _animatedTilesByTileset = _map.Tilesets + .ToDictionary(i => i, i => i.Tiles.OfType<TiledMapTilesetAnimatedTile>() + .ToList()); + } + + public void Dispose() + { + foreach (var layerModel in LayersOfLayerModels) + foreach (var model in layerModel.Value) + model.Dispose(); + } + + public ReadOnlyCollection<TiledMapTileset> Tilesets => _map.Tilesets; + public ReadOnlyCollection<TiledMapLayer> Layers => _map.Layers; + + // each layer has many models + public Dictionary<TiledMapLayer, TiledMapLayerModel[]> LayersOfLayerModels { get; } + + public IEnumerable<TiledMapTilesetAnimatedTile> GetAnimatedTiles(int tilesetIndex) + { + var tileset = _map.Tilesets[tilesetIndex]; + return _animatedTilesByTileset[tileset]; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapModelBuilder.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapModelBuilder.cs new file mode 100644 index 0000000..b96925e --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapModelBuilder.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled.Renderers +{ + public class TiledMapModelBuilder + { + private readonly GraphicsDevice _graphicsDevice; + + public TiledMapModelBuilder(GraphicsDevice graphicsDevice) + { + _graphicsDevice = graphicsDevice; + } + + private IEnumerable<TiledMapLayerModel> CreateLayerModels(TiledMap map, TiledMapLayer layer) + { + switch(layer) + { + case TiledMapTileLayer tileLayer: + return CreateTileLayerModels(map, tileLayer); + case TiledMapImageLayer imageLayer: + return CreateImageLayerModels(imageLayer); + default: + return new List<TiledMapLayerModel>(); + } + + } + + private IEnumerable<TiledMapLayerModel> CreateImageLayerModels(TiledMapImageLayer imageLayer) + { + var modelBuilder = new TiledMapStaticLayerModelBuilder(); + modelBuilder.AddSprite(imageLayer.Image, imageLayer.Position, imageLayer.Image.Bounds, TiledMapTileFlipFlags.None); + yield return modelBuilder.Build(_graphicsDevice, imageLayer.Image); + } + + private IEnumerable<TiledMapLayerModel> CreateTileLayerModels(TiledMap map, TiledMapTileLayer tileLayer) + { + var layerModels = new List<TiledMapLayerModel>(); + var staticLayerBuilder = new TiledMapStaticLayerModelBuilder(); + var animatedLayerBuilder = new TiledMapAnimatedLayerModelBuilder(); + + foreach (var tileset in map.Tilesets) + { + var firstGlobalIdentifier = map.GetTilesetFirstGlobalIdentifier(tileset); + var lastGlobalIdentifier = tileset.TileCount + firstGlobalIdentifier - 1; + var texture = tileset.Texture; + + foreach (var tile in tileLayer.Tiles.Where(t => firstGlobalIdentifier <= t.GlobalIdentifier && t.GlobalIdentifier <= lastGlobalIdentifier)) + { + var tileGid = tile.GlobalIdentifier; + var localTileIdentifier = tileGid - firstGlobalIdentifier; + var position = GetTilePosition(map, tile); + var sourceRectangle = tileset.GetTileRegion(localTileIdentifier); + var flipFlags = tile.Flags; + + // animated tiles + var tilesetTile = tileset.Tiles.FirstOrDefault(x => x.LocalTileIdentifier == localTileIdentifier); + if (tilesetTile?.Texture is not null) + { + position.Y += map.TileHeight - sourceRectangle.Height; + texture = tilesetTile.Texture; + } + + if (tilesetTile is TiledMapTilesetAnimatedTile animatedTilesetTile) + { + animatedLayerBuilder.AddSprite(texture, position, sourceRectangle, flipFlags); + animatedTilesetTile.CreateTextureRotations(tileset, flipFlags); + animatedLayerBuilder.AnimatedTilesetTiles.Add(animatedTilesetTile); + animatedLayerBuilder.AnimatedTilesetFlipFlags.Add(flipFlags); + + if (animatedLayerBuilder.IsFull) + layerModels.Add(animatedLayerBuilder.Build(_graphicsDevice, texture)); + } + else + { + staticLayerBuilder.AddSprite(texture, position, sourceRectangle, flipFlags); + + if (staticLayerBuilder.IsFull) + layerModels.Add(staticLayerBuilder.Build(_graphicsDevice, texture)); + } + } + + if (staticLayerBuilder.IsBuildable) + layerModels.Add(staticLayerBuilder.Build(_graphicsDevice, texture)); + + if (animatedLayerBuilder.IsBuildable) + layerModels.Add(animatedLayerBuilder.Build(_graphicsDevice, texture)); + } + + return layerModels; + } + + public TiledMapModel Build(TiledMap map) + { + var dictionary = new Dictionary<TiledMapLayer, TiledMapLayerModel[]>(); + foreach (var layer in map.Layers) + BuildLayer(map, layer, dictionary); + + return new TiledMapModel(map, dictionary); + } + + private void BuildLayer(TiledMap map, TiledMapLayer layer, Dictionary<TiledMapLayer, TiledMapLayerModel[]> dictionary) + { + if (layer is TiledMapGroupLayer groupLayer) + foreach (var subLayer in groupLayer.Layers) + BuildLayer(map, subLayer, dictionary); + else + dictionary.Add(layer, CreateLayerModels(map, layer).ToArray()); + } + + private static Point2 GetTilePosition(TiledMap map, TiledMapTile mapTile) + { + switch (map.Orientation) + { + case TiledMapOrientation.Orthogonal: + return TiledMapHelper.GetOrthogonalPosition(mapTile.X, mapTile.Y, map.TileWidth, map.TileHeight); + case TiledMapOrientation.Isometric: + return TiledMapHelper.GetIsometricPosition(mapTile.X, mapTile.Y, map.TileWidth, map.TileHeight); + default: + throw new NotSupportedException($"{map.Orientation} Tiled Maps are not yet implemented."); + } + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapRenderer.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapRenderer.cs new file mode 100644 index 0000000..66babad --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapRenderer.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled.Renderers +{ + public class TiledMapRenderer : IDisposable + { + private readonly TiledMapModelBuilder _mapModelBuilder; + private readonly TiledMapEffect _defaultEffect; + private readonly GraphicsDevice _graphicsDevice; + private TiledMapModel _mapModel; + private Matrix _worldMatrix = Matrix.Identity; + + public TiledMapRenderer(GraphicsDevice graphicsDevice, TiledMap map = null) + { + if (graphicsDevice == null) throw new ArgumentNullException(nameof(graphicsDevice)); + + _graphicsDevice = graphicsDevice; + _defaultEffect = new TiledMapEffect(graphicsDevice); + _mapModelBuilder = new TiledMapModelBuilder(graphicsDevice); + + if(map != null) + LoadMap(map); + } + + public void Dispose() + { + _mapModel?.Dispose(); + _defaultEffect.Dispose(); + } + + public void LoadMap(TiledMap map) + { + _mapModel?.Dispose(); + _mapModel = map != null ? _mapModelBuilder.Build(map) : null; + } + + public void Update(GameTime gameTime) + { + if(_mapModel == null) + return; + + for (var tilesetIndex = 0; tilesetIndex < _mapModel.Tilesets.Count; tilesetIndex++) + { + foreach (var animatedTilesetTile in _mapModel.GetAnimatedTiles(tilesetIndex)) + animatedTilesetTile.Update(gameTime); + } + + foreach(var layer in _mapModel.LayersOfLayerModels) + UpdateAnimatedLayerModels(layer.Value.OfType<TiledMapAnimatedLayerModel>()); + } + + private static unsafe void UpdateAnimatedLayerModels(IEnumerable<TiledMapAnimatedLayerModel> animatedLayerModels) + { + foreach (var animatedModel in animatedLayerModels) + { + // update the texture coordinates for each animated tile + fixed (VertexPositionTexture* fixedVerticesPointer = animatedModel.Vertices) + { + var verticesPointer = fixedVerticesPointer; + for (int i = 0; i < animatedModel.AnimatedTilesetTiles.Length; i++) + { + var currentFrameTextureCoordinates = animatedModel.AnimatedTilesetTiles[i].CurrentAnimationFrame.GetTextureCoordinates(animatedModel.AnimatedTilesetFlipFlags[i]); + + // ReSharper disable ArrangeRedundantParentheses + (*verticesPointer++).TextureCoordinate = currentFrameTextureCoordinates[0]; + (*verticesPointer++).TextureCoordinate = currentFrameTextureCoordinates[1]; + (*verticesPointer++).TextureCoordinate = currentFrameTextureCoordinates[2]; + (*verticesPointer++).TextureCoordinate = currentFrameTextureCoordinates[3]; + // ReSharper restore ArrangeRedundantParentheses + } + } + + // copy (upload) the updated vertices to the GPU's memory + animatedModel.VertexBuffer.SetData(animatedModel.Vertices, 0, animatedModel.Vertices.Length); + } + } + + public void Draw(Matrix? viewMatrix = null, Matrix? projectionMatrix = null, Effect effect = null, float depth = 0.0f) + { + var viewMatrix1 = viewMatrix ?? Matrix.Identity; + var projectionMatrix1 = projectionMatrix ?? Matrix.CreateOrthographicOffCenter(0, _graphicsDevice.Viewport.Width, _graphicsDevice.Viewport.Height, 0, 0, -1); + + Draw(ref viewMatrix1, ref projectionMatrix1, effect, depth); + } + + public void Draw(ref Matrix viewMatrix, ref Matrix projectionMatrix, Effect effect = null, float depth = 0.0f) + { + if (_mapModel == null) + return; + + for (var index = 0; index < _mapModel.Layers.Count; index++) + Draw(index, ref viewMatrix, ref projectionMatrix, effect, depth); + } + + public void Draw(TiledMapLayer layer, Matrix? viewMatrix = null, Matrix? projectionMatrix = null, Effect effect = null, float depth = 0.0f) + { + var viewMatrix1 = viewMatrix ?? Matrix.Identity; + var projectionMatrix1 = projectionMatrix ?? Matrix.CreateOrthographicOffCenter(0, _graphicsDevice.Viewport.Width, _graphicsDevice.Viewport.Height, 0, 0, -1); + + Draw(layer, ref viewMatrix1, ref projectionMatrix1, effect, depth); + } + + public void Draw(int layerIndex, Matrix? viewMatrix = null, Matrix? projectionMatrix = null, Effect effect = null, float depth = 0.0f) + { + var viewMatrix1 = viewMatrix ?? Matrix.Identity; + var projectionMatrix1 = projectionMatrix ?? Matrix.CreateOrthographicOffCenter(0, _graphicsDevice.Viewport.Width, _graphicsDevice.Viewport.Height, 0, 0, -1); + + Draw(layerIndex, ref viewMatrix1, ref projectionMatrix1, effect, depth); + } + + public void Draw(int layerIndex, ref Matrix viewMatrix, ref Matrix projectionMatrix, Effect effect = null, float depth = 0.0f) + { + var layer = _mapModel.Layers[layerIndex]; + + Draw(layer, ref viewMatrix, ref projectionMatrix, effect, depth); + } + + public void Draw(TiledMapLayer layer, ref Matrix viewMatrix, ref Matrix projectionMatrix, Effect effect = null, float depth = 0.0f) + { + if (_mapModel == null) + return; + + if (!layer.IsVisible) + return; + + if (layer is TiledMapObjectLayer) + return; + + Draw(layer, Vector2.Zero, Vector2.One, ref viewMatrix, ref projectionMatrix, effect, depth); + } + + private void Draw(TiledMapLayer layer, Vector2 parentOffset, Vector2 parentParallaxFactor, ref Matrix viewMatrix, ref Matrix projectionMatrix, Effect effect, float depth) + { + var offset = parentOffset + layer.Offset; + var parallaxFactor = parentParallaxFactor * layer.ParallaxFactor; + + if (layer is TiledMapGroupLayer groupLayer) + { + foreach (var subLayer in groupLayer.Layers) + Draw(subLayer, offset, parallaxFactor, ref viewMatrix, ref projectionMatrix, effect, depth); + } + else + { + _worldMatrix.Translation = new Vector3(offset, depth); + + var effect1 = effect ?? _defaultEffect; + var tiledMapEffect = effect1 as ITiledMapEffect; + if (tiledMapEffect == null) + return; + + // model-to-world transform + tiledMapEffect.World = _worldMatrix; + tiledMapEffect.View = parallaxFactor == Vector2.One ? viewMatrix : IncludeParallax(viewMatrix, parallaxFactor); + tiledMapEffect.Projection = projectionMatrix; + + foreach (var layerModel in _mapModel.LayersOfLayerModels[layer]) + { + // desired alpha + tiledMapEffect.Alpha = layer.Opacity; + + // desired texture + tiledMapEffect.Texture = layerModel.Texture; + + // bind the vertex and index buffer + _graphicsDevice.SetVertexBuffer(layerModel.VertexBuffer); + _graphicsDevice.Indices = layerModel.IndexBuffer; + + // for each pass in our effect + foreach (var pass in effect1.CurrentTechnique.Passes) + { + // apply the pass, effectively choosing which vertex shader and fragment (pixel) shader to use + pass.Apply(); + + // draw the geometry from the vertex buffer / index buffer + _graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, layerModel.TriangleCount); + } + } + } + } + + private Matrix IncludeParallax(Matrix viewMatrix, Vector2 parallaxFactor) + { + viewMatrix.Translation *=new Vector3(parallaxFactor, 1f); + return viewMatrix; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapStaticLayerModel.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapStaticLayerModel.cs new file mode 100644 index 0000000..34bf683 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapStaticLayerModel.cs @@ -0,0 +1,22 @@ +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled.Renderers +{ + public sealed class TiledMapStaticLayerModel : TiledMapLayerModel + { + public TiledMapStaticLayerModel(GraphicsDevice graphicsDevice, Texture2D texture, VertexPositionTexture[] vertices, ushort[] indices) + : base(graphicsDevice, texture, vertices, indices) + { + } + + protected override VertexBuffer CreateVertexBuffer(GraphicsDevice graphicsDevice, int vertexCount) + { + return new VertexBuffer(graphicsDevice, VertexPositionTexture.VertexDeclaration, vertexCount, BufferUsage.WriteOnly); + } + + protected override IndexBuffer CreateIndexBuffer(GraphicsDevice graphicsDevice, int indexCount) + { + return new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, indexCount, BufferUsage.WriteOnly); ; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapStaticLayerModelBuilder.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapStaticLayerModelBuilder.cs new file mode 100644 index 0000000..8c7d49b --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Renderers/TiledMapStaticLayerModelBuilder.cs @@ -0,0 +1,16 @@ +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled.Renderers +{ + public class TiledMapStaticLayerModelBuilder : TiledMapLayerModelBuilder<TiledMapStaticLayerModel> + { + protected override void ClearBuffers() + { + } + + protected override TiledMapStaticLayerModel CreateModel(GraphicsDevice graphicsDevice, Texture2D texture) + { + return new TiledMapStaticLayerModel(graphicsDevice, texture, Vertices.ToArray(), Indices.ToArray()); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapContent.cs new file mode 100644 index 0000000..30ecbfd --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapContent.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + [XmlRoot(ElementName = "map")] + public class TiledMapContent + { + public TiledMapContent() + { + Properties = new List<TiledMapPropertyContent>(); + Tilesets = new List<TiledMapTilesetContent>(); + Layers = new List<TiledMapLayerContent>(); + } + + [XmlIgnore] + public string Name { get; set; } + + [XmlIgnore] + public string FilePath { get; set; } + + // Deprecated as of Tiled 1.9.0 (replaced by "class" attribute) + [XmlAttribute(DataType = "string", AttributeName = "type")] + public string Type { get; set; } + + [XmlAttribute(DataType = "string", AttributeName = "class")] + public string Class { get; set; } + + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + + [XmlAttribute(AttributeName = "orientation")] + public TiledMapOrientationContent Orientation { get; set; } + + [XmlAttribute(AttributeName = "renderorder")] + public TiledMapTileDrawOrderContent RenderOrder { get; set; } + + [XmlAttribute(AttributeName = "backgroundcolor")] + public string BackgroundColor { get; set; } + + [XmlAttribute(AttributeName = "width")] + public int Width { get; set; } + + [XmlAttribute(AttributeName = "height")] + public int Height { get; set; } + + [XmlAttribute(AttributeName = "tilewidth")] + public int TileWidth { get; set; } + + [XmlAttribute(AttributeName = "tileheight")] + public int TileHeight { get; set; } + + [XmlAttribute(AttributeName = "hexsidelength")] + public int HexSideLength { get; set; } + + [XmlAttribute(AttributeName = "staggeraxis")] + public TiledMapStaggerAxisContent StaggerAxis { get; set; } + + [XmlAttribute(AttributeName = "staggerindex")] + public TiledMapStaggerIndexContent StaggerIndex { get; set; } + + [XmlElement(ElementName = "tileset")] + public List<TiledMapTilesetContent> Tilesets { get; set; } + + [XmlElement(ElementName = "layer", Type = typeof(TiledMapTileLayerContent))] + [XmlElement(ElementName = "imagelayer", Type = typeof(TiledMapImageLayerContent))] + [XmlElement(ElementName = "objectgroup", Type = typeof(TiledMapObjectLayerContent))] + [XmlElement(ElementName = "group", Type = typeof(TiledMapGroupLayerContent))] + public List<TiledMapLayerContent> Layers { get; set; } + + [XmlArray("properties")] + [XmlArrayItem("property")] + public List<TiledMapPropertyContent> Properties { get; set; } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapEllipseContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapEllipseContent.cs new file mode 100644 index 0000000..78f7bdd --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapEllipseContent.cs @@ -0,0 +1,6 @@ +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapEllipseContent + { + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapGroupLayerContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapGroupLayerContent.cs new file mode 100644 index 0000000..a72b276 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapGroupLayerContent.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapGroupLayerContent : TiledMapLayerContent + { + protected TiledMapGroupLayerContent() + : base(TiledMapLayerType.GroupLayer) + { + } + + [XmlElement(ElementName = "layer", Type = typeof(TiledMapTileLayerContent))] + [XmlElement(ElementName = "imagelayer", Type = typeof(TiledMapImageLayerContent))] + [XmlElement(ElementName = "objectgroup", Type = typeof(TiledMapObjectLayerContent))] + [XmlElement(ElementName = "group", Type = typeof(TiledMapGroupLayerContent))] + public List<TiledMapLayerContent> Layers { get; set; } + + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapImageContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapImageContent.cs new file mode 100644 index 0000000..714da37 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapImageContent.cs @@ -0,0 +1,44 @@ +using System.Xml.Serialization; +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapImageContent + { + //[XmlIgnore] + //public Texture2DContent Content { get; set; } + + //[XmlIgnore] + //public ExternalReference<Texture2DContent> ContentRef { get; set; } + + [XmlAttribute(AttributeName = "source")] + public string Source { get; set; } + + [XmlAttribute(AttributeName = "width")] + public int Width { get; set; } + + [XmlAttribute(AttributeName = "height")] + public int Height { get; set; } + + [XmlAttribute(AttributeName = "format")] + public string Format { get; set; } + + [XmlAttribute(AttributeName = "trans")] + public string RawTransparentColor { get; set; } = string.Empty; + + [XmlIgnore] + public Color TransparentColor + { + get => RawTransparentColor == string.Empty ? Color.Transparent : ColorHelper.FromHex(RawTransparentColor); + set => RawTransparentColor = ColorHelper.ToHex(value); + } + + [XmlElement(ElementName = "data")] + public TiledMapTileLayerDataContent Data { get; set; } + + public override string ToString() + { + return Source; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapImageLayerContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapImageLayerContent.cs new file mode 100644 index 0000000..f347d1e --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapImageLayerContent.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapImageLayerContent : TiledMapLayerContent + { + [XmlAttribute(AttributeName = "x")] + public int X { get; set; } + + [XmlAttribute(AttributeName = "y")] + public int Y { get; set; } + + [XmlElement(ElementName = "image")] + public TiledMapImageContent Image { get; set; } + + public TiledMapImageLayerContent() + : base(TiledMapLayerType.ImageLayer) + { + Opacity = 1.0f; + Visible = true; + Properties = new List<TiledMapPropertyContent>(); + } + + public override string ToString() + { + return $"{Name}: {Image}"; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapLayerContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapLayerContent.cs new file mode 100644 index 0000000..01d8a9d --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapLayerContent.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + [XmlInclude(typeof(TiledMapTileLayerContent))] + [XmlInclude(typeof(TiledMapImageLayerContent))] + [XmlInclude(typeof(TiledMapObjectLayerContent))] + public abstract class TiledMapLayerContent + { + protected TiledMapLayerContent(TiledMapLayerType layerType) + { + LayerType = layerType; + Opacity = 1.0f; + ParallaxX = 1.0f; + ParallaxY = 1.0f; + Visible = true; + Properties = new List<TiledMapPropertyContent>(); + } + + [XmlAttribute(AttributeName = "name")] + public string Name { get; set; } + + // Deprecated as of Tiled 1.9.0 (replaced by "class" attribute) + [XmlAttribute(DataType = "string", AttributeName = "type")] + public string Type { get; set; } + + [XmlAttribute(DataType = "string", AttributeName = "class")] + public string Class { get; set; } + + [XmlAttribute(AttributeName = "opacity")] + public float Opacity { get; set; } + + [XmlAttribute(AttributeName = "visible")] + public bool Visible { get; set; } + + [XmlAttribute(AttributeName = "offsetx")] + public float OffsetX { get; set; } + + [XmlAttribute(AttributeName = "offsety")] + public float OffsetY { get; set; } + + [XmlAttribute(AttributeName = "parallaxx")] + public float ParallaxX { get; set; } + + [XmlAttribute(AttributeName = "parallaxy")] + public float ParallaxY { get; set; } + + [XmlArray("properties")] + [XmlArrayItem("property")] + public List<TiledMapPropertyContent> Properties { get; set; } + + [XmlIgnore] + public TiledMapLayerType LayerType { get; } + + public override string ToString() + { + return Name; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapLayerModelContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapLayerModelContent.cs new file mode 100644 index 0000000..4c2a761 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapLayerModelContent.cs @@ -0,0 +1,139 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapLayerModelContent + { + private readonly List<VertexPositionTexture> _vertices; + private readonly List<ushort> _indices; + + public string LayerName { get; } + public ReadOnlyCollection<VertexPositionTexture> Vertices { get; } + public ReadOnlyCollection<ushort> Indices { get; } + public Size2 ImageSize { get; } + public string TextureAssetName { get; } + + public TiledMapLayerModelContent(string layerName, TiledMapImageContent image) + { + LayerName = layerName; + _vertices = new List<VertexPositionTexture>(); + Vertices = new ReadOnlyCollection<VertexPositionTexture>(_vertices); + _indices = new List<ushort>(); + Indices = new ReadOnlyCollection<ushort>(_indices); + ImageSize = new Size2(image.Width, image.Height); + TextureAssetName = Path.ChangeExtension(image.Source, null); + } + + public TiledMapLayerModelContent(string layerName, TiledMapTilesetContent tileset) + : this(layerName, tileset.Image) + { + } + + public void AddTileVertices(Point2 position, Rectangle? sourceRectangle = null, TiledMapTileFlipFlags flags = TiledMapTileFlipFlags.None) + { + float texelLeft, texelTop, texelRight, texelBottom; + var sourceRectangle1 = sourceRectangle ?? new Rectangle(0, 0, (int)ImageSize.Width, (int)ImageSize.Height); + + if (sourceRectangle.HasValue) + { + var reciprocalWidth = 1f / ImageSize.Width; + var reciprocalHeight = 1f / ImageSize.Height; + texelLeft = sourceRectangle1.X * reciprocalWidth; + texelTop = sourceRectangle1.Y * reciprocalHeight; + texelRight = (sourceRectangle1.X + sourceRectangle1.Width) * reciprocalWidth; + texelBottom = (sourceRectangle1.Y + sourceRectangle1.Height) * reciprocalHeight; + } + else + { + texelLeft = 0; + texelTop = 0; + texelBottom = 1; + texelRight = 1; + } + + VertexPositionTexture vertexTopLeft, vertexTopRight, vertexBottomLeft, vertexBottomRight; + + vertexTopLeft.Position = new Vector3(position, 0); + vertexTopRight.Position = new Vector3(position + new Vector2(sourceRectangle1.Width, 0), 0); + vertexBottomLeft.Position = new Vector3(position + new Vector2(0, sourceRectangle1.Height), 0); + vertexBottomRight.Position = + new Vector3(position + new Vector2(sourceRectangle1.Width, sourceRectangle1.Height), 0); + + vertexTopLeft.TextureCoordinate.Y = texelTop; + vertexTopLeft.TextureCoordinate.X = texelLeft; + + vertexTopRight.TextureCoordinate.Y = texelTop; + vertexTopRight.TextureCoordinate.X = texelRight; + + vertexBottomLeft.TextureCoordinate.Y = texelBottom; + vertexBottomLeft.TextureCoordinate.X = texelLeft; + + vertexBottomRight.TextureCoordinate.Y = texelBottom; + vertexBottomRight.TextureCoordinate.X = texelRight; + + var flipDiagonally = (flags & TiledMapTileFlipFlags.FlipDiagonally) != 0; + var flipHorizontally = (flags & TiledMapTileFlipFlags.FlipHorizontally) != 0; + var flipVertically = (flags & TiledMapTileFlipFlags.FlipVertically) != 0; + + if (flipDiagonally) + { + FloatHelper.Swap(ref vertexTopRight.TextureCoordinate.X, ref vertexBottomLeft.TextureCoordinate.X); + FloatHelper.Swap(ref vertexTopRight.TextureCoordinate.Y, ref vertexBottomLeft.TextureCoordinate.Y); + } + + if (flipHorizontally) + { + if (flipDiagonally) + { + FloatHelper.Swap(ref vertexTopLeft.TextureCoordinate.Y, ref vertexTopRight.TextureCoordinate.Y); + FloatHelper.Swap(ref vertexBottomLeft.TextureCoordinate.Y, ref vertexBottomRight.TextureCoordinate.Y); + } + else + { + FloatHelper.Swap(ref vertexTopLeft.TextureCoordinate.X, ref vertexTopRight.TextureCoordinate.X); + FloatHelper.Swap(ref vertexBottomLeft.TextureCoordinate.X, ref vertexBottomRight.TextureCoordinate.X); + } + } + + if (flipVertically) + if (flipDiagonally) + { + FloatHelper.Swap(ref vertexTopLeft.TextureCoordinate.X, ref vertexBottomLeft.TextureCoordinate.X); + FloatHelper.Swap(ref vertexTopRight.TextureCoordinate.X, ref vertexBottomRight.TextureCoordinate.X); + } + else + { + FloatHelper.Swap(ref vertexTopLeft.TextureCoordinate.Y, ref vertexBottomLeft.TextureCoordinate.Y); + FloatHelper.Swap(ref vertexTopRight.TextureCoordinate.Y, ref vertexBottomRight.TextureCoordinate.Y); + } + + _vertices.Add(vertexTopLeft); + _vertices.Add(vertexTopRight); + _vertices.Add(vertexBottomLeft); + _vertices.Add(vertexBottomRight); + + Debug.Assert(Vertices.Count <= TiledMapHelper.MaximumVerticesPerModel); + } + + public void AddTileIndices() + { + var indexOffset = Vertices.Count; + + Debug.Assert(3 + indexOffset <= TiledMapHelper.MaximumVerticesPerModel); + + _indices.Add((ushort)(0 + indexOffset)); + _indices.Add((ushort)(1 + indexOffset)); + _indices.Add((ushort)(2 + indexOffset)); + _indices.Add((ushort)(1 + indexOffset)); + _indices.Add((ushort)(3 + indexOffset)); + _indices.Add((ushort)(2 + indexOffset)); + + Debug.Assert(Indices.Count <= TiledMapHelper.MaximumIndicesPerModel); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectContent.cs new file mode 100644 index 0000000..76e2ba2 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectContent.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + // This content class is going to be a lot more complex than the others we use. + // Objects can reference a template file which has starting values for the + // object. The value in the object file overrides any value specified in the + // template. All values have to be able to store a null value so we know if the + // XML parser actually found a value for the property and not just a default + // value. Default values are used when the object and any templates don't + // specify a value. + public class TiledMapObjectContent + { + // TODO: HACK These shouldn't be public + public uint? _globalIdentifier; + public int? _identifier; + public float? _height; + public float? _rotation; + public bool? _visible; + public float? _width; + public float? _x; + public float? _y; + + [XmlAttribute(DataType = "int", AttributeName = "id")] + public int Identifier { get => _identifier ?? 0; set => _identifier = value; } + + [XmlAttribute(DataType = "string", AttributeName = "name")] + public string Name { get; set; } + + // Deprecated as of Tiled 1.9.0 (replaced by "class" attribute) + [XmlAttribute(DataType = "string", AttributeName = "type")] + public string Type { get; set; } + + [XmlAttribute(DataType = "string", AttributeName = "class")] + public string Class { get; set; } + + [XmlAttribute(DataType = "float", AttributeName = "x")] + public float X { get => _x ?? 0; set => _x = value; } + + [XmlAttribute(DataType = "float", AttributeName = "y")] + public float Y { get => _y ?? 0; set => _y = value; } + + [XmlAttribute(DataType = "float", AttributeName = "width")] + public float Width { get => _width ?? 0; set => _width = value; } + + [XmlAttribute(DataType = "float", AttributeName = "height")] + public float Height { get => _height ?? 0; set => _height = value; } + + [XmlAttribute(DataType = "float", AttributeName = "rotation")] + public float Rotation { get => _rotation ?? 0; set => _rotation = value; } + + [XmlAttribute(DataType = "boolean", AttributeName = "visible")] + public bool Visible { get => _visible ?? true; set => _visible = value; } + + [XmlArray("properties")] + [XmlArrayItem("property")] + public List<TiledMapPropertyContent> Properties { get; set; } + + [XmlAttribute(DataType = "unsignedInt", AttributeName = "gid")] + public uint GlobalIdentifier { get => _globalIdentifier??0; set => _globalIdentifier = value; } + + [XmlElement(ElementName = "ellipse")] + public TiledMapEllipseContent Ellipse { get; set; } + + [XmlElement(ElementName = "polygon")] + public TiledMapPolygonContent Polygon { get; set; } + + [XmlElement(ElementName = "polyline")] + public TiledMapPolylineContent Polyline { get; set; } + + [XmlAttribute(DataType = "string", AttributeName = "template")] + public string TemplateSource { get; set; } + + + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectDrawOrderContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectDrawOrderContent.cs new file mode 100644 index 0000000..f441375 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectDrawOrderContent.cs @@ -0,0 +1,10 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public enum TiledMapObjectDrawOrderContent : byte + { + [XmlEnum(Name = "topdown")] TopDown, + [XmlEnum(Name = "index")] Manual + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectLayerContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectLayerContent.cs new file mode 100644 index 0000000..30669b5 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectLayerContent.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapObjectLayerContent : TiledMapLayerContent + { + public TiledMapObjectLayerContent() + : base(TiledMapLayerType.ObjectLayer) + { + Objects = new List<TiledMapObjectContent>(); + } + + [XmlAttribute(AttributeName = "color")] + public string Color { get; set; } + + [XmlElement(ElementName = "object")] + public List<TiledMapObjectContent> Objects { get; set; } + + [XmlAttribute(AttributeName = "draworder")] + public TiledMapObjectDrawOrderContent DrawOrder { get; set; } + + public override string ToString() + { + return Name; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectTemplateContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectTemplateContent.cs new file mode 100644 index 0000000..096f528 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapObjectTemplateContent.cs @@ -0,0 +1,17 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + [XmlRoot(ElementName = "template")] + public class TiledMapObjectTemplateContent + { + [XmlElement(ElementName = "tileset")] + public TiledMapTilesetContent Tileset { get; set; } + + //[XmlIgnore] + //public ExternalReference<TiledMapTilesetContent> TilesetReference { get; set; } + + [XmlElement(ElementName = "object")] + public TiledMapObjectContent Object { get; set; } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapOrientationContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapOrientationContent.cs new file mode 100644 index 0000000..af83824 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapOrientationContent.cs @@ -0,0 +1,12 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public enum TiledMapOrientationContent : byte + { + [XmlEnum(Name = "orthogonal")] Orthogonal, + [XmlEnum(Name = "isometric")] Isometric, + [XmlEnum(Name = "staggered")] Staggered, + [XmlEnum(Name = "hexagonal")] Hexagonal + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapPolygonContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapPolygonContent.cs new file mode 100644 index 0000000..e4a3d5e --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapPolygonContent.cs @@ -0,0 +1,10 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapPolygonContent + { + [XmlAttribute(AttributeName = "points")] + public string Points { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapPolylineContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapPolylineContent.cs new file mode 100644 index 0000000..ba15f59 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapPolylineContent.cs @@ -0,0 +1,10 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapPolylineContent + { + [XmlAttribute(AttributeName = "points")] + public string Points { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapPropertyContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapPropertyContent.cs new file mode 100644 index 0000000..10cdfc1 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapPropertyContent.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapPropertyContent + { + [XmlAttribute(AttributeName = "name")] + public string Name { get; set; } + + [XmlAttribute(AttributeName = "value")] + public string ValueAttribute { get; set; } + + [XmlText] + public string ValueBody { get; set; } + + [XmlArray("properties")] + [XmlArrayItem("property")] + public List<TiledMapPropertyContent> Properties { get; set; } + + public string Value => ValueAttribute ?? ValueBody; + + public override string ToString() + { + return $"{Name}: {Value}"; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapStaggerAxisContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapStaggerAxisContent.cs new file mode 100644 index 0000000..7a073f5 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapStaggerAxisContent.cs @@ -0,0 +1,10 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public enum TiledMapStaggerAxisContent : byte + { + [XmlEnum("x")]X, + [XmlEnum("y")]Y + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapStaggerIndexContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapStaggerIndexContent.cs new file mode 100644 index 0000000..834c3a6 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapStaggerIndexContent.cs @@ -0,0 +1,10 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public enum TiledMapStaggerIndexContent : byte + { + [XmlEnum("even")]Even, + [XmlEnum("odd")]Odd + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileContent.cs new file mode 100644 index 0000000..fcd7a69 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileContent.cs @@ -0,0 +1,9 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public struct TiledMapTileContent + { + [XmlAttribute(AttributeName = "gid")] public uint GlobalIdentifier; + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileDrawOrderContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileDrawOrderContent.cs new file mode 100644 index 0000000..4762afd --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileDrawOrderContent.cs @@ -0,0 +1,12 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public enum TiledMapTileDrawOrderContent : byte + { + [XmlEnum(Name = "right-down")] RightDown, + [XmlEnum(Name = "right-up")] RightUp, + [XmlEnum(Name = "left-down")] LeftDown, + [XmlEnum(Name = "left-up")] LeftUp + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileLayerContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileLayerContent.cs new file mode 100644 index 0000000..49f5ccf --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileLayerContent.cs @@ -0,0 +1,30 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapTileLayerContent : TiledMapLayerContent + { + public TiledMapTileLayerContent() + : base(TiledMapLayerType.TileLayer) + { + } + + [XmlAttribute(AttributeName = "x")] + public int X { get; set; } + + [XmlAttribute(AttributeName = "y")] + public int Y { get; set; } + + [XmlAttribute(AttributeName = "width")] + public int Width { get; set; } + + [XmlAttribute(AttributeName = "height")] + public int Height { get; set; } + + [XmlElement(ElementName = "data")] + public TiledMapTileLayerDataContent Data { get; set; } + + [XmlIgnore] + public TiledMapTile[] Tiles { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileLayerDataChunkContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileLayerDataChunkContent.cs new file mode 100644 index 0000000..0ec10d9 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileLayerDataChunkContent.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapTileLayerDataChunkContent + { + [XmlAttribute(AttributeName = "x")] + public int X { get; set; } + + [XmlAttribute(AttributeName = "y")] + public int Y { get; set; } + + [XmlAttribute(AttributeName = "width")] + public int Width { get; set; } + + [XmlAttribute(AttributeName = "height")] + public int Height { get; set; } + + [XmlElement(ElementName = "tile")] + public List<TiledMapTileContent> Tiles { get; set; } + + [XmlText] + public string Value { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileLayerDataContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileLayerDataContent.cs new file mode 100644 index 0000000..c19a02e --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileLayerDataContent.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapTileLayerDataContent + { + public TiledMapTileLayerDataContent() + { + Tiles = new List<TiledMapTileContent>(); + } + + [XmlAttribute(AttributeName = "encoding")] + public string Encoding { get; set; } + + [XmlAttribute(AttributeName = "compression")] + public string Compression { get; set; } + + [XmlElement(ElementName = "tile")] + public List<TiledMapTileContent> Tiles { get; set; } + + [XmlElement(ElementName = "chunk")] + public List<TiledMapTileLayerDataChunkContent> Chunks { get; set; } + + [XmlText] + public string Value { get; set; } + + public override string ToString() + { + return $"{Encoding} {Compression}"; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileOffsetContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileOffsetContent.cs new file mode 100644 index 0000000..7d2af87 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTileOffsetContent.cs @@ -0,0 +1,17 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + [XmlRoot(ElementName = "tileoffset")] + public class TiledMapTileOffsetContent + { + [XmlAttribute(AttributeName = "x")] public int X; + + [XmlAttribute(AttributeName = "y")] public int Y; + + public override string ToString() + { + return $"{X}, {Y}"; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetContent.cs new file mode 100644 index 0000000..fa04c31 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetContent.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + [XmlRoot(ElementName = "tileset")] + public class TiledMapTilesetContent + { + public TiledMapTilesetContent() + { + TileOffset = new TiledMapTileOffsetContent(); + Tiles = new List<TiledMapTilesetTileContent>(); + Properties = new List<TiledMapPropertyContent>(); + } + + [XmlAttribute(AttributeName = "firstgid")] + public int FirstGlobalIdentifier { get; set; } + + [XmlAttribute(AttributeName = "source")] + public string Source { get; set; } + + [XmlAttribute(AttributeName = "name")] + public string Name { get; set; } + + // Deprecated as of Tiled 1.9.0 (replaced by "class" attribute) + [XmlAttribute(DataType = "string", AttributeName = "type")] + public string Type { get; set; } + + [XmlAttribute(DataType = "string", AttributeName = "class")] + public string Class { get; set; } + + [XmlAttribute(AttributeName = "tilewidth")] + public int TileWidth { get; set; } + + [XmlAttribute(AttributeName = "tileheight")] + public int TileHeight { get; set; } + + [XmlAttribute(AttributeName = "spacing")] + public int Spacing { get; set; } + + [XmlAttribute(AttributeName = "margin")] + public int Margin { get; set; } + + [XmlAttribute(AttributeName = "columns")] + public int Columns { get; set; } + + [XmlAttribute(AttributeName = "tilecount")] + public int TileCount { get; set; } + + [XmlElement(ElementName = "tileoffset")] + public TiledMapTileOffsetContent TileOffset { get; set; } + + [XmlElement(ElementName = "grid")] + public TiledMapTilesetGridContent Grid { get; set; } + + [XmlElement(ElementName = "tile")] + public List<TiledMapTilesetTileContent> Tiles { get; set; } + + [XmlArray("properties")] + [XmlArrayItem("property")] + public List<TiledMapPropertyContent> Properties { get; set; } + + [XmlElement(ElementName = "image")] + public TiledMapImageContent Image { get; set; } + + public bool ContainsGlobalIdentifier(int globalIdentifier) + { + return globalIdentifier >= FirstGlobalIdentifier && globalIdentifier < FirstGlobalIdentifier + TileCount; + } + + public override string ToString() + { + return $"{Name}: {Image}"; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetGridContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetGridContent.cs new file mode 100644 index 0000000..a9071b8 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetGridContent.cs @@ -0,0 +1,16 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapTilesetGridContent + { + [XmlAttribute(AttributeName = "orientation")] + public TiledMapOrientationContent Orientation { get; set; } + + [XmlAttribute(AttributeName = "width")] + public int Width { get; set; } + + [XmlAttribute(AttributeName = "height")] + public int Height { get; set; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetTileAnimationFrameContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetTileAnimationFrameContent.cs new file mode 100644 index 0000000..5a17137 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetTileAnimationFrameContent.cs @@ -0,0 +1,18 @@ +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapTilesetTileAnimationFrameContent + { + [XmlAttribute(AttributeName = "tileid")] + public int TileIdentifier { get; set; } + + [XmlAttribute(AttributeName = "duration")] + public int Duration { get; set; } + + public override string ToString() + { + return $"TileID: {TileIdentifier}, Duration: {Duration}"; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetTileContent.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetTileContent.cs new file mode 100644 index 0000000..2a82197 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/Serialization/TiledMapTilesetTileContent.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace MonoGame.Extended.Tiled.Serialization +{ + public class TiledMapTilesetTileContent + { + public TiledMapTilesetTileContent() + { + Properties = new List<TiledMapPropertyContent>(); + Type = string.Empty; + } + + [XmlAttribute(AttributeName = "id")] + public int LocalIdentifier { get; set; } + + [XmlAttribute(AttributeName = "type")] + public string Type { get; set; } + + [XmlElement(ElementName = "image")] + public TiledMapImageContent Image { get; set; } + + [XmlArray("objectgroup")] + [XmlArrayItem("object")] + public List<TiledMapObjectContent> Objects { get; set; } + + [XmlArray("animation")] + [XmlArrayItem("frame")] + public List<TiledMapTilesetTileAnimationFrameContent> Frames { get; set; } + + [XmlArray("properties")] + [XmlArrayItem("property")] + public List<TiledMapPropertyContent> Properties { get; set; } + + public override string ToString() + { + return LocalIdentifier.ToString(); + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMap.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMap.cs new file mode 100644 index 0000000..b3486bd --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMap.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public sealed class TiledMap + { + private readonly List<TiledMapImageLayer> _imageLayers = new List<TiledMapImageLayer>(); + private readonly List<TiledMapLayer> _layers = new List<TiledMapLayer>(); + private readonly Dictionary<string, TiledMapLayer> _layersByName = new Dictionary<string, TiledMapLayer>(); + private readonly List<TiledMapObjectLayer> _objectLayers = new List<TiledMapObjectLayer>(); + private readonly List<TiledMapTileLayer> _tileLayers = new List<TiledMapTileLayer>(); + private readonly List<TiledMapTileset> _tilesets = new List<TiledMapTileset>(); + private readonly List<Tuple<TiledMapTileset, int>> _firstGlobalIdentifiers = new List<Tuple<TiledMapTileset, int>>(); + + public string Name { get; } + public string Type { get; } + public int Width { get; } + public int Height { get; } + public int TileWidth { get; } + public int TileHeight { get; } + public TiledMapTileDrawOrder RenderOrder { get; } + public TiledMapOrientation Orientation { get; } + public TiledMapProperties Properties { get; } + public ReadOnlyCollection<TiledMapTileset> Tilesets { get; } + public ReadOnlyCollection<TiledMapLayer> Layers { get; } + public ReadOnlyCollection<TiledMapImageLayer> ImageLayers { get; } + public ReadOnlyCollection<TiledMapTileLayer> TileLayers { get; } + public ReadOnlyCollection<TiledMapObjectLayer> ObjectLayers { get; } + + public Color? BackgroundColor { get; set; } + public int WidthInPixels => Width * TileWidth; + public int HeightInPixels => Height * TileHeight; + + private TiledMap() + { + Layers = new ReadOnlyCollection<TiledMapLayer>(_layers); + ImageLayers = new ReadOnlyCollection<TiledMapImageLayer>(_imageLayers); + TileLayers = new ReadOnlyCollection<TiledMapTileLayer>(_tileLayers); + ObjectLayers = new ReadOnlyCollection<TiledMapObjectLayer>(_objectLayers); + Tilesets = new ReadOnlyCollection<TiledMapTileset>(_tilesets); + Properties = new TiledMapProperties(); + } + + public TiledMap(string name, string type, int width, int height, int tileWidth, int tileHeight, TiledMapTileDrawOrder renderOrder, TiledMapOrientation orientation, Color? backgroundColor = null) + : this() + { + Name = name; + Type = type; + Width = width; + Height = height; + TileWidth = tileWidth; + TileHeight = tileHeight; + RenderOrder = renderOrder; + Orientation = orientation; + BackgroundColor = backgroundColor; + } + + public void AddTileset(TiledMapTileset tileset, int firstGlobalIdentifier) + { + _tilesets.Add(tileset); + _firstGlobalIdentifiers.Add(new Tuple<TiledMapTileset, int>(tileset, firstGlobalIdentifier)); + } + + public void AddLayer(TiledMapLayer layer) + => AddLayer(layer, true); + + private void AddLayer(TiledMapLayer layer, bool root) + { + if (root) _layers.Add(layer); + + if (_layersByName.ContainsKey(layer.Name)) + throw new ArgumentException($"The TiledMap '{Name}' contains two or more layers named '{layer.Name}'. Please ensure all layers have unique names."); + + _layersByName.Add(layer.Name, layer); + + switch(layer) + { + case TiledMapImageLayer imageLayer: + _imageLayers.Add(imageLayer); + break; + case TiledMapTileLayer tileLayer: + _tileLayers.Add(tileLayer); + break; + case TiledMapObjectLayer objectLayer: + _objectLayers.Add(objectLayer); + break; + case TiledMapGroupLayer groupLayer: + foreach (var subLayer in groupLayer.Layers) + AddLayer(subLayer, false); + break; + } + } + + public TiledMapLayer GetLayer(string layerName) + { + TiledMapLayer layer; + _layersByName.TryGetValue(layerName, out layer); + return layer; + } + + public T GetLayer<T>(string layerName) + where T : TiledMapLayer + { + return GetLayer(layerName) as T; + } + + public TiledMapTileset GetTilesetByTileGlobalIdentifier(int tileIdentifier) + { + foreach (var tileset in _firstGlobalIdentifiers) + { + if (tileIdentifier >= tileset.Item2 && tileIdentifier < tileset.Item2 + tileset.Item1.TileCount) + return tileset.Item1; + } + + return null; + } + + public int GetTilesetFirstGlobalIdentifier(TiledMapTileset tileset) + { + return _firstGlobalIdentifiers.FirstOrDefault(t => t.Item1 == tileset).Item2; + } + + private static int CountLayers(TiledMapLayer layer) + { + var value = 0; + if (layer is TiledMapGroupLayer groupLayer) + foreach (var subLayer in groupLayer.Layers) + value += CountLayers(subLayer); + else + value = 1; + + return value; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapEllipseObject.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapEllipseObject.cs new file mode 100644 index 0000000..9140d62 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapEllipseObject.cs @@ -0,0 +1,17 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public sealed class TiledMapEllipseObject : TiledMapObject + { + public TiledMapEllipseObject(int identifier, string name, Size2 size, Vector2 position, float rotation = 0, float opacity = 1, bool isVisible = true, string type = null) + : base(identifier, name, size, position, rotation, opacity, isVisible, type) + { + Radius = new Vector2(size.Width / 2.0f, size.Height / 2.0f); + Center = new Vector2(position.X + Radius.X, position.Y); + } + + public Vector2 Center { get; } + public Vector2 Radius { get; } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapGroupLayer.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapGroupLayer.cs new file mode 100644 index 0000000..48d43d1 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapGroupLayer.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public class TiledMapGroupLayer : TiledMapLayer + { + public List<TiledMapLayer> Layers { get; } + public TiledMapGroupLayer(string name, string type, List<TiledMapLayer> layers, Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1, bool isVisible = true) + : base(name, type, offset, parallaxFactor, opacity, isVisible) + { + Layers = layers; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapHelper.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapHelper.cs new file mode 100644 index 0000000..43edf67 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapHelper.cs @@ -0,0 +1,51 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public static class TiledMapHelper + { + // 4 vertices per tile + public const int VerticesPerTile = 4; + // 2 triangles per tile (mesh), with each triangle indexing 3 out of 4 vertices, so 6 vertices + public const int IndicesPerTile = 6; + // by using ushort type for indices we are limited to indexing vertices from 0 to 65535 + // this limits us on how many vertices can fit inside a single vertex buffer (65536 vertices) + public const int MaximumVerticesPerModel = ushort.MaxValue + 1; + // and thus, we know how many tiles we can fit inside a vertex or index buffer (16384 tiles) + public const int MaximumTilesPerGeometryContent = MaximumVerticesPerModel / VerticesPerTile; + // and thus, we also know the maximum number of indices we can fit inside a single index buffer (98304 indices) + public const int MaximumIndicesPerModel = MaximumTilesPerGeometryContent * IndicesPerTile; + // these optimal maximum numbers of course are not considering texture bindings which would practically lower the actual number of tiles per vertex / index buffer + // thus, the reason why it is a good to have ONE giant tileset (at least per layer) + + internal static Rectangle GetTileSourceRectangle(int localTileIdentifier, int tileWidth, int tileHeight, int columns, int margin, int spacing) + { + var x = margin + localTileIdentifier % columns * (tileWidth + spacing); + var y = margin + localTileIdentifier / columns * (tileHeight + spacing); + return new Rectangle(x, y, tileWidth, tileHeight); + } + + internal static Point2 GetOrthogonalPosition(int tileX, int tileY, int tileWidth, int tileHeight) + { + var x = tileX * tileWidth; + var y = tileY * tileHeight; + return new Vector2(x, y); + } + + internal static Point2 GetIsometricPosition(int tileX, int tileY, int tileWidth, int tileHeight) + { + // You can think of an isometric Tiled map as a regular orthogonal map that is rotated -45 degrees + // i.e.: the origin (0, 0) is the top tile of the diamond grid; + // (mapWidth, 0) is the far right tile of the diamond grid + // (0, mapHeight) is the far left tile of the diamond grid + // (mapWidth, mapHeight) is the bottom tile of the diamond grid + + var halfTileWidth = tileWidth * 0.5f; + var halfTileHeight = tileHeight * 0.5f; + // -1 because we want the top the tile-diamond (top-center) to be the origin in tile space + var x = (tileX - tileY - 1) * halfTileWidth; + var y = (tileX + tileY) * halfTileHeight; + return new Vector2(x, y); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapImageLayer.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapImageLayer.cs new file mode 100644 index 0000000..33bb700 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapImageLayer.cs @@ -0,0 +1,18 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled +{ + public class TiledMapImageLayer : TiledMapLayer, IMovable + { + public TiledMapImageLayer(string name, string type, Texture2D image, Vector2? position = null, Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1.0f, bool isVisible = true) + : base(name, type, offset, parallaxFactor, opacity, isVisible) + { + Image = image; + Position = position ?? Vector2.Zero; + } + + public Texture2D Image { get; } + public Vector2 Position { get; set; } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapLayer.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapLayer.cs new file mode 100644 index 0000000..6226dd1 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapLayer.cs @@ -0,0 +1,26 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public abstract class TiledMapLayer + { + public string Name { get; } + public string Type { get; } + public bool IsVisible { get; set; } + public float Opacity { get; set; } + public Vector2 Offset { get; set; } + public Vector2 ParallaxFactor { get; set; } + public TiledMapProperties Properties { get; } + + protected TiledMapLayer(string name, string type, Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1.0f, bool isVisible = true) + { + Name = name; + Type = type; + Offset = offset ?? Vector2.Zero; + ParallaxFactor = parallaxFactor ?? Vector2.One; + Opacity = opacity; + IsVisible = isVisible; + Properties = new TiledMapProperties(); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapLayerType.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapLayerType.cs new file mode 100644 index 0000000..8af5a2e --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapLayerType.cs @@ -0,0 +1,10 @@ +namespace MonoGame.Extended.Tiled +{ + public enum TiledMapLayerType : byte + { + ImageLayer = 0, + TileLayer = 1, + ObjectLayer = 2, + GroupLayer = 3 + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObject.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObject.cs new file mode 100644 index 0000000..1e25084 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObject.cs @@ -0,0 +1,35 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public abstract class TiledMapObject + { + protected TiledMapObject(int identifier, string name, Size2 size, Vector2 position, float rotation = 0, float opacity = 1, bool isVisible = true, string type = null) + { + Identifier = identifier; + Name = name; + IsVisible = isVisible; + Rotation = rotation; + Position = position; + Size = size; + Opacity = opacity; + Type = type; + Properties = new TiledMapProperties(); + } + + public int Identifier { get; } + public string Name { get; set; } + public string Type { get; set; } + public bool IsVisible { get; set; } + public float Opacity { get; set; } + public float Rotation { get; set; } + public Vector2 Position { get; } + public Size2 Size { get; set; } + public TiledMapProperties Properties { get; } + + public override string ToString() + { + return $"{Identifier}"; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObjectDrawOrder.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObjectDrawOrder.cs new file mode 100644 index 0000000..1668f66 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObjectDrawOrder.cs @@ -0,0 +1,8 @@ +namespace MonoGame.Extended.Tiled +{ + public enum TiledMapObjectDrawOrder : byte + { + TopDown, + Index, + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObjectLayer.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObjectLayer.cs new file mode 100644 index 0000000..b39b8d7 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObjectLayer.cs @@ -0,0 +1,20 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public class TiledMapObjectLayer : TiledMapLayer + { + public TiledMapObjectLayer(string name, string type, TiledMapObject[] objects, Color? color = null, TiledMapObjectDrawOrder drawOrder = TiledMapObjectDrawOrder.TopDown, + Vector2? offset = null, Vector2? parallaxFactor = null, float opacity = 1.0f, bool isVisible = true) + : base(name, type, offset, parallaxFactor, opacity, isVisible) + { + Color = color; + DrawOrder = drawOrder; + Objects = objects; + } + + public Color? Color { get; } + public TiledMapObjectDrawOrder DrawOrder { get; } + public TiledMapObject[] Objects { get; } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObjectType.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObjectType.cs new file mode 100644 index 0000000..305500a --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapObjectType.cs @@ -0,0 +1,11 @@ +namespace MonoGame.Extended.Tiled +{ + public enum TiledMapObjectType : byte + { + Rectangle, + Ellipse, + Polygon, + Polyline, + Tile + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapOrientation.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapOrientation.cs new file mode 100644 index 0000000..798c1c8 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapOrientation.cs @@ -0,0 +1,9 @@ +namespace MonoGame.Extended.Tiled +{ + public enum TiledMapOrientation + { + Orthogonal, + Isometric, + Staggered + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapPolygonObject.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapPolygonObject.cs new file mode 100644 index 0000000..2df343f --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapPolygonObject.cs @@ -0,0 +1,15 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public sealed class TiledMapPolygonObject : TiledMapObject + { + public TiledMapPolygonObject(int identifier, string name, Point2[] points, Size2 size, Vector2 position, float rotation = 0, float opacity = 1, bool isVisible = true, string type = null) + : base(identifier, name, size, position, rotation, opacity, isVisible, type) + { + Points = points; + } + + public Point2[] Points { get; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapPolylineObject.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapPolylineObject.cs new file mode 100644 index 0000000..de50dbc --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapPolylineObject.cs @@ -0,0 +1,15 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public sealed class TiledMapPolylineObject : TiledMapObject + { + public TiledMapPolylineObject(int identifier, string name, Point2[] points, Size2 size, Vector2 position, float rotation = 0, float opacity = 1, bool isVisible = true, string type = null) + : base(identifier, name, size, position, rotation, opacity, isVisible, type) + { + Points = points; + } + + public Point2[] Points { get; } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapProperties.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapProperties.cs new file mode 100644 index 0000000..651ae5d --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapProperties.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace MonoGame.Extended.Tiled +{ + public class TiledMapProperties : Dictionary<string, TiledMapPropertyValue> + { + public bool TryGetValue(string key, out string value) + { + bool result = TryGetValue(key, out TiledMapPropertyValue tmpVal); + value = result ? null : tmpVal.Value; + return result; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapPropertyValue.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapPropertyValue.cs new file mode 100644 index 0000000..d7a0893 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapPropertyValue.cs @@ -0,0 +1,32 @@ +namespace MonoGame.Extended.Tiled; + +public class TiledMapPropertyValue +{ + public string Value { get; } + + public TiledMapProperties Properties; + + public TiledMapPropertyValue() + { + Value = string.Empty; + Properties = new(); + } + + public TiledMapPropertyValue(string value) + { + Value = value; + Properties = new(); + } + + public TiledMapPropertyValue(TiledMapProperties properties) + { + Value = string.Empty; + Properties = properties; + } + + public override string ToString() => Value; + + //public static implicit operator TiledMapPropertyValue(string value) => new(value); + public static implicit operator string(TiledMapPropertyValue value) => value.Value; + public static implicit operator TiledMapProperties(TiledMapPropertyValue value) => value.Properties; +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapReader.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapReader.cs new file mode 100644 index 0000000..bd49655 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapReader.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.Content; + +namespace MonoGame.Extended.Tiled +{ + public class TiledMapReader : ContentTypeReader<TiledMap> + { + protected override TiledMap Read(ContentReader reader, TiledMap existingInstance) + { + if (existingInstance != null) + return existingInstance; + + var map = ReadTiledMap(reader); + reader.ReadTiledMapProperties(map.Properties); + ReadTilesets(reader, map); + ReadLayers(reader, map); + return map; + } + + private static TiledMap ReadTiledMap(ContentReader reader) + { + var name = reader.AssetName; + var type = reader.ReadString(); + var width = reader.ReadInt32(); + var height = reader.ReadInt32(); + var tileWidth = reader.ReadInt32(); + var tileHeight = reader.ReadInt32(); + var backgroundColor = reader.ReadColor(); + var renderOrder = (TiledMapTileDrawOrder)reader.ReadByte(); + var orientation = (TiledMapOrientation)reader.ReadByte(); + + return new TiledMap(name, type, width, height, tileWidth, tileHeight, renderOrder, orientation, backgroundColor); + } + + private static void ReadTilesets(ContentReader reader, TiledMap map) + { + var tilesetCount = reader.ReadInt32(); + + for (var i = 0; i < tilesetCount; i++) + { + var firstGlobalIdentifier = reader.ReadInt32(); + var tileset = ReadTileset(reader, map); + map.AddTileset(tileset, firstGlobalIdentifier); + } + } + + private static TiledMapTileset ReadTileset(ContentReader reader, TiledMap map) + { + var external = reader.ReadBoolean(); + var tileset = external ? reader.ReadExternalReference<TiledMapTileset>() : TiledMapTilesetReader.ReadTileset(reader); + + return tileset; + } + + private static void ReadLayers(ContentReader reader, TiledMap map) + { + foreach (var layer in ReadGroup(reader, map)) + map.AddLayer(layer); + } + private static List<TiledMapLayer> ReadGroup(ContentReader reader, TiledMap map) + { + var layerCount = reader.ReadInt32(); + var value = new List<TiledMapLayer>(layerCount); + + for (var i = 0; i < layerCount; i++) + value.Add(ReadLayer(reader, map)); + + return value; + } + + private static TiledMapLayer ReadLayer(ContentReader reader, TiledMap map) + { + var layerType = (TiledMapLayerType)reader.ReadByte(); + var name = reader.ReadString(); + var type = reader.ReadString(); + var isVisible = reader.ReadBoolean(); + var opacity = reader.ReadSingle(); + var offsetX = reader.ReadSingle(); + var offsetY = reader.ReadSingle(); + var offset = new Vector2(offsetX, offsetY); + var parallaxX = reader.ReadSingle(); + var parallaxY = reader.ReadSingle(); + var parallaxFactor = new Vector2(parallaxX, parallaxY); + var properties = new TiledMapProperties(); + + reader.ReadTiledMapProperties(properties); + + TiledMapLayer layer; + + switch (layerType) + { + case TiledMapLayerType.ImageLayer: + layer = ReadImageLayer(reader, name, type, offset, parallaxFactor, opacity, isVisible); + break; + case TiledMapLayerType.TileLayer: + layer = ReadTileLayer(reader, name, type, offset, parallaxFactor, opacity, isVisible, map); + break; + case TiledMapLayerType.ObjectLayer: + layer = ReadObjectLayer(reader, name, type, offset, parallaxFactor, opacity, isVisible, map); + break; + case TiledMapLayerType.GroupLayer: + layer = new TiledMapGroupLayer(name, type, ReadGroup(reader, map), offset, parallaxFactor, opacity, isVisible); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + foreach (var property in properties) + layer.Properties.Add(property.Key, property.Value); + + return layer; + } + + private static TiledMapLayer ReadObjectLayer(ContentReader reader, string name, string type, Vector2 offset, Vector2 parallaxFactor, float opacity, bool isVisible, TiledMap map) + { + var color = reader.ReadColor(); + var drawOrder = (TiledMapObjectDrawOrder)reader.ReadByte(); + var objectCount = reader.ReadInt32(); + var objects = new TiledMapObject[objectCount]; + + for (var i = 0; i < objectCount; i++) + objects[i] = ReadTiledMapObject(reader, map); + + return new TiledMapObjectLayer(name, type, objects, color, drawOrder, offset, parallaxFactor, opacity, isVisible); + } + + private static TiledMapObject ReadTiledMapObject(ContentReader reader, TiledMap map) + { + var objectType = (TiledMapObjectType)reader.ReadByte(); + var identifier = reader.ReadInt32(); + var name = reader.ReadString(); + var type = reader.ReadString(); + var position = new Vector2(reader.ReadSingle(), reader.ReadSingle()); + var width = reader.ReadSingle(); + var height = reader.ReadSingle(); + var size = new Size2(width, height); + var rotation = reader.ReadSingle(); + var isVisible = reader.ReadBoolean(); + var properties = new TiledMapProperties(); + const float opacity = 1.0f; + + reader.ReadTiledMapProperties(properties); + + TiledMapObject mapObject; + + switch (objectType) + { + case TiledMapObjectType.Rectangle: + mapObject = new TiledMapRectangleObject(identifier, name, size, position, rotation, opacity, isVisible, type); + break; + case TiledMapObjectType.Tile: + var globalTileIdentifierWithFlags = reader.ReadUInt32(); + var tile = new TiledMapTile(globalTileIdentifierWithFlags, (ushort)position.X, (ushort)position.Y); + var tileset = map.GetTilesetByTileGlobalIdentifier(tile.GlobalIdentifier); + var localTileIdentifier = tile.GlobalIdentifier - map.GetTilesetFirstGlobalIdentifier(tileset); + var tilesetTile = tileset.Tiles.FirstOrDefault(x => x.LocalTileIdentifier == localTileIdentifier); + mapObject = new TiledMapTileObject(identifier, name, tileset, tilesetTile, size, position, rotation, opacity, isVisible, type); + break; + case TiledMapObjectType.Ellipse: + mapObject = new TiledMapEllipseObject(identifier, name, size, position, rotation, opacity, isVisible); + break; + case TiledMapObjectType.Polygon: + mapObject = new TiledMapPolygonObject(identifier, name, ReadPoints(reader), size, position, rotation, opacity, isVisible, type); + break; + case TiledMapObjectType.Polyline: + mapObject = new TiledMapPolylineObject(identifier, name, ReadPoints(reader), size, position, rotation, opacity, isVisible, type); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + foreach (var property in properties) + mapObject.Properties.Add(property.Key, property.Value); + + return mapObject; + } + + private static Point2[] ReadPoints(ContentReader reader) + { + var pointCount = reader.ReadInt32(); + var points = new Point2[pointCount]; + + for (var i = 0; i < pointCount; i++) + { + var x = reader.ReadSingle(); + var y = reader.ReadSingle(); + points[i] = new Point2(x, y); + } + + return points; + } + + private static TiledMapImageLayer ReadImageLayer(ContentReader reader, string name, string type, Vector2 offset, Vector2 parallaxFactor, float opacity, bool isVisible) + { + var texture = reader.ReadExternalReference<Texture2D>(); + var x = reader.ReadSingle(); + var y = reader.ReadSingle(); + var position = new Vector2(x, y); + return new TiledMapImageLayer(name, type, texture, position, offset, parallaxFactor, opacity, isVisible); + } + + private static TiledMapTileLayer ReadTileLayer(ContentReader reader, string name, string type, Vector2 offset, Vector2 parallaxFactor, float opacity, bool isVisible, TiledMap map) + { + var width = reader.ReadInt32(); + var height = reader.ReadInt32(); + var tileWidth = map.TileWidth; + var tileHeight = map.TileHeight; + + var tileCount = reader.ReadInt32(); + var layer = new TiledMapTileLayer(name, type, width, height, tileWidth, tileHeight, offset, parallaxFactor, opacity, isVisible); + + for (var i = 0; i < tileCount; i++) + { + var globalTileIdentifierWithFlags = reader.ReadUInt32(); + var x = reader.ReadUInt16(); + var y = reader.ReadUInt16(); + + layer.Tiles[x + y * width] = new TiledMapTile(globalTileIdentifierWithFlags, x, y); + } + + return layer; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapRectangleObject.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapRectangleObject.cs new file mode 100644 index 0000000..de4c7ec --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapRectangleObject.cs @@ -0,0 +1,12 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public sealed class TiledMapRectangleObject : TiledMapObject + { + public TiledMapRectangleObject(int identifier, string name, Size2 size, Vector2 position, float rotation = 0, float opacity = 1, bool isVisible = true, string type = null) + : base(identifier, name, size, position, rotation, opacity, isVisible, type) + { + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTile.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTile.cs new file mode 100644 index 0000000..d3147a4 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTile.cs @@ -0,0 +1,28 @@ +namespace MonoGame.Extended.Tiled +{ + public struct TiledMapTile + { + public readonly ushort X; + public readonly ushort Y; + public readonly uint GlobalTileIdentifierWithFlags; + + public int GlobalIdentifier => (int)(GlobalTileIdentifierWithFlags & ~(uint)TiledMapTileFlipFlags.All); + public bool IsFlippedHorizontally => (GlobalTileIdentifierWithFlags & (uint)TiledMapTileFlipFlags.FlipHorizontally) != 0; + public bool IsFlippedVertically => (GlobalTileIdentifierWithFlags & (uint)TiledMapTileFlipFlags.FlipVertically) != 0; + public bool IsFlippedDiagonally => (GlobalTileIdentifierWithFlags & (uint)TiledMapTileFlipFlags.FlipDiagonally) != 0; + public bool IsBlank => GlobalIdentifier == 0; + public TiledMapTileFlipFlags Flags => (TiledMapTileFlipFlags)(GlobalTileIdentifierWithFlags & (uint)TiledMapTileFlipFlags.All); + + public TiledMapTile(uint globalTileIdentifierWithFlags, ushort x, ushort y) + { + GlobalTileIdentifierWithFlags = globalTileIdentifierWithFlags; + X = x; + Y = y; + } + + public override string ToString() + { + return $"GlobalIdentifier: {GlobalIdentifier}, Flags: {Flags}"; + } + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileDrawOrder.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileDrawOrder.cs new file mode 100644 index 0000000..a40b596 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileDrawOrder.cs @@ -0,0 +1,10 @@ +namespace MonoGame.Extended.Tiled +{ + public enum TiledMapTileDrawOrder : byte + { + RightDown, + RightUp, + LeftDown, + LeftUp + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileFlipFlags.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileFlipFlags.cs new file mode 100644 index 0000000..f883bad --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileFlipFlags.cs @@ -0,0 +1,14 @@ +using System; + +namespace MonoGame.Extended.Tiled +{ + [Flags] + public enum TiledMapTileFlipFlags : uint + { + None = 0, + FlipDiagonally = 0x20000000, + FlipVertically = 0x40000000, + FlipHorizontally = 0x80000000, + All = FlipDiagonally | FlipVertically | FlipHorizontally + } +}
\ No newline at end of file diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileLayer.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileLayer.cs new file mode 100644 index 0000000..5d07f06 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileLayer.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public class TiledMapTileLayer : TiledMapLayer + { + public TiledMapTileLayer(string name, string type, int width, int height, int tileWidth, int tileHeight, Vector2? offset = null, + Vector2? parallaxFactor = null, float opacity = 1, bool isVisible = true) + : base(name, type, offset, parallaxFactor, opacity, isVisible) + { + Width = width; + Height = height; + TileWidth = tileWidth; + TileHeight = tileHeight; + Tiles = new TiledMapTile[Width * Height]; + } + + public int Width { get; } + public int Height { get; } + public int TileWidth { get; } + public int TileHeight { get; } + public TiledMapTile[] Tiles { get; } + + public int GetTileIndex(ushort x, ushort y) + { + return x + y * Width; + } + + public bool TryGetTile(ushort x, ushort y, out TiledMapTile? tile) + { + if (x >= Width) + { + tile = null; + return false; + } + var index = GetTileIndex(x, y); + + if (index < 0 || index >= Tiles.Length) + { + tile = null; + return false; + } + + tile = Tiles[index]; + return true; + } + + public TiledMapTile GetTile(ushort x, ushort y) + { + var index = GetTileIndex(x, y); + return Tiles[index]; + } + + public void SetTile(ushort x, ushort y, uint globalIdentifier) + { + var index = GetTileIndex(x, y); + Tiles[index] = new TiledMapTile(globalIdentifier, x, y); + } + + public void RemoveTile(ushort x, ushort y) + { + SetTile(x, y, 0); + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileObject.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileObject.cs new file mode 100644 index 0000000..b4e8679 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileObject.cs @@ -0,0 +1,18 @@ +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public sealed class TiledMapTileObject : TiledMapObject + { + public TiledMapTileObject(int identifier, string name, TiledMapTileset tileset, TiledMapTilesetTile tile, + Size2 size, Vector2 position, float rotation = 0, float opacity = 1, bool isVisible = true, string type = null) + : base(identifier, name, size, position, rotation, opacity, isVisible, type) + { + Tileset = tileset; + Tile = tile; + } + + public TiledMapTilesetTile Tile { get; } + public TiledMapTileset Tileset { get; } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileset.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileset.cs new file mode 100644 index 0000000..ed14e15 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTileset.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.TextureAtlases; + +namespace MonoGame.Extended.Tiled +{ + public interface ITileset + { + int ActualWidth { get; } + int Columns { get; } + int ActualHeight { get; } + int Rows { get; } + int TileWidth { get; } + int TileHeight { get; } + Texture2D Texture { get; } + TextureRegion2D GetRegion(int column, int row); + } + + public class TiledMapTileset : ITileset + { + public TiledMapTileset(Texture2D texture, string type, int tileWidth, int tileHeight, int tileCount, int spacing, int margin, int columns) + { + Texture = texture; + Type = type; + TileWidth = tileWidth; + TileHeight = tileHeight; + TileCount = tileCount; + Spacing = spacing; + Margin = margin; + Columns = columns; + Properties = new TiledMapProperties(); + Tiles = new List<TiledMapTilesetTile>(); + } + + public string Name => Texture.Name; + public Texture2D Texture { get; } + + public TextureRegion2D GetRegion(int column, int row) + { + var x = Margin + column * (TileWidth + Spacing); + var y = Margin + row * (TileHeight + Spacing); + return new TextureRegion2D(Texture, x, y, TileWidth, TileHeight); + } + + public string Type { get; } + public int TileWidth { get; } + public int TileHeight { get; } + public int Spacing { get; } + public int Margin { get; } + public int TileCount { get; } + public int Columns { get; } + public List<TiledMapTilesetTile> Tiles { get; } + public TiledMapProperties Properties { get; } + + public int Rows => (int)Math.Ceiling((double) TileCount / Columns); + public int ActualWidth => TileWidth * Columns; + public int ActualHeight => TileHeight * Rows; + + public Rectangle GetTileRegion(int localTileIdentifier) + { + return Texture is not null + ? TiledMapHelper.GetTileSourceRectangle(localTileIdentifier, TileWidth, TileHeight, Columns, Margin, + Spacing) + : Tiles.FirstOrDefault(x => x.LocalTileIdentifier == localTileIdentifier).Texture.Bounds; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetAnimatedTile.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetAnimatedTile.cs new file mode 100644 index 0000000..1bbb770 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetAnimatedTile.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.ObjectModel; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled +{ + public class TiledMapTilesetAnimatedTile : TiledMapTilesetTile + { + private TimeSpan _timer = TimeSpan.Zero; + private int _frameIndex; + + public ReadOnlyCollection<TiledMapTilesetTileAnimationFrame> AnimationFrames { get; } + public TiledMapTilesetTileAnimationFrame CurrentAnimationFrame { get; private set; } + + public TiledMapTilesetAnimatedTile(int localTileIdentifier, + TiledMapTilesetTileAnimationFrame[] frames, string type = null, TiledMapObject[] objects = null, Texture2D texture = null) + : base(localTileIdentifier, type, objects, texture) + { + if (frames.Length == 0) throw new InvalidOperationException("There must be at least one tileset animation frame"); + + AnimationFrames = new ReadOnlyCollection<TiledMapTilesetTileAnimationFrame>(frames); + CurrentAnimationFrame = AnimationFrames[0]; + } + + public void CreateTextureRotations(TiledMapTileset tileset, TiledMapTileFlipFlags flipFlags) + { + for (int i = 0; i < AnimationFrames.Count; i++) + { + AnimationFrames[i].CreateTextureRotations(tileset, flipFlags); + } + } + + public void Update(GameTime gameTime) + { + _timer += gameTime.ElapsedGameTime; + + if (_timer <= CurrentAnimationFrame.Duration) + return; + + _timer -= CurrentAnimationFrame.Duration; + _frameIndex = (_frameIndex + 1) % AnimationFrames.Count; + CurrentAnimationFrame = AnimationFrames[_frameIndex]; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetReader.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetReader.cs new file mode 100644 index 0000000..646f835 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetReader.cs @@ -0,0 +1,150 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace MonoGame.Extended.Tiled +{ + public class TiledMapTilesetReader : ContentTypeReader<TiledMapTileset> + { + protected override TiledMapTileset Read(ContentReader reader, TiledMapTileset existingInstance) + { + if (existingInstance != null) + return existingInstance; + + return ReadTileset(reader); + } + + public static TiledMapTileset ReadTileset(ContentReader reader) + { + var texture = reader.ReadExternalReference<Texture2D>(); + var @class = reader.ReadString(); + var tileWidth = reader.ReadInt32(); + var tileHeight = reader.ReadInt32(); + var tileCount = reader.ReadInt32(); + var spacing = reader.ReadInt32(); + var margin = reader.ReadInt32(); + var columns = reader.ReadInt32(); + var explicitTileCount = reader.ReadInt32(); + + var tileset = new TiledMapTileset(texture, @class, tileWidth, tileHeight, tileCount, spacing, margin, columns); + + for (var tileIndex = 0; tileIndex < explicitTileCount; tileIndex++) + ReadTile(reader, tileset); + + reader.ReadTiledMapProperties(tileset.Properties); + return tileset; + } + + private static void ReadTile(ContentReader reader, TiledMapTileset tileset) + { + var texture = reader.ReadExternalReference<Texture2D>(); + + var localTileIdentifier = reader.ReadInt32(); + var type = reader.ReadString(); + var animationFramesCount = reader.ReadInt32(); + var objectCount = reader.ReadInt32(); + var objects = new TiledMapObject[objectCount]; + + for (var i = 0; i < objectCount; i++) + objects[i] = ReadTiledMapObject(reader, tileset); + + var tilesetTile = animationFramesCount <= 0 + ? new TiledMapTilesetTile(localTileIdentifier, type, objects, texture) + : new TiledMapTilesetAnimatedTile(localTileIdentifier, + ReadTiledMapTilesetAnimationFrames(reader, tileset, animationFramesCount), type, objects, texture); + + reader.ReadTiledMapProperties(tilesetTile.Properties); + tileset.Tiles.Add(tilesetTile); + } + + private static TiledMapTilesetTileAnimationFrame[] ReadTiledMapTilesetAnimationFrames(ContentReader reader, TiledMapTileset tileset, int animationFramesCount) + { + var animationFrames = new TiledMapTilesetTileAnimationFrame[animationFramesCount]; + + for (var i = 0; i < animationFramesCount; i++) + { + var localTileIdentifierForFrame = reader.ReadInt32(); + var frameDurationInMilliseconds = reader.ReadInt32(); + var tileSetTileFrame = new TiledMapTilesetTileAnimationFrame(tileset, localTileIdentifierForFrame, frameDurationInMilliseconds); + animationFrames[i] = tileSetTileFrame; + } + + return animationFrames; + } + + private static TiledMapTilesetTile ReadTiledMapTilesetTile(ContentReader reader, TiledMapTileset tileset, Func<TiledMapObject[], TiledMapTilesetTile> createTile) + { + var texture = reader.ReadExternalReference<Texture2D>(); + var objectCount = reader.ReadInt32(); + var objects = new TiledMapObject[objectCount]; + + for (var i = 0; i < objectCount; i++) + objects[i] = ReadTiledMapObject(reader, tileset); + + return createTile(objects); + } + + private static TiledMapObject ReadTiledMapObject(ContentReader reader, TiledMapTileset tileset) + { + var objectType = (TiledMapObjectType)reader.ReadByte(); + var identifier = reader.ReadInt32(); + var name = reader.ReadString(); + var type = reader.ReadString(); + var position = new Vector2(reader.ReadSingle(), reader.ReadSingle()); + var width = reader.ReadSingle(); + var height = reader.ReadSingle(); + var size = new Size2(width, height); + var rotation = reader.ReadSingle(); + var isVisible = reader.ReadBoolean(); + var properties = new TiledMapProperties(); + const float opacity = 1.0f; + + reader.ReadTiledMapProperties(properties); + + TiledMapObject mapObject; + + switch (objectType) + { + case TiledMapObjectType.Rectangle: + mapObject = new TiledMapRectangleObject(identifier, name, size, position, rotation, opacity, isVisible, type); + break; + case TiledMapObjectType.Tile: + reader.ReadUInt32(); // Tile objects within TiledMapTilesetTiles currently ignore the gid and behave like rectangle objects. + mapObject = new TiledMapRectangleObject(identifier, name, size, position, rotation, opacity, isVisible, type); + break; + case TiledMapObjectType.Ellipse: + mapObject = new TiledMapEllipseObject(identifier, name, size, position, rotation, opacity, isVisible); + break; + case TiledMapObjectType.Polygon: + mapObject = new TiledMapPolygonObject(identifier, name, ReadPoints(reader), size, position, rotation, opacity, isVisible, type); + break; + case TiledMapObjectType.Polyline: + mapObject = new TiledMapPolylineObject(identifier, name, ReadPoints(reader), size, position, rotation, opacity, isVisible, type); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + foreach (var property in properties) + mapObject.Properties.Add(property.Key, property.Value); + + return mapObject; + } + + private static Point2[] ReadPoints(ContentReader reader) + { + var pointCount = reader.ReadInt32(); + var points = new Point2[pointCount]; + + for (var i = 0; i < pointCount; i++) + { + var x = reader.ReadSingle(); + var y = reader.ReadSingle(); + points[i] = new Point2(x, y); + } + + return points; + } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetTile.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetTile.cs new file mode 100644 index 0000000..e378aa3 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetTile.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Xna.Framework.Graphics; + +namespace MonoGame.Extended.Tiled +{ + [DebuggerDisplay("{LocalTileIdentifier}: Type: {Type}, Properties: {Properties.Count}, Objects: {Objects.Count}")] + public class TiledMapTilesetTile + { + // For remove libraries + public TiledMapTilesetTile(int localTileIdentifier, string type = null, + TiledMapObject[] objects = null) + { + LocalTileIdentifier = localTileIdentifier; + Type = type; + Objects = objects != null ? new List<TiledMapObject>(objects) : new List<TiledMapObject>(); + Properties = new TiledMapProperties(); + } + + public TiledMapTilesetTile(int localTileIdentifier, string type = null, + TiledMapObject[] objects = null, Texture2D texture = null) + { + Texture = texture; + LocalTileIdentifier = localTileIdentifier; + Type = type; + Objects = objects != null ? new List<TiledMapObject>(objects) : new List<TiledMapObject>(); + Properties = new TiledMapProperties(); + } + + public int LocalTileIdentifier { get; } + public string Type { get; } + public TiledMapProperties Properties { get; } + public List<TiledMapObject> Objects { get; } + public Texture2D Texture { get; } + } +} diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetTileAnimationFrame.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetTileAnimationFrame.cs new file mode 100644 index 0000000..9dbca80 --- /dev/null +++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tiled/TiledMapTilesetTileAnimationFrame.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended.Tiled +{ + public struct TiledMapTilesetTileAnimationFrame + { + public readonly int LocalTileIdentifier; + public readonly TimeSpan Duration; + public readonly Vector2[] TextureCoordinates; + private readonly Dictionary<TiledMapTileFlipFlags, Vector2[]> _flipDictionary = new Dictionary<TiledMapTileFlipFlags, Vector2[]>(); + + internal TiledMapTilesetTileAnimationFrame(TiledMapTileset tileset, int localTileIdentifier, int durationInMilliseconds) + { + LocalTileIdentifier = localTileIdentifier; + Duration = new TimeSpan(0, 0, 0, 0, durationInMilliseconds); + TextureCoordinates = new Vector2[4]; + CreateTextureCoordinates(tileset); + } + + public Vector2[] GetTextureCoordinates(TiledMapTileFlipFlags flipFlags) + { + if (!_flipDictionary.TryGetValue(flipFlags, out Vector2[] flippedTextureCoordiantes)) + { + return TextureCoordinates; + } + else + { + return flippedTextureCoordiantes; + } + } + + public void CreateTextureRotations(TiledMapTileset tileset, TiledMapTileFlipFlags flipFlags) + { + if (!_flipDictionary.ContainsKey(flipFlags)) + { + if (flipFlags == TiledMapTileFlipFlags.None) + { + _flipDictionary.Add(flipFlags, TextureCoordinates); + } + else + { + _flipDictionary.Add(flipFlags, TransformTextureCoordinates(tileset, flipFlags)); + } + } + } + + public Vector2[] TransformTextureCoordinates(TiledMapTileset tileset, TiledMapTileFlipFlags flipFlags) + { + var sourceRectangle = tileset.GetTileRegion(LocalTileIdentifier); + var texture = tileset.Texture; + var texelLeft = (float)sourceRectangle.X / texture.Width; + var texelTop = (float)sourceRectangle.Y / texture.Height; + var texelRight = (sourceRectangle.X + sourceRectangle.Width) / (float)texture.Width; + var texelBottom = (sourceRectangle.Y + sourceRectangle.Height) / (float)texture.Height; + + var flipDiagonally = (flipFlags & TiledMapTileFlipFlags.FlipDiagonally) != 0; + var flipHorizontally = (flipFlags & TiledMapTileFlipFlags.FlipHorizontally) != 0; + var flipVertically = (flipFlags & TiledMapTileFlipFlags.FlipVertically) != 0; + var transform = new Vector2[4]; + + transform[0].X = texelLeft; + transform[0].Y = texelTop; + + transform[1].X = texelRight; + transform[1].Y = texelTop; + + transform[2].X = texelLeft; + transform[2].Y = texelBottom; + + transform[3].X = texelRight; + transform[3].Y = texelBottom; + + if (flipDiagonally) + { + FloatHelper.Swap(ref transform[1].X, ref transform[2].X); + FloatHelper.Swap(ref transform[1].Y, ref transform[2].Y); + } + + if (flipHorizontally) + { + if (flipDiagonally) + { + FloatHelper.Swap(ref transform[0].Y, ref transform[1].Y); + FloatHelper.Swap(ref transform[2].Y, ref transform[3].Y); + } + else + { + FloatHelper.Swap(ref transform[0].X, ref transform[1].X); + FloatHelper.Swap(ref transform[2].X, ref transform[3].X); + } + } + + if (flipVertically) + { + if (flipDiagonally) + { + FloatHelper.Swap(ref transform[0].X, ref transform[2].X); + FloatHelper.Swap(ref transform[1].X, ref transform[3].X); + } + else + { + FloatHelper.Swap(ref transform[0].Y, ref transform[2].Y); + FloatHelper.Swap(ref transform[1].Y, ref transform[3].Y); + } + } + + transform[0] = transform[0]; + transform[1] = transform[1]; + transform[2] = transform[2]; + transform[3] = transform[3]; + + return transform; + } + + private void CreateTextureCoordinates(TiledMapTileset tileset) + { + var sourceRectangle = tileset.GetTileRegion(LocalTileIdentifier); + var texture = tileset.Texture; + var texelLeft = (float)sourceRectangle.X / texture.Width; + var texelTop = (float)sourceRectangle.Y / texture.Height; + var texelRight = (sourceRectangle.X + sourceRectangle.Width) / (float)texture.Width; + var texelBottom = (sourceRectangle.Y + sourceRectangle.Height) / (float)texture.Height; + + TextureCoordinates[0].X = texelLeft; + TextureCoordinates[0].Y = texelTop; + + TextureCoordinates[1].X = texelRight; + TextureCoordinates[1].Y = texelTop; + + TextureCoordinates[2].X = texelLeft; + TextureCoordinates[2].Y = texelBottom; + + TextureCoordinates[3].X = texelRight; + TextureCoordinates[3].Y = texelBottom; + } + + public override string ToString() + { + return $"{LocalTileIdentifier}:{Duration}"; + } + } +} |