830 lines
36 KiB
C#
830 lines
36 KiB
C#
|
using System;
|
||
|
using System.Linq;
|
||
|
using System.Reflection;
|
||
|
using System.Collections.Generic;
|
||
|
using UnityEngine.Tilemaps;
|
||
|
using UnityEngine.Serialization;
|
||
|
|
||
|
namespace UnityEngine
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Generic visual tile for creating different tilesets like terrain, pipeline, random or animated tiles.
|
||
|
/// This is templated to accept a Neighbor Rule Class for Custom Rules.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">Neighbor Rule Class for Custom Rules</typeparam>
|
||
|
public class RuleTile<T> : RuleTile
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Returns the Neighbor Rule Class type for this Rule Tile.
|
||
|
/// </summary>
|
||
|
public sealed override Type m_NeighborType => typeof(T);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Generic visual tile for creating different tilesets like terrain, pipeline, random or animated tiles.
|
||
|
/// </summary>
|
||
|
[Serializable]
|
||
|
[HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.tilemap.extras@latest/index.html?subfolder=/manual/RuleTile.html")]
|
||
|
public class RuleTile : TileBase
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Returns the default Neighbor Rule Class type.
|
||
|
/// </summary>
|
||
|
public virtual Type m_NeighborType => typeof(TilingRuleOutput.Neighbor);
|
||
|
|
||
|
/// <summary>
|
||
|
/// The Default Sprite set when creating a new Rule.
|
||
|
/// </summary>
|
||
|
public Sprite m_DefaultSprite;
|
||
|
/// <summary>
|
||
|
/// The Default GameObject set when creating a new Rule.
|
||
|
/// </summary>
|
||
|
public GameObject m_DefaultGameObject;
|
||
|
/// <summary>
|
||
|
/// The Default Collider Type set when creating a new Rule.
|
||
|
/// </summary>
|
||
|
public Tile.ColliderType m_DefaultColliderType = Tile.ColliderType.Sprite;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Angle in which the RuleTile is rotated by for matching in Degrees.
|
||
|
/// </summary>
|
||
|
public virtual int m_RotationAngle => 90;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Number of rotations the RuleTile can be rotated by for matching.
|
||
|
/// </summary>
|
||
|
public int m_RotationCount => 360 / m_RotationAngle;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The data structure holding the Rule information for matching Rule Tiles with
|
||
|
/// its neighbors.
|
||
|
/// </summary>
|
||
|
[Serializable]
|
||
|
public class TilingRuleOutput
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Id for this Rule.
|
||
|
/// </summary>
|
||
|
public int m_Id;
|
||
|
/// <summary>
|
||
|
/// The output Sprites for this Rule.
|
||
|
/// </summary>
|
||
|
public Sprite[] m_Sprites = new Sprite[1];
|
||
|
/// <summary>
|
||
|
/// The output GameObject for this Rule.
|
||
|
/// </summary>
|
||
|
public GameObject m_GameObject;
|
||
|
/// <summary>
|
||
|
/// The output minimum Animation Speed for this Rule.
|
||
|
/// </summary>
|
||
|
[FormerlySerializedAs("m_AnimationSpeed")]
|
||
|
public float m_MinAnimationSpeed = 1f;
|
||
|
/// <summary>
|
||
|
/// The output maximum Animation Speed for this Rule.
|
||
|
/// </summary>
|
||
|
[FormerlySerializedAs("m_AnimationSpeed")]
|
||
|
public float m_MaxAnimationSpeed = 1f;
|
||
|
/// <summary>
|
||
|
/// The perlin scale factor for this Rule.
|
||
|
/// </summary>
|
||
|
public float m_PerlinScale = 0.5f;
|
||
|
/// <summary>
|
||
|
/// The output type for this Rule.
|
||
|
/// </summary>
|
||
|
public OutputSprite m_Output = OutputSprite.Single;
|
||
|
/// <summary>
|
||
|
/// The output Collider Type for this Rule.
|
||
|
/// </summary>
|
||
|
public Tile.ColliderType m_ColliderType = Tile.ColliderType.Sprite;
|
||
|
/// <summary>
|
||
|
/// The randomized transform output for this Rule.
|
||
|
/// </summary>
|
||
|
public Transform m_RandomTransform;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The enumeration for matching Neighbors when matching Rule Tiles
|
||
|
/// </summary>
|
||
|
public class Neighbor
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// The Rule Tile will check if the contents of the cell in that direction is an instance of this Rule Tile.
|
||
|
/// If not, the rule will fail.
|
||
|
/// </summary>
|
||
|
public const int This = 1;
|
||
|
/// <summary>
|
||
|
/// The Rule Tile will check if the contents of the cell in that direction is not an instance of this Rule Tile.
|
||
|
/// If it is, the rule will fail.
|
||
|
/// </summary>
|
||
|
public const int NotThis = 2;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The enumeration for the transform rule used when matching Rule Tiles.
|
||
|
/// </summary>
|
||
|
public enum Transform
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// The Rule Tile will match Tiles exactly as laid out in its neighbors.
|
||
|
/// </summary>
|
||
|
Fixed,
|
||
|
/// <summary>
|
||
|
/// The Rule Tile will rotate and match its neighbors.
|
||
|
/// </summary>
|
||
|
Rotated,
|
||
|
/// <summary>
|
||
|
/// The Rule Tile will mirror in the X axis and match its neighbors.
|
||
|
/// </summary>
|
||
|
MirrorX,
|
||
|
/// <summary>
|
||
|
/// The Rule Tile will mirror in the Y axis and match its neighbors.
|
||
|
/// </summary>
|
||
|
MirrorY,
|
||
|
/// <summary>
|
||
|
/// The Rule Tile will mirror in the X or Y axis and match its neighbors.
|
||
|
/// </summary>
|
||
|
MirrorXY
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The Output for the Tile which fits this Rule.
|
||
|
/// </summary>
|
||
|
public enum OutputSprite
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// A Single Sprite will be output.
|
||
|
/// </summary>
|
||
|
Single,
|
||
|
/// <summary>
|
||
|
/// A Random Sprite will be output.
|
||
|
/// </summary>
|
||
|
Random,
|
||
|
/// <summary>
|
||
|
/// A Sprite Animation will be output.
|
||
|
/// </summary>
|
||
|
Animation
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The data structure holding the Rule information for matching Rule Tiles with
|
||
|
/// its neighbors.
|
||
|
/// </summary>
|
||
|
[Serializable]
|
||
|
public class TilingRule : TilingRuleOutput
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// The matching Rule conditions for each of its neighboring Tiles.
|
||
|
/// </summary>
|
||
|
public List<int> m_Neighbors = new List<int>();
|
||
|
/// <summary>
|
||
|
/// * Preset this list to RuleTile backward compatible, but not support for HexagonalRuleTile backward compatible.
|
||
|
/// </summary>
|
||
|
public List<Vector3Int> m_NeighborPositions = new List<Vector3Int>()
|
||
|
{
|
||
|
new Vector3Int(-1, 1, 0),
|
||
|
new Vector3Int(0, 1, 0),
|
||
|
new Vector3Int(1, 1, 0),
|
||
|
new Vector3Int(-1, 0, 0),
|
||
|
new Vector3Int(1, 0, 0),
|
||
|
new Vector3Int(-1, -1, 0),
|
||
|
new Vector3Int(0, -1, 0),
|
||
|
new Vector3Int(1, -1, 0),
|
||
|
};
|
||
|
/// <summary>
|
||
|
/// The transform matching Rule for this Rule.
|
||
|
/// </summary>
|
||
|
public Transform m_RuleTransform;
|
||
|
|
||
|
/// <summary>
|
||
|
/// This clones a copy of the TilingRule.
|
||
|
/// </summary>
|
||
|
/// <returns>A copy of the TilingRule.</returns>
|
||
|
public TilingRule Clone()
|
||
|
{
|
||
|
TilingRule rule = new TilingRule
|
||
|
{
|
||
|
m_Neighbors = new List<int>(m_Neighbors),
|
||
|
m_NeighborPositions = new List<Vector3Int>(m_NeighborPositions),
|
||
|
m_RuleTransform = m_RuleTransform,
|
||
|
m_Sprites = new Sprite[m_Sprites.Length],
|
||
|
m_GameObject = m_GameObject,
|
||
|
m_MinAnimationSpeed = m_MinAnimationSpeed,
|
||
|
m_MaxAnimationSpeed = m_MaxAnimationSpeed,
|
||
|
m_PerlinScale = m_PerlinScale,
|
||
|
m_Output = m_Output,
|
||
|
m_ColliderType = m_ColliderType,
|
||
|
m_RandomTransform = m_RandomTransform,
|
||
|
};
|
||
|
Array.Copy(m_Sprites, rule.m_Sprites, m_Sprites.Length);
|
||
|
return rule;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns all neighbors of this Tile as a dictionary
|
||
|
/// </summary>
|
||
|
/// <returns>A dictionary of neighbors for this Tile</returns>
|
||
|
public Dictionary<Vector3Int, int> GetNeighbors()
|
||
|
{
|
||
|
Dictionary<Vector3Int, int> dict = new Dictionary<Vector3Int, int>();
|
||
|
|
||
|
for (int i = 0; i < m_Neighbors.Count && i < m_NeighborPositions.Count; i++)
|
||
|
dict.Add(m_NeighborPositions[i], m_Neighbors[i]);
|
||
|
|
||
|
return dict;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Applies the values from the given dictionary as this Tile's neighbors
|
||
|
/// </summary>
|
||
|
/// <param name="dict">Dictionary to apply values from</param>
|
||
|
public void ApplyNeighbors(Dictionary<Vector3Int, int> dict)
|
||
|
{
|
||
|
m_NeighborPositions = dict.Keys.ToList();
|
||
|
m_Neighbors = dict.Values.ToList();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the cell bounds of the TilingRule.
|
||
|
/// </summary>
|
||
|
/// <returns>Returns the cell bounds of the TilingRule.</returns>
|
||
|
public BoundsInt GetBounds()
|
||
|
{
|
||
|
BoundsInt bounds = new BoundsInt(Vector3Int.zero, Vector3Int.one);
|
||
|
foreach (var neighbor in GetNeighbors())
|
||
|
{
|
||
|
bounds.xMin = Mathf.Min(bounds.xMin, neighbor.Key.x);
|
||
|
bounds.yMin = Mathf.Min(bounds.yMin, neighbor.Key.y);
|
||
|
bounds.xMax = Mathf.Max(bounds.xMax, neighbor.Key.x + 1);
|
||
|
bounds.yMax = Mathf.Max(bounds.yMax, neighbor.Key.y + 1);
|
||
|
}
|
||
|
return bounds;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Attribute which marks a property which cannot be overridden by a RuleOverrideTile
|
||
|
/// </summary>
|
||
|
public class DontOverride : Attribute { }
|
||
|
|
||
|
/// <summary>
|
||
|
/// A list of Tiling Rules for the Rule Tile.
|
||
|
/// </summary>
|
||
|
[HideInInspector] public List<TilingRule> m_TilingRules = new List<RuleTile.TilingRule>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a set of neighboring positions for this RuleTile
|
||
|
/// </summary>
|
||
|
public HashSet<Vector3Int> neighborPositions
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (m_NeighborPositions.Count == 0)
|
||
|
UpdateNeighborPositions();
|
||
|
|
||
|
return m_NeighborPositions;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private HashSet<Vector3Int> m_NeighborPositions = new HashSet<Vector3Int>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Updates the neighboring positions of this RuleTile
|
||
|
/// </summary>
|
||
|
public void UpdateNeighborPositions()
|
||
|
{
|
||
|
m_CacheTilemapsNeighborPositions.Clear();
|
||
|
|
||
|
HashSet<Vector3Int> positions = m_NeighborPositions;
|
||
|
positions.Clear();
|
||
|
|
||
|
foreach (TilingRule rule in m_TilingRules)
|
||
|
{
|
||
|
foreach (var neighbor in rule.GetNeighbors())
|
||
|
{
|
||
|
Vector3Int position = neighbor.Key;
|
||
|
positions.Add(position);
|
||
|
|
||
|
// Check rule against rotations of 0, 90, 180, 270
|
||
|
if (rule.m_RuleTransform == TilingRuleOutput.Transform.Rotated)
|
||
|
{
|
||
|
for (int angle = m_RotationAngle; angle < 360; angle += m_RotationAngle)
|
||
|
{
|
||
|
positions.Add(GetRotatedPosition(position, angle));
|
||
|
}
|
||
|
}
|
||
|
// Check rule against x-axis, y-axis mirror
|
||
|
else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorXY)
|
||
|
{
|
||
|
positions.Add(GetMirroredPosition(position, true, true));
|
||
|
positions.Add(GetMirroredPosition(position, true, false));
|
||
|
positions.Add(GetMirroredPosition(position, false, true));
|
||
|
}
|
||
|
// Check rule against x-axis mirror
|
||
|
else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorX)
|
||
|
{
|
||
|
positions.Add(GetMirroredPosition(position, true, false));
|
||
|
}
|
||
|
// Check rule against y-axis mirror
|
||
|
else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorY)
|
||
|
{
|
||
|
positions.Add(GetMirroredPosition(position, false, true));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// StartUp is called on the first frame of the running Scene.
|
||
|
/// </summary>
|
||
|
/// <param name="position">Position of the Tile on the Tilemap.</param>
|
||
|
/// <param name="tilemap">The Tilemap the tile is present on.</param>
|
||
|
/// <param name="instantiatedGameObject">The GameObject instantiated for the Tile.</param>
|
||
|
/// <returns>Whether StartUp was successful</returns>
|
||
|
public override bool StartUp(Vector3Int position, ITilemap tilemap, GameObject instantiatedGameObject)
|
||
|
{
|
||
|
if (instantiatedGameObject != null)
|
||
|
{
|
||
|
Tilemap tmpMap = tilemap.GetComponent<Tilemap>();
|
||
|
Matrix4x4 orientMatrix = tmpMap.orientationMatrix;
|
||
|
|
||
|
var iden = Matrix4x4.identity;
|
||
|
Vector3 gameObjectTranslation = new Vector3();
|
||
|
Quaternion gameObjectRotation = new Quaternion();
|
||
|
Vector3 gameObjectScale = new Vector3();
|
||
|
|
||
|
bool ruleMatched = false;
|
||
|
Matrix4x4 transform = iden;
|
||
|
foreach (TilingRule rule in m_TilingRules)
|
||
|
{
|
||
|
if (RuleMatches(rule, position, tilemap, ref transform))
|
||
|
{
|
||
|
transform = orientMatrix * transform;
|
||
|
|
||
|
// Converts the tile's translation, rotation, & scale matrix to values to be used by the instantiated GameObject
|
||
|
gameObjectTranslation = new Vector3(transform.m03, transform.m13, transform.m23);
|
||
|
gameObjectRotation = Quaternion.LookRotation(new Vector3(transform.m02, transform.m12, transform.m22), new Vector3(transform.m01, transform.m11, transform.m21));
|
||
|
gameObjectScale = transform.lossyScale;
|
||
|
|
||
|
ruleMatched = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!ruleMatched)
|
||
|
{
|
||
|
// Fallback to just using the orientMatrix for the translation, rotation, & scale values.
|
||
|
gameObjectTranslation = new Vector3(orientMatrix.m03, orientMatrix.m13, orientMatrix.m23);
|
||
|
gameObjectRotation = Quaternion.LookRotation(new Vector3(orientMatrix.m02, orientMatrix.m12, orientMatrix.m22), new Vector3(orientMatrix.m01, orientMatrix.m11, orientMatrix.m21));
|
||
|
gameObjectScale = orientMatrix.lossyScale;
|
||
|
}
|
||
|
|
||
|
instantiatedGameObject.transform.localPosition = gameObjectTranslation + tmpMap.CellToLocalInterpolated(position + tmpMap.tileAnchor);
|
||
|
instantiatedGameObject.transform.localRotation = gameObjectRotation;
|
||
|
instantiatedGameObject.transform.localScale = gameObjectScale;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Retrieves any tile rendering data from the scripted tile.
|
||
|
/// </summary>
|
||
|
/// <param name="position">Position of the Tile on the Tilemap.</param>
|
||
|
/// <param name="tilemap">The Tilemap the tile is present on.</param>
|
||
|
/// <param name="tileData">Data to render the tile.</param>
|
||
|
public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
|
||
|
{
|
||
|
var iden = Matrix4x4.identity;
|
||
|
|
||
|
tileData.sprite = m_DefaultSprite;
|
||
|
tileData.gameObject = m_DefaultGameObject;
|
||
|
tileData.colliderType = m_DefaultColliderType;
|
||
|
tileData.flags = TileFlags.LockTransform;
|
||
|
tileData.transform = iden;
|
||
|
|
||
|
Matrix4x4 transform = iden;
|
||
|
foreach (TilingRule rule in m_TilingRules)
|
||
|
{
|
||
|
if (RuleMatches(rule, position, tilemap, ref transform))
|
||
|
{
|
||
|
switch (rule.m_Output)
|
||
|
{
|
||
|
case TilingRuleOutput.OutputSprite.Single:
|
||
|
case TilingRuleOutput.OutputSprite.Animation:
|
||
|
tileData.sprite = rule.m_Sprites[0];
|
||
|
break;
|
||
|
case TilingRuleOutput.OutputSprite.Random:
|
||
|
int index = Mathf.Clamp(Mathf.FloorToInt(GetPerlinValue(position, rule.m_PerlinScale, 100000f) * rule.m_Sprites.Length), 0, rule.m_Sprites.Length - 1);
|
||
|
tileData.sprite = rule.m_Sprites[index];
|
||
|
if (rule.m_RandomTransform != TilingRuleOutput.Transform.Fixed)
|
||
|
transform = ApplyRandomTransform(rule.m_RandomTransform, transform, rule.m_PerlinScale, position);
|
||
|
break;
|
||
|
}
|
||
|
tileData.transform = transform;
|
||
|
tileData.gameObject = rule.m_GameObject;
|
||
|
tileData.colliderType = rule.m_ColliderType;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a Perlin Noise value based on the given inputs.
|
||
|
/// </summary>
|
||
|
/// <param name="position">Position of the Tile on the Tilemap.</param>
|
||
|
/// <param name="scale">The Perlin Scale factor of the Tile.</param>
|
||
|
/// <param name="offset">Offset of the Tile on the Tilemap.</param>
|
||
|
/// <returns>A Perlin Noise value based on the given inputs.</returns>
|
||
|
public static float GetPerlinValue(Vector3Int position, float scale, float offset)
|
||
|
{
|
||
|
return Mathf.PerlinNoise((position.x + offset) * scale, (position.y + offset) * scale);
|
||
|
}
|
||
|
|
||
|
static Dictionary<Tilemap, KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>>> m_CacheTilemapsNeighborPositions = new Dictionary<Tilemap, KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>>>();
|
||
|
static TileBase[] m_AllocatedUsedTileArr = Array.Empty<TileBase>();
|
||
|
|
||
|
static bool IsTilemapUsedTilesChange(Tilemap tilemap, out KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>> hashSet)
|
||
|
{
|
||
|
if (!m_CacheTilemapsNeighborPositions.TryGetValue(tilemap, out hashSet))
|
||
|
return true;
|
||
|
|
||
|
var oldUsedTiles = hashSet.Key;
|
||
|
int newUsedTilesCount = tilemap.GetUsedTilesCount();
|
||
|
if (newUsedTilesCount != oldUsedTiles.Count)
|
||
|
return true;
|
||
|
|
||
|
if (m_AllocatedUsedTileArr.Length < newUsedTilesCount)
|
||
|
Array.Resize(ref m_AllocatedUsedTileArr, newUsedTilesCount);
|
||
|
|
||
|
tilemap.GetUsedTilesNonAlloc(m_AllocatedUsedTileArr);
|
||
|
for (int i = 0; i < newUsedTilesCount; i++)
|
||
|
{
|
||
|
TileBase newUsedTile = m_AllocatedUsedTileArr[i];
|
||
|
if (!oldUsedTiles.Contains(newUsedTile))
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>> CachingTilemapNeighborPositions(Tilemap tilemap)
|
||
|
{
|
||
|
int usedTileCount = tilemap.GetUsedTilesCount();
|
||
|
HashSet<TileBase> usedTiles = new HashSet<TileBase>();
|
||
|
HashSet<Vector3Int> neighborPositions = new HashSet<Vector3Int>();
|
||
|
|
||
|
if (m_AllocatedUsedTileArr.Length < usedTileCount)
|
||
|
Array.Resize(ref m_AllocatedUsedTileArr, usedTileCount);
|
||
|
|
||
|
tilemap.GetUsedTilesNonAlloc(m_AllocatedUsedTileArr);
|
||
|
|
||
|
for (int i = 0; i < usedTileCount; i++)
|
||
|
{
|
||
|
TileBase tile = m_AllocatedUsedTileArr[i];
|
||
|
usedTiles.Add(tile);
|
||
|
RuleTile ruleTile = null;
|
||
|
|
||
|
if (tile is RuleTile rt)
|
||
|
ruleTile = rt;
|
||
|
else if (tile is RuleOverrideTile ot)
|
||
|
ruleTile = ot.m_Tile;
|
||
|
|
||
|
if (ruleTile)
|
||
|
foreach (Vector3Int neighborPosition in ruleTile.neighborPositions)
|
||
|
neighborPositions.Add(neighborPosition);
|
||
|
}
|
||
|
|
||
|
var value = new KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>>(usedTiles, neighborPositions);
|
||
|
m_CacheTilemapsNeighborPositions[tilemap] = value;
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
static bool NeedRelease()
|
||
|
{
|
||
|
foreach (var keypair in m_CacheTilemapsNeighborPositions)
|
||
|
{
|
||
|
if (keypair.Key == null)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static void ReleaseDestroyedTilemapCacheData()
|
||
|
{
|
||
|
if (!NeedRelease())
|
||
|
return;
|
||
|
|
||
|
var hasCleared = false;
|
||
|
var keys = m_CacheTilemapsNeighborPositions.Keys.ToArray();
|
||
|
foreach (var key in keys)
|
||
|
{
|
||
|
if (key == null && m_CacheTilemapsNeighborPositions.Remove(key))
|
||
|
hasCleared = true;
|
||
|
}
|
||
|
if (hasCleared)
|
||
|
{
|
||
|
// TrimExcess
|
||
|
m_CacheTilemapsNeighborPositions = new Dictionary<Tilemap, KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>>>(m_CacheTilemapsNeighborPositions);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Retrieves any tile animation data from the scripted tile.
|
||
|
/// </summary>
|
||
|
/// <param name="position">Position of the Tile on the Tilemap.</param>
|
||
|
/// <param name="tilemap">The Tilemap the tile is present on.</param>
|
||
|
/// <param name="tileAnimationData">Data to run an animation on the tile.</param>
|
||
|
/// <returns>Whether the call was successful.</returns>
|
||
|
public override bool GetTileAnimationData(Vector3Int position, ITilemap tilemap, ref TileAnimationData tileAnimationData)
|
||
|
{
|
||
|
Matrix4x4 transform = Matrix4x4.identity;
|
||
|
foreach (TilingRule rule in m_TilingRules)
|
||
|
{
|
||
|
if (rule.m_Output == TilingRuleOutput.OutputSprite.Animation)
|
||
|
{
|
||
|
if (RuleMatches(rule, position, tilemap, ref transform))
|
||
|
{
|
||
|
tileAnimationData.animatedSprites = rule.m_Sprites;
|
||
|
tileAnimationData.animationSpeed = Random.Range( rule.m_MinAnimationSpeed, rule.m_MaxAnimationSpeed);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This method is called when the tile is refreshed.
|
||
|
/// </summary>
|
||
|
/// <param name="position">Position of the Tile on the Tilemap.</param>
|
||
|
/// <param name="tilemap">The Tilemap the tile is present on.</param>
|
||
|
public override void RefreshTile(Vector3Int position, ITilemap tilemap)
|
||
|
{
|
||
|
base.RefreshTile(position, tilemap);
|
||
|
|
||
|
Tilemap baseTilemap = tilemap.GetComponent<Tilemap>();
|
||
|
|
||
|
ReleaseDestroyedTilemapCacheData(); // Prevent memory leak
|
||
|
|
||
|
if (IsTilemapUsedTilesChange(baseTilemap, out var neighborPositionsSet))
|
||
|
neighborPositionsSet = CachingTilemapNeighborPositions(baseTilemap);
|
||
|
|
||
|
var neighborPositionsRuleTile = neighborPositionsSet.Value;
|
||
|
foreach (Vector3Int offset in neighborPositionsRuleTile)
|
||
|
{
|
||
|
Vector3Int offsetPosition = GetOffsetPositionReverse(position, offset);
|
||
|
TileBase tile = tilemap.GetTile(offsetPosition);
|
||
|
RuleTile ruleTile = null;
|
||
|
|
||
|
if (tile is RuleTile rt)
|
||
|
ruleTile = rt;
|
||
|
else if (tile is RuleOverrideTile ot)
|
||
|
ruleTile = ot.m_Tile;
|
||
|
|
||
|
if (ruleTile != null)
|
||
|
if (ruleTile == this || ruleTile.neighborPositions.Contains(offset))
|
||
|
base.RefreshTile(offsetPosition, tilemap);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Does a Rule Match given a Tiling Rule and neighboring Tiles.
|
||
|
/// </summary>
|
||
|
/// <param name="rule">The Tiling Rule to match with.</param>
|
||
|
/// <param name="position">Position of the Tile on the Tilemap.</param>
|
||
|
/// <param name="tilemap">The tilemap to match with.</param>
|
||
|
/// <param name="transform">A transform matrix which will match the Rule.</param>
|
||
|
/// <returns>True if there is a match, False if not.</returns>
|
||
|
public virtual bool RuleMatches(TilingRule rule, Vector3Int position, ITilemap tilemap, ref Matrix4x4 transform)
|
||
|
{
|
||
|
if (RuleMatches(rule, position, tilemap, 0))
|
||
|
{
|
||
|
transform = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0f, 0f, 0f), Vector3.one);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Check rule against rotations of 0, 90, 180, 270
|
||
|
if (rule.m_RuleTransform == TilingRuleOutput.Transform.Rotated)
|
||
|
{
|
||
|
for (int angle = m_RotationAngle; angle < 360; angle += m_RotationAngle)
|
||
|
{
|
||
|
if (RuleMatches(rule, position, tilemap, angle))
|
||
|
{
|
||
|
transform = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0f, 0f, -angle), Vector3.one);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Check rule against x-axis, y-axis mirror
|
||
|
else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorXY)
|
||
|
{
|
||
|
if (RuleMatches(rule, position, tilemap, true, true))
|
||
|
{
|
||
|
transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(-1f, -1f, 1f));
|
||
|
return true;
|
||
|
}
|
||
|
if (RuleMatches(rule, position, tilemap, true, false))
|
||
|
{
|
||
|
transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(-1f, 1f, 1f));
|
||
|
return true;
|
||
|
}
|
||
|
if (RuleMatches(rule, position, tilemap, false, true))
|
||
|
{
|
||
|
transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(1f, -1f, 1f));
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
// Check rule against x-axis mirror
|
||
|
else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorX)
|
||
|
{
|
||
|
if (RuleMatches(rule, position, tilemap, true, false))
|
||
|
{
|
||
|
transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(-1f, 1f, 1f));
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
// Check rule against y-axis mirror
|
||
|
else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorY)
|
||
|
{
|
||
|
if (RuleMatches(rule, position, tilemap, false, true))
|
||
|
{
|
||
|
transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(1f, -1f, 1f));
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns a random transform matrix given the random transform rule.
|
||
|
/// </summary>
|
||
|
/// <param name="type">Random transform rule.</param>
|
||
|
/// <param name="original">The original transform matrix.</param>
|
||
|
/// <param name="perlinScale">The Perlin Scale factor of the Tile.</param>
|
||
|
/// <param name="position">Position of the Tile on the Tilemap.</param>
|
||
|
/// <returns>A random transform matrix.</returns>
|
||
|
public virtual Matrix4x4 ApplyRandomTransform(TilingRuleOutput.Transform type, Matrix4x4 original, float perlinScale, Vector3Int position)
|
||
|
{
|
||
|
float perlin = GetPerlinValue(position, perlinScale, 200000f);
|
||
|
switch (type)
|
||
|
{
|
||
|
case TilingRuleOutput.Transform.MirrorXY:
|
||
|
return original * Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(Math.Abs(perlin - 0.5) > 0.25 ? 1f : -1f, perlin < 0.5 ? 1f : -1f, 1f));
|
||
|
case TilingRuleOutput.Transform.MirrorX:
|
||
|
return original * Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(perlin < 0.5 ? 1f : -1f, 1f, 1f));
|
||
|
case TilingRuleOutput.Transform.MirrorY:
|
||
|
return original * Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(1f, perlin < 0.5 ? 1f : -1f, 1f));
|
||
|
case TilingRuleOutput.Transform.Rotated:
|
||
|
int angle = Mathf.Clamp(Mathf.FloorToInt(perlin * m_RotationCount), 0, m_RotationCount - 1) * m_RotationAngle;
|
||
|
return Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0f, 0f, -angle), Vector3.one);
|
||
|
}
|
||
|
return original;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns custom fields for this RuleTile
|
||
|
/// </summary>
|
||
|
/// <param name="isOverrideInstance">Whether override fields are returned</param>
|
||
|
/// <returns>Custom fields for this RuleTile</returns>
|
||
|
public FieldInfo[] GetCustomFields(bool isOverrideInstance)
|
||
|
{
|
||
|
return this.GetType().GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
|
||
|
.Where(field => typeof(RuleTile).GetField(field.Name) == null)
|
||
|
.Where(field => field.IsPublic || field.IsDefined(typeof(SerializeField)))
|
||
|
.Where(field => !field.IsDefined(typeof(HideInInspector)))
|
||
|
.Where(field => !isOverrideInstance || !field.IsDefined(typeof(DontOverride)))
|
||
|
.ToArray();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Checks if there is a match given the neighbor matching rule and a Tile.
|
||
|
/// </summary>
|
||
|
/// <param name="neighbor">Neighbor matching rule.</param>
|
||
|
/// <param name="other">Tile to match.</param>
|
||
|
/// <returns>True if there is a match, False if not.</returns>
|
||
|
public virtual bool RuleMatch(int neighbor, TileBase other)
|
||
|
{
|
||
|
if (other is RuleOverrideTile ot)
|
||
|
other = ot.m_InstanceTile;
|
||
|
|
||
|
switch (neighbor)
|
||
|
{
|
||
|
case TilingRuleOutput.Neighbor.This: return other == this;
|
||
|
case TilingRuleOutput.Neighbor.NotThis: return other != this;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Checks if there is a match given the neighbor matching rule and a Tile with a rotation angle.
|
||
|
/// </summary>
|
||
|
/// <param name="rule">Neighbor matching rule.</param>
|
||
|
/// <param name="position">Position of the Tile on the Tilemap.</param>
|
||
|
/// <param name="tilemap">Tilemap to match.</param>
|
||
|
/// <param name="angle">Rotation angle for matching.</param>
|
||
|
/// <returns>True if there is a match, False if not.</returns>
|
||
|
public bool RuleMatches(TilingRule rule, Vector3Int position, ITilemap tilemap, int angle)
|
||
|
{
|
||
|
var minCount = Math.Min(rule.m_Neighbors.Count, rule.m_NeighborPositions.Count);
|
||
|
for (int i = 0; i < minCount ; i++)
|
||
|
{
|
||
|
int neighbor = rule.m_Neighbors[i];
|
||
|
Vector3Int positionOffset = GetRotatedPosition(rule.m_NeighborPositions[i], angle);
|
||
|
TileBase other = tilemap.GetTile(GetOffsetPosition(position, positionOffset));
|
||
|
if (!RuleMatch(neighbor, other))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Checks if there is a match given the neighbor matching rule and a Tile with mirrored axii.
|
||
|
/// </summary>
|
||
|
/// <param name="rule">Neighbor matching rule.</param>
|
||
|
/// <param name="position">Position of the Tile on the Tilemap.</param>
|
||
|
/// <param name="tilemap">Tilemap to match.</param>
|
||
|
/// <param name="mirrorX">Mirror X Axis for matching.</param>
|
||
|
/// <param name="mirrorY">Mirror Y Axis for matching.</param>
|
||
|
/// <returns>True if there is a match, False if not.</returns>
|
||
|
public bool RuleMatches(TilingRule rule, Vector3Int position, ITilemap tilemap, bool mirrorX, bool mirrorY)
|
||
|
{
|
||
|
var minCount = Math.Min(rule.m_Neighbors.Count, rule.m_NeighborPositions.Count);
|
||
|
for (int i = 0; i < minCount; i++)
|
||
|
{
|
||
|
int neighbor = rule.m_Neighbors[i];
|
||
|
Vector3Int positionOffset = GetMirroredPosition(rule.m_NeighborPositions[i], mirrorX, mirrorY);
|
||
|
TileBase other = tilemap.GetTile(GetOffsetPosition(position, positionOffset));
|
||
|
if (!RuleMatch(neighbor, other))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets a rotated position given its original position and the rotation in degrees.
|
||
|
/// </summary>
|
||
|
/// <param name="position">Original position of Tile.</param>
|
||
|
/// <param name="rotation">Rotation in degrees.</param>
|
||
|
/// <returns>Rotated position of Tile.</returns>
|
||
|
public virtual Vector3Int GetRotatedPosition(Vector3Int position, int rotation)
|
||
|
{
|
||
|
switch (rotation)
|
||
|
{
|
||
|
case 0:
|
||
|
return position;
|
||
|
case 90:
|
||
|
return new Vector3Int(position.y, -position.x, 0);
|
||
|
case 180:
|
||
|
return new Vector3Int(-position.x, -position.y, 0);
|
||
|
case 270:
|
||
|
return new Vector3Int(-position.y, position.x, 0);
|
||
|
}
|
||
|
return position;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets a mirrored position given its original position and the mirroring axii.
|
||
|
/// </summary>
|
||
|
/// <param name="position">Original position of Tile.</param>
|
||
|
/// <param name="mirrorX">Mirror in the X Axis.</param>
|
||
|
/// <param name="mirrorY">Mirror in the Y Axis.</param>
|
||
|
/// <returns>Mirrored position of Tile.</returns>
|
||
|
public virtual Vector3Int GetMirroredPosition(Vector3Int position, bool mirrorX, bool mirrorY)
|
||
|
{
|
||
|
if (mirrorX)
|
||
|
position.x *= -1;
|
||
|
if (mirrorY)
|
||
|
position.y *= -1;
|
||
|
return position;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get the offset for the given position with the given offset.
|
||
|
/// </summary>
|
||
|
/// <param name="position">Position to offset.</param>
|
||
|
/// <param name="offset">Offset for the position.</param>
|
||
|
/// <returns>The offset position.</returns>
|
||
|
public virtual Vector3Int GetOffsetPosition(Vector3Int position, Vector3Int offset)
|
||
|
{
|
||
|
return position + offset;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get the reversed offset for the given position with the given offset.
|
||
|
/// </summary>
|
||
|
/// <param name="position">Position to offset.</param>
|
||
|
/// <param name="offset">Offset for the position.</param>
|
||
|
/// <returns>The reversed offset position.</returns>
|
||
|
public virtual Vector3Int GetOffsetPositionReverse(Vector3Int position, Vector3Int offset)
|
||
|
{
|
||
|
return position - offset;
|
||
|
}
|
||
|
}
|
||
|
}
|