/************************************************************************************************************************/ /// <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); }
/************************************************************************************************************************/ private static void GatherFromComponents(GameObject gameObject, HashSet <AnimationClip> clips) { var root = AnimancerEditorUtilities.FindRoot(gameObject); var components = ObjectPool.AcquireList <MonoBehaviour>(); root.GetComponentsInChildren(true, components); GatherFromComponents(components, clips); ObjectPool.Release(components); }
/************************************************************************************************************************/ /// <summary> /// Uses <see cref="HandleDragAndDrop"/> to deal with drag and drop operations involving /// <see cref="AnimationClip"/>s of <see cref="IAnimationClipSource"/>s. /// </summary> public static void HandleDragAndDropAnimations(Rect dropArea, Action <AnimationClip> onDrop) { HandleDragAndDrop(dropArea, (clip) => !clip.legacy, onDrop); HandleDragAndDrop <IAnimationClipSource>(dropArea, null, (source) => { var clips = ObjectPool.AcquireList <AnimationClip>(); source.GetAnimationClips(clips); TryDrop(clips, (clip) => !clip.legacy, onDrop, true); ObjectPool.Release(clips); }); }
/************************************************************************************************************************/ /// <summary> /// Sorts an array according to an array of dependants. /// If ItemA depends on ItemB, ItemA will be put later in the returned list. /// </summary> /// <param name="collection">The collection to sort. If any element depends on something that isn't present, it will be added automatically.</param> /// <param name="getDependancies">A delegate that can return the dependancies of any given element.</param> /// <param name="comparer">The equality comparer to use. Null will use the default comparer.</param> /// <param name="ignoreCycles">If false, an <see cref="ArgumentException"/> will be thrown when a cyclic dependancy is encountered</param> public static List <T> TopologicalSort <T>(IEnumerable <T> collection, Func <T, IEnumerable <T> > getDependancies, IEqualityComparer <T> comparer = null, bool ignoreCycles = false) { var sorted = ObjectPool.AcquireList <T>(); var visiting = new Dictionary <T, bool>(comparer); foreach (var item in collection) { Visit(item, getDependancies, sorted, visiting, ignoreCycles); } return(sorted); }
/************************************************************************************************************************/ public static bool Append(Dictionary <EditorCurveBinding, bool> bindings, List <EditorCurveBinding> sortedBindings, ref int index, StringBuilder message) { var binding = sortedBindings[index]; if (binding.type != typeof(Transform)) { return(false); } if (string.IsNullOrEmpty(binding.path)) { message.AppendLine().Append('>'); } else { message.Append(':'); } var otherBindings = ObjectPool.AcquireList <EditorCurveBinding>(); var flags = GetFlags(bindings, sortedBindings, ref index, otherBindings, out var anyExists); message.Append(anyExists ? " [o]" : " [x]"); var first = true; AppendProperty(message, ref first, flags, PositionFlags, "position", "xyz"); AppendProperty(message, ref first, flags, RotationFlags, "rotation", "wxyz"); AppendProperty(message, ref first, flags, ScaleFlags, "scale", "xyz"); for (int i = 0; i < otherBindings.Count; i++) { if (anyExists) { message.Append(','); } binding = otherBindings[i]; message .Append(" [") .Append(bindings[binding] ? 'o' : 'x') .Append("] ") .Append(binding.propertyName); } ObjectPool.Release(otherBindings); return(true); }
/************************************************************************************************************************/ /// <summary>Removes any items from the `dictionary` that use destroyed objects as their key.</summary> public static void RemoveDestroyedObjects <TKey, TValue>(Dictionary <TKey, TValue> dictionary) where TKey : Object { var oldObjects = ObjectPool.AcquireList <TKey>(); foreach (var obj in dictionary.Keys) { if (obj == null) { oldObjects.Add(obj); } } for (int i = 0; i < oldObjects.Count; i++) { dictionary.Remove(oldObjects[i]); } ObjectPool.Release(oldObjects); }
/************************************************************************************************************************/ /// <summary> /// Sorts an array according to an array of dependants. /// If ItemA depends on ItemB, ItemA will be put later in the returned list. /// </summary> /// <param name="list">The list to sort. If any element depends on something that isn't present, it will be added automatically.</param> /// <param name="skip">The index at which to start sorting. Everything before this index is kept in the same order as the input list.</param> /// <param name="getDependancies">A delegate that can return the dependancies of any given element.</param> /// <param name="comparer">The equality comparer to use. Null will use the default comparer.</param> /// <param name="ignoreCycles">If false, an <see cref="ArgumentException"/> will be thrown when a cyclic dependancy is encountered</param> public static List <T> TopologicalSort <T>(List <T> list, int skip, Func <T, IEnumerable <T> > getDependancies, IEqualityComparer <T> comparer = null, bool ignoreCycles = false) { var sorted = ObjectPool.AcquireList <T>(); var visiting = new Dictionary <T, bool>(comparer); for (int i = 0; i < skip; i++) { var item = list[i]; sorted.Add(item); visiting.Add(item, false); } for (; skip < list.Count; skip++) { Visit(list[skip], getDependancies, sorted, visiting, ignoreCycles); } return(sorted); }
private static void AppendBindings(StringBuilder message, Dictionary <EditorCurveBinding, bool> bindings, int existingBindings) { if (bindings == null) { return; } message.AppendLine() .Append(LinePrefix + "This message has been copied to the clipboard" + " (in case it is too long for Unity to display in the Console)."); message.AppendLine() .Append(LinePrefix) .Append(bindings.Count - existingBindings) .Append(" of ") .Append(bindings.Count) .Append(" bindings do not exist in the Rig: [x] = Missing, [o] = Exists"); var sortedBindings = ObjectPool.AcquireList <EditorCurveBinding>(); sortedBindings.AddRange(bindings.Keys); sortedBindings.Sort((a, b) => { var result = a.path.CompareTo(b.path); if (result != 0) { return(result); } if (a.type != b.type) { if (a.type == typeof(Transform)) { return(-1); } else if (b.type == typeof(Transform)) { return(1); } result = a.type.Name.CompareTo(b.type.Name); if (result != 0) { return(result); } } return(a.propertyName.CompareTo(b.propertyName)); }); var previousBinding = default(EditorCurveBinding); var pathSplit = NoStrings; for (int iBinding = 0; iBinding < sortedBindings.Count; iBinding++) { var binding = sortedBindings[iBinding]; if (binding.path != previousBinding.path) { var newPathSplit = binding.path.Split('/'); var iSegment = Math.Min(newPathSplit.Length - 1, pathSplit.Length - 1); for (; iSegment >= 0; iSegment--) { if (pathSplit[iSegment] == newPathSplit[iSegment]) { break; } } iSegment++; if (!string.IsNullOrEmpty(binding.path)) { for (; iSegment < newPathSplit.Length; iSegment++) { message.AppendLine(); for (int iIndent = 0; iIndent < iSegment; iIndent++) { message.Append(Strings.Indent); } message.Append("> ").Append(newPathSplit[iSegment]); } } pathSplit = newPathSplit; } if (TransformBindings.Append(bindings, sortedBindings, ref iBinding, message)) { continue; } message.AppendLine(); if (binding.path.Length > 0) { for (int iIndent = 0; iIndent < pathSplit.Length; iIndent++) { message.Append(Strings.Indent); } } message .Append('[') .Append(bindings[binding] ? 'o' : 'x') .Append("] ") .Append(binding.type.Name) .Append('.') .Append(binding.propertyName); previousBinding = binding; } ObjectPool.Release(sortedBindings); }