/************************************************************************************************************************/

        /// <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());
        }
        private static void LogDescriptionOfStates(MenuCommand command)
        {
            var animancer = command.context as AnimancerComponent;
            var message   = new StringBuilder();

            message.Append(animancer.ToString());
            if (animancer.IsPlayableInitialised)
            {
                message.Append(":\n");
                animancer.Playable.AppendDescription(message);
            }
            else
            {
                message.Append(": Playable is not initialised.");
            }

            AnimancerEditorUtilities.AppendNonCriticalIssues(message);

            Debug.Log(message, animancer);
        }
Example #3
0
        /// <summary>Returns a cached <see cref="BindingData"/> representing the specified `gameObject`.</summary>
        /// <remarks>Note that the cache is cleared by <see cref="EditorApplication.hierarchyChanged"/>.</remarks>
        public static BindingData GetBindings(GameObject gameObject, bool forceGather = true)
        {
            if (AnimancerEditorUtilities.InitialiseCleanDictionary(ref _ObjectToBindings))
            {
                EditorApplication.hierarchyChanged += _ObjectToBindings.Clear;
            }

            if (!_ObjectToBindings.TryGetValue(gameObject, out var bindings))
            {
                if (!forceGather && !CanGatherBindings())
                {
                    return(null);
                }

                bindings = new BindingData(gameObject);
                _ObjectToBindings.Add(gameObject, bindings);
            }

            return(bindings);
        }
Example #4
0
            /************************************************************************************************************************/

            /// <summary>Refreshes the <see cref="Names"/>.</summary>
            private void UpdateNames()
            {
                if (!_NamesAreDirty)
                {
                    return;
                }

                _NamesAreDirty = false;

                var sprites = Sprites;

                AnimancerEditorUtilities.SetCount(Names, sprites.Count);

                if (string.IsNullOrEmpty(_NewName))
                {
                    for (int i = 0; i < sprites.Count; i++)
                    {
                        Names[i] = sprites[i].name;
                    }
                }
                else
                {
                    var digits = Mathf.FloorToInt(Mathf.Log10(Names.Count)) + 1;
                    if (digits < _MinimumDigits)
                    {
                        digits = _MinimumDigits;
                    }

                    var formatCharacters = new char[digits];
                    for (int i = 0; i < digits; i++)
                    {
                        formatCharacters[i] = '0';
                    }
                    var format = new string(formatCharacters);

                    for (int i = 0; i < Names.Count; i++)
                    {
                        Names[i] = _NewName + (i + 1).ToString(format);
                    }
                }
            }
Example #5
0
        /// <summary>
        /// Draws the animator reference field followed by its fields that are relevant to Animancer.
        /// </summary>
        public void DoInspectorGUI()
        {
            _OnEndGUI = null;

            DoAnimatorGUI();

            GatherAnimatorProperties();

            if (_SerializedAnimator == null)
            {
                return;
            }

            _SerializedAnimator.Update();

            AnimancerEditorUtilities.BeginVerticalBox(EditorStyles.helpBox);
            {
                if (!_IsAnimatorOnSameObject)
                {
                    EditorGUILayout.HelpBox("It is recommended that you keep this component on the same GameObject" +
                                            " as its target Animator so that they get enabled and disabled at the same time.",
                                            MessageType.Info);
                }

                DoControllerGUI();
                EditorGUILayout.PropertyField(_Avatar, AnimancerEditorUtilities.TempContent("Avatar", "The Avatar used by the Animator"));
                DoRootMotionGUI();
                DoUpdateModeGUI(true);
                DoCullingModeGUI();
                DoStopOnDisableGUI(_KeepStateOnDisable, false);
            }
            AnimancerEditorUtilities.EndVerticalBox(EditorStyles.helpBox);

            _SerializedAnimator.ApplyModifiedProperties();

            if (_OnEndGUI != null)
            {
                _OnEndGUI();
                _OnEndGUI = null;
            }
        }
