/// <summary> /// Gathers the current details of the <see cref="AnimancerNode.Root"/> and register this /// <see cref="CustomFade"/> to be updated by it so that it can replace the regular fade behaviour. /// </summary> protected void Apply(AnimancerNode node) { #if UNITY_ASSERTIONS AnimancerUtilities.Assert(node != null, "Node is null."); AnimancerUtilities.Assert(node.IsValid, "Node is not valid."); AnimancerUtilities.Assert(node.FadeSpeed != 0, $"Node is not fading ({nameof(node.FadeSpeed)} is 0)."); var animancer = node.Root; AnimancerUtilities.Assert(animancer != null, $"{nameof(node)}.{nameof(node.Root)} is null."); if (OptionalWarning.CustomFadeBounds.IsEnabled()) { if (CalculateWeight(0) != 0) { OptionalWarning.CustomFadeBounds.Log("CalculateWeight(0) != 0.", animancer.Component); } if (CalculateWeight(1) != 1) { OptionalWarning.CustomFadeBounds.Log("CalculateWeight(1) != 1.", animancer.Component); } } #endif _Time = 0; _Target = new NodeWeight(node); _FadeSpeed = node.FadeSpeed; _Layer = node.Layer; _CommandCount = _Layer.CommandCount; node.FadeSpeed = 0; FadeOutNodes.Clear(); node.Root.RequirePreUpdate(this); }
/************************************************************************************************************************/ /// <summary>Returns a spare <see cref="HashSet{T}"/> if there are any, or creates a new one.</summary> /// <remarks>Remember to <see cref="Release{T}(HashSet{T})"/> it when you are done.</remarks> public static HashSet <T> AcquireSet <T>() { var set = ObjectPool <HashSet <T> > .Acquire(); AnimancerUtilities.Assert(set.Count == 0, "A pooled set is not empty." + NotClearError); return(set); }
/************************************************************************************************************************/ /// <summary> /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`. /// </summary> public static ObjectPool <HashSet <T> > .Disposable AcquireSet <T>(out HashSet <T> set) { var disposable = new ObjectPool <HashSet <T> > .Disposable(out set, onRelease : (s) => s.Clear()); AnimancerUtilities.Assert(set.Count == 0, "A pooled set is not empty." + NotClearError); return(disposable); }
/************************************************************************************************************************/ /// <summary>Returns a spare <see cref="List{T}"/> if there are any, or creates a new one.</summary> /// <remarks>Remember to <see cref="Release{T}(List{T})"/> it when you are done.</remarks> public static List <T> AcquireList <T>() { var list = ObjectPool <List <T> > .Acquire(); AnimancerUtilities.Assert(list.Count == 0, "A pooled list is not empty." + NotClearError); return(list); }
/************************************************************************************************************************/ /// <summary>Returns a spare <see cref="StringBuilder"/> if there are any, or creates a new one.</summary> /// <remarks>Remember to <see cref="Release(StringBuilder)"/> it when you are done.</remarks> public static StringBuilder AcquireStringBuilder() { var builder = ObjectPool <StringBuilder> .Acquire(); AnimancerUtilities.Assert(builder.Length == 0, $"A pooled {nameof(StringBuilder)} is not empty." + NotClearError); return(builder); }
/************************************************************************************************************************/ /// <summary> /// Creates a new <see cref="ObjectPool{T}.Disposable"/> and calls <see cref="ObjectPool{T}.Acquire"/> to set the /// <see cref="ObjectPool{T}.Disposable.Item"/> and `item`. /// </summary> public static ObjectPool <List <T> > .Disposable AcquireList <T>(out List <T> list) { var disposable = new ObjectPool <List <T> > .Disposable(out list, onRelease : (l) => l.Clear()); AnimancerUtilities.Assert(list.Count == 0, "A pooled list is not empty." + NotClearError); return(disposable); }
public static void AssertCanRemoveChild(AnimancerState state, IList <AnimancerState> states) { #if UNITY_ASSERTIONS var index = state.Index; if (index < 0) { throw new InvalidOperationException( "Cannot remove a child state that did not have an Index assigned"); } if (index > states.Count) { throw new IndexOutOfRangeException( "AnimancerState.Index (" + state.Index + ") is outside the collection of states (count " + states.Count + ")"); } if (states[state.Index] != state) { throw new InvalidOperationException( "Cannot remove a child state that was not actually connected to its port on " + state.Parent + ":" + "\n Port: " + state.Index + "\n Connected Child: " + AnimancerUtilities.ToStringOrNull(states[state.Index]) + "\n Disconnecting Child: " + AnimancerUtilities.ToStringOrNull(state)); } #endif }
public static void AssertCanRemoveChild(AnimancerState state, IList <AnimancerState> states) { #if UNITY_ASSERTIONS var index = state.Index; if (index < 0) { throw new InvalidOperationException( $"Cannot remove a child state that did not have an {nameof(state.Index)} assigned"); } if (index > states.Count) { throw new IndexOutOfRangeException( $"{nameof(AnimancerState)}.{nameof(state.Index)} ({state.Index})" + $" is outside the collection of states (count {states.Count})"); } if (states[state.Index] != state) { throw new InvalidOperationException( $"Cannot remove a child state that was not actually connected to its port on {state.Parent}:" + $"\n• Port: {state.Index}" + $"\n• Connected Child: {AnimancerUtilities.ToStringOrNull(states[state.Index])}" + $"\n• Disconnecting Child: {AnimancerUtilities.ToStringOrNull(state)}"); } #endif }
/************************************************************************************************************************/ /// <summary>Sets the animation associated with the specified `direction`.</summary> public void SetClip(Direction direction, AnimationClip clip) { switch (direction) { case Direction.Up: SetUp(clip); break; case Direction.Right: SetRight(clip); break; case Direction.Down: SetDown(clip); break; case Direction.Left: SetLeft(clip); break; case Direction.UpRight: _UpRight = clip; break; case Direction.DownRight: _DownRight = clip; break; case Direction.DownLeft: _DownLeft = clip; break; case Direction.UpLeft: _UpLeft = clip; break; default: throw new ArgumentException($"Unsupported {nameof(Direction)}: {direction}"); } AnimancerUtilities.SetDirty(this); }
/************************************************************************************************************************/ /// <summary> /// Recalculates the <see cref="CurrentSpeeds"/> depending on the <see cref="AnimationClip.length"/> of /// their animations so that they all take the same amount of time to play fully. /// </summary> private static void NormalizeDurations(SerializedProperty property) { var speedCount = CurrentSpeeds.arraySize; var lengths = new float[CurrentAnimations.arraySize]; if (lengths.Length <= 1) { return; } int nonZeroLengths = 0; float totalLength = 0; float totalSpeed = 0; for (int i = 0; i < lengths.Length; i++) { var state = CurrentAnimations.GetArrayElementAtIndex(i).objectReferenceValue; if (AnimancerUtilities.TryGetLength(state, out var length) && length > 0) { nonZeroLengths++; totalLength += length; lengths[i] = length; if (speedCount > 0) { totalSpeed += CurrentSpeeds.GetArrayElementAtIndex(i).floatValue; } } } if (nonZeroLengths == 0) { return; } var averageLength = totalLength / nonZeroLengths; var averageSpeed = speedCount > 0 ? totalSpeed / nonZeroLengths : 1; CurrentSpeeds.arraySize = lengths.Length; InitializeSpeeds(speedCount); for (int i = 0; i < lengths.Length; i++) { if (lengths[i] == 0) { continue; } CurrentSpeeds.GetArrayElementAtIndex(i).floatValue = averageSpeed * lengths[i] / averageLength; } TryCollapseArrays(); }
/************************************************************************************************************************/ /// <summary> /// Called by <see cref="AnimancerNode.AppendDescription"/> to append the details of this node. /// </summary> protected override void AppendDetails(StringBuilder text, string delimiter) { base.AppendDetails(text, delimiter); text.Append(delimiter).Append("CurrentState: ").Append(CurrentState); text.Append(delimiter).Append("CommandCount: ").Append(CommandCount); text.Append(delimiter).Append("IsAdditive: ").Append(IsAdditive); #if UNITY_EDITOR text.Append(delimiter).Append("AvatarMask: ").Append(AnimancerUtilities.ToStringOrNull(_Mask)); #endif }
/************************************************************************************************************************/ /// <summary> /// Registers the `callback` to be triggered when the <see cref="AnimancerNode.EffectiveWeight"/> becomes 0. /// </summary> /// <remarks> /// The <see cref="AnimancerNode.EffectiveWeight"/> is only checked at the end of the animation update so if it /// is set to 0 then back to a higher number before the next update, the callback will not be triggered. /// </remarks> public static void Register(AnimancerNode node, Action callback) { #if UNITY_ASSERTIONS AnimancerUtilities.Assert(node != null, "Node is null."); AnimancerUtilities.Assert(node.IsValid, "Node is not valid."); #endif var exit = ObjectPool.Acquire <ExitEvent>(); exit._Callback = callback; exit._Node = node; node.Root.RequirePostUpdate(exit); }
/************************************************************************************************************************/ /// <summary>Sets the animation associated with the specified `direction`.</summary> public void SetClip(Direction direction, AnimationClip clip) { switch (direction) { case Direction.Up: _Up = clip; break; case Direction.Right: _Right = clip; break; case Direction.Down: _Down = clip; break; case Direction.Left: _Left = clip; break; default: throw new ArgumentException("Unhandled direction: " + direction); } AnimancerUtilities.SetDirty(this); }
/************************************************************************************************************************/ /// <summary> /// Gathers the current details of the <see cref="AnimancerNode.Root"/> and register this /// <see cref="CustomFade"/> to be updated by it so that it can replace the regular fade behaviour. /// </summary> protected void Apply(AnimancerState state) { AnimancerUtilities.Assert(state.Parent != null, "Node is not connected to a layer."); Apply((AnimancerNode)state); var parent = state.Parent; for (int i = parent.ChildCount - 1; i >= 0; i--) { var other = parent.GetChild(i); if (other != state && other.FadeSpeed != 0) { other.FadeSpeed = 0; FadeOutNodes.Add(new NodeWeight(other)); } } }
/************************************************************************************************************************/ #region Threshold Calculation Functions /************************************************************************************************************************/ /// <inheritdoc/> protected override void AddThresholdFunctionsToMenu(GenericMenu menu) { AddCalculateThresholdsFunction(menu, "From Velocity/XY", (state, threshold) => { if (AnimancerUtilities.TryGetAverageVelocity(state, out var velocity)) { return(new Vector2(velocity.x, velocity.y)); } else { return(new Vector2(float.NaN, float.NaN)); } }); AddCalculateThresholdsFunction(menu, "From Velocity/XZ", (state, threshold) => { if (AnimancerUtilities.TryGetAverageVelocity(state, out var velocity)) { return(new Vector2(velocity.x, velocity.z)); } else { return(new Vector2(float.NaN, float.NaN)); } }); AddCalculateThresholdsFunctionPerAxis(menu, "From Speed", (state, threshold) => AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) ? velocity.magnitude : float.NaN); AddCalculateThresholdsFunctionPerAxis(menu, "From Velocity X", (state, threshold) => AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) ? velocity.x : float.NaN); AddCalculateThresholdsFunctionPerAxis(menu, "From Velocity Y", (state, threshold) => AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) ? velocity.y : float.NaN); AddCalculateThresholdsFunctionPerAxis(menu, "From Velocity Z", (state, threshold) => AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) ? velocity.z : float.NaN); AddCalculateThresholdsFunctionPerAxis(menu, "From Angular Speed (Rad)", (state, threshold) => AnimancerUtilities.TryGetAverageAngularSpeed(state, out var speed) ? speed : float.NaN); AddCalculateThresholdsFunctionPerAxis(menu, "From Angular Speed (Deg)", (state, threshold) => AnimancerUtilities.TryGetAverageAngularSpeed(state, out var speed) ? speed * Mathf.Rad2Deg : float.NaN); AddPropertyModifierFunction(menu, "Initialize 4 Directions", Initialize4Directions); AddPropertyModifierFunction(menu, "Initialize 8 Directions", Initialize8Directions); }
/************************************************************************************************************************/ /// <inheritdoc/> protected override void AddThresholdFunctionsToMenu(UnityEditor.GenericMenu menu) { const string EvenlySpaced = "Evenly Spaced"; var count = CurrentThresholds.arraySize; if (count <= 1) { menu.AddDisabledItem(new GUIContent(EvenlySpaced)); } else { var first = CurrentThresholds.GetArrayElementAtIndex(0).floatValue; var last = CurrentThresholds.GetArrayElementAtIndex(count - 1).floatValue; if (last == first) { last++; } AddPropertyModifierFunction(menu, $"{EvenlySpaced} ({first} to {last})", (_) => { for (int i = 0; i < count; i++) { CurrentThresholds.GetArrayElementAtIndex(i).floatValue = Mathf.Lerp(first, last, i / (float)(count - 1)); } }); } AddCalculateThresholdsFunction(menu, "From Speed", (state, threshold) => AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) ? velocity.magnitude : float.NaN); AddCalculateThresholdsFunction(menu, "From Velocity X", (state, threshold) => AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) ? velocity.x : float.NaN); AddCalculateThresholdsFunction(menu, "From Velocity Y", (state, threshold) => AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) ? velocity.y : float.NaN); AddCalculateThresholdsFunction(menu, "From Velocity Z", (state, threshold) => AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) ? velocity.z : float.NaN); AddCalculateThresholdsFunction(menu, "From Angular Speed (Rad)", (state, threshold) => AnimancerUtilities.TryGetAverageAngularSpeed(state, out var speed) ? speed : float.NaN); AddCalculateThresholdsFunction(menu, "From Angular Speed (Deg)", (state, threshold) => AnimancerUtilities.TryGetAverageAngularSpeed(state, out var speed) ? speed * Mathf.Rad2Deg : float.NaN); }
/************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Initialisation //初始 /************************************************************************************************************************/ #if UNITY_EDITOR /// <summary>[仅在编辑模式] /// 当这个组件第一次被添加时(在编辑模式下),以及当重置命令从它的上下文菜单中执行时,由Unity编辑器调用 /// <para></para> /// 如果已初始化,则销毁播放模式的数据 /// 在此对象或其子对象或父对象上搜索<see cref="UnityEngine.Animator"/> /// 删除 <see cref="Animator.runtimeAnimatorController"/> 如果找到的话 /// <para></para> /// 此方法还可以防止将此组件的多个副本添加到单个对象。这样做将立即销毁新字段并更改旧字段的类型以匹配新字段, /// 从而允许您更改类型而不会丢失它们共享的任何序列化字段的值. /// </summary> protected virtual void Reset() { OnDestroy(); _Animator = Editor.AnimancerEditorUtilities.GetComponentInHierarchy <Animator>(gameObject);//获取层次结构中的组件 if (_Animator != null) { _Animator.runtimeAnimatorController = null; Editor.AnimancerEditorUtilities.SetIsInspectorExpanded(_Animator, false); // 折叠Animator属性,因为自定义检查器使用它来控制是否展开Animator的检查器 using (var serializedObject = new UnityEditor.SerializedObject(this)) { var property = serializedObject.FindProperty("_Animator"); property.isExpanded = false; serializedObject.ApplyModifiedProperties(); } } AnimancerUtilities.IfMultiComponentThenChangeType(this); }
/// <summary>[Editor-Only] /// If a <see cref="Source"/> and <see cref="FunctionName"/> have been assigned but the /// <see cref="AnimancerState.Clip"/> has no event with that name, this method logs a warning. /// </summary> private void ValidateSourceHasCorrectEvent() { if (FunctionName == null || _Source == null || AnimancerUtilities.HasEvent(_Source, FunctionName)) { return; } var message = ObjectPool.AcquireStringBuilder() .Append("No Animation Event was found in ") .Append(_Source.Clip) .Append(" with the Function Name '") .Append(FunctionName) .Append('\''); if (_Source != null) { message.Append('\n'); _Source.Root.AppendDescription(message); } Debug.LogWarning(message.ReleaseToString(), _Source.Root?.Component as Object); }
/************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Initialisation /************************************************************************************************************************/ #if UNITY_EDITOR /// <summary>[Editor-Only] /// Called by the Unity Editor when this component is first added (in Edit Mode) and whenever the Reset command /// is executed from its context menu. /// <para></para> /// Destroys the playable if one has been initialised. /// Searches for an <see cref="UnityEngine.Animator"/> on this object, or it's children or parents. /// Removes the <see cref="Animator.runtimeAnimatorController"/> if it finds one. /// <para></para> /// This method also prevents you from adding multiple copies of this component to a single object. Doing so /// will destroy the new one immediately and change the old one's type to match the new one, allowing you to /// change the type without losing the values of any serialized fields they share. /// </summary> protected virtual void Reset() { OnDestroy(); _Animator = Editor.AnimancerEditorUtilities.GetComponentInHierarchy <Animator>(gameObject); if (_Animator != null) { _Animator.runtimeAnimatorController = null; Editor.AnimancerEditorUtilities.SetIsInspectorExpanded(_Animator, false); // Collapse the Animator property because the custom Inspector uses that to control whether the // Animator's Inspector is expanded. using (var serializedObject = new UnityEditor.SerializedObject(this)) { var property = serializedObject.FindProperty("_Animator"); property.isExpanded = false; serializedObject.ApplyModifiedProperties(); } } AnimancerUtilities.IfMultiComponentThenChangeType(this); }
/************************************************************************************************************************/ /// <summary>Resizes the `values` if necessary and copies the value of each property into it.</summary> public void GetValues(ref TValue[] values) { AnimancerUtilities.SetLength(ref values, _Values.Length); _Values.CopyTo(values); }
/// <summary>Sets the <see cref="UpLeft"/> animation.</summary> /// <remarks>This is not simply a property setter because the animations will usually not need to be changed by scripts.</remarks> public void SetUpLeft(AnimationClip clip) { _UpLeft = clip; AnimancerUtilities.SetDirty(this); }
/// <summary>Sets the <see cref="DownRight"/> animation.</summary> /// <remarks>This is not simply a property setter because the animations will usually not need to be changed by scripts.</remarks> public void SetDownRight(AnimationClip clip) { _DownRight = clip; AnimancerUtilities.SetDirty(this); }
/************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Sync /************************************************************************************************************************/ /// <summary>Draws a "Sync" header.</summary> protected void DoSyncHeaderGUI(Rect area) { using (ObjectPool.Disposable.AcquireContent(out var label, "Sync", "Determines which child states have their normalized times constantly synchronized")) { DoHeaderDropdownGUI(area, CurrentSpeeds, label, (menu) => { var syncCount = CurrentSynchronizeChildren.arraySize; var allState = syncCount == 0 ? MenuFunctionState.Selected : MenuFunctionState.Normal; AddPropertyModifierFunction(menu, "All", allState, (_) => CurrentSynchronizeChildren.arraySize = 0); var syncNone = syncCount == CurrentAnimations.arraySize; if (syncNone) { for (int i = 0; i < syncCount; i++) { if (CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue) { syncNone = false; break; } } } var noneState = syncNone ? MenuFunctionState.Selected : MenuFunctionState.Normal; AddPropertyModifierFunction(menu, "None", noneState, (_) => { var count = CurrentSynchronizeChildren.arraySize = CurrentAnimations.arraySize; for (int i = 0; i < count; i++) { CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue = false; } }); AddPropertyModifierFunction(menu, "Invert", MenuFunctionState.Normal, (_) => { var count = CurrentSynchronizeChildren.arraySize; for (int i = 0; i < count; i++) { var property = CurrentSynchronizeChildren.GetArrayElementAtIndex(i); property.boolValue = !property.boolValue; } var newCount = CurrentSynchronizeChildren.arraySize = CurrentAnimations.arraySize; for (int i = count; i < newCount; i++) { CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue = false; } }); AddPropertyModifierFunction(menu, "Non-Stationary", MenuFunctionState.Normal, (_) => { var count = CurrentAnimations.arraySize; for (int i = 0; i < count; i++) { var state = CurrentAnimations.GetArrayElementAtIndex(i).objectReferenceValue; if (state == null) { continue; } if (i >= syncCount) { CurrentSynchronizeChildren.arraySize = i + 1; for (int j = syncCount; j < i; j++) { CurrentSynchronizeChildren.GetArrayElementAtIndex(j).boolValue = true; } syncCount = i + 1; } CurrentSynchronizeChildren.GetArrayElementAtIndex(i).boolValue = AnimancerUtilities.TryGetAverageVelocity(state, out var velocity) && velocity != default; } TryCollapseSync(); }); }); } }
/************************************************************************************************************************/ /// <summary> /// Destroys the <see cref="_Playable"/> and restores the graph connection it was intercepting. /// </summary> /// <remarks> /// This method is NOT called automatically, so if you need to guarantee that things will get cleaned up you /// should use <see cref="AnimancerPlayable.Disposables"/>. /// </remarks> public virtual void Destroy() { AnimancerUtilities.RemovePlayable(_Playable); }