diff --git a/Documentation~/RandomTerrainTile.md b/Documentation~/RandomTerrainTile.md new file mode 100644 index 0000000..1ead717 --- /dev/null +++ b/Documentation~/RandomTerrainTile.md @@ -0,0 +1,33 @@ +# Random Terrain Tile + +Very similar to the Terrain Tile, but this supports an array of sprites for each property, and applies them randomly, at the time of the placement. + +## Properties + +The following properties describe the appearance of Sprites representing terrain or walls. Assign Sprites that matches the description to each of these properties. + +| Property | Function | +| ------------------------------------- | ------------------------------------------------------------ | +| 1. __Filled__ | Sprites with all sides filled. | +| 2. __Three Sides__ | Sprites with three sides. | +| 3. __Two Sides and One Corner__ | Sprites with two sides and one corner without adjacent sides. | +| 4. __Two Adjacent Sides__ | Sprites with two adjacent sides. | +| 5. __Two Opposite Sides__ | Sprites with two opposite sides across each other. | +| 6. __One Side and Two Corners__ | Sprites with a single side and two corners without adjacent sides. | +| 7. __One Side and One Lower Corner__ | Sprites with one side and a corner in the lower half of the Sprite. | +| 8. __One Side and One Upper Corner__ | Sprites with one side and a corner in the upper half of the Sprite. | +| 9. __One Side__ | Sprites with a single side. | +| 10. __Four Corners__ | Sprites with four unconnected corners without adjacent sides. | +| 11. __Three Corners__ | Sprites with three corners without adjacent sides. | +| 12. __Two Adjacent Corners__ | Sprites with two adjacent corners. | +| 13. __Two Opposite Corners__ | Sprites with two opposite corners across each other. | +| 14. __One Corner__ | Sprites with a single corner with no adjacent sides. | +| 15. __Empty__ | Sprites without any terrain. | + +## Usage + +The Random Terrain Tile is a copy of Terrain Tile with the only difference that it supports multiple sprites for each level. + +The image below may be of help to know how to position tiles in a way to facilitate their positioning. Such layout works for Terrain Tiles too, by the way. + +![Random Terrain Tile Positioning](images/RandomTerrainTilePositioning.png) diff --git a/Documentation~/Tiles.md b/Documentation~/Tiles.md index 605fffe..d8be8ff 100644 --- a/Documentation~/Tiles.md +++ b/Documentation~/Tiles.md @@ -12,5 +12,6 @@ The following are some implementations and examples of __Scriptables Tiles__ whi - [Rule Override Tile](RuleOverrideTile.md) - [Terrain Tile](TerrainTile.md) - [Weighted Random Tile](WeightedRandomTile.md) +- [Random Terrain Tile](RandomTerrainTile.md) Refer to the [Scriptable Tiles](https://docs.unity3d.com/Manual/Tilemap-ScriptableTiles.html) documentation page for more information on creating your own Scriptable Tiles. \ No newline at end of file diff --git a/Documentation~/images/RandomTerrainTilePositioning.png b/Documentation~/images/RandomTerrainTilePositioning.png new file mode 100644 index 0000000..6fa86f5 Binary files /dev/null and b/Documentation~/images/RandomTerrainTilePositioning.png differ diff --git a/Runtime/Tiles/RandomTerrainTile/RandomTerrainTile.cs b/Runtime/Tiles/RandomTerrainTile/RandomTerrainTile.cs new file mode 100644 index 0000000..47cf073 --- /dev/null +++ b/Runtime/Tiles/RandomTerrainTile/RandomTerrainTile.cs @@ -0,0 +1,349 @@ +using System; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace UnityEngine.Tilemaps +{ + + /// + /// Terrain Tiles, similar to Pipeline Tiles, are tiles which take into consideration its orthogonal and diagonal neighboring tiles and displays a sprite depending on whether the neighboring tile is the same tile. + /// + [Serializable] + [CreateAssetMenu(fileName = "New Random Terrain Tile", menuName = "2D Extras/Tiles/Random Terrain Tile", order = 360)] + public class RandomTerrainTile : TileBase + { + [SerializeField] public Sprite[] Filled; + [SerializeField] public Sprite[] ThreeSides; + [SerializeField] public Sprite[] TwoSidesAndOneCorner; + [SerializeField] public Sprite[] TwoAdjacentSides; + [SerializeField] public Sprite[] TwoOppositeSides; + [SerializeField] public Sprite[] OneSideAndTwoCorners; + [SerializeField] public Sprite[] OneSideAndOneLowerCorner; + [SerializeField] public Sprite[] OneSideAndOneUpperCorner; + [SerializeField] public Sprite[] OneSide; + [SerializeField] public Sprite[] FourCorners; + [SerializeField] public Sprite[] ThreeCorners; + [SerializeField] public Sprite[] TwoAdjacentCorners; + [SerializeField] public Sprite[] TwoOppositeCorners; + [SerializeField] public Sprite[] OneCorner; + [SerializeField] public Sprite[] Empty; + + /// + /// The Sprites used for defining the Terrain. + /// + // [SerializeField] + // public Sprite[] m_Sprites; + + /// + /// This method is called when the tile is refreshed. + /// + /// Position of the Tile on the Tilemap. + /// The Tilemap the tile is present on. + public override void RefreshTile(Vector3Int location, ITilemap tileMap) + { + for (int yd = -1; yd <= 1; yd++) + for (int xd = -1; xd <= 1; xd++) + { + Vector3Int position = new Vector3Int(location.x + xd, location.y + yd, location.z); + if (TileValue(tileMap, position)) + tileMap.RefreshTile(position); + } + } + + /// + /// Retrieves any tile rendering data from the scripted tile. + /// + /// Position of the Tile on the Tilemap. + /// The Tilemap the tile is present on. + /// Data to render the tile. + public override void GetTileData(Vector3Int location, ITilemap tileMap, ref TileData tileData) + { + UpdateTile(location, tileMap, ref tileData); + } + + private void UpdateTile(Vector3Int location, ITilemap tileMap, ref TileData tileData) + { + tileData.transform = Matrix4x4.identity; + tileData.color = Color.white; + + int mask = TileValue(tileMap, location + new Vector3Int(0, 1, 0)) ? 1 : 0; + mask += TileValue(tileMap, location + new Vector3Int(1, 1, 0)) ? 2 : 0; + mask += TileValue(tileMap, location + new Vector3Int(1, 0, 0)) ? 4 : 0; + mask += TileValue(tileMap, location + new Vector3Int(1, -1, 0)) ? 8 : 0; + mask += TileValue(tileMap, location + new Vector3Int(0, -1, 0)) ? 16 : 0; + mask += TileValue(tileMap, location + new Vector3Int(-1, -1, 0)) ? 32 : 0; + mask += TileValue(tileMap, location + new Vector3Int(-1, 0, 0)) ? 64 : 0; + mask += TileValue(tileMap, location + new Vector3Int(-1, 1, 0)) ? 128 : 0; + + byte original = (byte)mask; + if ((original | 254) < 255) { mask = mask & 125; } + if ((original | 251) < 255) { mask = mask & 245; } + if ((original | 239) < 255) { mask = mask & 215; } + if ((original | 191) < 255) { mask = mask & 95; } + + Sprite[][] m_Sprites = GetSpritesAsOne(); + + int index = GetIndex((byte)mask); + if (index >= 0 && index < m_Sprites.Length && TileValue(tileMap, location)) + { + tileData.sprite = GetRandomSprite(m_Sprites[index]); + tileData.transform = GetTransform((byte)mask); + tileData.color = Color.white; + tileData.flags = TileFlags.LockTransform | TileFlags.LockColor; + tileData.colliderType = Tile.ColliderType.Sprite; + } + } + + private Sprite GetRandomSprite(Sprite[] spriteList) { + if (spriteList.Length == 0) { + return null; + } + + int spriteIndex = Random.Range(0, spriteList.Length - 1); + return spriteList[spriteIndex]; + } + + private Sprite[][] GetSpritesAsOne() { + Sprite[][] m_Sprites = new Sprite[15][]; + m_Sprites[0] = Filled; + m_Sprites[1] = ThreeSides; + m_Sprites[2] = TwoSidesAndOneCorner; + m_Sprites[3] = TwoAdjacentSides; + m_Sprites[4] = TwoOppositeSides; + m_Sprites[5] = OneSideAndTwoCorners; + m_Sprites[6] = OneSideAndOneLowerCorner; + m_Sprites[7] = OneSideAndOneUpperCorner; + m_Sprites[8] = OneSide; + m_Sprites[9] = FourCorners; + m_Sprites[10] = ThreeCorners; + m_Sprites[11] = TwoAdjacentCorners; + m_Sprites[12] = TwoOppositeCorners; + m_Sprites[13] = OneCorner; + m_Sprites[14] = Empty; + return m_Sprites; + } + + private bool TileValue(ITilemap tileMap, Vector3Int position) + { + TileBase tile = tileMap.GetTile(position); + return (tile != null && tile == this); + } + + private int GetIndex(byte mask) + { + switch (mask) + { + case 0: return 0; + case 1: + case 4: + case 16: + case 64: return 1; + case 5: + case 20: + case 80: + case 65: return 2; + case 7: + case 28: + case 112: + case 193: return 3; + case 17: + case 68: return 4; + case 21: + case 84: + case 81: + case 69: return 5; + case 23: + case 92: + case 113: + case 197: return 6; + case 29: + case 116: + case 209: + case 71: return 7; + case 31: + case 124: + case 241: + case 199: return 8; + case 85: return 9; + case 87: + case 93: + case 117: + case 213: return 10; + case 95: + case 125: + case 245: + case 215: return 11; + case 119: + case 221: return 12; + case 127: + case 253: + case 247: + case 223: return 13; + case 255: return 14; + } + return -1; + } + + private Matrix4x4 GetTransform(byte mask) + { + switch (mask) + { + case 4: + case 20: + case 28: + case 68: + case 84: + case 92: + case 116: + case 124: + case 93: + case 125: + case 221: + case 253: + return Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0f, 0f, -90f), Vector3.one); + case 16: + case 80: + case 112: + case 81: + case 113: + case 209: + case 241: + case 117: + case 245: + case 247: + return Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0f, 0f, -180f), Vector3.one); + case 64: + case 65: + case 193: + case 69: + case 197: + case 71: + case 199: + case 213: + case 215: + case 223: + return Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0f, 0f, -270f), Vector3.one); + } + return Matrix4x4.identity; + } + } + +#if UNITY_EDITOR + [CustomEditor(typeof(RandomTerrainTile))] + public class RandomTerrainTileEditor : Editor + { + private RandomTerrainTile tile { get { return (target as RandomTerrainTile); } } + + public void OnEnable() + { + bool setDirty = false; + + if (tile.Filled == null || tile.Filled.Length == 0) { + tile.Filled = new Sprite[1]; setDirty = true; + } + + if (tile.ThreeSides == null || tile.ThreeSides.Length == 0) { + tile.ThreeSides = new Sprite[1]; setDirty = true; + } + + if (tile.TwoSidesAndOneCorner == null || tile.TwoSidesAndOneCorner.Length == 0) { + tile.TwoSidesAndOneCorner = new Sprite[1]; setDirty = true; + } + + if (tile.TwoAdjacentSides == null || tile.TwoAdjacentSides.Length == 0) { + tile.TwoAdjacentSides = new Sprite[1]; setDirty = true; + } + + if (tile.TwoOppositeSides == null || tile.TwoOppositeSides.Length == 0) { + tile.TwoOppositeSides = new Sprite[1]; setDirty = true; + } + + if (tile.OneSideAndTwoCorners == null || tile.OneSideAndTwoCorners.Length == 0) { + tile.OneSideAndTwoCorners = new Sprite[1]; setDirty = true; + } + + if (tile.OneSideAndOneLowerCorner == null || tile.OneSideAndOneLowerCorner.Length == 0) { + tile.OneSideAndOneLowerCorner = new Sprite[1]; setDirty = true; + } + + if (tile.OneSideAndOneUpperCorner == null || tile.OneSideAndOneUpperCorner.Length == 0) { + tile.OneSideAndOneUpperCorner = new Sprite[1]; setDirty = true; + } + + if (tile.OneSide == null || tile.OneSide.Length == 0) { + tile.OneSide = new Sprite[1]; setDirty = true; + } + + if (tile.FourCorners == null || tile.FourCorners.Length == 0) { + tile.FourCorners = new Sprite[1]; setDirty = true; + } + + if (tile.ThreeCorners == null || tile.ThreeCorners.Length == 0) { + tile.ThreeCorners = new Sprite[1]; setDirty = true; + } + + if (tile.TwoAdjacentCorners == null || tile.TwoAdjacentCorners.Length == 0) { + tile.TwoAdjacentCorners = new Sprite[1]; setDirty = true; + } + + if (tile.TwoOppositeCorners == null || tile.TwoOppositeCorners.Length == 0) { + tile.TwoOppositeCorners = new Sprite[1]; setDirty = true; + } + + if (tile.OneCorner == null || tile.OneCorner.Length == 0) { + tile.OneCorner = new Sprite[1]; setDirty = true; + } + + if (tile.Empty == null || tile.Empty.Length == 0) { + tile.Empty = new Sprite[1]; setDirty = true; + } + + if (setDirty) { + EditorUtility.SetDirty(tile); + } + } + + // public void OnEnable() + // { + // if (tile.m_Sprites == null || tile.m_Sprites.Length != 15) + // { + // tile.m_Sprites = new Sprite[15]; + // EditorUtility.SetDirty(tile); + // } + // } + + + // public override void OnInspectorGUI() + // { + // EditorGUILayout.LabelField("Place sprites shown based on the contents of the sprite."); + // EditorGUILayout.Space(); + + // float oldLabelWidth = EditorGUIUtility.labelWidth; + // EditorGUIUtility.labelWidth = 210; + + // EditorGUI.BeginChangeCheck(); + // tile.m_Sprites[0] = (Sprite) EditorGUILayout.ObjectField("Filled", tile.m_Sprites[0], typeof(Sprite), false, null); + // tile.m_Sprites[1] = (Sprite) EditorGUILayout.ObjectField("Three Sides", tile.m_Sprites[1], typeof(Sprite), false, null); + // tile.m_Sprites[2] = (Sprite) EditorGUILayout.ObjectField("Two Sides and One Corner", tile.m_Sprites[2], typeof(Sprite), false, null); + // tile.m_Sprites[3] = (Sprite) EditorGUILayout.ObjectField("Two Adjacent Sides", tile.m_Sprites[3], typeof(Sprite), false, null); + // tile.m_Sprites[4] = (Sprite) EditorGUILayout.ObjectField("Two Opposite Sides", tile.m_Sprites[4], typeof(Sprite), false, null); + // tile.m_Sprites[5] = (Sprite) EditorGUILayout.ObjectField("One Side and Two Corners", tile.m_Sprites[5], typeof(Sprite), false, null); + // tile.m_Sprites[6] = (Sprite) EditorGUILayout.ObjectField("One Side and One Lower Corner", tile.m_Sprites[6], typeof(Sprite), false, null); + // tile.m_Sprites[7] = (Sprite) EditorGUILayout.ObjectField("One Side and One Upper Corner", tile.m_Sprites[7], typeof(Sprite), false, null); + // tile.m_Sprites[8] = (Sprite) EditorGUILayout.ObjectField("One Side", tile.m_Sprites[8], typeof(Sprite), false, null); + // tile.m_Sprites[9] = (Sprite) EditorGUILayout.ObjectField("Four Corners", tile.m_Sprites[9], typeof(Sprite), false, null); + // tile.m_Sprites[10] = (Sprite) EditorGUILayout.ObjectField("Three Corners", tile.m_Sprites[10], typeof(Sprite), false, null); + // tile.m_Sprites[11] = (Sprite) EditorGUILayout.ObjectField("Two Adjacent Corners", tile.m_Sprites[11], typeof(Sprite), false, null); + // tile.m_Sprites[12] = (Sprite) EditorGUILayout.ObjectField("Two Opposite Corners", tile.m_Sprites[12], typeof(Sprite), false, null); + // tile.m_Sprites[13] = (Sprite) EditorGUILayout.ObjectField("One Corner", tile.m_Sprites[13], typeof(Sprite), false, null); + // tile.m_Sprites[14] = (Sprite) EditorGUILayout.ObjectField("Empty", tile.m_Sprites[14], typeof(Sprite), false, null); + // if (EditorGUI.EndChangeCheck()) + // EditorUtility.SetDirty(tile); + + // EditorGUIUtility.labelWidth = oldLabelWidth; + // } + } +#endif +} diff --git a/Runtime/Tiles/RandomTerrainTile/RandomTerrainTile.cs.meta b/Runtime/Tiles/RandomTerrainTile/RandomTerrainTile.cs.meta new file mode 100644 index 0000000..b5b25e2 --- /dev/null +++ b/Runtime/Tiles/RandomTerrainTile/RandomTerrainTile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: af8ebeabaad034943bca08de2636b4b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: