// Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik // using UnityEngine; namespace Animancer { /// <summary> /// Interface for components to indicate which <see cref="GameObject"/> is the root of a character when /// <see cref="Editor.AnimancerEditorUtilities.FindRoot(GameObject)"/> is called. /// </summary> /// https://kybernetik.com.au/animancer/api/Animancer/ICharacterRoot /// public interface ICharacterRoot { /************************************************************************************************************************/ #pragma warning disable IDE1006 // Naming Styles. /************************************************************************************************************************/ /// <summary> /// The <see cref="Transform"/> to search for <see cref="AnimationClip"/>s beneath. /// </summary> /// /// <example> /// Implementing this interface in a <see cref="MonoBehaviour"/> will automatically inherit this property so /// you do not need to do anything else: /// <para></para><code> /// public class MyComponent : MonoBehaviour, IAnimancerRoot /// { /// } /// </code> /// But if you want to have your script point to a different object as the root, you can explicitly implement /// this property: /// <para></para><code> /// public class MyComponent : MonoBehaviour, IAnimancerRoot /// { /// Transform IAnimancerRoot.transform => ???; /// } /// </code></example> Transform transform { get; } /************************************************************************************************************************/ #pragma warning restore IDE1006 // Naming Styles. /************************************************************************************************************************/ } } /************************************************************************************************************************/ #if UNITY_EDITOR /************************************************************************************************************************/ namespace Animancer.Editor { /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerEditorUtilities partial class AnimancerEditorUtilities { /************************************************************************************************************************/ /// <summary>Takes a `gameObject` and returns the root <see cref="Transform"/> of the character it is part of.</summary> /// /// <remarks> /// This method first searches all parents for an <see cref="ICharacterRoot"/>. If it finds one, it returns the /// <see cref="ICharacterRoot.transform"/>. /// <para></para> /// Otherwise, if the object is part of a prefab then it returns the root of that prefab instance. /// <para></para> /// Otherwise, it counts the number of Animators in the children of the `gameObject` then does /// the same for each parent. If it finds a parent with a different number of child Animators, it /// assumes that object is the parent of multiple characters and returns the previous parent as the root. /// </remarks> /// /// <example> /// <h2>Simple Hierarchy</h2> /// <code> /// - Character - Rigidbody, etc. /// - Model - Animator, AnimancerComponent /// - States - Various components which reference the AnimationClips they will play /// </code> /// Passing the <c>Model</c> into this method will return the <c>Character</c> because it has the same /// number of Animator components in its children. /// /// <h2>Shared Hierarchy</h2> /// <code> /// - Characters - Empty object used to group all characters /// - Character - Rigidbody, etc. /// - Model - Animator, AnimancerComponent /// - States - Various components which reference the AnimationClips they will play /// - Another Character /// - Model /// - States /// </code> /// <list type="bullet"> /// <item><c>Model</c> has one Animator and no more in its children.</item> /// <item>And <c>Character</c> has one Animator in its children (the same one).</item> /// <item>But <c>Characters</c> has two Animators in its children (one on each character).</item> /// </list> /// So it picks the <c>Character</c> as the root. /// /// <h2>Complex Hierarchy</h2> /// <code> /// - Character - Rigidbody, etc. /// - Model - Animator, AnimancerComponent /// - States - Various components which reference the AnimationClips they will play /// - Another Model - Animator (maybe the character is holding a gun which has a reload animation) /// </code> /// In this case, the automatic system would see that the <c>Character</c> already has more child /// <see cref="Animator"/>s than the selected <c>Model</c> so it would only return the <c>Model</c> itself. /// This can be fixed by making any of the scripts on the <c>Character</c> implement <see cref="ICharacterRoot"/> /// to tell the system which object you want it to use as the root. /// </example> public static Transform FindRoot(GameObject gameObject) { var root = gameObject.GetComponentInParent<ICharacterRoot>(); if (root != null) return root.transform; #if UNITY_EDITOR var path = UnityEditor.AssetDatabase.GetAssetPath(gameObject); if (!string.IsNullOrEmpty(path)) return gameObject.transform.root; var status = UnityEditor.PrefabUtility.GetPrefabInstanceStatus(gameObject); if (status != UnityEditor.PrefabInstanceStatus.NotAPrefab) { gameObject = UnityEditor.PrefabUtility.GetOutermostPrefabInstanceRoot(gameObject); return gameObject.transform; } #endif var animators = ObjectPool.AcquireList<Animator>(); gameObject.GetComponentsInChildren(true, animators); var animatorCount = animators.Count; var parent = gameObject.transform; while (parent.parent != null) { animators.Clear(); parent.parent.GetComponentsInChildren(true, animators); if (animatorCount == 0) animatorCount = animators.Count; else if (animatorCount != animators.Count) break; parent = parent.parent; } ObjectPool.Release(animators); return parent; } /************************************************************************************************************************/ /// <summary> /// Calls <see cref="FindRoot(GameObject)"/> if the specified `obj` is a <see cref="GameObject"/> or /// <see cref="Component"/>. /// </summary> public static Transform FindRoot(Object obj) { if (obj is ICharacterRoot iRoot) return iRoot.transform; return TryGetGameObject(obj, out var gameObject) ? FindRoot(gameObject) : null; } /************************************************************************************************************************/ /// <summary>Outputs the <see cref="GameObject"/> assignated with the `obj` and returns true if it exists.</summary> /// <remarks> /// If the `obj` is a <see cref="GameObject"/> it is used as the result. /// <para></para> /// Or if the `obj` is a <see cref="Component"/> then its <see cref="Component.gameObject"/> is used as the result. /// </remarks> public static bool TryGetGameObject(Object obj, out GameObject gameObject) { if (obj is GameObject go) { gameObject = go; return true; } if (obj is Component component) { gameObject = component.gameObject; return true; } gameObject = null; return false; } /************************************************************************************************************************/ } } /************************************************************************************************************************/ #endif /************************************************************************************************************************/