diff --git a/CHANGELOG.md b/CHANGELOG.md index 6473793..9a1ca9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Additions - Added StaticSpriteBatch class - Added missing easing functions Quart and Quint to Easings - Added RotationVector extension methods for Matrix and Quaternion +- Added DrawExtendedAutoTile to the AutoTiling class Improvements - Cache TokenizedString inner offsets for non-Left text alignments to improve performance diff --git a/Demos/AutoTilingDemo.cs b/Demos/AutoTilingDemo.cs index 3b44f98..a734d03 100644 --- a/Demos/AutoTilingDemo.cs +++ b/Demos/AutoTilingDemo.cs @@ -9,6 +9,7 @@ namespace Demos { /// public class AutoTilingDemo : Demo { + private const int TileSize = 8; private Texture2D texture; private string[] layout; @@ -17,37 +18,26 @@ namespace Demos { public override void LoadContent() { base.LoadContent(); - // The layout of the texture is important for auto tiling to work correctly. - // It needs to be laid out as follows: Five tiles aligned horizontally within the texture file, with the following information - // 1. The texture used for filling big areas - // 2. The texture used for straight, horizontal borders, with the borders facing away from the center - // 3. The texture used for outer corners, with the corners facing away from the center - // 4. The texture used for straight, vertical borders, with the borders facing away from the center - // 5. The texture used for inner corners, with the corners facing away from the center - // Note that the AutoTiling.png texture contains an example of this layout + // The layout of the texture is important for auto tiling to work correctly, and is explained in the XML docs for the methods used this.texture = LoadContent("Textures/AutoTiling"); // in this example, a simple string array is used for layout purposes. As the AutoTiling method allows any kind of // comparison, the actual implementation can vary (for instance, based on a more in-depth tile map) this.layout = new[] { - "XXX X", - "XXXXX", - "XX ", - "XXXX ", - " X " + "XXX X ", + "XXXXXX", + "XXX X", + "XXXXX ", + " X " }; } public override void DoDraw(GameTime gameTime) { this.GraphicsDevice.Clear(Color.Black); - // the texture region supplied to the AutoTiling method should only encompass the first filler tile's location and size - const int tileSize = 8; - var region = new Rectangle(0, 0, tileSize, tileSize); - // drawing the auto tiles this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, transformMatrix: Matrix.CreateScale(10)); - for (var x = 0; x < 5; x++) { + for (var x = 0; x < 6; x++) { for (var y = 0; y < 5; y++) { // don't draw non-grass tiles ( ) if (this.layout[y][x] != 'X') @@ -61,13 +51,17 @@ namespace Demos { // and where there are none ( ). bool ConnectsTo(int xOff, int yOff) { // don't auto-tile out of bounds - if (x1 + xOff < 0 || y1 + yOff < 0 || x1 + xOff >= 5 || y1 + yOff >= 5) + if (x1 + xOff < 0 || y1 + yOff < 0 || x1 + xOff >= 6 || y1 + yOff >= 5) return false; // check if the neighboring tile is also grass (X) return this.layout[y1 + yOff][x1 + xOff] == 'X'; } - AutoTiling.DrawAutoTile(this.SpriteBatch, new Vector2(x, y) * tileSize, this.texture, region, ConnectsTo, Color.White); + // the texture region supplied to the AutoTiling method should only encompass the first filler tile's location and size + AutoTiling.DrawAutoTile(this.SpriteBatch, new Vector2(x + 1, y + 1) * TileSize, this.texture, new Rectangle(0, 0, TileSize, TileSize), ConnectsTo, Color.White); + + // when drawing extended auto-tiles, the same rules apply, but the source texture layout is different + AutoTiling.DrawExtendedAutoTile(this.SpriteBatch, new Vector2(x + 8, y + 1) * TileSize, this.texture, new Rectangle(0, TileSize * 2, TileSize, TileSize), ConnectsTo, Color.White); } } this.SpriteBatch.End(); diff --git a/Demos/Content/Textures/AutoTiling.png b/Demos/Content/Textures/AutoTiling.png index 5496de7..e2b6a68 100644 Binary files a/Demos/Content/Textures/AutoTiling.png and b/Demos/Content/Textures/AutoTiling.png differ diff --git a/MLEM/Misc/AutoTiling.cs b/MLEM/Misc/AutoTiling.cs index 272f0f7..9014801 100644 --- a/MLEM/Misc/AutoTiling.cs +++ b/MLEM/Misc/AutoTiling.cs @@ -3,9 +3,8 @@ using Microsoft.Xna.Framework.Graphics; namespace MLEM.Misc { /// - /// This class contains a method that allows users to easily draw a tile with automatic connections. - /// For auto-tiling in this manner to work, auto-tiled textures have to be laid out in a format described in . - /// Note that can also be used for drawing by using the method instead. + /// This class contains a method that allows users to easily draw a tile with automatic connections, as well as a more complex method. + /// Note that can also be used for drawing by using the and methods instead. /// public static class AutoTiling { @@ -21,7 +20,7 @@ namespace MLEM.Misc { /// The texture used for straight, vertical borders, with the borders facing away from the center /// The texture used for inner corners, with the corners facing away from the center /// - /// For more information and an example, see https://github.com/Ellpeck/MLEM/blob/main/Demos/AutoTilingDemo.cs#L20-L28. + /// For more information and an example, see https://github.com/Ellpeck/MLEM/blob/main/Demos/AutoTilingDemo.cs and its source texture https://github.com/Ellpeck/MLEM/blob/main/Demos/Content/Textures/AutoTiling.png. /// /// The sprite batch to use for drawing. /// The position to draw at. @@ -53,6 +52,61 @@ namespace MLEM.Misc { batch.Add(texture, p4, r4, color, 0, orig, sc, SpriteEffects.None, layerDepth); } + /// + /// This method allows for a tiled texture to be drawn in an auto-tiling mode. + /// This allows, for example, a grass patch on a tilemap to have nice looking edges that transfer over into a path without any hard edges between tiles. + /// + /// This method is a more complex version of that overlays separate border textures on a base texture, which also allows for non-rectangular texture areas to be used easily. + /// For auto-tiling in this way to work, the tiles have to be laid out as follows: 17 tiles aligned horizontally within the texture file, with the following information: + /// + /// The texture used for filling big areas + /// The texture used for straight, horizontal borders, with the borders facing away from the center, split up into four parts: top left, then top right, then bottom left, then bottom right + /// The texture used for outer corners, with the corners facing away from the center, split up into four parts: top left, then top right, then bottom left, then bottom right + /// The texture used for straight, vertical borders, with the borders facing away from the center, split up into four parts: top left, then top right, then bottom left, then bottom right + /// The texture used for inner corners, with the corners facing away from the center, split up into four parts: top left, then top right, then bottom left, then bottom right + /// + /// For more information and an example, see https://github.com/Ellpeck/MLEM/blob/main/Demos/AutoTilingDemo.cs and its source texture https://github.com/Ellpeck/MLEM/blob/main/Demos/Content/Textures/AutoTiling.png. + /// + /// The sprite batch to use for drawing. + /// The position to draw at. + /// The texture to use for drawing. + /// The location of the first texture region, as described in the summary. + /// A function that determines whether two positions should connect. + /// The color to draw with. + /// The origin to draw from. + /// The scale to draw with. + /// The layer depth to draw with. + public static void DrawExtendedAutoTile(SpriteBatch batch, Vector2 pos, Texture2D texture, Rectangle textureRegion, ConnectsTo connectsTo, Color color, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0) { + var orig = origin ?? Vector2.Zero; + var sc = scale ?? Vector2.One; + var (p1, r1, p2, r2, p3, r3, p4, r4) = CalculateExtendedAutoTile(pos, textureRegion, connectsTo, sc); + batch.Draw(texture, pos, textureRegion, color, 0, orig, sc, SpriteEffects.None, layerDepth); + if (r1 != Rectangle.Empty) + batch.Draw(texture, p1, r1, color, 0, orig, sc, SpriteEffects.None, layerDepth); + if (r2 != Rectangle.Empty) + batch.Draw(texture, p2, r2, color, 0, orig, sc, SpriteEffects.None, layerDepth); + if (r3 != Rectangle.Empty) + batch.Draw(texture, p3, r3, color, 0, orig, sc, SpriteEffects.None, layerDepth); + if (r4 != Rectangle.Empty) + batch.Draw(texture, p4, r4, color, 0, orig, sc, SpriteEffects.None, layerDepth); + } + + /// + public static void AddExtendedAutoTile(StaticSpriteBatch batch, Vector2 pos, Texture2D texture, Rectangle textureRegion, ConnectsTo connectsTo, Color color, float rotation = 0, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0) { + var orig = origin ?? Vector2.Zero; + var sc = scale ?? Vector2.One; + var (p1, r1, p2, r2, p3, r3, p4, r4) = CalculateExtendedAutoTile(pos, textureRegion, connectsTo, sc); + batch.Add(texture, pos, textureRegion, color, 0, orig, sc, SpriteEffects.None, layerDepth); + if (r1 != Rectangle.Empty) + batch.Add(texture, p1, r1, color, 0, orig, sc, SpriteEffects.None, layerDepth); + if (r2 != Rectangle.Empty) + batch.Add(texture, p2, r2, color, 0, orig, sc, SpriteEffects.None, layerDepth); + if (r3 != Rectangle.Empty) + batch.Add(texture, p3, r3, color, 0, orig, sc, SpriteEffects.None, layerDepth); + if (r4 != Rectangle.Empty) + batch.Add(texture, p4, r4, color, 0, orig, sc, SpriteEffects.None, layerDepth); + } + private static (Vector2, Rectangle, Vector2, Rectangle, Vector2, Rectangle, Vector2, Rectangle) CalculateAutoTile(Vector2 pos, Rectangle textureRegion, ConnectsTo connectsTo, Vector2 scale) { var up = connectsTo(0, -1); var down = connectsTo(0, 1); @@ -73,6 +127,26 @@ namespace MLEM.Misc { new Vector2(pos.X + w2 * scale.X, pos.Y + h2 * scale.Y), new Rectangle(textureRegion.X + w2 + xDr * w, textureRegion.Y + h2, w2, h2)); } + private static (Vector2, Rectangle, Vector2, Rectangle, Vector2, Rectangle, Vector2, Rectangle) CalculateExtendedAutoTile(Vector2 pos, Rectangle textureRegion, ConnectsTo connectsTo, Vector2 scale) { + var up = connectsTo(0, -1); + var down = connectsTo(0, 1); + var left = connectsTo(-1, 0); + var right = connectsTo(1, 0); + var xUl = up && left ? connectsTo(-1, -1) ? -1 : 13 : left ? 1 : up ? 9 : 5; + var xUr = up && right ? connectsTo(1, -1) ? -1 : 14 : right ? 2 : up ? 10 : 6; + var xDl = down && left ? connectsTo(-1, 1) ? -1 : 15 : left ? 3 : down ? 11 : 7; + var xDr = down && right ? connectsTo(1, 1) ? -1 : 16 : right ? 4 : down ? 12 : 8; + + var (w, h) = textureRegion.Size; + var (w2, h2) = new Point(w / 2, h / 2); + + return ( + new Vector2(pos.X, pos.Y), xUl < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + xUl * w, textureRegion.Y, w2, h2), + new Vector2(pos.X + w2 * scale.X, pos.Y), xUr < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + w2 + xUr * w, textureRegion.Y, w2, h2), + new Vector2(pos.X, pos.Y + h2 * scale.Y), xDl < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + xDl * w, textureRegion.Y + h2, w2, h2), + new Vector2(pos.X + w2 * scale.X, pos.Y + h2 * scale.Y), xDr < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + w2 + xDr * w, textureRegion.Y + h2, w2, h2)); + } + /// /// A delegate function that determines if a given offset position connects to an auto-tile location. ///