using System; using System.Collections.Generic; using ABI.CCK.Components; using ABI.CCK.Scripts.Editor; using UnityEditor; using UnityEditorInternal; using UnityEngine; using AnimatorController = UnityEditor.Animations.AnimatorController; using AnimatorControllerParameter = UnityEngine.AnimatorControllerParameter; using AnimatorControllerParameterType = UnityEngine.AnimatorControllerParameterType; [CustomEditor(typeof(AnimatorDriver))] public class CCK_AnimatorDriverEditor : Editor { private AnimatorDriver _animatorDriver; private ReorderableList _onEnterList; private ReorderableList _onExitList; private readonly List _animatorParamNames = new List(); private readonly List _animatorParamTypes = new List(); private bool _showLocalOnlyHelp; private bool _isInPlayMode; #region Unity Events private void OnEnable() { if (target == null) return; _animatorDriver = (AnimatorDriver)target; _isInPlayMode = Application.isPlaying; _animatorParamNames.Clear(); _animatorParamTypes.Clear(); // TODO: add parameter type display in parameter dropdown var behaviorContext = AnimatorController.FindStateMachineBehaviourContext(_animatorDriver); if (behaviorContext.Length > 0) { AnimatorController controller = behaviorContext[0].animatorController; foreach (AnimatorControllerParameter parameter in controller.parameters) { switch (parameter.type) { case AnimatorControllerParameterType.Bool: _animatorParamNames.Add($"{parameter.name}"); _animatorParamTypes.Add(AnimatorDriverTask.ParameterType.Bool); break; case AnimatorControllerParameterType.Float: _animatorParamNames.Add($"{parameter.name}"); _animatorParamTypes.Add(AnimatorDriverTask.ParameterType.Float); break; case AnimatorControllerParameterType.Int: _animatorParamNames.Add($"{parameter.name}"); _animatorParamTypes.Add(AnimatorDriverTask.ParameterType.Int); break; case AnimatorControllerParameterType.Trigger: _animatorParamNames.Add($"{parameter.name}"); _animatorParamTypes.Add(AnimatorDriverTask.ParameterType.Trigger); break; } } } if (_onEnterList == null) { _onEnterList = new ReorderableList(_animatorDriver.EnterTasks, typeof(AnimatorDriverTask), true, true, !_isInPlayMode, !_isInPlayMode) { drawHeaderCallback = OnDrawHeaderTaskEnter, drawElementCallback = OnDrawElementTaskEnter, elementHeightCallback = OnHeightElementTaskEnter }; } if (_onExitList == null) { _onExitList = new ReorderableList(_animatorDriver.ExitTasks, typeof(AnimatorDriverTask), true, true, !_isInPlayMode, !_isInPlayMode) { drawHeaderCallback = OnDrawHeaderTaskExit, drawElementCallback = OnDrawElementTaskExit, elementHeightCallback = OnHeightElementTaskExit }; } } public override void OnInspectorGUI() { if (_animatorDriver == null) return; EditorGUI.BeginChangeCheck(); using (new EditorGUILayout.VerticalScope(new GUIStyle() { padding = new RectOffset(10, 10, 10, 10) })) { DrawLocalOnlyToggle(); DrawTaskLists(); } if (EditorGUI.EndChangeCheck()) EditorUtility.SetDirty(_animatorDriver); } #endregion #region GUI Drawing private void DrawLocalOnlyToggle() { using (new EditorGUILayout.HorizontalScope()) { _animatorDriver.localOnly = EditorGUILayout.Toggle("Local Only", _animatorDriver.localOnly); if (GUILayout.Button("?", GUILayout.Width(25))) _showLocalOnlyHelp = !_showLocalOnlyHelp; GUILayout.Space(5); } if (_showLocalOnlyHelp) { // TODO: Add to LocalizationProvider EditorGUILayout.HelpBox("When 'Local Only' is enabled, the animator driver is executed locally and not for remote users.", MessageType.Info); EditorGUILayout.Space(5); EditorGUILayout.HelpBox("Avatars: Only the wearer.\nSpawnables: Only the prop's owner.\nWorlds: This option is ignored.", MessageType.Info); } EditorGUILayout.Space(); } private void DrawTaskLists() { EditorGUILayout.Space(); _onEnterList.DoLayoutList(); EditorGUILayout.Space(); _onExitList.DoLayoutList(); } #endregion #region ReorderableList Drawing private void OnDrawHeaderTaskEnter(Rect rect) { Rect labelRect = new Rect(rect.x, rect.y, rect.width - 35, EditorGUIUtility.singleLineHeight); GUI.Label(labelRect, "On Enter Tasks"); EditorGUIExtensions.UtilityMenu(rect, _onEnterList, appendAdditionalMenuItems: (menuBuilder, list) => { AppendComponentMenu(menuBuilder, list, true); }); } private void OnDrawElementTaskEnter(Rect rect, int index, bool isactive, bool isfocused) { if (index >= _animatorDriver.EnterTasks.Count) return; AnimatorDriverTask element = _animatorDriver.EnterTasks[index]; using (new EditorGUI.DisabledScope(_isInPlayMode)) RenderTask(rect, element); } private float OnHeightElementTaskEnter(int index) { const int length = 3; if (index >= _animatorDriver.EnterTasks.Count) return 1.25f * length * EditorGUIUtility.singleLineHeight; AnimatorDriverTask task = _animatorDriver.EnterTasks[index]; return (length + TaskHeight(task)) * 1.25f * EditorGUIUtility.singleLineHeight; } private void OnDrawHeaderTaskExit(Rect rect) { Rect labelRect = new Rect(rect.x, rect.y, rect.width - 35, EditorGUIUtility.singleLineHeight); GUI.Label(labelRect, "On Exit Tasks"); EditorGUIExtensions.UtilityMenu(rect, _onExitList, appendAdditionalMenuItems: (menuBuilder, list) => { AppendComponentMenu(menuBuilder, list, false); }); } private void OnDrawElementTaskExit(Rect rect, int index, bool isactive, bool isfocused) { if (index >= _animatorDriver.ExitTasks.Count) return; AnimatorDriverTask element = _animatorDriver.ExitTasks[index]; using (new EditorGUI.DisabledScope(_isInPlayMode)) RenderTask(rect, element); } private float OnHeightElementTaskExit(int index) { const int length = 3; if (index >= _animatorDriver.ExitTasks.Count) return 1.25f * length * EditorGUIUtility.singleLineHeight; AnimatorDriverTask task = _animatorDriver.ExitTasks[index]; return (length + TaskHeight(task)) * 1.25f * EditorGUIUtility.singleLineHeight; } #endregion #region AnimatorDriverTask Drawing private int TaskHeight(AnimatorDriverTask task) { int length = 2; if (task.aType == AnimatorDriverTask.SourceType.Random) length += 1; if (task.op == AnimatorDriverTask.Operator.Set) return length; length += task.bType == AnimatorDriverTask.SourceType.Random ? 3 : 2; return length; } private void RenderTask(Rect rect, AnimatorDriverTask task) { Rect _rect = new Rect(rect.x, rect.y + 2, rect.width, EditorGUIUtility.singleLineHeight); float spacing = EditorGUIUtility.singleLineHeight * 1.25f; float originalLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = 100; task.targetName = EditorGUIExtensions.AdvancedDropdownInput(_rect, task.targetName, _animatorParamNames, "Parameter", "No Parameters"); _rect.y += spacing; var formulaDisplay = $"{task.targetName} = "; int parameterIndex = Math.Max(_animatorParamNames.FindIndex(m => m == task.targetName), 0); if (_animatorParamNames.Count != 0 && _animatorParamTypes.Count >= parameterIndex) task.targetType = _animatorParamTypes[parameterIndex]; task.op = (AnimatorDriverTask.Operator)EditorGUI.EnumPopup(_rect, "Operation", task.op); _rect.y += spacing; task.aType = (AnimatorDriverTask.SourceType)EditorGUI.EnumPopup(_rect, "A Type", task.aType); _rect.y += spacing; switch (task.aType) { case AnimatorDriverTask.SourceType.Static: task.aValue = EditorGUI.FloatField(_rect, "A Value", task.aValue); _rect.y += spacing; formulaDisplay += $"{task.aValue} "; break; case AnimatorDriverTask.SourceType.Parameter: task.aName = EditorGUIExtensions.AdvancedDropdownInput(_rect, task.aName, _animatorParamNames, "Parameter A", "No Parameters"); _rect.y += spacing; parameterIndex = Math.Max(_animatorParamNames.FindIndex(m => m == task.aName), 0); task.aParamType = _animatorParamTypes[parameterIndex]; formulaDisplay += $"{task.aName} "; break; case AnimatorDriverTask.SourceType.Random: task.aValue = EditorGUI.FloatField(_rect, "A Min", task.aValue); _rect.y += spacing; task.aMax = EditorGUI.FloatField(_rect, "A Max", task.aMax); _rect.y += spacing; formulaDisplay += $"Rand({task.aValue}, {task.aMax}) "; break; } if (task.op != AnimatorDriverTask.Operator.Set) { switch (task.op) { case AnimatorDriverTask.Operator.Addition: formulaDisplay += "+ "; break; case AnimatorDriverTask.Operator.Subtraction: formulaDisplay += "- "; break; case AnimatorDriverTask.Operator.Multiplication: formulaDisplay += "* "; break; case AnimatorDriverTask.Operator.Division: formulaDisplay += "/ "; break; case AnimatorDriverTask.Operator.Modulo: formulaDisplay += "% "; break; case AnimatorDriverTask.Operator.Power: formulaDisplay += "pow "; break; case AnimatorDriverTask.Operator.Log: formulaDisplay += "log "; break; case AnimatorDriverTask.Operator.Equal: formulaDisplay += "== "; break; case AnimatorDriverTask.Operator.LessThen: formulaDisplay += "< "; break; case AnimatorDriverTask.Operator.LessEqual: formulaDisplay += "<= "; break; case AnimatorDriverTask.Operator.MoreThen: formulaDisplay += "> "; break; case AnimatorDriverTask.Operator.MoreEqual: formulaDisplay += ">= "; break; case AnimatorDriverTask.Operator.NotEqual: formulaDisplay += "!= "; break; } task.bType = (AnimatorDriverTask.SourceType) EditorGUI.EnumPopup(_rect, "B Type", task.bType); _rect.y += spacing; switch (task.bType) { case AnimatorDriverTask.SourceType.Static: task.bValue = EditorGUI.FloatField(_rect, "B Value", task.bValue); _rect.y += spacing; formulaDisplay += $"{task.bValue} "; break; case AnimatorDriverTask.SourceType.Parameter: task.bName = EditorGUIExtensions.AdvancedDropdownInput(_rect, task.bName, _animatorParamNames, "Parameter B", "No Parameters"); _rect.y += spacing; parameterIndex = Math.Max(_animatorParamNames.FindIndex(m => m == task.bName), 0); task.bParamType = _animatorParamTypes[parameterIndex]; formulaDisplay += $"{task.bName} "; break; case AnimatorDriverTask.SourceType.Random: task.bValue = EditorGUI.FloatField(_rect, "B Min", task.bValue); _rect.y += spacing; task.bMax = EditorGUI.FloatField(_rect, "B Max", task.bMax); _rect.y += spacing; formulaDisplay += $"Rand({task.bValue}, {task.bMax}) "; break; } } EditorGUI.LabelField(_rect, task.targetName == "" ? "No Parameter" : formulaDisplay, new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold }); EditorGUIUtility.labelWidth = originalLabelWidth; } #endregion #region Utility private void AppendComponentMenu(GenericMenuBuilder genericMenuBuilder, ReorderableList list, bool isEnterList) { bool hasSelectedTask = list.index != -1; bool hasTasks = list.count > 0; if (isEnterList) { genericMenuBuilder.AddMenuItem("To Exit Task", hasTasks && hasSelectedTask, () => ConvertSelectedTask(list, _animatorDriver.EnterTasks, _animatorDriver.ExitTasks)); genericMenuBuilder.AddMenuItem("All to Exit Task", hasTasks, ConvertAllTasksToExit); } else { genericMenuBuilder.AddMenuItem("To Enter Task", hasTasks && hasSelectedTask, () => ConvertSelectedTask(list, _animatorDriver.ExitTasks, _animatorDriver.EnterTasks)); genericMenuBuilder.AddMenuItem("All to Enter Task", hasTasks, ConvertAllTasksToEnter); } } private void ConvertAllTasksToEnter() { _animatorDriver.EnterTasks.AddRange(_animatorDriver.ExitTasks); _animatorDriver.ExitTasks.Clear(); } private void ConvertAllTasksToExit() { _animatorDriver.ExitTasks.AddRange(_animatorDriver.EnterTasks); _animatorDriver.EnterTasks.Clear(); } private void ConvertSelectedTask(ReorderableList list, List fromTasks, List toTasks) { if (list.index == -1 || list.index >= fromTasks.Count) return; AnimatorDriverTask selectedTask = fromTasks[list.index]; fromTasks.RemoveAt(list.index); toTasks.Add(selectedTask); } #endregion }