Example #6
0
        /************************************************************************************************************************/

        private void DoAnimatorGUI()
        {
            var hasAnimator = AnimatorProperty.objectReferenceValue != null;

            var color = GUI.color;

            if (!hasAnimator)
            {
                GUI.color = AnimancerGUI.WarningFieldColor;
            }

            EditorGUILayout.PropertyField(AnimatorProperty);

            if (!hasAnimator)
            {
                GUI.color = color;

                EditorGUILayout.HelpBox("An Animator is required in order to play animations." +
                                        " Click here to search for one nearby.",
                                        MessageType.Warning);

                if (AnimancerGUI.TryUseClickEventInLastRect())
                {
                    Serialization.ForEachTarget(AnimatorProperty, (property) =>
                    {
                        var target = (IAnimancerComponent)property.serializedObject.targetObject;

                        var animator = AnimancerEditorUtilities.GetComponentInHierarchy <Animator>(target.gameObject);
                        if (animator == null)
                        {
                            Debug.Log("No Animator found on '" + target + "' or any of its parents or children." +
                                      " You must assign one manually.", target.gameObject);
                            return;
                        }

                        property.objectReferenceValue = animator;
                    });
                }
            }
        }
        /************************************************************************************************************************/

        /// <summary>[Editor-Only]
        /// Adds the details of this state to the menu.
        /// By default, that means a single item showing the path of the <see cref="AnimancerState.MainObject"/>.
        /// </summary>
        protected virtual void AddContextMenuFunctions(GenericMenu menu)
        {
            if (State.HasLength)
            {
                menu.AddDisabledItem(new GUIContent(DetailsPrefix + "Length: " + State.Length));
            }

            menu.AddDisabledItem(new GUIContent(DetailsPrefix + "Playable Path: " + State.GetPath()));

            var mainAsset = State.MainObject;

            if (mainAsset != null)
            {
                var assetPath = AssetDatabase.GetAssetPath(mainAsset);
                if (assetPath != null)
                {
                    menu.AddDisabledItem(new GUIContent(DetailsPrefix + "Asset Path: " + assetPath.Replace("/", "->")));
                }
            }

            if (State.OnEnd != null)
            {
                const string OnEndPrefix = "On End/";

                var label = OnEndPrefix +
                            (State.OnEnd.Target != null ? ("Target: " + State.OnEnd.Target) : "Target: null");

                var targetObject = State.OnEnd.Target as Object;
                AnimancerEditorUtilities.AddMenuItem(menu, label,
                                                     targetObject != null,
                                                     () => Selection.activeObject = targetObject);

                menu.AddDisabledItem(new GUIContent(OnEndPrefix + "Declaring Type: " + State.OnEnd.Method.DeclaringType.FullName));
                menu.AddDisabledItem(new GUIContent(OnEndPrefix + "Method: " + State.OnEnd.Method));

                menu.AddItem(new GUIContent(OnEndPrefix + "Clear"), false, () => State.OnEnd = null);
                menu.AddItem(new GUIContent(OnEndPrefix + "Invoke"), false, () => State.OnEnd());
            }
        }
        /// <summary>
        /// Checks if the current event is a context menu click within the 'clickArea' and opens a context menu with various
        /// functions for the <see cref="State"/>.
        /// </summary>
        protected void CheckContextMenu(Rect clickArea)
        {
            if (!AnimancerEditorUtilities.TryUseContextClick(clickArea))
            {
                return;
            }

            var menu = new GenericMenu();

            menu.AddDisabledItem(new GUIContent(DetailsPrefix + "State: " + State.ToString()));

            var key = State.Key;

            if (key != null)
            {
                menu.AddDisabledItem(new GUIContent(DetailsPrefix + "Key: " + key));
            }

            AnimancerEditorUtilities.AddMenuItem(menu, "Play",
                                                 !State.IsPlaying || State.Weight != 1,
                                                 () => State.Root.Play(State));

            AnimancerEditorUtilities.AddFadeFunction(menu, "Cross Fade (Ctrl + Click)",
                                                     State.Weight != 1,
                                                     State, (duration) => State.Root.CrossFade(State, duration));

            AddContextMenuFunctions(menu);

            menu.AddItem(new GUIContent(DetailsPrefix + "Log Details"), false,
                         () => Debug.Log("AnimancerState: " + State.GetDescription(true)));

            menu.AddSeparator("");
            menu.AddItem(new GUIContent("Destroy State"), false, () => State.Dispose());

            menu.AddSeparator("");
            AnimancerEditorUtilities.AddDocumentationLink(menu, "State Documentation", "/docs/manual/animancer-states");

            menu.ShowAsContext();
        }
