You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
8.1 KiB
C#
181 lines
8.1 KiB
C#
3 months ago
|
// Animancer // Copyright 2020 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>
|
||
|
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:
|
||
|
/// <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:
|
||
|
/// <code>public class MyComponent : MonoBehaviour, IAnimancerRoot
|
||
|
/// {
|
||
|
/// Transform IAnimancerRoot.transform { get { return ???; } }
|
||
|
/// }</code>
|
||
|
/// </example>
|
||
|
Transform transform { get; }
|
||
|
#pragma warning restore IDE1006 // Naming Styles.
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#if UNITY_EDITOR
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
namespace Editor
|
||
|
{
|
||
|
public static partial class AnimancerEditorUtilities
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>
|
||
|
/// Takes a `gameObject` and returns the root <see cref="Transform"/> of the character it is part of.
|
||
|
/// <para></para>
|
||
|
/// 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 <see cref="Animator"/>s in the children of the `gameObject` then does
|
||
|
/// the same for each parent. If it finds a parent with a different number of child <see cref="Animator"/>s, it
|
||
|
/// assumes that object is the parent of multiple characters and returns the previous parent as the root.
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <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 <see cref="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 <see cref="Animator"/> and no more in its children.</item>
|
||
|
/// <item>And <c>Character</c> has one <see cref="Animator"/> in its children (the same one).</item>
|
||
|
/// <item>But <c>Characters</c> has two <see cref="Animator"/>s 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;
|
||
|
|
||
|
#if !UNITY_2018_3_OR_NEWER
|
||
|
|
||
|
var type = UnityEditor.PrefabUtility.GetPrefabType(gameObject);
|
||
|
if (type != UnityEditor.PrefabType.None)
|
||
|
{
|
||
|
gameObject = UnityEditor.PrefabUtility.FindPrefabRoot(gameObject);
|
||
|
return gameObject.transform;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
#if UNITY_2018_3_OR_NEWER
|
||
|
|
||
|
var status = UnityEditor.PrefabUtility.GetPrefabInstanceStatus(gameObject);
|
||
|
if (status != UnityEditor.PrefabInstanceStatus.NotAPrefab)
|
||
|
{
|
||
|
gameObject = UnityEditor.PrefabUtility.GetOutermostPrefabInstanceRoot(gameObject);
|
||
|
return gameObject.transform;
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
#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)
|
||
|
{
|
||
|
var iRoot = obj as ICharacterRoot;
|
||
|
if (iRoot != null)
|
||
|
return iRoot.transform;
|
||
|
|
||
|
var gameObject = obj as GameObject;
|
||
|
if (gameObject == null)
|
||
|
{
|
||
|
var component = obj as Component;
|
||
|
if (component != null)
|
||
|
gameObject = component.gameObject;
|
||
|
else
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return FindRoot(gameObject);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endif
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
|