/************************************************************************************************************************/ /// <summary> /// Gathers all animations linked to the target <see cref="IAnimancerComponent"/> by objects under the same /// <see cref="Transform.root"/> as it. /// </summary> public static void GatherAnimationClips <TSource>(IAnimancerComponent animancer, List <AnimationClip> clips, TSource[] sources, Func <int, IAnimancerComponent> sourceIndexToAnimancer) { if (AlreadyGathering.Contains(animancer)) { return; } AlreadyGathering.Add(animancer); int i = 0; GatherClips: try { for (; i < sources.Length; i++) { if (sourceIndexToAnimancer(i) == animancer) { GatherAnimationClips(sources[i], clips, typeof(TSource)); } } } catch (NullReferenceException) { // If something throws a null exception, just ignore it and go to the next object. i++; goto GatherClips; } AlreadyGathering.Remove(animancer); }
private static bool ShouldEditModePlay(IAnimancerComponent animancer, AnimationClip clip) { return (ShouldEditModeSample(clip, animancer?.Animator) && (!(animancer is Object obj) || obj != null)); #endif }
/************************************************************************************************************************/ private void DoStatesGUI(IAnimancerComponent owner) { if (HideInactiveStates) { DoStatesGUI("Active States", ActiveStates, owner); } else if (SeparateActiveFromInactiveStates) { DoStatesGUI("Active States", ActiveStates, owner); DoStatesGUI("Inactive States", InactiveStates, owner); } else { DoStatesGUI("States", ActiveStates, owner); } if (Target.Index == 0 && Target.Weight != 0 && !Target.IsAdditive && !Mathf.Approximately(Target.GetTotalWeight(), 1)) { EditorGUILayout.HelpBox( "The total Weight of all states in this layer does not equal 1, which will likely give undesirable results." + " Click here for more information.", MessageType.Warning); if (AnimancerGUI.TryUseClickEventInLastRect()) { EditorUtility.OpenWithDefaultApp(Strings.DocsURLs.Fading); } } }
/// <summary>Initialises a single property.</summary> public AnimatedProperty(IAnimancerComponent animancer, string propertyName) : this(animancer, 1, NativeArrayOptions.UninitializedMemory) { var animator = animancer.Animator; _Properties[0] = animator.BindStreamProperty(animator.transform, typeof(Animator), propertyName); }
/************************************************************************************************************************/ /// <summary> /// If <see cref="AnimationClip"/>s or <see cref="IAnimationClipSource"/>s are dropped inside the 'dropArea', /// this method creates a new state in the 'target' for each animation. /// </summary> public static void HandleDragAndDropAnimations(Rect dropArea, IAnimancerComponent target, int layerIndex) { AnimancerEditorUtilities.HandleDragAndDropAnimations(dropArea, (clip) => { target.GetOrCreateState(clip, layerIndex); }); }
public static void EditModePlay(IAnimancerComponent animancer, AnimationClip clip, bool pauseImmediately = true) { #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode || animancer == null || clip == null) { return; } // Delay for a frame in case this was called at a bad time (such as during OnValidate). UnityEditor.EditorApplication.delayCall += () => { if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode || animancer == null || clip == null) { return; } animancer.Playable.Play(clip); if (pauseImmediately) { animancer.Playable.Evaluate(); animancer.Playable.PauseGraph(); } }; #endif }
/// <summary> /// If <see cref="AnimationClip"/>s or <see cref="IAnimationClipSource"/>s are dropped inside the `dropArea`, /// this method creates a new state in the `target` for each animation. /// </summary> public static void HandleDragAndDropAnimations(Rect dropArea, IAnimancerComponent target, int layerIndex) { AnimancerGUI.HandleDragAndDropAnimations(dropArea, (clip) => { target.Playable.Layers[layerIndex].GetOrCreateState(clip); }); }
/************************************************************************************************************************/ /// <summary> Draws the details of the target state in the GUI.</summary> protected virtual void DoDetailsGUI(IAnimancerComponent owner) { EditorGUI.indentLevel++; DoTimeSliderGUI(); DoPlayingDetailsGUI(); State.DoFadeDetailsGUI(AnimancerEditorUtilities.GetRect(true)); DoOnEndGUI(); EditorGUI.indentLevel--; }
/************************************************************************************************************************/ #region Initialisation /************************************************************************************************************************/ /// <summary> /// Allocates room for a specified number of properties to be filled by /// <see cref="InitialiseProperty(int, Transform, Type, string)"/>. /// </summary> public AnimatedProperty(IAnimancerComponent animancer, int propertyCount, NativeArrayOptions options = NativeArrayOptions.ClearMemory) { _Properties = new NativeArray <PropertyStreamHandle>(propertyCount, Allocator.Persistent, options); _Values = new NativeArray <TValue>(propertyCount, Allocator.Persistent); CreateJob(); var playable = animancer.Playable; CreatePlayable(playable); playable.Disposables.Add(this); }
/************************************************************************************************************************/ /// <summary> Draws the details of the target state in the GUI.</summary> protected override void DoDetailsGUI(IAnimancerComponent owner) { if (!IsExpanded) { return; } EditorGUI.indentLevel++; DoTimeSliderGUI(); DoNodeDetailsGUI(); DoOnEndGUI(); EditorGUI.indentLevel--; }
/// <summary>Draws the Inspector for the given `state`.</summary> private void DoStateGUI(AnimancerState state, IAnimancerComponent owner) { IAnimancerNodeDrawer Inspector; if (!StateInspectors.TryGetValue(state, out Inspector)) { Inspector = state.GetDrawer(); StateInspectors.Add(state, Inspector); } Inspector.DoGUI(owner); DoChildStatesGUI(state, owner); }
/// <summary>Initialises a group of properties.</summary> public AnimatedProperty(IAnimancerComponent animancer, params string[] propertyNames) : this(animancer, propertyNames.Length, NativeArrayOptions.UninitializedMemory) { var count = propertyNames.Length; var animator = animancer.Animator; var transform = animator.transform; for (int i = 0; i < count; i++) { InitialiseProperty(animator, i, transform, typeof(Animator), propertyNames[i]); } }
/************************************************************************************************************************/ /// <summary> /// Draws the details and controls for the target <see cref="AnimancerNodeDrawer{T}.Target"/> in the Inspector. /// </summary> public override void DoGUI(IAnimancerComponent owner) { if (!Target.IsValid) { return; } base.DoGUI(owner); var area = GUILayoutUtility.GetLastRect(); HandleDragAndDropAnimations(area, owner, Target.Index); }
/************************************************************************************************************************/ private void DoLayerWeightWarningGUI(IAnimancerComponent target) { if (_LayerCount == 0) { EditorGUILayout.HelpBox( "No layers have been created, which likely means no animations have been played yet.", MessageType.Warning); return; } if (!target.gameObject.activeInHierarchy || !target.enabled || (target.Animator != null && target.Animator.runtimeAnimatorController != null)) { return; } if (_LayerCount == 1) { var layer = LayerInfos[0].Target; if (layer.Weight == 0) { EditorGUILayout.HelpBox( layer + " is at 0 weight, which likely means no animations have been played yet.", MessageType.Warning); } return; } for (int i = 0; i < _LayerCount; i++) { var layer = LayerInfos[i].Target; if (layer.Weight == 1 && !layer.IsAdditive && layer._Mask == null && Mathf.Approximately(layer.GetTotalWeight(), 1)) { return; } } EditorGUILayout.HelpBox( "There are no Override layers at weight 1, which will likely give undesirable results." + " Click here for more information.", MessageType.Warning); if (AnimancerGUI.TryUseClickEventInLastRect()) { EditorUtility.OpenWithDefaultApp(Strings.DocsURLs.Layers + "#blending"); } }
/// <summary> /// Validates the <see cref="Clip"/> type compared to the owner's <see cref="Animator"/> type. /// </summary> private void DoAnimationTypeWarningGUI(IAnimancerComponent owner) { // Validate the clip type compared to the owner. if (owner.Animator == null) { _AnimationTypeWarning = null; return; } if (_AnimationTypeWarningOwner != owner.Animator) { _AnimationTypeWarning = null; _AnimationTypeWarningOwner = owner.Animator; } if (_AnimationTypeWarning == null) { var ownerAnimationType = Editor.AnimancerEditorUtilities.GetAnimationType(_AnimationTypeWarningOwner); var clipAnimationType = Editor.AnimancerEditorUtilities.GetAnimationType(Target._Clip); if (ownerAnimationType == clipAnimationType) { _AnimationTypeWarning = ""; } else { var text = new StringBuilder() .Append("Possible animation type mismatch:\n - Animator type is ") .Append(ownerAnimationType) .Append("\n - AnimationClip type is ") .Append(clipAnimationType) .Append("\nThis means that the clip may not work correctly," + " however this check is not totally accurate. Click here for more info."); _AnimationTypeWarning = text.ToString(); } } if (_AnimationTypeWarning != "") { UnityEditor.EditorGUILayout.HelpBox(_AnimationTypeWarning, UnityEditor.MessageType.Warning); if (Editor.AnimancerGUI.TryUseClickEventInLastRect()) { UnityEditor.EditorUtility.OpenWithDefaultApp( Strings.DocsURLs.AnimationTypes); } } }
/************************************************************************************************************************/ /// <summary>Draws the details and controls for the target <see cref="Target"/> in the Inspector.</summary> public virtual void DoGUI(IAnimancerComponent owner) { if (!Target.IsValid) { return; } AnimancerGUI.BeginVerticalBox(RegionStyle); { DoHeaderGUI(); DoDetailsGUI(owner); } AnimancerGUI.EndVerticalBox(RegionStyle); CheckContextMenu(GUILayoutUtility.GetLastRect()); }
/************************************************************************************************************************/ /// <summary>Draws all child states of the `state`.</summary> private void DoChildStatesGUI(AnimancerState state, IAnimancerComponent owner) { EditorGUI.indentLevel++; foreach (var child in state) { if (child == null) { continue; } DoStateGUI(child, owner); } EditorGUI.indentLevel--; }
/************************************************************************************************************************/ /// <summary>Draws the GUI of the <see cref="IAnimancerComponent.Playable"/>.</summary> public void DoGUI(IAnimancerComponent target) { if (!target.IsPlayableInitialised) { DoPlayableNotInitialisedGUI(target); return; } EditorGUI.BeginChangeCheck(); // Gather the during the layout event and use the same ones during subsequent events to avoid GUI errors // in case they change (they shouldn't, but this is also more efficient). if (Event.current.type == EventType.Layout) { AnimancerLayerDrawer.GatherLayerEditors(target.Playable, LayerInfos, out _LayerCount); _IsGraphPlaying = target.Playable.IsGraphPlaying; } if (!_IsGraphPlaying) { AnimancerGUI.BeginVerticalBox(GUI.skin.box); _IsGraphPlaying = EditorGUILayout.Toggle("Is Graph Playing", _IsGraphPlaying); AnimancerGUI.EndVerticalBox(GUI.skin.box); if (_IsGraphPlaying) { target.Playable.UnpauseGraph(); } } for (int i = 0; i < _LayerCount; i++) { LayerInfos[i].DoGUI(target); } DoLayerWeightWarningGUI(); if (AnimancerLayerDrawer.ShowUpdatingNodes) { target.Playable.DoUpdateListGUI(); } if (EditorGUI.EndChangeCheck() && !_IsGraphPlaying) { target.Playable.Evaluate(); } }
/************************************************************************************************************************/ /// <summary>Draws all `states` in the given list.</summary> private void DoStatesGUI(string label, List <AnimancerState> states, IAnimancerComponent owner) { var area = AnimancerGUI.LayoutSingleLineRect(); var width = AnimancerGUI.CalculateLabelWidth("Weight"); GUI.Label(AnimancerGUI.StealFromRight(ref area, width), "Weight"); EditorGUI.LabelField(area, label, states.Count.ToString()); EditorGUI.indentLevel++; for (int i = 0; i < states.Count; i++) { DoStateGUI(states[i], owner); } EditorGUI.indentLevel--; }
/************************************************************************************************************************/ /// <summary>Draws all 'states' in the given list.</summary> private void DoStatesGUI(string label, List <AnimancerState> states, IAnimancerComponent owner) { var area = AnimancerEditorUtilities.GetRect(); var width = GUI.skin.label.CalculateWidth("Weight"); GUI.Label(AnimancerEditorUtilities.StealWidth(ref area, width), "Weight"); EditorGUI.LabelField(area, label, states.Count.ToString()); EditorGUI.indentLevel++; for (int i = 0; i < states.Count; i++) { DoStateGUI(states[i], owner); } EditorGUI.indentLevel--; }
/************************************************************************************************************************/ /// <summary>Draws all states in the given layer.</summary> public void DoInspectorGUI(IAnimancerComponent owner) { AnimancerEditorUtilities.BeginVerticalBox(GUI.skin.box); DoHeaderGUI(); if (HideInactiveStates) { DoStatesGUI("Active States", ActiveStates, owner); } else if (SeparateActiveFromInactiveStates) { DoStatesGUI("Active States", ActiveStates, owner); DoStatesGUI("Inactive States", InactiveStates, owner); } else { DoStatesGUI("States", ActiveStates, owner); } if (Layer.PortIndex == 0 && Layer.Weight != 0 && !Layer.IsAdditive && !Mathf.Approximately(Layer.GetTotalWeight(), 1)) { EditorGUILayout.HelpBox( "The total Weight of all states in this layer does not equal 1, which will likely give undesirable results." + " Click here for more information.", MessageType.Warning); if (AnimancerEditorUtilities.TryUseClickInLastRect()) { EditorUtility.OpenWithDefaultApp( AnimancerPlayable.APIDocumentationURL + "/docs/manual/smooth-transitions"); } } AnimancerEditorUtilities.EndVerticalBox(GUI.skin.box); var totalArea = GUILayoutUtility.GetLastRect(); HandleDragAndDropAnimations(totalArea, owner, Layer.PortIndex); CheckContextMenu(totalArea); }
/************************************************************************************************************************/ /// <summary>Draws the details and controls for the target <see cref="State"/> in the inspector.</summary> public virtual void DoGUI(IAnimancerComponent owner) { GUILayout.BeginVertical(); { var position = AnimancerEditorUtilities.GetRect(true); string label; DoFoldoutGUI(position, out label); DoLabelGUI(ref position, label); if (_IsExpanded) { DoDetailsGUI(owner); } } GUILayout.EndVertical(); CheckContextMenu(GUILayoutUtility.GetLastRect()); }
/************************************************************************************************************************/ /// <summary> Draws the details of the target state in the GUI.</summary> protected override void DoDetailsGUI(IAnimancerComponent owner) { if (IsExpanded) { EditorGUI.indentLevel++; GUILayout.BeginHorizontal(); GUILayout.Space(FoldoutIndent); GUILayout.BeginVertical(); DoLayerDetailsGUI(); DoNodeDetailsGUI(); GUILayout.EndVertical(); GUILayout.EndHorizontal(); EditorGUI.indentLevel--; } DoStatesGUI(owner); }
/************************************************************************************************************************/ /// <summary>Draws the GUI of the <see cref="IAnimancerComponent.Playable"/>.</summary> public void DoGUI(IAnimancerComponent target) { DoNativeAnimatorControllerGUI(target); if (!target.IsPlayableInitialized) { DoPlayableNotInitializedGUI(target); return; } EditorGUI.BeginChangeCheck(); var playable = target.Playable; // Gather the during the layout event and use the same ones during subsequent events to avoid GUI errors // in case they change (they shouldn't, but this is also more efficient). if (Event.current.type == EventType.Layout) { AnimancerLayerDrawer.GatherLayerEditors(playable, LayerInfos, out _LayerCount); } DoRootGUI(playable); for (int i = 0; i < _LayerCount; i++) { LayerInfos[i].DoGUI(); } DoLayerWeightWarningGUI(target); if (ShowInternalDetails) { DoInternalDetailsGUI(playable); } if (EditorGUI.EndChangeCheck() && !playable.IsGraphPlaying) { playable.Evaluate(); } }
/************************************************************************************************************************/ private void DoPlayableNotInitializedGUI(IAnimancerComponent target) { if (!EditorApplication.isPlaying || target.Animator == null || EditorUtility.IsPersistent(target.Animator)) { return; } EditorGUILayout.HelpBox("Playable is not initialized." + " It will be initialized automatically when something needs it, such as playing an animation.", MessageType.Info); if (AnimancerGUI.TryUseClickEventInLastRect(1)) { var menu = new GenericMenu(); menu.AddItem(new GUIContent("Initialize"), false, () => target.Playable.Evaluate()); AnimancerEditorUtilities.AddDocumentationLink(menu, "Layer Documentation", Strings.DocsURLs.Layers); menu.ShowAsContext(); } }
/// <summary>Initializes a group of properties.</summary> public AnimatedBool(IAnimancerComponent animancer, params string[] propertyNames) : base(animancer, propertyNames) { }
/// <summary>Initializes a single property.</summary> public AnimatedBool(IAnimancerComponent animancer, string propertyName) : base(animancer, propertyName) { }
/************************************************************************************************************************/ /// <summary> /// Allocates room for a specified number of properties to be filled by /// <see cref="InitializeProperty(int, Transform, Type, string)"/>. /// </summary> public AnimatedBool(IAnimancerComponent animancer, int propertyCount, NativeArrayOptions options = NativeArrayOptions.ClearMemory) : base(animancer, propertyCount, options) { }
/************************************************************************************************************************/ /// <summary>Draws a GUI for the <see cref="Animator.runtimeAnimatorController"/> if there is one.</summary> private void DoNativeAnimatorControllerGUI(IAnimancerComponent target) { if (!EditorApplication.isPlaying && !target.IsPlayableInitialized) { return; } var animator = target.Animator; if (animator == null) { return; } var controller = (AnimatorController)animator.runtimeAnimatorController; if (controller == null) { return; } AnimancerGUI.BeginVerticalBox(GUI.skin.box); var label = AnimancerGUI.GetNarrowText("Native Animator Controller"); EditorGUI.BeginChangeCheck(); controller = (AnimatorController)EditorGUILayout.ObjectField(label, controller, typeof(AnimatorController), true); if (EditorGUI.EndChangeCheck()) { animator.runtimeAnimatorController = controller; } var layers = controller.layers; for (int i = 0; i < layers.Length; i++) { var layer = layers[i]; var runtimeState = animator.IsInTransition(i) ? animator.GetNextAnimatorStateInfo(i) : animator.GetCurrentAnimatorStateInfo(i); var states = layer.stateMachine.states; var editorState = GetState(states, runtimeState.shortNameHash); var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.Before); var weight = i == 0 ? 1 : animator.GetLayerWeight(i); string stateName; if (editorState != null) { stateName = editorState.name; var isLooping = editorState.motion != null && editorState.motion.isLooping; AnimancerStateDrawer <ClipState> .DoTimeHighlightBarGUI( area, true, weight, runtimeState.normalizedTime *runtimeState.length, runtimeState.length, isLooping); } else { stateName = "State Not Found"; } AnimancerGUI.DoWeightLabel(ref area, weight); stateName = AnimancerGUI.GetNarrowText(stateName); EditorGUI.LabelField(area, layer.name, stateName); } AnimancerGUI.EndVerticalBox(GUI.skin.box); }
/************************************************************************************************************************/ /// <summary> Draws the details of the target state in the GUI.</summary> protected override void DoDetailsGUI(IAnimancerComponent owner) { base.DoDetailsGUI(owner); DoAnimationTypeWarningGUI(owner); DoEndEventWarningGUI(); }