Example #9
0
        private static void OnControllerChanged()
        {
            if (_OnControllerChanged == null)
            {
                const string TypeName   = "UnityEditorInternal.AnimationWindowUtility";
                const string MethodName = "ControllerChanged";

                try
                {
                    var type = typeof(UnityEditorInternal.InternalEditorUtility);
                    type = type.Assembly.GetType(TypeName);
                    var method = type.GetMethod(MethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
                    _OnControllerChanged = (Action)Delegate.CreateDelegate(typeof(Action), method);
                }
                catch
                {
                    AnimancerEditorUtilities.RegisterNonCriticalMissingMember(TypeName, MethodName);
                    _OnControllerChanged = () => { };
                }
            }

            _OnControllerChanged();
        }
Example #10
0
        /************************************************************************************************************************/

        /// <summary>
        /// Draws the <see cref="Animator.keepAnimatorControllerStateOnDisable"/> field.
        /// </summary>
        public static void DoStopOnDisableGUI(SerializedProperty keepStateOnDisable, bool updateAndApply)
        {
#if UNITY_2018_1_OR_NEWER
            var area = AnimancerEditorUtilities.GetRect();

            var label = AnimancerEditorUtilities.TempContent("Stop On Disable",
                                                             "If true, disabling this object will stop and rewind all animations." +
                                                             " Otherwise they will simply be paused and will resume from their current states when it is re-enabled.");

            if (keepStateOnDisable != null)
            {
                if (updateAndApply)
                {
                    keepStateOnDisable.serializedObject.Update();
                }

                label = EditorGUI.BeginProperty(area, label, keepStateOnDisable);

                keepStateOnDisable.boolValue = !EditorGUI.Toggle(area, label, !keepStateOnDisable.boolValue);

                EditorGUI.EndProperty();

                if (updateAndApply)
                {
                    keepStateOnDisable.serializedObject.ApplyModifiedProperties();
                }
            }
            else
            {
                var enabled = GUI.enabled;
                GUI.enabled = false;
                EditorGUI.Toggle(area, label, false);
                GUI.enabled = enabled;
            }
#endif
        }
        private void DoAnimationsField(SerializedProperty property, GUIContent label)
        {
            GUILayout.Space(EditorGUIUtility.standardVerticalSpacing - 1);

            if (_Animations == null)
            {
                _Animations = new ReorderableList(property.serializedObject, property.Copy())
                {
                    drawHeaderCallback  = DrawAnimationsHeader,
                    drawElementCallback = DrawAnimationElement,
                    elementHeight       = EditorGUIUtility.singleLineHeight,
                    onRemoveCallback    = RemoveSelectedElement,
                };
            }

            _RemoveAnimationIndex = -1;

            GUILayout.BeginVertical();
            _Animations.DoLayoutList();
            GUILayout.EndVertical();

            if (_RemoveAnimationIndex >= 0)
            {
                property.DeleteArrayElementAtIndex(_RemoveAnimationIndex);
            }

            AnimancerEditorUtilities.HandleDragAndDropAnimations(GUILayoutUtility.GetLastRect(), (clip) =>
            {
                var index                    = property.arraySize;
                property.arraySize           = index + 1;
                var element                  = property.GetArrayElementAtIndex(index);
                element.objectReferenceValue = clip;
            });

            GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
        }
Example #12
0
        /************************************************************************************************************************/

        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();
            }
        }
