BoundingBoxFollowerGraphic.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated July 28, 2023. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2023, Esoteric Software LLC
  6. *
  7. * Integration of the Spine Runtimes into software or otherwise creating
  8. * derivative works of the Spine Runtimes is permitted under the terms and
  9. * conditions of Section 2 of the Spine Editor License Agreement:
  10. * http://esotericsoftware.com/spine-editor-license
  11. *
  12. * Otherwise, it is permitted to integrate the Spine Runtimes into software or
  13. * otherwise create derivative works of the Spine Runtimes (collectively,
  14. * "Products"), provided that each user of the Products must obtain their own
  15. * Spine Editor license and redistribution of the Products in any form must
  16. * include this license and copyright notice.
  17. *
  18. * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
  27. * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. #if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
  30. #define NEW_PREFAB_SYSTEM
  31. #endif
  32. using System.Collections.Generic;
  33. using UnityEngine;
  34. namespace Spine.Unity {
  35. #if NEW_PREFAB_SYSTEM
  36. [ExecuteAlways]
  37. #else
  38. [ExecuteInEditMode]
  39. #endif
  40. [HelpURL("http://esotericsoftware.com/spine-unity#BoundingBoxFollowerGraphic")]
  41. public class BoundingBoxFollowerGraphic : MonoBehaviour {
  42. internal static bool DebugMessages = true;
  43. #region Inspector
  44. public SkeletonGraphic skeletonGraphic;
  45. [SpineSlot(dataField: "skeletonGraphic", containsBoundingBoxes: true)]
  46. public string slotName;
  47. public bool isTrigger, usedByEffector, usedByComposite;
  48. public bool clearStateOnDisable = true;
  49. #endregion
  50. Slot slot;
  51. BoundingBoxAttachment currentAttachment;
  52. string currentAttachmentName;
  53. PolygonCollider2D currentCollider;
  54. public readonly Dictionary<BoundingBoxAttachment, PolygonCollider2D> colliderTable = new Dictionary<BoundingBoxAttachment, PolygonCollider2D>();
  55. public readonly Dictionary<BoundingBoxAttachment, string> nameTable = new Dictionary<BoundingBoxAttachment, string>();
  56. public Slot Slot { get { return slot; } }
  57. public BoundingBoxAttachment CurrentAttachment { get { return currentAttachment; } }
  58. public string CurrentAttachmentName { get { return currentAttachmentName; } }
  59. public PolygonCollider2D CurrentCollider { get { return currentCollider; } }
  60. public bool IsTrigger { get { return isTrigger; } }
  61. void Start () {
  62. Initialize();
  63. }
  64. void OnEnable () {
  65. if (skeletonGraphic != null) {
  66. skeletonGraphic.OnRebuild -= HandleRebuild;
  67. skeletonGraphic.OnRebuild += HandleRebuild;
  68. }
  69. Initialize();
  70. }
  71. void HandleRebuild (SkeletonGraphic sr) {
  72. //if (BoundingBoxFollowerGraphic.DebugMessages) Debug.Log("Skeleton was rebuilt. Repopulating BoundingBoxFollowerGraphic.");
  73. Initialize();
  74. }
  75. /// <summary>
  76. /// Initialize and instantiate the BoundingBoxFollowerGraphic colliders. This is method checks if the BoundingBoxFollowerGraphic has already been initialized for the skeleton instance and slotName and prevents overwriting unless it detects a new setup.</summary>
  77. public void Initialize (bool overwrite = false) {
  78. if (skeletonGraphic == null)
  79. return;
  80. skeletonGraphic.Initialize(false);
  81. if (string.IsNullOrEmpty(slotName))
  82. return;
  83. // Don't reinitialize if the setup did not change.
  84. if (!overwrite &&
  85. colliderTable.Count > 0 && slot != null && // Slot is set and colliders already populated.
  86. skeletonGraphic.Skeleton == slot.Skeleton && // Skeleton object did not change.
  87. slotName == slot.Data.Name // Slot object did not change.
  88. )
  89. return;
  90. slot = null;
  91. currentAttachment = null;
  92. currentAttachmentName = null;
  93. currentCollider = null;
  94. colliderTable.Clear();
  95. nameTable.Clear();
  96. Skeleton skeleton = skeletonGraphic.Skeleton;
  97. if (skeleton == null)
  98. return;
  99. slot = skeleton.FindSlot(slotName);
  100. if (slot == null) {
  101. if (BoundingBoxFollowerGraphic.DebugMessages)
  102. Debug.LogWarning(string.Format("Slot '{0}' not found for BoundingBoxFollowerGraphic on '{1}'. (Previous colliders were disposed.)", slotName, this.gameObject.name));
  103. return;
  104. }
  105. int slotIndex = slot.Data.Index;
  106. int requiredCollidersCount = 0;
  107. PolygonCollider2D[] colliders = GetComponents<PolygonCollider2D>();
  108. if (this.gameObject.activeInHierarchy) {
  109. float scale = skeletonGraphic.MeshScale;
  110. foreach (Skin skin in skeleton.Data.Skins)
  111. AddCollidersForSkin(skin, slotIndex, colliders, scale, ref requiredCollidersCount);
  112. if (skeleton.Skin != null)
  113. AddCollidersForSkin(skeleton.Skin, slotIndex, colliders, scale, ref requiredCollidersCount);
  114. }
  115. DisposeExcessCollidersAfter(requiredCollidersCount);
  116. if (BoundingBoxFollowerGraphic.DebugMessages) {
  117. bool valid = colliderTable.Count != 0;
  118. if (!valid) {
  119. if (this.gameObject.activeInHierarchy)
  120. Debug.LogWarning("Bounding Box Follower not valid! Slot [" + slotName + "] does not contain any Bounding Box Attachments!");
  121. else
  122. Debug.LogWarning("Bounding Box Follower tried to rebuild as a prefab.");
  123. }
  124. }
  125. }
  126. void AddCollidersForSkin (Skin skin, int slotIndex, PolygonCollider2D[] previousColliders, float scale, ref int collidersCount) {
  127. if (skin == null) return;
  128. List<Skin.SkinEntry> skinEntries = new List<Skin.SkinEntry>();
  129. skin.GetAttachments(slotIndex, skinEntries);
  130. foreach (Skin.SkinEntry entry in skinEntries) {
  131. Attachment attachment = skin.GetAttachment(slotIndex, entry.Name);
  132. BoundingBoxAttachment boundingBoxAttachment = attachment as BoundingBoxAttachment;
  133. if (BoundingBoxFollowerGraphic.DebugMessages && attachment != null && boundingBoxAttachment == null)
  134. Debug.Log("BoundingBoxFollowerGraphic tried to follow a slot that contains non-boundingbox attachments: " + slotName);
  135. if (boundingBoxAttachment != null) {
  136. if (!colliderTable.ContainsKey(boundingBoxAttachment)) {
  137. PolygonCollider2D bbCollider = collidersCount < previousColliders.Length ?
  138. previousColliders[collidersCount] : gameObject.AddComponent<PolygonCollider2D>();
  139. ++collidersCount;
  140. SkeletonUtility.SetColliderPointsLocal(bbCollider, slot, boundingBoxAttachment, scale);
  141. bbCollider.isTrigger = isTrigger;
  142. bbCollider.usedByEffector = usedByEffector;
  143. bbCollider.usedByComposite = usedByComposite;
  144. bbCollider.enabled = false;
  145. bbCollider.hideFlags = HideFlags.NotEditable;
  146. colliderTable.Add(boundingBoxAttachment, bbCollider);
  147. nameTable.Add(boundingBoxAttachment, entry.Name);
  148. }
  149. }
  150. }
  151. }
  152. void OnDisable () {
  153. if (clearStateOnDisable)
  154. ClearState();
  155. if (skeletonGraphic != null)
  156. skeletonGraphic.OnRebuild -= HandleRebuild;
  157. }
  158. public void ClearState () {
  159. if (colliderTable != null)
  160. foreach (PolygonCollider2D col in colliderTable.Values)
  161. col.enabled = false;
  162. currentAttachment = null;
  163. currentAttachmentName = null;
  164. currentCollider = null;
  165. }
  166. void DisposeExcessCollidersAfter (int requiredCount) {
  167. PolygonCollider2D[] colliders = GetComponents<PolygonCollider2D>();
  168. if (colliders.Length == 0) return;
  169. for (int i = requiredCount; i < colliders.Length; ++i) {
  170. PolygonCollider2D collider = colliders[i];
  171. if (collider != null) {
  172. #if UNITY_EDITOR
  173. if (Application.isEditor && !Application.isPlaying)
  174. DestroyImmediate(collider);
  175. else
  176. #endif
  177. Destroy(collider);
  178. }
  179. }
  180. }
  181. void LateUpdate () {
  182. if (slot != null && slot.Attachment != currentAttachment)
  183. MatchAttachment(slot.Attachment);
  184. }
  185. /// <summary>Sets the current collider to match attachment.</summary>
  186. /// <param name="attachment">If the attachment is not a bounding box, it will be treated as null.</param>
  187. void MatchAttachment (Attachment attachment) {
  188. BoundingBoxAttachment bbAttachment = attachment as BoundingBoxAttachment;
  189. if (BoundingBoxFollowerGraphic.DebugMessages && attachment != null && bbAttachment == null)
  190. Debug.LogWarning("BoundingBoxFollowerGraphic tried to match a non-boundingbox attachment. It will treat it as null.");
  191. if (currentCollider != null)
  192. currentCollider.enabled = false;
  193. if (bbAttachment == null) {
  194. currentCollider = null;
  195. currentAttachment = null;
  196. currentAttachmentName = null;
  197. } else {
  198. PolygonCollider2D foundCollider;
  199. colliderTable.TryGetValue(bbAttachment, out foundCollider);
  200. if (foundCollider != null) {
  201. currentCollider = foundCollider;
  202. currentCollider.enabled = true;
  203. currentAttachment = bbAttachment;
  204. currentAttachmentName = nameTable[bbAttachment];
  205. } else {
  206. currentCollider = null;
  207. currentAttachment = bbAttachment;
  208. currentAttachmentName = null;
  209. if (BoundingBoxFollowerGraphic.DebugMessages) Debug.LogFormat("Collider for BoundingBoxAttachment named '{0}' was not initialized. It is possibly from a new skin. currentAttachmentName will be null. You may need to call BoundingBoxFollowerGraphic.Initialize(overwrite: true);", bbAttachment.Name);
  210. }
  211. }
  212. }
  213. }
  214. }