/// <summary>Draws a box describing the internal details of the `playable`.</summary> internal void DoInternalDetailsGUI(AnimancerPlayable playable) { if (Event.current.type == EventType.Layout) { var text = ObjectPool.AcquireStringBuilder(); playable.AppendInternalDetails(text, "", " - "); _UpdateListLabel = text.ReleaseToString(); } if (_UpdateListLabel == null) { return; } if (_InternalDetailsStyle == null) { _InternalDetailsStyle = new GUIStyle(GUI.skin.box) { alignment = TextAnchor.MiddleLeft, wordWrap = false, stretchWidth = true, } } ; using (ObjectPool.Disposable.AcquireContent(out var content, _UpdateListLabel, null, false)) { var height = _InternalDetailsStyle.CalcHeight(content, 0); var area = GUILayoutUtility.GetRect(0, height, _InternalDetailsStyle); GUI.Box(area, content, _InternalDetailsStyle); CheckContextMenu(area, playable); } }
/// <summary> /// Ensures that the <see cref="_BoneCount"/> is positive and not larger than the number of bones between the /// <see cref="_EndBone"/> and the <see cref="Animator"/>. /// </summary> /// <remarks> /// Called by the Unity Editor in Edit Mode whenever an instance of this script is loaded or a value is changed /// in the Inspector. /// </remarks> private void Validate(AnimancerPlayable animancer, int boneCount, Transform endBone) { if (boneCount < 1) { boneCount = 1; } else if (endBone != null && animancer != null && animancer.Component.Animator != null) { var root = animancer.Component.Animator.transform; var bone = endBone; for (int i = 0; i < boneCount; i++) { bone = bone.parent; if (bone == root) { boneCount = i + 1; break; } else if (bone == null) { endBone = null; Debug.LogWarning("The End Bone must be a child of the Animator."); break; } } } }
/************************************************************************************************************************/ #region Context Menu /************************************************************************************************************************/ /// <summary> /// Checks if the current event is a context menu click within the `clickArea` and opens a context menu with various /// functions for the `playable`. /// </summary> private void CheckContextMenu(Rect clickArea, AnimancerPlayable playable) { if (!AnimancerGUI.TryUseClickEvent(clickArea, 1)) { return; } var menu = new GenericMenu(); menu.AddDisabledItem(new GUIContent(playable.Graph.GetEditorName() ?? "Unnamed Graph"), false); menu.AddDisabledItem(new GUIContent("Command Count: " + playable.CommandCount), false); menu.AddDisabledItem(new GUIContent("Frame ID: " + playable.FrameID), false); AddDisposablesFunctions(menu, playable.Disposables); AddUpdateModeFunctions(menu, playable); AnimancerEditorUtilities.AddContextMenuIK(menu, playable); AddRootFunctions(menu, playable); menu.AddSeparator(""); AddDisplayOptions(menu); menu.AddItem(new GUIContent("Log Details Of Everything"), false, () => Debug.Log(playable.GetDescription(), playable.Component as Object)); AddPlayableGraphVisualizerFunction(menu, "", playable._Graph); AnimancerEditorUtilities.AddDocumentationLink(menu, "Inspector Documentation", Strings.DocsURLs.Inspector); menu.ShowAsContext(); }
/************************************************************************************************************************/ private void DoCurrentAnimationGUI(AnimancerPlayable animancer) { string text; if (animancer != null) { var transition = Transition; if (transition.IsValid && transition.Key != null) { text = animancer.States.GetOrCreate(transition).ToString(); } else { text = transition.ToString(); } } else { text = _Instance._TransitionProperty.Property.GetFriendlyPath(); } if (text != null) { EditorGUILayout.LabelField("Current Animation", text); } }
/************************************************************************************************************************/ private void OnSwingEnd() { _State = State.Idle; // Since the swing animation used for this example has an 'End' event, we can allow it to determine how // long the fade should take instead of having this script pick a value (or using the default 0.3 seconds). var fadeDuration = AnimancerPlayable.GetFadeOutDuration(); _Animancer.CrossFade(_Idle, fadeDuration); }
/************************************************************************************************************************/ /// <summary> /// As with <see cref="HitBall"/>, this method is called in various different ways depending on which event /// system is being used. /// <para></para> /// Most of them register this method to be called when the <see cref="_Swing"/> animation ends, but /// <see cref="GolfHitControllerAnimancer"/> assumes that the event was already set up in the Inspector. /// </summary> public void EndSwing() { _State = State.Idle; // Since the swing animation is ending early, we want it to calculate the fade duration to fade out over // the remainder of that animation instead of the value specified by the _Idle transition. var fadeDuration = AnimancerPlayable.GetFadeOutDuration(); _Animancer.Play(_Idle, fadeDuration); }
/************************************************************************************************************************/ /// <summary>Adds menu functions to set the <see cref="DirectorUpdateMode"/>.</summary> private void AddUpdateModeFunctions(GenericMenu menu, AnimancerPlayable playable) { var modes = Enum.GetValues(typeof(DirectorUpdateMode)); for (int i = 0; i < modes.Length; i++) { var mode = (DirectorUpdateMode)modes.GetValue(i); menu.AddItem(new GUIContent("Update Mode/" + mode), playable.UpdateMode == mode, () => playable.UpdateMode = mode); } }
/************************************************************************************************************************/ /// <summary>Adds functions for controlling the `playable`.</summary> public static void AddRootFunctions(GenericMenu menu, AnimancerPlayable playable) { menu.AddFunction("Add Layer", playable.Layers.Count < AnimancerPlayable.LayerList.DefaultCapacity, () => playable.Layers.Count++); menu.AddFunction("Remove Layer", playable.Layers.Count > 0, () => playable.Layers.Count--); menu.AddItem(new GUIContent("Keep Children Connected ?"), playable.KeepChildrenConnected, () => playable.KeepChildrenConnected = !playable.KeepChildrenConnected); }
/************************************************************************************************************************/ #region Initialisation /************************************************************************************************************************/ public SimpleLean(AnimancerPlayable animancer, Vector3 axis, NativeArray <TransformStreamHandle> leanBones) { var animator = animancer.Component.Animator; _Job = new Job { root = animator.BindStreamTransform(animator.transform), bones = leanBones, axis = axis, angle = AnimancerUtilities.CreateNativeReference <float>(), }; CreatePlayable(animancer); animancer.Disposables.Add(this); }
public Damping(AnimancerPlayable animancer, int boneCount, Transform endBone) { // Create the job and initialise all its arrays. // They are all Persistent because we want them to last for the full lifetime of the job. // Most of them can use UninitializedMemory which is faster because we will be immediately filling them. // But the velocities will use the default ClearMemory to make sure all the values start at zero. Validate(animancer, boneCount, endBone); // Since we are about to use these values several times, we can shorten the following lines a bit by using constants: const Allocator Persistent = Allocator.Persistent; const NativeArrayOptions UninitializedMemory = NativeArrayOptions.UninitializedMemory; _Job = new DampingJob() { jointHandles = new NativeArray <TransformStreamHandle>(boneCount, Persistent, UninitializedMemory), localPositions = new NativeArray <Vector3>(boneCount, Persistent, UninitializedMemory), localRotations = new NativeArray <Quaternion>(boneCount, Persistent, UninitializedMemory), positions = new NativeArray <Vector3>(boneCount, Persistent, UninitializedMemory), velocities = new NativeArray <Vector3>(boneCount, Persistent), }; // Initialise the contents of the arrays for each bone. var animator = animancer.Component.Animator; var bone = endBone; for (int i = boneCount - 1; i >= 0; i--) { _Job.jointHandles[i] = animator.BindStreamTransform(bone); _Job.localPositions[i] = bone.localPosition; _Job.localRotations[i] = bone.localRotation; _Job.positions[i] = bone.position; bone = bone.parent; } _Job.rootHandle = animator.BindStreamTransform(bone); // Add the job to Animancer's output. // animancer.InsertOutputJob(_Job); CreatePlayable(animancer); animancer.Disposables.Add(this); }
/************************************************************************************************************************/ private void DoCurrentAnimationGUI(AnimancerPlayable animancer) { const string Label = "Current Animation"; var enabled = GUI.enabled; GUI.enabled = false; string text = null; if (animancer != null) { var transition = _Instance.GetTransition(); var state = animancer.States[transition]; if (state != null) { var mainObject = state.MainObject; if (mainObject != null) { EditorGUILayout.ObjectField(Label, mainObject, typeof(Object), true); } else { text = state.ToString(); } } else { text = _Instance._TransitionProperty.Property.GetFriendlyPath(); } } else { text = _Instance._TransitionProperty.Property.GetFriendlyPath(); } if (text != null) { EditorGUILayout.LabelField(Label, text); } GUI.enabled = enabled; }
/************************************************************************************************************************/ #region Gathering /************************************************************************************************************************/ /// <summary> /// Initializes an editor in the list for each layer in the `animancer`. /// <para></para> /// The `count` indicates the number of elements actually being used. Spare elements are kept in the list in /// case they need to be used again later. /// </summary> internal static void GatherLayerEditors(AnimancerPlayable animancer, List <AnimancerLayerDrawer> editors, out int count) { count = animancer.Layers.Count; for (int i = 0; i < count; i++) { AnimancerLayerDrawer editor; if (editors.Count <= i) { editor = new AnimancerLayerDrawer(); editors.Add(editor); } else { editor = editors[i]; } editor.GatherStates(animancer.Layers._Layers[i]); } }
/************************************************************************************************************************/ private void DoRootGUI(AnimancerPlayable playable) { var labelWidth = EditorGUIUtility.labelWidth; AnimancerGUI.BeginVerticalBox(GUI.skin.box); using (ObjectPool.Disposable.AcquireContent(out var label, "Is Graph Playing")) { const string SpeedLabel = "Speed"; var isPlayingWidth = AnimancerGUI.CalculateLabelWidth(label.text); var speedWidth = AnimancerGUI.CalculateLabelWidth(SpeedLabel); var area = AnimancerGUI.LayoutSingleLineRect(); var isPlayingArea = area; var speedArea = area; isPlayingArea.width = isPlayingWidth + AnimancerGUI.ToggleWidth; speedArea.xMin = isPlayingArea.xMax; EditorGUIUtility.labelWidth = isPlayingWidth; playable.IsGraphPlaying = EditorGUI.Toggle(isPlayingArea, label, playable.IsGraphPlaying); EditorGUIUtility.labelWidth = speedWidth; EditorGUI.BeginChangeCheck(); var speed = EditorGUI.FloatField(speedArea, SpeedLabel, playable.Speed); if (EditorGUI.EndChangeCheck()) { playable.Speed = speed; } if (AnimancerGUI.TryUseClickEvent(speedArea, 2)) { playable.Speed = playable.Speed != 1 ? 1 : 0; } } AnimancerGUI.EndVerticalBox(GUI.skin.box); EditorGUIUtility.labelWidth = labelWidth; CheckContextMenu(GUILayoutUtility.GetLastRect(), playable); }
/************************************************************************************************************************/ /// <summary> /// Draws the <see cref="Animator.updateMode"/> field with any appropriate warnings. /// </summary> private void DoUpdateModeGUI(bool showWithoutWarning) { if (_UpdateMode == null) { return; } var label = AnimancerGUI.TempContent("Update Mode", "Controls when and how often the animations are updated"); var initialUpdateMode = Targets[0].InitialUpdateMode; var updateMode = (AnimatorUpdateMode)_UpdateMode.intValue; EditorGUI.BeginChangeCheck(); if (!EditorApplication.isPlaying || !AnimancerPlayable.HasChangedToOrFromAnimatePhysics(initialUpdateMode, updateMode)) { if (showWithoutWarning) { EditorGUILayout.PropertyField(_UpdateMode, label); } } else { GUILayout.BeginHorizontal(); var color = GUI.color; GUI.color = AnimancerGUI.WarningFieldColor; EditorGUILayout.PropertyField(_UpdateMode, label); GUI.color = color; label = AnimancerGUI.TempContent("Revert", "Revert to initial mode"); if (GUILayout.Button(label, EditorStyles.miniButton, AnimancerGUI.DontExpandWidth)) { _UpdateMode.intValue = (int)initialUpdateMode.Value; } GUILayout.EndHorizontal(); EditorGUILayout.HelpBox( "Changing to or from AnimatePhysics mode at runtime has no effect when using the" + " Playables API. It will continue using the original mode it had on startup.", MessageType.Warning); if (AnimancerGUI.TryUseClickEventInLastRect()) { EditorUtility.OpenWithDefaultApp(Strings.DocsURLs.UpdateModes); } } if (EditorGUI.EndChangeCheck()) { _OnEndGUI += () => { for (int i = 0; i < _Animators.Length; i++) { var animator = _Animators[i]; if (animator != null) { AnimancerEditorUtilities.Invoke(animator, "OnUpdateModeChanged"); } } }; } }
/************************************************************************************************************************/ private void DoAnimatorGUI(SerializedProperty property, GUIContent label) { var hasAnimator = property.objectReferenceValue != null; var color = GUI.color; if (!hasAnimator) { GUI.color = AnimancerGUI.WarningFieldColor; } EditorGUILayout.PropertyField(property, label); if (!hasAnimator) { GUI.color = color; EditorGUILayout.HelpBox($"An {nameof(Animator)} is required in order to play animations." + " Click here to search for one nearby.", MessageType.Warning); if (AnimancerGUI.TryUseClickEventInLastRect()) { Serialization.ForEachTarget(property, (targetProperty) => { var target = (IAnimancerComponent)targetProperty.serializedObject.targetObject; var animator = AnimancerEditorUtilities.GetComponentInHierarchy <Animator>(target.gameObject); if (animator == null) { Debug.Log($"No {nameof(Animator)} found on '{target.gameObject.name}' or any of its parents or children." + " You must assign one manually.", target.gameObject); return; } targetProperty.objectReferenceValue = animator; }); } } else if (property.objectReferenceValue is Animator animator) { if (animator.gameObject != Targets[0].gameObject) { EditorGUILayout.HelpBox( $"It is recommended that you keep this component on the same {nameof(GameObject)}" + $" as its target {nameof(Animator)} so that they get enabled and disabled at the same time.", MessageType.Info); } var initialUpdateMode = Targets[0].InitialUpdateMode; var updateMode = animator.updateMode; if (AnimancerPlayable.HasChangedToOrFromAnimatePhysics(initialUpdateMode, updateMode)) { EditorGUILayout.HelpBox( $"Changing to or from {nameof(AnimatorUpdateMode.AnimatePhysics)} mode at runtime has no effect" + $" when using the Playables API. It will continue using the original mode it had on startup.", MessageType.Warning); if (AnimancerGUI.TryUseClickEventInLastRect()) { EditorUtility.OpenWithDefaultApp(Strings.DocsURLs.UpdateModes); } } } }
/************************************************************************************************************************/ /// <summary>Creates a new <see cref="TransitionPreviewDetails"/>.</summary> public TransitionPreviewDetails(AnimancerPlayable animancer) { Animancer = animancer; }
/************************************************************************************************************************/ public TranslateRoot(AnimancerPlayable animancer) : this(animancer, Vector3.forward, GetDefaultHumanoidLeanBones(animancer.Component.Animator)) { }
/************************************************************************************************************************/ public TranslateRoot(AnimancerPlayable animancer, IEnumerable <Transform> leanBones) : this(animancer, Vector3.forward, GetTransformStreamHandles(animancer.Component.Animator, leanBones)) { }
/************************************************************************************************************************/ public SimpleLean(AnimancerPlayable animancer) : this(animancer, Vector3.forward, GetDefaultHumanoidLeanBones(animancer.Component.Animator)) { }
/************************************************************************************************************************/ /// <summary>Creates a new <see cref="DummyAnimancerComponent"/>.</summary> public DummyAnimancerComponent(Animator animator, AnimancerPlayable playable) { Animator = animator; Playable = playable; InitialUpdateMode = animator.updateMode; }