Example #13
0
        /************************************************************************************************************************/

        /// <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");
                        }
                    }
                };
            }
        }
Example #14
0
        /************************************************************************************************************************/
        #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 <see cref="Layer"/>.
        /// </summary>
        private void CheckContextMenu(Rect clickArea)
        {
            if (!AnimancerEditorUtilities.TryUseContextClick(clickArea))
            {
                return;
            }

            var menu = new GenericMenu();

            menu.AddDisabledItem(new GUIContent(Layer.ToString()));

            AnimancerEditorUtilities.AddMenuItem(menu, "Stop",
                                                 HasAnyStates((state) => state.IsPlaying || state.Weight != 0),
                                                 () => Layer.Stop());

            AnimancerEditorUtilities.AddFadeFunction(menu, "Fade In",
                                                     Layer.PortIndex > 0 && Layer.Weight != 1, Layer,
                                                     (duration) => Layer.StartFade(1, duration));
            AnimancerEditorUtilities.AddFadeFunction(menu, "Fade Out",
                                                     Layer.PortIndex > 0 && Layer.Weight != 0, Layer,
                                                     (duration) => Layer.StartFade(0, duration));

            menu.AddItem(new GUIContent("Inverse Kinematics/Apply Animator IK"),
                         Layer.ApplyAnimatorIK,
                         () => Layer.ApplyAnimatorIK = !Layer.ApplyAnimatorIK);
            menu.AddItem(new GUIContent("Inverse Kinematics/Default Apply Animator IK"),
                         Layer.DefaultApplyAnimatorIK,
                         () => Layer.DefaultApplyAnimatorIK = !Layer.DefaultApplyAnimatorIK);
            menu.AddItem(new GUIContent("Inverse Kinematics/Apply Foot IK"),
                         Layer.ApplyFootIK,
                         () => Layer.ApplyFootIK = !Layer.ApplyFootIK);
            menu.AddItem(new GUIContent("Inverse Kinematics/Default Apply Foot IK"),
                         Layer.DefaultApplyFootIK,
                         () => Layer.DefaultApplyFootIK = !Layer.DefaultApplyFootIK);

            menu.AddSeparator("");

            AnimancerEditorUtilities.AddMenuItem(menu, "Destroy States",
                                                 ActiveStates.Count > 0 || InactiveStates.Count > 0,
                                                 () => Layer.DestroyStates());

            AnimancerEditorUtilities.AddMenuItem(menu, "Add Layer",
                                                 Layer.Root.LayerCount < AnimancerPlayable.maxLayerCount,
                                                 () => Layer.Root.LayerCount++);
            AnimancerEditorUtilities.AddMenuItem(menu, "Remove Layer",
                                                 Layer.Root.LayerCount > 0,
                                                 () => Layer.Root.LayerCount--);

            menu.AddSeparator("");

            menu.AddItem(new GUIContent("Keep Weightless Playables Connected"),
                         Layer.Root.KeepPlayablesConnected,
                         () => Layer.Root.KeepPlayablesConnected = !Layer.Root.KeepPlayablesConnected);

            AddPrefFunctions(menu);

            menu.AddSeparator("");

            AnimancerEditorUtilities.AddDocumentationLink(menu, "Layer Documentation", "/docs/manual/animation-layers");
            AddPlayableGraphVisualizerFunction(menu);

            menu.ShowAsContext();
        }
            /// <summary>
            /// Determines the <see cref="MatchType"/> representing the properties animated by the `clip` in
            /// comparison to the properties that actually exist on the target <see cref="GameObject"/> and its
            /// children.
            /// <para></para>
            /// Also compiles a `message` explaining the differences if that paraneter is not null.
            /// </summary>
            public MatchType GetMatchType(AnimationClip clip, StringBuilder message,
                                          Dictionary <EditorCurveBinding, bool> bindingsInMessage, ref int existingBindings)
            {
                AnimancerEditorUtilities.InitialiseCleanDictionary(ref _BindingMatches);

                if (_BindingMatches.TryGetValue(clip, out var match) &&
                    bindingsInMessage == null)
                {
                    return(match);
                }

                var objectType = ObjectType;
                var clipType   = GetAnimationType(clip);

                if (clipType != objectType)
                {
                    if (message != null)
                    {
                        message.AppendLine()
                        .Append($"{LinePrefix}The {nameof(AnimationType)} of the '")
                        .Append(clip.name)
                        .Append("' animation is ")
                        .Append(clipType)
                        .Append(" while the '")
                        .Append(GameObject.name)
                        .Append("' Rig is ")
                        .Append(objectType)
                        .Append(".");
                    }

                    switch (clipType)
                    {
                    default:
                    case AnimationType.None:
                    case AnimationType.Humanoid:
                        match = MatchType.Error;
                        if (message == null)
                        {
                            goto SetMatch;
                        }
                        else
                        {
                            break;
                        }

                    case AnimationType.Generic:
                    case AnimationType.Sprite:
                        match = MatchType.Warning;
                        break;
                    }
                }

                var bindingMatch = GetMatchType(GetBindings(clip), bindingsInMessage, ref existingBindings);

                if (match < bindingMatch)
                {
                    match = bindingMatch;
                }

SetMatch:
                _BindingMatches[clip] = match;

                return(match);
            }
        /************************************************************************************************************************/

        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);
                    }
                }
            }
        }
