From ad1d6a864e382d0ed626f7da6343bc4061f151ec Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Sat, 27 Nov 2021 15:03:30 +0100 Subject: [PATCH] Added DrawExtendedAutoTile to the AutoTiling class --- CHANGELOG.md | 1 + Demos/AutoTilingDemo.cs | 34 +++++------ Demos/Content/Textures/AutoTiling.png | Bin 604 -> 878 bytes MLEM/Misc/AutoTiling.cs | 82 ++++++++++++++++++++++++-- 4 files changed, 93 insertions(+), 24 deletions(-) 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 5496de78aeab0b65c3992ee5b4c7d09410603430..e2b6a68365490467d9846ae5b58386923342061f 100644 GIT binary patch literal 878 zcmV-!1CjiRP)Px&C`m*?RCt{2nlVq?KorOSNlw8a8HiMdN>Ft|>X4xWLz~l>sSZL81~;LpdO}WpW+NJ6{@y*vSPN^nQz-dw2KF?>(QN<5zg| zrKT`~1>xZO(>VZOeNE{0le1``-};(>>y#n=5CHT!^&|OV?FIl)^-4o>061;L$IW(8 z%RhVH3!~P@%pQ@}=vExau8$z{wewu3tmW%#&}~P@*U31ePgW>+0N@mT0Kj4G234=D z^)L^I0Fr>9V30> zk~vPk*)AfG3K!j;))`2J&2|w__oLJ`G55z%l1h09L2o9Mcl_ISWQ?tPr2%;s;^2wr z?ritrRvZMfH)a%bUknAvK!RFv;B|hEo#ePXTDDPH)`=+PDD;*B#5+v#wjvP9;n!e> zVHk#C7=~dOhG7_nnKgxXZ(oh92NA--xflNnMxXuP?|-|qK4Z7BrOZFLrmg3AU3;m_ zdXp^r&3Y7@`gisYGR@U$)|GAjzadl-001(~QgOGu;)O)y2S&w)o@ncTi|G>nhKfyU2U@QomNS zu51(><<(DXu%$n*Zrca|xV-9Pb!ABhMr3^1on&zjc|nhjNo{G%2Ra84!Wjq*;={+U zbM0)(N%9(up5s}c`~R!{Y0)`IT5#B))vPOe$VG`3C0RsaA107*qoM6N<$ Ef+^Uf*Z=?k literal 604 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc4Ie#S978JN-rjJ`5^)q^xG+!RjLVDz4SFpu z_a$cPHKfP{6io_fbHI2`5BB8PS zmrMQnUlP9j`=6)pcFayr-g{s6ZSCog-}Zieb9lLa`sIr*N7(+}zWd^bNq=_C^Uyy9 z-_zynEq9m-^Ut5}`^$a(^>dG_^Y48tDq`Gw@6d9it!8iaC3t?{oIgi4y-2I^YSz1t z=jPh15BZ~YYpb`2S8V&!Q%oyb!t1S0>4g?4X*TYK)a(_C zn5Wa=;dSTF>64jT{aaseJMlHL@3ZZJJ+tMdw`JVl|L=vL+v6Nl@y9n-9Bo_MryVzY zhyBsy!{Wso>pCCL*#H04qqNDd%{O!I`6Jio63VoCYw=pMeJje|Cde0TPP1cve0lvm zwuwE5#pR3sY%gnz{<-zSJIPm?AKpC@s9*fLj(KhMm*q - /// 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. ///