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.
199 lines
8.8 KiB
199 lines
8.8 KiB
// Animancer // https://kybernetik.com.au/animancer // Copyright 2021 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 |
|
/************************************************************************************************************************/ |
|
|
|
|