Ingen beskrivning
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RuleTile.cs 36KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. using System;
  2. using System.Linq;
  3. using System.Reflection;
  4. using System.Collections.Generic;
  5. using UnityEngine.Tilemaps;
  6. using UnityEngine.Serialization;
  7. namespace UnityEngine
  8. {
  9. /// <summary>
  10. /// Generic visual tile for creating different tilesets like terrain, pipeline, random or animated tiles.
  11. /// This is templated to accept a Neighbor Rule Class for Custom Rules.
  12. /// </summary>
  13. /// <typeparam name="T">Neighbor Rule Class for Custom Rules</typeparam>
  14. public class RuleTile<T> : RuleTile
  15. {
  16. /// <summary>
  17. /// Returns the Neighbor Rule Class type for this Rule Tile.
  18. /// </summary>
  19. public sealed override Type m_NeighborType => typeof(T);
  20. }
  21. /// <summary>
  22. /// Generic visual tile for creating different tilesets like terrain, pipeline, random or animated tiles.
  23. /// </summary>
  24. [Serializable]
  25. [HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.tilemap.extras@latest/index.html?subfolder=/manual/RuleTile.html")]
  26. public class RuleTile : TileBase
  27. {
  28. /// <summary>
  29. /// Returns the default Neighbor Rule Class type.
  30. /// </summary>
  31. public virtual Type m_NeighborType => typeof(TilingRuleOutput.Neighbor);
  32. /// <summary>
  33. /// The Default Sprite set when creating a new Rule.
  34. /// </summary>
  35. public Sprite m_DefaultSprite;
  36. /// <summary>
  37. /// The Default GameObject set when creating a new Rule.
  38. /// </summary>
  39. public GameObject m_DefaultGameObject;
  40. /// <summary>
  41. /// The Default Collider Type set when creating a new Rule.
  42. /// </summary>
  43. public Tile.ColliderType m_DefaultColliderType = Tile.ColliderType.Sprite;
  44. /// <summary>
  45. /// Angle in which the RuleTile is rotated by for matching in Degrees.
  46. /// </summary>
  47. public virtual int m_RotationAngle => 90;
  48. /// <summary>
  49. /// Number of rotations the RuleTile can be rotated by for matching.
  50. /// </summary>
  51. public int m_RotationCount => 360 / m_RotationAngle;
  52. /// <summary>
  53. /// The data structure holding the Rule information for matching Rule Tiles with
  54. /// its neighbors.
  55. /// </summary>
  56. [Serializable]
  57. public class TilingRuleOutput
  58. {
  59. /// <summary>
  60. /// Id for this Rule.
  61. /// </summary>
  62. public int m_Id;
  63. /// <summary>
  64. /// The output Sprites for this Rule.
  65. /// </summary>
  66. public Sprite[] m_Sprites = new Sprite[1];
  67. /// <summary>
  68. /// The output GameObject for this Rule.
  69. /// </summary>
  70. public GameObject m_GameObject;
  71. /// <summary>
  72. /// The output minimum Animation Speed for this Rule.
  73. /// </summary>
  74. [FormerlySerializedAs("m_AnimationSpeed")]
  75. public float m_MinAnimationSpeed = 1f;
  76. /// <summary>
  77. /// The output maximum Animation Speed for this Rule.
  78. /// </summary>
  79. [FormerlySerializedAs("m_AnimationSpeed")]
  80. public float m_MaxAnimationSpeed = 1f;
  81. /// <summary>
  82. /// The perlin scale factor for this Rule.
  83. /// </summary>
  84. public float m_PerlinScale = 0.5f;
  85. /// <summary>
  86. /// The output type for this Rule.
  87. /// </summary>
  88. public OutputSprite m_Output = OutputSprite.Single;
  89. /// <summary>
  90. /// The output Collider Type for this Rule.
  91. /// </summary>
  92. public Tile.ColliderType m_ColliderType = Tile.ColliderType.Sprite;
  93. /// <summary>
  94. /// The randomized transform output for this Rule.
  95. /// </summary>
  96. public Transform m_RandomTransform;
  97. /// <summary>
  98. /// The enumeration for matching Neighbors when matching Rule Tiles
  99. /// </summary>
  100. public class Neighbor
  101. {
  102. /// <summary>
  103. /// The Rule Tile will check if the contents of the cell in that direction is an instance of this Rule Tile.
  104. /// If not, the rule will fail.
  105. /// </summary>
  106. public const int This = 1;
  107. /// <summary>
  108. /// The Rule Tile will check if the contents of the cell in that direction is not an instance of this Rule Tile.
  109. /// If it is, the rule will fail.
  110. /// </summary>
  111. public const int NotThis = 2;
  112. }
  113. /// <summary>
  114. /// The enumeration for the transform rule used when matching Rule Tiles.
  115. /// </summary>
  116. public enum Transform
  117. {
  118. /// <summary>
  119. /// The Rule Tile will match Tiles exactly as laid out in its neighbors.
  120. /// </summary>
  121. Fixed,
  122. /// <summary>
  123. /// The Rule Tile will rotate and match its neighbors.
  124. /// </summary>
  125. Rotated,
  126. /// <summary>
  127. /// The Rule Tile will mirror in the X axis and match its neighbors.
  128. /// </summary>
  129. MirrorX,
  130. /// <summary>
  131. /// The Rule Tile will mirror in the Y axis and match its neighbors.
  132. /// </summary>
  133. MirrorY,
  134. /// <summary>
  135. /// The Rule Tile will mirror in the X or Y axis and match its neighbors.
  136. /// </summary>
  137. MirrorXY
  138. }
  139. /// <summary>
  140. /// The Output for the Tile which fits this Rule.
  141. /// </summary>
  142. public enum OutputSprite
  143. {
  144. /// <summary>
  145. /// A Single Sprite will be output.
  146. /// </summary>
  147. Single,
  148. /// <summary>
  149. /// A Random Sprite will be output.
  150. /// </summary>
  151. Random,
  152. /// <summary>
  153. /// A Sprite Animation will be output.
  154. /// </summary>
  155. Animation
  156. }
  157. }
  158. /// <summary>
  159. /// The data structure holding the Rule information for matching Rule Tiles with
  160. /// its neighbors.
  161. /// </summary>
  162. [Serializable]
  163. public class TilingRule : TilingRuleOutput
  164. {
  165. /// <summary>
  166. /// The matching Rule conditions for each of its neighboring Tiles.
  167. /// </summary>
  168. public List<int> m_Neighbors = new List<int>();
  169. /// <summary>
  170. /// * Preset this list to RuleTile backward compatible, but not support for HexagonalRuleTile backward compatible.
  171. /// </summary>
  172. public List<Vector3Int> m_NeighborPositions = new List<Vector3Int>()
  173. {
  174. new Vector3Int(-1, 1, 0),
  175. new Vector3Int(0, 1, 0),
  176. new Vector3Int(1, 1, 0),
  177. new Vector3Int(-1, 0, 0),
  178. new Vector3Int(1, 0, 0),
  179. new Vector3Int(-1, -1, 0),
  180. new Vector3Int(0, -1, 0),
  181. new Vector3Int(1, -1, 0),
  182. };
  183. /// <summary>
  184. /// The transform matching Rule for this Rule.
  185. /// </summary>
  186. public Transform m_RuleTransform;
  187. /// <summary>
  188. /// This clones a copy of the TilingRule.
  189. /// </summary>
  190. /// <returns>A copy of the TilingRule.</returns>
  191. public TilingRule Clone()
  192. {
  193. TilingRule rule = new TilingRule
  194. {
  195. m_Neighbors = new List<int>(m_Neighbors),
  196. m_NeighborPositions = new List<Vector3Int>(m_NeighborPositions),
  197. m_RuleTransform = m_RuleTransform,
  198. m_Sprites = new Sprite[m_Sprites.Length],
  199. m_GameObject = m_GameObject,
  200. m_MinAnimationSpeed = m_MinAnimationSpeed,
  201. m_MaxAnimationSpeed = m_MaxAnimationSpeed,
  202. m_PerlinScale = m_PerlinScale,
  203. m_Output = m_Output,
  204. m_ColliderType = m_ColliderType,
  205. m_RandomTransform = m_RandomTransform,
  206. };
  207. Array.Copy(m_Sprites, rule.m_Sprites, m_Sprites.Length);
  208. return rule;
  209. }
  210. /// <summary>
  211. /// Returns all neighbors of this Tile as a dictionary
  212. /// </summary>
  213. /// <returns>A dictionary of neighbors for this Tile</returns>
  214. public Dictionary<Vector3Int, int> GetNeighbors()
  215. {
  216. Dictionary<Vector3Int, int> dict = new Dictionary<Vector3Int, int>();
  217. for (int i = 0; i < m_Neighbors.Count && i < m_NeighborPositions.Count; i++)
  218. dict.Add(m_NeighborPositions[i], m_Neighbors[i]);
  219. return dict;
  220. }
  221. /// <summary>
  222. /// Applies the values from the given dictionary as this Tile's neighbors
  223. /// </summary>
  224. /// <param name="dict">Dictionary to apply values from</param>
  225. public void ApplyNeighbors(Dictionary<Vector3Int, int> dict)
  226. {
  227. m_NeighborPositions = dict.Keys.ToList();
  228. m_Neighbors = dict.Values.ToList();
  229. }
  230. /// <summary>
  231. /// Gets the cell bounds of the TilingRule.
  232. /// </summary>
  233. /// <returns>Returns the cell bounds of the TilingRule.</returns>
  234. public BoundsInt GetBounds()
  235. {
  236. BoundsInt bounds = new BoundsInt(Vector3Int.zero, Vector3Int.one);
  237. foreach (var neighbor in GetNeighbors())
  238. {
  239. bounds.xMin = Mathf.Min(bounds.xMin, neighbor.Key.x);
  240. bounds.yMin = Mathf.Min(bounds.yMin, neighbor.Key.y);
  241. bounds.xMax = Mathf.Max(bounds.xMax, neighbor.Key.x + 1);
  242. bounds.yMax = Mathf.Max(bounds.yMax, neighbor.Key.y + 1);
  243. }
  244. return bounds;
  245. }
  246. }
  247. /// <summary>
  248. /// Attribute which marks a property which cannot be overridden by a RuleOverrideTile
  249. /// </summary>
  250. public class DontOverride : Attribute { }
  251. /// <summary>
  252. /// A list of Tiling Rules for the Rule Tile.
  253. /// </summary>
  254. [HideInInspector] public List<TilingRule> m_TilingRules = new List<RuleTile.TilingRule>();
  255. /// <summary>
  256. /// Returns a set of neighboring positions for this RuleTile
  257. /// </summary>
  258. public HashSet<Vector3Int> neighborPositions
  259. {
  260. get
  261. {
  262. if (m_NeighborPositions.Count == 0)
  263. UpdateNeighborPositions();
  264. return m_NeighborPositions;
  265. }
  266. }
  267. private HashSet<Vector3Int> m_NeighborPositions = new HashSet<Vector3Int>();
  268. /// <summary>
  269. /// Updates the neighboring positions of this RuleTile
  270. /// </summary>
  271. public void UpdateNeighborPositions()
  272. {
  273. m_CacheTilemapsNeighborPositions.Clear();
  274. HashSet<Vector3Int> positions = m_NeighborPositions;
  275. positions.Clear();
  276. foreach (TilingRule rule in m_TilingRules)
  277. {
  278. foreach (var neighbor in rule.GetNeighbors())
  279. {
  280. Vector3Int position = neighbor.Key;
  281. positions.Add(position);
  282. // Check rule against rotations of 0, 90, 180, 270
  283. if (rule.m_RuleTransform == TilingRuleOutput.Transform.Rotated)
  284. {
  285. for (int angle = m_RotationAngle; angle < 360; angle += m_RotationAngle)
  286. {
  287. positions.Add(GetRotatedPosition(position, angle));
  288. }
  289. }
  290. // Check rule against x-axis, y-axis mirror
  291. else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorXY)
  292. {
  293. positions.Add(GetMirroredPosition(position, true, true));
  294. positions.Add(GetMirroredPosition(position, true, false));
  295. positions.Add(GetMirroredPosition(position, false, true));
  296. }
  297. // Check rule against x-axis mirror
  298. else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorX)
  299. {
  300. positions.Add(GetMirroredPosition(position, true, false));
  301. }
  302. // Check rule against y-axis mirror
  303. else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorY)
  304. {
  305. positions.Add(GetMirroredPosition(position, false, true));
  306. }
  307. }
  308. }
  309. }
  310. /// <summary>
  311. /// StartUp is called on the first frame of the running Scene.
  312. /// </summary>
  313. /// <param name="position">Position of the Tile on the Tilemap.</param>
  314. /// <param name="tilemap">The Tilemap the tile is present on.</param>
  315. /// <param name="instantiatedGameObject">The GameObject instantiated for the Tile.</param>
  316. /// <returns>Whether StartUp was successful</returns>
  317. public override bool StartUp(Vector3Int position, ITilemap tilemap, GameObject instantiatedGameObject)
  318. {
  319. if (instantiatedGameObject != null)
  320. {
  321. Tilemap tmpMap = tilemap.GetComponent<Tilemap>();
  322. Matrix4x4 orientMatrix = tmpMap.orientationMatrix;
  323. var iden = Matrix4x4.identity;
  324. Vector3 gameObjectTranslation = new Vector3();
  325. Quaternion gameObjectRotation = new Quaternion();
  326. Vector3 gameObjectScale = new Vector3();
  327. bool ruleMatched = false;
  328. Matrix4x4 transform = iden;
  329. foreach (TilingRule rule in m_TilingRules)
  330. {
  331. if (RuleMatches(rule, position, tilemap, ref transform))
  332. {
  333. transform = orientMatrix * transform;
  334. // Converts the tile's translation, rotation, & scale matrix to values to be used by the instantiated GameObject
  335. gameObjectTranslation = new Vector3(transform.m03, transform.m13, transform.m23);
  336. gameObjectRotation = Quaternion.LookRotation(new Vector3(transform.m02, transform.m12, transform.m22), new Vector3(transform.m01, transform.m11, transform.m21));
  337. gameObjectScale = transform.lossyScale;
  338. ruleMatched = true;
  339. break;
  340. }
  341. }
  342. if (!ruleMatched)
  343. {
  344. // Fallback to just using the orientMatrix for the translation, rotation, & scale values.
  345. gameObjectTranslation = new Vector3(orientMatrix.m03, orientMatrix.m13, orientMatrix.m23);
  346. gameObjectRotation = Quaternion.LookRotation(new Vector3(orientMatrix.m02, orientMatrix.m12, orientMatrix.m22), new Vector3(orientMatrix.m01, orientMatrix.m11, orientMatrix.m21));
  347. gameObjectScale = orientMatrix.lossyScale;
  348. }
  349. instantiatedGameObject.transform.localPosition = gameObjectTranslation + tmpMap.CellToLocalInterpolated(position + tmpMap.tileAnchor);
  350. instantiatedGameObject.transform.localRotation = gameObjectRotation;
  351. instantiatedGameObject.transform.localScale = gameObjectScale;
  352. }
  353. return true;
  354. }
  355. /// <summary>
  356. /// Retrieves any tile rendering data from the scripted tile.
  357. /// </summary>
  358. /// <param name="position">Position of the Tile on the Tilemap.</param>
  359. /// <param name="tilemap">The Tilemap the tile is present on.</param>
  360. /// <param name="tileData">Data to render the tile.</param>
  361. public override void GetTileData(Vector3Int position, ITilemap tilemap, ref TileData tileData)
  362. {
  363. var iden = Matrix4x4.identity;
  364. tileData.sprite = m_DefaultSprite;
  365. tileData.gameObject = m_DefaultGameObject;
  366. tileData.colliderType = m_DefaultColliderType;
  367. tileData.flags = TileFlags.LockTransform;
  368. tileData.transform = iden;
  369. Matrix4x4 transform = iden;
  370. foreach (TilingRule rule in m_TilingRules)
  371. {
  372. if (RuleMatches(rule, position, tilemap, ref transform))
  373. {
  374. switch (rule.m_Output)
  375. {
  376. case TilingRuleOutput.OutputSprite.Single:
  377. case TilingRuleOutput.OutputSprite.Animation:
  378. tileData.sprite = rule.m_Sprites[0];
  379. break;
  380. case TilingRuleOutput.OutputSprite.Random:
  381. int index = Mathf.Clamp(Mathf.FloorToInt(GetPerlinValue(position, rule.m_PerlinScale, 100000f) * rule.m_Sprites.Length), 0, rule.m_Sprites.Length - 1);
  382. tileData.sprite = rule.m_Sprites[index];
  383. if (rule.m_RandomTransform != TilingRuleOutput.Transform.Fixed)
  384. transform = ApplyRandomTransform(rule.m_RandomTransform, transform, rule.m_PerlinScale, position);
  385. break;
  386. }
  387. tileData.transform = transform;
  388. tileData.gameObject = rule.m_GameObject;
  389. tileData.colliderType = rule.m_ColliderType;
  390. break;
  391. }
  392. }
  393. }
  394. /// <summary>
  395. /// Returns a Perlin Noise value based on the given inputs.
  396. /// </summary>
  397. /// <param name="position">Position of the Tile on the Tilemap.</param>
  398. /// <param name="scale">The Perlin Scale factor of the Tile.</param>
  399. /// <param name="offset">Offset of the Tile on the Tilemap.</param>
  400. /// <returns>A Perlin Noise value based on the given inputs.</returns>
  401. public static float GetPerlinValue(Vector3Int position, float scale, float offset)
  402. {
  403. return Mathf.PerlinNoise((position.x + offset) * scale, (position.y + offset) * scale);
  404. }
  405. static Dictionary<Tilemap, KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>>> m_CacheTilemapsNeighborPositions = new Dictionary<Tilemap, KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>>>();
  406. static TileBase[] m_AllocatedUsedTileArr = Array.Empty<TileBase>();
  407. static bool IsTilemapUsedTilesChange(Tilemap tilemap, out KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>> hashSet)
  408. {
  409. if (!m_CacheTilemapsNeighborPositions.TryGetValue(tilemap, out hashSet))
  410. return true;
  411. var oldUsedTiles = hashSet.Key;
  412. int newUsedTilesCount = tilemap.GetUsedTilesCount();
  413. if (newUsedTilesCount != oldUsedTiles.Count)
  414. return true;
  415. if (m_AllocatedUsedTileArr.Length < newUsedTilesCount)
  416. Array.Resize(ref m_AllocatedUsedTileArr, newUsedTilesCount);
  417. tilemap.GetUsedTilesNonAlloc(m_AllocatedUsedTileArr);
  418. for (int i = 0; i < newUsedTilesCount; i++)
  419. {
  420. TileBase newUsedTile = m_AllocatedUsedTileArr[i];
  421. if (!oldUsedTiles.Contains(newUsedTile))
  422. return true;
  423. }
  424. return false;
  425. }
  426. static KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>> CachingTilemapNeighborPositions(Tilemap tilemap)
  427. {
  428. int usedTileCount = tilemap.GetUsedTilesCount();
  429. HashSet<TileBase> usedTiles = new HashSet<TileBase>();
  430. HashSet<Vector3Int> neighborPositions = new HashSet<Vector3Int>();
  431. if (m_AllocatedUsedTileArr.Length < usedTileCount)
  432. Array.Resize(ref m_AllocatedUsedTileArr, usedTileCount);
  433. tilemap.GetUsedTilesNonAlloc(m_AllocatedUsedTileArr);
  434. for (int i = 0; i < usedTileCount; i++)
  435. {
  436. TileBase tile = m_AllocatedUsedTileArr[i];
  437. usedTiles.Add(tile);
  438. RuleTile ruleTile = null;
  439. if (tile is RuleTile rt)
  440. ruleTile = rt;
  441. else if (tile is RuleOverrideTile ot)
  442. ruleTile = ot.m_Tile;
  443. if (ruleTile)
  444. foreach (Vector3Int neighborPosition in ruleTile.neighborPositions)
  445. neighborPositions.Add(neighborPosition);
  446. }
  447. var value = new KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>>(usedTiles, neighborPositions);
  448. m_CacheTilemapsNeighborPositions[tilemap] = value;
  449. return value;
  450. }
  451. static bool NeedRelease()
  452. {
  453. foreach (var keypair in m_CacheTilemapsNeighborPositions)
  454. {
  455. if (keypair.Key == null)
  456. {
  457. return true;
  458. }
  459. }
  460. return false;
  461. }
  462. static void ReleaseDestroyedTilemapCacheData()
  463. {
  464. if (!NeedRelease())
  465. return;
  466. var hasCleared = false;
  467. var keys = m_CacheTilemapsNeighborPositions.Keys.ToArray();
  468. foreach (var key in keys)
  469. {
  470. if (key == null && m_CacheTilemapsNeighborPositions.Remove(key))
  471. hasCleared = true;
  472. }
  473. if (hasCleared)
  474. {
  475. // TrimExcess
  476. m_CacheTilemapsNeighborPositions = new Dictionary<Tilemap, KeyValuePair<HashSet<TileBase>, HashSet<Vector3Int>>>(m_CacheTilemapsNeighborPositions);
  477. }
  478. }
  479. /// <summary>
  480. /// Retrieves any tile animation data from the scripted tile.
  481. /// </summary>
  482. /// <param name="position">Position of the Tile on the Tilemap.</param>
  483. /// <param name="tilemap">The Tilemap the tile is present on.</param>
  484. /// <param name="tileAnimationData">Data to run an animation on the tile.</param>
  485. /// <returns>Whether the call was successful.</returns>
  486. public override bool GetTileAnimationData(Vector3Int position, ITilemap tilemap, ref TileAnimationData tileAnimationData)
  487. {
  488. Matrix4x4 transform = Matrix4x4.identity;
  489. foreach (TilingRule rule in m_TilingRules)
  490. {
  491. if (rule.m_Output == TilingRuleOutput.OutputSprite.Animation)
  492. {
  493. if (RuleMatches(rule, position, tilemap, ref transform))
  494. {
  495. tileAnimationData.animatedSprites = rule.m_Sprites;
  496. tileAnimationData.animationSpeed = Random.Range( rule.m_MinAnimationSpeed, rule.m_MaxAnimationSpeed);
  497. return true;
  498. }
  499. }
  500. }
  501. return false;
  502. }
  503. /// <summary>
  504. /// This method is called when the tile is refreshed.
  505. /// </summary>
  506. /// <param name="position">Position of the Tile on the Tilemap.</param>
  507. /// <param name="tilemap">The Tilemap the tile is present on.</param>
  508. public override void RefreshTile(Vector3Int position, ITilemap tilemap)
  509. {
  510. base.RefreshTile(position, tilemap);
  511. Tilemap baseTilemap = tilemap.GetComponent<Tilemap>();
  512. ReleaseDestroyedTilemapCacheData(); // Prevent memory leak
  513. if (IsTilemapUsedTilesChange(baseTilemap, out var neighborPositionsSet))
  514. neighborPositionsSet = CachingTilemapNeighborPositions(baseTilemap);
  515. var neighborPositionsRuleTile = neighborPositionsSet.Value;
  516. foreach (Vector3Int offset in neighborPositionsRuleTile)
  517. {
  518. Vector3Int offsetPosition = GetOffsetPositionReverse(position, offset);
  519. TileBase tile = tilemap.GetTile(offsetPosition);
  520. RuleTile ruleTile = null;
  521. if (tile is RuleTile rt)
  522. ruleTile = rt;
  523. else if (tile is RuleOverrideTile ot)
  524. ruleTile = ot.m_Tile;
  525. if (ruleTile != null)
  526. if (ruleTile == this || ruleTile.neighborPositions.Contains(offset))
  527. base.RefreshTile(offsetPosition, tilemap);
  528. }
  529. }
  530. /// <summary>
  531. /// Does a Rule Match given a Tiling Rule and neighboring Tiles.
  532. /// </summary>
  533. /// <param name="rule">The Tiling Rule to match with.</param>
  534. /// <param name="position">Position of the Tile on the Tilemap.</param>
  535. /// <param name="tilemap">The tilemap to match with.</param>
  536. /// <param name="transform">A transform matrix which will match the Rule.</param>
  537. /// <returns>True if there is a match, False if not.</returns>
  538. public virtual bool RuleMatches(TilingRule rule, Vector3Int position, ITilemap tilemap, ref Matrix4x4 transform)
  539. {
  540. if (RuleMatches(rule, position, tilemap, 0))
  541. {
  542. transform = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0f, 0f, 0f), Vector3.one);
  543. return true;
  544. }
  545. // Check rule against rotations of 0, 90, 180, 270
  546. if (rule.m_RuleTransform == TilingRuleOutput.Transform.Rotated)
  547. {
  548. for (int angle = m_RotationAngle; angle < 360; angle += m_RotationAngle)
  549. {
  550. if (RuleMatches(rule, position, tilemap, angle))
  551. {
  552. transform = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0f, 0f, -angle), Vector3.one);
  553. return true;
  554. }
  555. }
  556. }
  557. // Check rule against x-axis, y-axis mirror
  558. else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorXY)
  559. {
  560. if (RuleMatches(rule, position, tilemap, true, true))
  561. {
  562. transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(-1f, -1f, 1f));
  563. return true;
  564. }
  565. if (RuleMatches(rule, position, tilemap, true, false))
  566. {
  567. transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(-1f, 1f, 1f));
  568. return true;
  569. }
  570. if (RuleMatches(rule, position, tilemap, false, true))
  571. {
  572. transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(1f, -1f, 1f));
  573. return true;
  574. }
  575. }
  576. // Check rule against x-axis mirror
  577. else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorX)
  578. {
  579. if (RuleMatches(rule, position, tilemap, true, false))
  580. {
  581. transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(-1f, 1f, 1f));
  582. return true;
  583. }
  584. }
  585. // Check rule against y-axis mirror
  586. else if (rule.m_RuleTransform == TilingRuleOutput.Transform.MirrorY)
  587. {
  588. if (RuleMatches(rule, position, tilemap, false, true))
  589. {
  590. transform = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(1f, -1f, 1f));
  591. return true;
  592. }
  593. }
  594. return false;
  595. }
  596. /// <summary>
  597. /// Returns a random transform matrix given the random transform rule.
  598. /// </summary>
  599. /// <param name="type">Random transform rule.</param>
  600. /// <param name="original">The original transform matrix.</param>
  601. /// <param name="perlinScale">The Perlin Scale factor of the Tile.</param>
  602. /// <param name="position">Position of the Tile on the Tilemap.</param>
  603. /// <returns>A random transform matrix.</returns>
  604. public virtual Matrix4x4 ApplyRandomTransform(TilingRuleOutput.Transform type, Matrix4x4 original, float perlinScale, Vector3Int position)
  605. {
  606. float perlin = GetPerlinValue(position, perlinScale, 200000f);
  607. switch (type)
  608. {
  609. case TilingRuleOutput.Transform.MirrorXY:
  610. 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));
  611. case TilingRuleOutput.Transform.MirrorX:
  612. return original * Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(perlin < 0.5 ? 1f : -1f, 1f, 1f));
  613. case TilingRuleOutput.Transform.MirrorY:
  614. return original * Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(1f, perlin < 0.5 ? 1f : -1f, 1f));
  615. case TilingRuleOutput.Transform.Rotated:
  616. int angle = Mathf.Clamp(Mathf.FloorToInt(perlin * m_RotationCount), 0, m_RotationCount - 1) * m_RotationAngle;
  617. return Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0f, 0f, -angle), Vector3.one);
  618. }
  619. return original;
  620. }
  621. /// <summary>
  622. /// Returns custom fields for this RuleTile
  623. /// </summary>
  624. /// <param name="isOverrideInstance">Whether override fields are returned</param>
  625. /// <returns>Custom fields for this RuleTile</returns>
  626. public FieldInfo[] GetCustomFields(bool isOverrideInstance)
  627. {
  628. return this.GetType().GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
  629. .Where(field => typeof(RuleTile).GetField(field.Name) == null)
  630. .Where(field => field.IsPublic || field.IsDefined(typeof(SerializeField)))
  631. .Where(field => !field.IsDefined(typeof(HideInInspector)))
  632. .Where(field => !isOverrideInstance || !field.IsDefined(typeof(DontOverride)))
  633. .ToArray();
  634. }
  635. /// <summary>
  636. /// Checks if there is a match given the neighbor matching rule and a Tile.
  637. /// </summary>
  638. /// <param name="neighbor">Neighbor matching rule.</param>
  639. /// <param name="other">Tile to match.</param>
  640. /// <returns>True if there is a match, False if not.</returns>
  641. public virtual bool RuleMatch(int neighbor, TileBase other)
  642. {
  643. if (other is RuleOverrideTile ot)
  644. other = ot.m_InstanceTile;
  645. switch (neighbor)
  646. {
  647. case TilingRuleOutput.Neighbor.This: return other == this;
  648. case TilingRuleOutput.Neighbor.NotThis: return other != this;
  649. }
  650. return true;
  651. }
  652. /// <summary>
  653. /// Checks if there is a match given the neighbor matching rule and a Tile with a rotation angle.
  654. /// </summary>
  655. /// <param name="rule">Neighbor matching rule.</param>
  656. /// <param name="position">Position of the Tile on the Tilemap.</param>
  657. /// <param name="tilemap">Tilemap to match.</param>
  658. /// <param name="angle">Rotation angle for matching.</param>
  659. /// <returns>True if there is a match, False if not.</returns>
  660. public bool RuleMatches(TilingRule rule, Vector3Int position, ITilemap tilemap, int angle)
  661. {
  662. var minCount = Math.Min(rule.m_Neighbors.Count, rule.m_NeighborPositions.Count);
  663. for (int i = 0; i < minCount ; i++)
  664. {
  665. int neighbor = rule.m_Neighbors[i];
  666. Vector3Int positionOffset = GetRotatedPosition(rule.m_NeighborPositions[i], angle);
  667. TileBase other = tilemap.GetTile(GetOffsetPosition(position, positionOffset));
  668. if (!RuleMatch(neighbor, other))
  669. {
  670. return false;
  671. }
  672. }
  673. return true;
  674. }
  675. /// <summary>
  676. /// Checks if there is a match given the neighbor matching rule and a Tile with mirrored axii.
  677. /// </summary>
  678. /// <param name="rule">Neighbor matching rule.</param>
  679. /// <param name="position">Position of the Tile on the Tilemap.</param>
  680. /// <param name="tilemap">Tilemap to match.</param>
  681. /// <param name="mirrorX">Mirror X Axis for matching.</param>
  682. /// <param name="mirrorY">Mirror Y Axis for matching.</param>
  683. /// <returns>True if there is a match, False if not.</returns>
  684. public bool RuleMatches(TilingRule rule, Vector3Int position, ITilemap tilemap, bool mirrorX, bool mirrorY)
  685. {
  686. var minCount = Math.Min(rule.m_Neighbors.Count, rule.m_NeighborPositions.Count);
  687. for (int i = 0; i < minCount; i++)
  688. {
  689. int neighbor = rule.m_Neighbors[i];
  690. Vector3Int positionOffset = GetMirroredPosition(rule.m_NeighborPositions[i], mirrorX, mirrorY);
  691. TileBase other = tilemap.GetTile(GetOffsetPosition(position, positionOffset));
  692. if (!RuleMatch(neighbor, other))
  693. {
  694. return false;
  695. }
  696. }
  697. return true;
  698. }
  699. /// <summary>
  700. /// Gets a rotated position given its original position and the rotation in degrees.
  701. /// </summary>
  702. /// <param name="position">Original position of Tile.</param>
  703. /// <param name="rotation">Rotation in degrees.</param>
  704. /// <returns>Rotated position of Tile.</returns>
  705. public virtual Vector3Int GetRotatedPosition(Vector3Int position, int rotation)
  706. {
  707. switch (rotation)
  708. {
  709. case 0:
  710. return position;
  711. case 90:
  712. return new Vector3Int(position.y, -position.x, 0);
  713. case 180:
  714. return new Vector3Int(-position.x, -position.y, 0);
  715. case 270:
  716. return new Vector3Int(-position.y, position.x, 0);
  717. }
  718. return position;
  719. }
  720. /// <summary>
  721. /// Gets a mirrored position given its original position and the mirroring axii.
  722. /// </summary>
  723. /// <param name="position">Original position of Tile.</param>
  724. /// <param name="mirrorX">Mirror in the X Axis.</param>
  725. /// <param name="mirrorY">Mirror in the Y Axis.</param>
  726. /// <returns>Mirrored position of Tile.</returns>
  727. public virtual Vector3Int GetMirroredPosition(Vector3Int position, bool mirrorX, bool mirrorY)
  728. {
  729. if (mirrorX)
  730. position.x *= -1;
  731. if (mirrorY)
  732. position.y *= -1;
  733. return position;
  734. }
  735. /// <summary>
  736. /// Get the offset for the given position with the given offset.
  737. /// </summary>
  738. /// <param name="position">Position to offset.</param>
  739. /// <param name="offset">Offset for the position.</param>
  740. /// <returns>The offset position.</returns>
  741. public virtual Vector3Int GetOffsetPosition(Vector3Int position, Vector3Int offset)
  742. {
  743. return position + offset;
  744. }
  745. /// <summary>
  746. /// Get the reversed offset for the given position with the given offset.
  747. /// </summary>
  748. /// <param name="position">Position to offset.</param>
  749. /// <param name="offset">Offset for the position.</param>
  750. /// <returns>The reversed offset position.</returns>
  751. public virtual Vector3Int GetOffsetPositionReverse(Vector3Int position, Vector3Int offset)
  752. {
  753. return position - offset;
  754. }
  755. }
  756. }