Singularity/Library/PackageCache/com.unity.2d.tilemap.extras.../Runtime/Tiles/RuleTile/RuleTile.cs
2024-05-06 11:45:45 -07:00

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;
}
}
}