using System; using System.Collections.Generic; namespace UnityEngine.Tilemaps { /// /// Tile using AutoTiling mask and rules /// [HelpURL( "https://docs.unity3d.com/Packages/com.unity.2d.tilemap.extras@latest/index.html?subfolder=/manual/AutoTile.html")] public class AutoTile : TileBase { internal static readonly float s_DefaultTextureScale = 1f; [Serializable] internal abstract class SerializedDictionary : Dictionary, ISerializationCallbackReceiver { [SerializeField, HideInInspector] private List keyData = new List(); [SerializeField, HideInInspector] private List valueData = new List(); void ISerializationCallbackReceiver.OnAfterDeserialize() { Clear(); for (int i = 0; i < keyData.Count && i < valueData.Count; i++) { this[keyData[i]] = valueData[i]; } keyData.Clear(); valueData.Clear(); } void ISerializationCallbackReceiver.OnBeforeSerialize() { keyData.Clear(); valueData.Clear(); foreach (var item in this) { keyData.Add(item.Key); valueData.Add(item.Value); } } } [Serializable] internal class AutoTileData { [SerializeField] public List spriteList = new List(); [SerializeField] public List textureList = new List(); } [Serializable] internal class AutoTileDictionary : SerializedDictionary { }; /// /// MaskType for AutoTile /// public enum AutoTileMaskType { /// /// Mask for 2x2 blocks /// Mask_2x2, /// /// Mask for 3x3 blocks /// Mask_3x3 } #region Tile Data /// /// The Default Sprite set when creating a new Rule. /// [SerializeField] public Sprite m_DefaultSprite; /// /// The Default GameObject set when creating a new Rule. /// [SerializeField] public GameObject m_DefaultGameObject; /// /// The Default Collider Type set when creating a new Rule. /// [SerializeField] public Tile.ColliderType m_DefaultColliderType = Tile.ColliderType.Sprite; /// /// Mask Type for the AutoTile /// [SerializeField] public AutoTileMaskType m_MaskType; [SerializeField, HideInInspector] internal AutoTileDictionary m_AutoTileDictionary = new AutoTileDictionary(); #endregion #region Editor Data /// /// List of Texture2Ds used by the AutoTile /// [SerializeField] public List m_TextureList = new List(); /// /// List of Texture Scale used by the AutoTile /// [SerializeField] public List m_TextureScaleList = new List(); #endregion #region Runtime Data private readonly TileBase[] m_CachedTiles = new TileBase[9]; #endregion /// /// 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 position, ITilemap tilemap) { for (var y = -1; y <= 1; ++y) { for (var x = -1; x <= 1; ++x) { tilemap.RefreshTile(new Vector3Int(position.x + x, position.y + y, position.z)); } } } /// /// 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 position, ITilemap itilemap, ref TileData tileData) { var iden = Matrix4x4.identity; tileData.sprite = m_DefaultSprite; tileData.gameObject = m_DefaultGameObject; tileData.colliderType = m_DefaultColliderType; tileData.flags = TileFlags.LockAll; tileData.transform = iden; uint mask = 0; var index = 0; for (var y = -1; y <= 1; ++y) { for (var x = -1; x <= 1; ++x) { var tilePosition = new Vector3Int(position.x + x, position.y + y, position.z); m_CachedTiles[index] = itilemap.GetTile(tilePosition); if (m_CachedTiles[index] == this) mask |= (uint) 1 << index; index++; } } mask = m_MaskType switch { AutoTileMaskType.Mask_2x2 => Convert2x2Mask(mask), AutoTileMaskType.Mask_3x3 => Convert3x3Mask(mask), _ => mask }; if (m_AutoTileDictionary.TryGetValue(mask, out var autoTileData)) { tileData.sprite = autoTileData.spriteList.Count > 0 ? autoTileData.spriteList[0] : m_DefaultSprite; } } internal void AddSprite(Sprite sprite, Texture2D texture, uint mask) { if ((m_MaskType == AutoTileMaskType.Mask_2x2 && (mask >> 4) > 0) || (mask >> 9) > 0) { throw new ArgumentOutOfRangeException($"Mask {mask} is not valid for {m_MaskType}"); } if (!m_AutoTileDictionary.TryGetValue(mask, out var autoTileData)) { autoTileData = new AutoTileData(); m_AutoTileDictionary.Add(mask, autoTileData); } var isInList = false; foreach (var spriteData in autoTileData.spriteList) { isInList = spriteData == sprite; if (isInList) break; } if (!isInList) { autoTileData.spriteList.Add(sprite); autoTileData.textureList.Add(texture); } } internal void RemoveSprite(Sprite sprite, uint mask) { if (!m_AutoTileDictionary.TryGetValue(mask, out var autoTileData)) return; var index = autoTileData.spriteList.IndexOf(sprite); if (index < 0) return; autoTileData.spriteList.RemoveAt(index); autoTileData.textureList.RemoveAt(index); } /// /// Validate AutoTile Data /// public void Validate() { if (m_MaskType == AutoTileMaskType.Mask_2x2) { var keyList = new List(m_AutoTileDictionary.Keys); foreach (var mask in keyList) { if ((mask >> 4) > 0) { m_AutoTileDictionary.Remove(mask); } } } foreach (var pair in m_AutoTileDictionary) { var autoTileData = pair.Value; for (var i = 0; i < autoTileData.spriteList.Count;) { var sprite = autoTileData.spriteList[i]; var texture = autoTileData.textureList[i]; if (m_TextureList.Contains(texture)) { ++i; } else { autoTileData.spriteList.RemoveAt(i); autoTileData.textureList.RemoveAt(i); } } } if (m_TextureList.Count != m_TextureScaleList.Count) { if (m_TextureList.Count > m_TextureScaleList.Count) while (m_TextureList.Count - m_TextureScaleList.Count > 0) m_TextureScaleList.Add(s_DefaultTextureScale); else if (m_TextureList.Count < m_TextureScaleList.Count) while (m_TextureScaleList.Count - m_TextureList.Count > 0) m_TextureScaleList.RemoveAt(m_TextureScaleList.Count-1); } } private uint Convert2x2Mask(uint mask) { // 4 8 // 1 2 uint newMask = 0; if ((mask & 1 << 0) > 0 && (mask & 1 << 1) > 0 && (mask & 1 << 3) > 0) newMask |= 1 << 0; if ((mask & 1 << 1) > 0 && (mask & 1 << 2) > 0 && (mask & 1 << 5) > 0) newMask |= 1 << 1; if ((mask & 1 << 3) > 0 && (mask & 1 << 6) > 0 && (mask & 1 << 7) > 0) newMask |= 1 << 2; if ((mask & 1 << 5) > 0 && (mask & 1 << 7) > 0 && (mask & 1 << 8) > 0) newMask |= 1 << 3; return newMask; } private uint Convert3x3Mask(uint mask) { // 64 128 256 // 8 16 32 // 1 2 4 switch (mask) { // Left case 1 + 16 + 32: case 4 + 16 + 32: case 16 + 32 + 64: case 16 + 32 + 256: case 1 + 16 + 32 + 64: case 4 + 16 + 32 + 256: case 1 + 4 + 16 + 32: case 1 + 16 + 32 + 256: case 4 + 16 + 32 + 64: case 16 + 32 + 64 + 256: case 1 + 16 + 32 + 64 + 256: case 1 + 4 + 16 + 32 + 64: case 1 + 4 + 16 + 32 + 256: case 4 + 16 + 32 + 64 + 256: case 1 + 4 + 16 + 32 + 64 + 256: { mask = 16 + 32; break; } // Right case 1 + 8 + 16: case 8 + 16 + 64: case 1 + 8 + 16 + 64: case 4 + 8 + 16: case 8 + 16 + 256: case 1 + 4 + 8 + 16: case 1 + 8 + 16 + 256: case 4 + 8 + 16 + 64: case 4 + 8 + 16 + 256: case 8 + 16 + 64 + 256: case 1 + 4 + 8 + 16 + 64: case 1 + 4 + 8 + 16 + 256: case 1 + 8 + 16 + 64 + 256: case 4 + 8 + 16 + 64 + 256: case 1 + 4 + 8 + 16 + 64 + 256: { mask = 8 + 16; break; } // Top case 1 + 2 + 16: case 2 + 4 + 16: case 1 + 2 + 4 + 16: case 2 + 16 + 64: case 2 + 16 + 256: case 2 + 16 + 64 + 256: case 1 + 2 + 16 + 64: case 1 + 2 + 16 + 256: case 1 + 2 + 16 + 64 + 256: case 2 + 4 + 16 + 64: case 2 + 4 + 16 + 256: case 2 + 4 + 16 + 64 + 256: case 1 + 2 + 4 + 16 + 64: case 1 + 2 + 4 + 16 + 256: case 1 + 2 + 4 + 16 + 64 + 256: { mask = 2 + 16; break; } // Bottom case 16 + 64 + 128: case 16 + 128 + 256: case 16 + 64 + 128 + 256: case 1 + 16 + 64 + 128: case 4 + 16 + 64 + 128: case 1 + 4 + 16 + 64 + 128: case 1 + 16 + 128 + 256: case 4 + 16 + 128 + 256: case 1 + 4 + 16 + 128 + 256: case 1 + 16 + 64 + 128 + 256: case 4 + 16 + 64 + 128 + 256: case 1 + 4 + 16 + 64 + 128 + 256: case 1 + 16 + 128: case 4 + 16 + 128: case 1 + 4 + 16 + 128: { mask = 16 + 128; break; } // Vertical Straight case 1 + 2 + 16 + 128: case 2 + 4 + 16 + 128: case 1 + 2 + 4 + 16 + 128: case 2 + 16 + 64 + 128: case 2 + 16 + 128 + 256: case 2 + 16 + 64 + 128 + 256: case 1 + 2 + 16 + 64 + 128: case 1 + 2 + 16 + 128 + 256: case 2 + 4 + 16 + 64 + 128: case 2 + 4 + 16 + 128 + 256: case 1 + 2 + 16 + 64 + 128 + 256: case 1 + 2 + 4 + 64 + 128 + 256: case 1 + 2 + 4 + 16 + 128 + 256: case 1 + 2 + 4 + 16 + 64 + 128: case 2 + 4 + 16 + 64 + 128 + 256: case 1 + 2 + 4 + 16 + 64 + 128 + 256: { mask = 2 + 16 + 128; break; } // Horizontal Straight case 1 + 8 + 16 + 32: case 8 + 16 + 32 + 64: case 1 + 8 + 16 + 32 + 64: case 4 + 8 + 16 + 32: case 8 + 16 + 32 + 256: case 4 + 8 + 16 + 32 + 64: case 4 + 8 + 16 + 32 + 256: case 1 + 4 + 8 + 16 + 32: case 1 + 8 + 16 + 32 + 256: case 8 + 16 + 32 + 64 + 256: case 1 + 4 + 8 + 16 + 32 + 64: case 1 + 4 + 8 + 16 + 32 + 256: case 1 + 8 + 16 + 32 + 64 + 256: case 4 + 8 + 16 + 32 + 64 + 256: case 1 + 4 + 8 + 16 + 32 + 64 + 256: { mask = 8 + 16 + 32; break; } // Top Left Corner case 1 + 2 + 4 + 16 + 32: case 2 + 4 + 16 + 32 + 256: case 2 + 4 + 16 + 32 + 64: case 1 + 2 + 4 + 16 + 32 + 256: case 1 + 2 + 4 + 16 + 32 + 64: case 2 + 4 + 16 + 32 + 64 + 256: case 1 + 2 + 4 + 16 + 32 + 64 + 256: { mask = 2 + 4 + 16 + 32; break; } // Bottom Left Corner case 1 + 16 + 32 + 128 + 256: case 4 + 16 + 32 + 128 + 256: case 16 + 32 + 64 + 128 + 256: case 4 + 16 + 32 + 64 + 128 + 256: case 1 + 4 + 16 + 32 + 128 + 256: case 1 + 16 + 32 + 64 + 128 + 256: case 1 + 4 + 16 + 32 + 64 + 128 + 256: { mask = 16 + 32 + 128 + 256; break; } // Top Right Corner case 1 + 2 + 4 + 8 + 16: case 1 + 2 + 8 + 16 + 64: case 1 + 2 + 8 + 16 + 256: case 1 + 2 + 4 + 8 + 16 + 64: case 1 + 2 + 8 + 16 + 64 + 256: case 1 + 2 + 4 + 8 + 16 + 256: case 1 + 2 + 4 + 8 + 16 + 64 + 256: { mask = 1 + 2 + 8 + 16; break; } // Bottom Right Corner case 1 + 8 + 16 + 64 + 128: case 8 + 16 + 64 + 128 + 256: case 4 + 8 + 16 + 64 + 128: case 1 + 4 + 8 + 16 + 64 + 128: case 1 + 8 + 16 + 64 + 128 + 256: case 4 + 8 + 16 + 64 + 128 + 256: case 1 + 4 + 8 + 16 + 64 + 128 + 256: { mask = 8 + 16 + 64 + 128; break; } // Full Top case 1 + 2 + 4 + 8 + 16 + 32 + 64: case 1 + 2 + 4 + 8 + 16 + 32 + 256: case 1 + 2 + 4 + 8 + 16 + 32 + 64 + 256: { mask = 1 + 2 + 4 + 8 + 16 + 32; break; } // Full Bottom case 1 + 8 + 16 + 32 + 64 + 128 + 256: case 4 + 8 + 16 + 32 + 64 + 128 + 256: case 1 + 4 + 8 + 16 + 32 + 64 + 128 + 256: { mask = 8 + 16 + 32 + 64 + 128 + 256; break; } // Full Left case 1 + 2 + 4 + 16 + 32 + 128 + 256: case 2 + 4 + 16 + 32 + 64 + 128 + 256: case 1 + 2 + 4 + 16 + 32 + 64 + 128 + 256: { mask = 2 + 4 + 16 + 32 + 128 + 256; break; } // Full Right case 1 + 2 + 4 + 8 + 16 + 64 + 128: case 1 + 2 + 8 + 16 + 64 + 128 + 256: case 1 + 2 + 4 + 8 + 16 + 64 + 128 + 256: { mask = 1 + 2 + 8 + 16 + 64 + 128; break; } // Top Left Tricorner case 1 + 2 + 16 + 32: case 2 + 16 + 32 + 64: case 2 + 16 + 32 + 256: case 1 + 2 + 16 + 32 + 64: case 1 + 2 + 16 + 32 + 256: case 2 + 16 + 32 + 64 + 256: case 1 + 2 + 16 + 32 + 64 + 256: { mask = 2 + 16 + 32; break; } // Bottom Left Tricorner case 1 + 16 + 32 + 128: case 4 + 16 + 32 + 128: case 16 + 32 + 64 + 128: case 4 + 16 + 32 + 64 + 128: case 1 + 16 + 32 + 64 + 128: case 1 + 4 + 16 + 32 + 64 + 128: case 1 + 4 + 16 + 32 + 128: { mask = 16 + 32 + 128; break; } // Top Right Tricorner case 2 + 4 + 8 + 16: case 2 + 8 + 16 + 64: case 2 + 8 + 16 + 256: case 2 + 4 + 8 + 16 + 64: case 2 + 8 + 16 + 64 + 256: case 2 + 4 + 8 + 16 + 256: case 2 + 4 + 8 + 16 + 64 + 256: { mask = 2 + 8 + 16; break; } // Bottom Right Tricorner case 1 + 8 + 16 + 128: case 4 + 8 + 16 + 128: case 8 + 16 + 128 + 256: case 1 + 8 + 16 + 128 + 256: case 1 + 4 + 8 + 16 + 128: case 4 + 8 + 16 + 128 + 256: case 1 + 4 + 8 + 16 + 128 + 256: { mask = 8 + 16 + 128; break; } // Three-way Left case 2 + 4 + 8 + 16 + 128: case 2 + 8 + 16 + 128 + 256: case 2 + 4 + 8 + 16 + 128 + 256: { mask = 2 + 8 + 16 + 128; break; } // Three-way Right case 1 + 2 + 16 + 32 + 128: case 2 + 16 + 32 + 64 + 128: case 1 + 2 + 16 + 32 + 64 + 128: { mask = 2 + 16 + 32 + 128; break; } // Three-way Top case 1 + 8 + 16 + 32 + 128: case 4 + 8 + 16 + 32 + 128: case 1 + 4 + 8 + 16 + 32 + 128: { mask = 8 + 16 + 32 + 128; break; } // Three-way Bottom case 2 + 8 + 16 + 32 + 64: case 2 + 8 + 16 + 32 + 256: case 2 + 8 + 16 + 32 + 64 + 256: { mask = 2 + 8 + 16 + 32; break; } // Three-corner Top Left case 2 + 4 + 8 + 16 + 32 + 64: case 2 + 4 + 8 + 16 + 32 + 256: case 2 + 4 + 8 + 16 + 32 + 64 + 256: { mask = 2 + 4 + 8 + 16 + 32; break; } // Three-corner Bottom Left case 1 + 8 + 16 + 32 + 128 + 256: case 4 + 8 + 16 + 32 + 128 + 256: case 1 + 4 + 8 + 16 + 32 + 128 + 256: { mask = 8 + 16 + 32 + 128 + 256; break; } // Three-corner Top Right case 1 + 2 + 8 + 16 + 32 + 64: case 1 + 2 + 8 + 16 + 32 + 256: case 1 + 2 + 8 + 16 + 32 + 64 + 256: { mask = 1 + 2 + 8 + 16 + 32; break; } // Three-corner Bottom Right case 1 + 8 + 16 + 32 + 64 + 128: case 4 + 8 + 16 + 32 + 64 + 128: case 1 + 4 + 8 + 16 + 32 + 64 + 128: { mask = 8 + 16 + 32 + 64 + 128; break; } // Left Side Top Right Corner case 1 + 2 + 4 + 16 + 32 + 128: case 2 + 4 + 16 + 32 + 64 + 128: case 1 + 2 + 4 + 16 + 32 + 64 + 128: { mask = 2 + 4 + 16 + 32 + 128; break; } // Left Side Bottom Right Corner case 1 + 2 + 16 + 32 + 128 + 256: case 2 + 16 + 32 + 64 + 128 + 256: case 1 + 2 + 16 + 32 + 64 + 128 + 256: { mask = 2 + 16 + 32 + 128 + 256; break; } // Right Side Top Left Corner case 1 + 2 + 4 + 8 + 16 + 128: case 1 + 2 + 8 + 16 + 128 + 256: case 1 + 2 + 4 + 8 + 16 + 128 + 256: { mask = 1 + 2 + 8 + 16 + 128; break; } // Right Side Bottom Left Corner case 2 + 4 + 8 + 16 + 64 + 128: case 2 + 8 + 16 + 64 + 128 + 256: case 2 + 4 + 8 + 16 + 64 + 128 + 256: { mask = 2 + 8 + 16 + 64 + 128; break; } // Single case 1 + 16: case 4 + 16: case 16 + 64: case 16 + 256: case 1 + 4 + 16: case 1 + 16 + 64: case 1 + 16 + 256: case 4 + 16 + 64: case 4 + 16 + 256: case 16 + 64 + 256: case 1 + 4 + 16 + 64: case 1 + 4 + 16 + 256: case 1 + 16 + 64 + 256: case 4 + 16 + 64 + 256: case 1 + 4 + 16 + 64 + 256: { mask = 16; break; } } return mask; } } }