Example #17
0
        /************************************************************************************************************************/

        /// <summary>
        /// Draws the <see cref="Animator.runtimeAnimatorController"/> field with a warning if a controller is
        /// assigned.
        /// </summary>
        private void DoControllerGUI()
        {
            if (_Controller == null)
            {
                return;
            }

            var controller = _Animators[0].runtimeAnimatorController;

            var showMixedValue = EditorGUI.showMixedValue;

            for (int i = 1; i < _Animators.Length; i++)
            {
                if (_Animators[i].runtimeAnimatorController != controller)
                {
                    EditorGUI.showMixedValue = true;
                    break;
                }
            }

            if (controller == null && !EditorGUI.showMixedValue)
            {
                return;
            }

            var label = AnimancerEditorUtilities.TempContent("Controller");

            EditorGUI.BeginChangeCheck();

            var area = EditorGUILayout.BeginHorizontal();

            label = EditorGUI.BeginProperty(area, label, _Controller);

            var color = GUI.color;

            GUI.color = AnimancerEditorUtilities.WarningFieldColor;

            controller = (RuntimeAnimatorController)EditorGUILayout.ObjectField(label, controller,
                                                                                typeof(RuntimeAnimatorController), false);

            GUI.color = color;

            if (GUILayout.Button("Remove", EditorStyles.miniButton, AnimancerEditorUtilities.DontExpandWidth))
            {
                controller = null;
            }

            GUILayout.EndHorizontal();
            if (EditorGUI.EndChangeCheck())
            {
                Undo.RecordObjects(_Animators, "Changed AnimatorController");
                for (int i = 0; i < _Animators.Length; i++)
                {
                    _Animators[i].runtimeAnimatorController = controller;
                }
                OnControllerChanged();
            }

            EditorGUI.showMixedValue = showMixedValue;

            EditorGUILayout.HelpBox(
                "The AnimatorController will not affect the model while Animancer is active," +
                " however Unity will still execute it's state machine in the background," +
                " which may be a waste of processing time if you aren't using it intentionally." +
                " Click here for more information.", MessageType.Warning);

            if (AnimancerEditorUtilities.TryUseClickInLastRect())
            {
                EditorUtility.OpenWithDefaultApp(AnimancerPlayable.APIDocumentationURL + "/docs/manual/animator-controller-states");
            }
        }
