cvr-props/Assets/ABI.CCK/Scripts/Editor/CCK_AnimatorDriverEditor.cs

384 lines
No EOL
15 KiB
C#
Executable file

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<string> _animatorParamNames = new List<string>();
private readonly List<AnimatorDriverTask.ParameterType> _animatorParamTypes = new List<AnimatorDriverTask.ParameterType>();
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<AnimatorDriverTask> fromTasks, List<AnimatorDriverTask> toTasks)
{
if (list.index == -1 || list.index >= fromTasks.Count) return;
AnimatorDriverTask selectedTask = fromTasks[list.index];
fromTasks.RemoveAt(list.index);
toTasks.Add(selectedTask);
}
#endregion
}