/****************************************************************************** * Spine Runtimes License Agreement * Last updated January 1, 2020. Replaces all prior versions. * * Copyright (c) 2013-2020, Esoteric Software LLC * * Integration of the Spine Runtimes into software or otherwise creating * derivative works of the Spine Runtimes is permitted under the terms and * conditions of Section 2 of the Spine Editor License Agreement: * http://esotericsoftware.com/spine-editor-license * * Otherwise, it is permitted to integrate the Spine Runtimes into software * or otherwise create derivative works of the Spine Runtimes (collectively, * "Products"), provided that each user of the Products must obtain their own * Spine Editor license and redistribution of the Products in any form must * include this license and copyright notice. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER #define NEW_PREFAB_SYSTEM #endif using UnityEngine; using System.Collections.Generic; namespace Spine.Unity { #if NEW_PREFAB_SYSTEM [ExecuteAlways] #else [ExecuteInEditMode] #endif [HelpURL("http://esotericsoftware.com/spine-unity#BoundingBoxFollower")] public class BoundingBoxFollower : MonoBehaviour { internal static bool DebugMessages = true; #region Inspector public SkeletonRenderer skeletonRenderer; [SpineSlot(dataField: "skeletonRenderer", containsBoundingBoxes: true)] public string slotName; public bool isTrigger; public bool clearStateOnDisable = true; #endregion Slot slot; BoundingBoxAttachment currentAttachment; string currentAttachmentName; PolygonCollider2D currentCollider; public readonly Dictionary colliderTable = new Dictionary(); public readonly Dictionary nameTable = new Dictionary(); public Slot Slot { get { return slot; } } public BoundingBoxAttachment CurrentAttachment { get { return currentAttachment; } } public string CurrentAttachmentName { get { return currentAttachmentName; } } public PolygonCollider2D CurrentCollider { get { return currentCollider; } } public bool IsTrigger { get { return isTrigger; } } void Start () { Initialize(); } void OnEnable () { if (skeletonRenderer != null) { skeletonRenderer.OnRebuild -= HandleRebuild; skeletonRenderer.OnRebuild += HandleRebuild; } Initialize(); } void HandleRebuild (SkeletonRenderer sr) { //if (BoundingBoxFollower.DebugMessages) Debug.Log("Skeleton was rebuilt. Repopulating BoundingBoxFollower."); Initialize(); } /// /// Initialize and instantiate the BoundingBoxFollower colliders. This is method checks if the BoundingBoxFollower has already been initialized for the skeleton instance and slotName and prevents overwriting unless it detects a new setup. public void Initialize (bool overwrite = false) { if (skeletonRenderer == null) return; skeletonRenderer.Initialize(false); if (string.IsNullOrEmpty(slotName)) return; // Don't reinitialize if the setup did not change. if (!overwrite && colliderTable.Count > 0 && slot != null // Slot is set and colliders already populated. && skeletonRenderer.skeleton == slot.Skeleton // Skeleton object did not change. && slotName == slot.data.name // Slot object did not change. ) return; slot = null; currentAttachment = null; currentAttachmentName = null; currentCollider = null; colliderTable.Clear(); nameTable.Clear(); var skeleton = skeletonRenderer.skeleton; if (skeleton == null) return; slot = skeleton.FindSlot(slotName); int slotIndex = skeleton.FindSlotIndex(slotName); if (slot == null) { if (BoundingBoxFollower.DebugMessages) Debug.LogWarning(string.Format("Slot '{0}' not found for BoundingBoxFollower on '{1}'. (Previous colliders were disposed.)", slotName, this.gameObject.name)); return; } int requiredCollidersCount = 0; var colliders = GetComponents(); if (this.gameObject.activeInHierarchy) { foreach (var skin in skeleton.Data.Skins) AddCollidersForSkin(skin, slotIndex, colliders, ref requiredCollidersCount); if (skeleton.skin != null) AddCollidersForSkin(skeleton.skin, slotIndex, colliders, ref requiredCollidersCount); } DisposeExcessCollidersAfter(requiredCollidersCount); if (BoundingBoxFollower.DebugMessages) { bool valid = colliderTable.Count != 0; if (!valid) { if (this.gameObject.activeInHierarchy) Debug.LogWarning("Bounding Box Follower not valid! Slot [" + slotName + "] does not contain any Bounding Box Attachments!"); else Debug.LogWarning("Bounding Box Follower tried to rebuild as a prefab."); } } } void AddCollidersForSkin (Skin skin, int slotIndex, PolygonCollider2D[] previousColliders, ref int collidersCount) { if (skin == null) return; var skinEntries = new List(); skin.GetAttachments(slotIndex, skinEntries); foreach (var entry in skinEntries) { var attachment = skin.GetAttachment(slotIndex, entry.Name); var boundingBoxAttachment = attachment as BoundingBoxAttachment; if (BoundingBoxFollower.DebugMessages && attachment != null && boundingBoxAttachment == null) Debug.Log("BoundingBoxFollower tried to follow a slot that contains non-boundingbox attachments: " + slotName); if (boundingBoxAttachment != null) { if (!colliderTable.ContainsKey(boundingBoxAttachment)) { var bbCollider = collidersCount < previousColliders.Length ? previousColliders[collidersCount] : gameObject.AddComponent(); ++collidersCount; SkeletonUtility.SetColliderPointsLocal(bbCollider, slot, boundingBoxAttachment); bbCollider.isTrigger = isTrigger; bbCollider.enabled = false; bbCollider.hideFlags = HideFlags.NotEditable; bbCollider.isTrigger = IsTrigger; colliderTable.Add(boundingBoxAttachment, bbCollider); nameTable.Add(boundingBoxAttachment, entry.Name); } } } } void OnDisable () { if (clearStateOnDisable) ClearState(); if (skeletonRenderer != null) skeletonRenderer.OnRebuild -= HandleRebuild; } public void ClearState () { if (colliderTable != null) foreach (var col in colliderTable.Values) col.enabled = false; currentAttachment = null; currentAttachmentName = null; currentCollider = null; } void DisposeExcessCollidersAfter (int requiredCount) { var colliders = GetComponents(); if (colliders.Length == 0) return; for (int i = requiredCount; i < colliders.Length; ++i) { var collider = colliders[i]; if (collider != null) { #if UNITY_EDITOR if (Application.isEditor && !Application.isPlaying) DestroyImmediate(collider); else #endif Destroy(collider); } } } void LateUpdate () { if (slot != null && slot.Attachment != currentAttachment) MatchAttachment(slot.Attachment); } /// Sets the current collider to match attachment. /// If the attachment is not a bounding box, it will be treated as null. void MatchAttachment (Attachment attachment) { var bbAttachment = attachment as BoundingBoxAttachment; if (BoundingBoxFollower.DebugMessages && attachment != null && bbAttachment == null) Debug.LogWarning("BoundingBoxFollower tried to match a non-boundingbox attachment. It will treat it as null."); if (currentCollider != null) currentCollider.enabled = false; if (bbAttachment == null) { currentCollider = null; currentAttachment = null; currentAttachmentName = null; } else { PolygonCollider2D foundCollider; colliderTable.TryGetValue(bbAttachment, out foundCollider); if (foundCollider != null) { currentCollider = foundCollider; currentCollider.enabled = true; currentAttachment = bbAttachment; currentAttachmentName = nameTable[bbAttachment]; } else { currentCollider = null; currentAttachment = bbAttachment; currentAttachmentName = null; if (BoundingBoxFollower.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 BoundingBoxFollower.Initialize(overwrite: true);", bbAttachment.Name); } } } } }