Example #18
0
            /// <summary>
            /// Determines the <see cref="MatchType"/> representing the properties animated by the `clip` in
            /// comparison to the properties that actually exist on the target <see cref="GameObject"/> and its
            /// children.
            /// <para></para>
            /// Also compiles a `message` explaining the differences if that paraneter is not null.
            /// </summary>
            public MatchType GetMatchType(AnimationClip clip, StringBuilder message,
                                          Dictionary <EditorCurveBinding, bool> bindingsInMessage, ref int existingBindings, bool forceGather = true)
            {
                AnimancerEditorUtilities.InitialiseCleanDictionary(ref _BindingMatches);

                if (_BindingMatches.TryGetValue(clip, out var match))
                {
                    if (bindingsInMessage == null)
                    {
                        return(match);
                    }
                }
                else if (!forceGather && !CanGatherBindings())
                {
                    return(MatchType.Unknown);
                }

                var objectType = ObjectType;
                var clipType   = GetAnimationType(clip);

                if (clipType != objectType)
                {
                    if (message != null)
                    {
                        message.AppendLine()
                        .Append($"{LinePrefix}This message does not necessarily mean anything is wrong," +
                                $" but if something is wrong then this might help you identify the problem.");

                        message.AppendLine()
                        .Append($"{LinePrefix}The {nameof(AnimationType)} of the '")
                        .Append(clip.name)
                        .Append("' animation is ")
                        .Append(clipType)
                        .Append(" while the '")
                        .Append(GameObject.name)
                        .Append("' Rig is ")
                        .Append(objectType)
                        .Append(". See the documentation for more information about Animation Types:" +
                                $" {Strings.DocsURLs.Inspector}#animation-types");
                    }

                    switch (clipType)
                    {
                    default:
                    case AnimationType.None:
                    case AnimationType.Humanoid:
                        match = MatchType.Error;
                        if (message == null)
                        {
                            goto SetMatch;
                        }
                        else
                        {
                            break;
                        }

                    case AnimationType.Generic:
                    case AnimationType.Sprite:
                        match = MatchType.Warning;
                        break;
                    }
                }

                var bindingMatch = GetMatchType(GetBindings(clip), bindingsInMessage, ref existingBindings);

                if (match < bindingMatch)
                {
                    match = bindingMatch;
                }

SetMatch:
                _BindingMatches[clip] = match;

                return(match);
            }
        /************************************************************************************************************************/

        /// <summary> Draws the details of the target state in the GUI.</summary>
        protected override void DoDetailsGUI(IAnimancerComponent owner)
        {
            base.DoDetailsGUI(owner);

            var animator = owner.Animator;

            if (animator == null)
            {
                return;
            }

            var count = ParameterCount;

            if (count <= 0)
            {
                return;
            }

            var labelWidth = EditorGUIUtility.labelWidth;

            EditorGUIUtility.labelWidth -= AnimancerEditorUtilities.IndentSize;

            var area = AnimancerEditorUtilities.GetRect(true);

            area = EditorGUI.IndentedRect(area);
            EditorGUI.LabelField(area, "Parameters", count.ToString());

            for (int i = 0; i < count; i++)
            {
                var type = GetParameterType(i);
                if (type == 0)
                {
                    continue;
                }

                var name  = GetParameterName(i);
                var value = GetParameterValue(i);

                EditorGUI.BeginChangeCheck();

                area = AnimancerEditorUtilities.GetRect(true);
                area = EditorGUI.IndentedRect(area);

                switch (type)
                {
                case AnimatorControllerParameterType.Float:
                    value = EditorGUI.FloatField(area, name, (float)value);
                    break;

                case AnimatorControllerParameterType.Int:
                    value = EditorGUI.IntField(area, name, (int)value);
                    break;

                case AnimatorControllerParameterType.Bool:
                    value = EditorGUI.Toggle(area, name, (bool)value);
                    break;

                case AnimatorControllerParameterType.Trigger:
                    value = EditorGUI.Toggle(area, name, (bool)value, EditorStyles.radioButton);
                    break;

                default:
                    EditorGUI.LabelField(area, name, "Unhandled Type: " + type);
                    break;
                }

                if (EditorGUI.EndChangeCheck())
                {
                    SetParameterValue(i, value);
                }
            }

            EditorGUIUtility.labelWidth = labelWidth;
        }