502 lines
17 KiB
C#
502 lines
17 KiB
C#
|
#if UNITY_TIMELINE_EXIST
|
|||
|
|
|||
|
using System.Collections.Generic;
|
|||
|
using UnityEditor;
|
|||
|
using UnityEditor.Timeline;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEngine.Timeline;
|
|||
|
using System;
|
|||
|
using System.Linq;
|
|||
|
using System.Reflection;
|
|||
|
|
|||
|
namespace FMODUnity
|
|||
|
{
|
|||
|
[CustomEditor(typeof(FMODEventPlayable))]
|
|||
|
public class FMODEventPlayableEditor : Editor
|
|||
|
{
|
|||
|
private FMODEventPlayable eventPlayable;
|
|||
|
private EditorEventRef editorEventRef;
|
|||
|
private List<EditorParamRef> missingInitialParameterValues = new List<EditorParamRef>();
|
|||
|
private List<EditorParamRef> missingParameterAutomations = new List<EditorParamRef>();
|
|||
|
|
|||
|
private SerializedProperty parametersProperty;
|
|||
|
private SerializedProperty parameterLinksProperty;
|
|||
|
private SerializedProperty parameterAutomationProperty;
|
|||
|
|
|||
|
private ListView parameterLinksView;
|
|||
|
private ListView initialParameterValuesView;
|
|||
|
|
|||
|
private string eventPath;
|
|||
|
|
|||
|
public void OnEnable()
|
|||
|
{
|
|||
|
eventPlayable = target as FMODEventPlayable;
|
|||
|
|
|||
|
parametersProperty = serializedObject.FindProperty("Parameters");
|
|||
|
parameterLinksProperty = serializedObject.FindProperty("Template.ParameterLinks");
|
|||
|
parameterAutomationProperty = serializedObject.FindProperty("Template.ParameterAutomation");
|
|||
|
|
|||
|
parameterLinksView = new ListView(parameterLinksProperty);
|
|||
|
parameterLinksView.drawElementWithLabelCallback = DrawParameterLink;
|
|||
|
parameterLinksView.onCanAddCallback = (list) => missingParameterAutomations.Count > 0;
|
|||
|
parameterLinksView.onAddDropdownCallback = DoAddParameterLinkMenu;
|
|||
|
parameterLinksView.onRemoveCallback = (list) => DeleteParameterAutomation(list.index);
|
|||
|
|
|||
|
initialParameterValuesView = new ListView(parametersProperty);
|
|||
|
initialParameterValuesView.drawElementWithLabelCallback = DrawInitialParameterValue;
|
|||
|
initialParameterValuesView.onCanAddCallback = (list) => missingInitialParameterValues.Count > 0;
|
|||
|
initialParameterValuesView.onAddDropdownCallback = DoAddInitialParameterValueMenu;
|
|||
|
initialParameterValuesView.onRemoveCallback = (list) => DeleteInitialParameterValue(list.index);
|
|||
|
|
|||
|
RefreshEventRef();
|
|||
|
|
|||
|
Undo.undoRedoPerformed += OnUndoRedo;
|
|||
|
}
|
|||
|
|
|||
|
public void OnDestroy()
|
|||
|
{
|
|||
|
Undo.undoRedoPerformed -= OnUndoRedo;
|
|||
|
}
|
|||
|
|
|||
|
private void OnUndoRedo()
|
|||
|
{
|
|||
|
RefreshMissingParameterLists();
|
|||
|
|
|||
|
// This is in case the undo/redo modified any curves on the Playable's clip
|
|||
|
RefreshTimelineEditor();
|
|||
|
}
|
|||
|
|
|||
|
private void RefreshEventRef()
|
|||
|
{
|
|||
|
if (eventPath != eventPlayable.EventReference.Path)
|
|||
|
{
|
|||
|
eventPath = eventPlayable.EventReference.Path;
|
|||
|
|
|||
|
if (!string.IsNullOrEmpty(eventPath))
|
|||
|
{
|
|||
|
editorEventRef = EventManager.EventFromPath(eventPath);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
editorEventRef = null;
|
|||
|
}
|
|||
|
|
|||
|
if (editorEventRef != null)
|
|||
|
{
|
|||
|
eventPlayable.UpdateEventDuration(
|
|||
|
editorEventRef.IsOneShot ? editorEventRef.Length : float.PositiveInfinity);
|
|||
|
}
|
|||
|
|
|||
|
ValidateParameterSettings();
|
|||
|
RefreshMissingParameterLists();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void ValidateParameterSettings()
|
|||
|
{
|
|||
|
if (editorEventRef != null)
|
|||
|
{
|
|||
|
List<string> namesToDelete = new List<string>();
|
|||
|
|
|||
|
for (int i = 0; i < parametersProperty.arraySize; ++i)
|
|||
|
{
|
|||
|
SerializedProperty current = parametersProperty.GetArrayElementAtIndex(i);
|
|||
|
SerializedProperty name = current.FindPropertyRelative("Name");
|
|||
|
|
|||
|
EditorParamRef paramRef = editorEventRef.LocalParameters.FirstOrDefault(p => p.Name == name.stringValue);
|
|||
|
|
|||
|
if (paramRef != null)
|
|||
|
{
|
|||
|
SerializedProperty value = current.FindPropertyRelative("Value");
|
|||
|
value.floatValue = Mathf.Clamp(value.floatValue, paramRef.Min, paramRef.Max);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
namesToDelete.Add(name.stringValue);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
foreach(string name in namesToDelete)
|
|||
|
{
|
|||
|
DeleteInitialParameterValue(name);
|
|||
|
}
|
|||
|
|
|||
|
namesToDelete.Clear();
|
|||
|
|
|||
|
for (int i = 0; i < parameterLinksProperty.arraySize; ++i)
|
|||
|
{
|
|||
|
SerializedProperty current = parameterLinksProperty.GetArrayElementAtIndex(i);
|
|||
|
SerializedProperty name = current.FindPropertyRelative("Name");
|
|||
|
|
|||
|
if (!editorEventRef.LocalParameters.Any(p => p.Name == name.stringValue))
|
|||
|
{
|
|||
|
namesToDelete.Add(name.stringValue);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
foreach(string name in namesToDelete)
|
|||
|
{
|
|||
|
DeleteParameterAutomation(name);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void RefreshMissingParameterLists()
|
|||
|
{
|
|||
|
if (editorEventRef != null)
|
|||
|
{
|
|||
|
serializedObject.Update();
|
|||
|
|
|||
|
missingInitialParameterValues =
|
|||
|
editorEventRef.LocalParameters.Where(p => !InitialParameterValueExists(p.Name)).ToList();
|
|||
|
missingParameterAutomations =
|
|||
|
editorEventRef.LocalParameters.Where(p => !ParameterLinkExists(p.Name)).ToList();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
missingInitialParameterValues.Clear();
|
|||
|
missingParameterAutomations.Clear();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public override void OnInspectorGUI()
|
|||
|
{
|
|||
|
serializedObject.Update();
|
|||
|
|
|||
|
RefreshEventRef();
|
|||
|
|
|||
|
var eventReference = serializedObject.FindProperty("EventReference");
|
|||
|
var stopType = serializedObject.FindProperty("StopType");
|
|||
|
|
|||
|
const string EventReferenceLabel = "Event";
|
|||
|
|
|||
|
EditorUtils.DrawLegacyEvent(serializedObject.FindProperty("eventName"), EventReferenceLabel);
|
|||
|
|
|||
|
EditorGUILayout.PropertyField(eventReference, new GUIContent(EventReferenceLabel));
|
|||
|
EditorGUILayout.PropertyField(stopType, new GUIContent("Stop Mode"));
|
|||
|
|
|||
|
DrawInitialParameterValues();
|
|||
|
DrawParameterAutomations();
|
|||
|
|
|||
|
eventPlayable.OnValidate();
|
|||
|
|
|||
|
serializedObject.ApplyModifiedProperties();
|
|||
|
}
|
|||
|
|
|||
|
private void DrawInitialParameterValues()
|
|||
|
{
|
|||
|
if (editorEventRef != null)
|
|||
|
{
|
|||
|
parametersProperty.isExpanded =
|
|||
|
EditorGUILayout.Foldout(parametersProperty.isExpanded, "Initial Parameter Values", true);
|
|||
|
|
|||
|
if (parametersProperty.isExpanded)
|
|||
|
{
|
|||
|
initialParameterValuesView.DrawLayout();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void DoAddInitialParameterValueMenu(Rect rect, UnityEditorInternal.ReorderableList list)
|
|||
|
{
|
|||
|
GenericMenu menu = new GenericMenu();
|
|||
|
menu.AddItem(new GUIContent("All"), false, () =>
|
|||
|
{
|
|||
|
foreach (EditorParamRef parameter in missingInitialParameterValues)
|
|||
|
{
|
|||
|
AddInitialParameterValue(parameter);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
menu.AddSeparator(string.Empty);
|
|||
|
|
|||
|
foreach (EditorParamRef parameter in missingInitialParameterValues)
|
|||
|
{
|
|||
|
string text = parameter.Name;
|
|||
|
|
|||
|
if (ParameterLinkExists(parameter.Name))
|
|||
|
{
|
|||
|
text += " (automated)";
|
|||
|
}
|
|||
|
|
|||
|
menu.AddItem(new GUIContent(text), false,
|
|||
|
(userData) =>
|
|||
|
{
|
|||
|
AddInitialParameterValue(userData as EditorParamRef);
|
|||
|
},
|
|||
|
parameter);
|
|||
|
}
|
|||
|
|
|||
|
menu.DropDown(rect);
|
|||
|
}
|
|||
|
|
|||
|
private void DrawInitialParameterValue(Rect rect, float labelRight, int index, bool active, bool focused)
|
|||
|
{
|
|||
|
if (editorEventRef == null)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
SerializedProperty property = parametersProperty.GetArrayElementAtIndex(index);
|
|||
|
|
|||
|
string name = property.FindPropertyRelative("Name").stringValue;
|
|||
|
|
|||
|
EditorParamRef paramRef = editorEventRef.LocalParameters.FirstOrDefault(p => p.Name == name);
|
|||
|
|
|||
|
if (paramRef == null)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
Rect nameLabelRect = rect;
|
|||
|
nameLabelRect.xMax = labelRight;
|
|||
|
|
|||
|
Rect sliderRect = rect;
|
|||
|
sliderRect.xMin = nameLabelRect.xMax;
|
|||
|
|
|||
|
SerializedProperty valueProperty = property.FindPropertyRelative("Value");
|
|||
|
|
|||
|
GUI.Label(nameLabelRect, name);
|
|||
|
|
|||
|
using (new NoIndentScope())
|
|||
|
{
|
|||
|
valueProperty.floatValue =
|
|||
|
EditorGUI.Slider(sliderRect, valueProperty.floatValue, paramRef.Min, paramRef.Max);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void DrawParameterAutomations()
|
|||
|
{
|
|||
|
if (editorEventRef != null)
|
|||
|
{
|
|||
|
parameterLinksProperty.isExpanded =
|
|||
|
EditorGUILayout.Foldout(parameterLinksProperty.isExpanded, "Parameter Automations", true);
|
|||
|
|
|||
|
if (parameterLinksProperty.isExpanded)
|
|||
|
{
|
|||
|
parameterLinksView.DrawLayout();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void DoAddParameterLinkMenu(Rect rect, UnityEditorInternal.ReorderableList list)
|
|||
|
{
|
|||
|
GenericMenu menu = new GenericMenu();
|
|||
|
menu.AddItem(new GUIContent("All"), false, () =>
|
|||
|
{
|
|||
|
foreach (EditorParamRef parameter in missingParameterAutomations)
|
|||
|
{
|
|||
|
AddParameterAutomation(parameter.Name);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
menu.AddSeparator(string.Empty);
|
|||
|
|
|||
|
foreach (EditorParamRef parameter in missingParameterAutomations)
|
|||
|
{
|
|||
|
string text = parameter.Name;
|
|||
|
|
|||
|
if (InitialParameterValueExists(parameter.Name))
|
|||
|
{
|
|||
|
text += " (has initial value)";
|
|||
|
}
|
|||
|
|
|||
|
menu.AddItem(new GUIContent(text), false,
|
|||
|
(userData) =>
|
|||
|
{
|
|||
|
AddParameterAutomation(userData as string);
|
|||
|
},
|
|||
|
parameter.Name);
|
|||
|
}
|
|||
|
|
|||
|
menu.DropDown(rect);
|
|||
|
}
|
|||
|
|
|||
|
private void DrawParameterLink(Rect rect, float labelRight, int index, bool active, bool focused)
|
|||
|
{
|
|||
|
if (editorEventRef == null)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
SerializedProperty linkProperty = parameterLinksProperty.GetArrayElementAtIndex(index);
|
|||
|
|
|||
|
string name = linkProperty.FindPropertyRelative("Name").stringValue;
|
|||
|
|
|||
|
EditorParamRef paramRef = editorEventRef.LocalParameters.FirstOrDefault(p => p.Name == name);
|
|||
|
|
|||
|
if (paramRef == null)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
int slot = linkProperty.FindPropertyRelative("Slot").intValue;
|
|||
|
|
|||
|
string slotName = string.Format("Slot{0:D2}", slot);
|
|||
|
SerializedProperty valueProperty = parameterAutomationProperty.FindPropertyRelative(slotName);
|
|||
|
|
|||
|
GUIStyle slotStyle = GUI.skin.label;
|
|||
|
|
|||
|
Rect slotRect = rect;
|
|||
|
slotRect.width = slotStyle.CalcSize(new GUIContent("slot 00:")).x;
|
|||
|
|
|||
|
Rect nameRect = rect;
|
|||
|
nameRect.xMin = slotRect.xMax;
|
|||
|
nameRect.xMax = labelRight;
|
|||
|
|
|||
|
Rect valueRect = rect;
|
|||
|
valueRect.xMin = nameRect.xMax;
|
|||
|
|
|||
|
using (new EditorGUI.PropertyScope(rect, GUIContent.none, valueProperty))
|
|||
|
{
|
|||
|
GUI.Label(slotRect, string.Format("slot {0:D2}:", slot), slotStyle);
|
|||
|
GUI.Label(nameRect, name);
|
|||
|
|
|||
|
using (new NoIndentScope())
|
|||
|
{
|
|||
|
valueProperty.floatValue =
|
|||
|
EditorGUI.Slider(valueRect, valueProperty.floatValue, paramRef.Min, paramRef.Max);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private bool InitialParameterValueExists(string name)
|
|||
|
{
|
|||
|
return parametersProperty.ArrayContains("Name", p => p.stringValue == name);
|
|||
|
}
|
|||
|
|
|||
|
private bool ParameterLinkExists(string name)
|
|||
|
{
|
|||
|
return parameterLinksProperty.ArrayContains("Name", p => p.stringValue == name);
|
|||
|
}
|
|||
|
|
|||
|
private void AddInitialParameterValue(EditorParamRef editorParamRef)
|
|||
|
{
|
|||
|
serializedObject.Update();
|
|||
|
|
|||
|
if (!InitialParameterValueExists(editorParamRef.Name))
|
|||
|
{
|
|||
|
DeleteParameterAutomation(editorParamRef.Name);
|
|||
|
|
|||
|
parametersProperty.ArrayAdd(p => {
|
|||
|
p.FindPropertyRelative("Name").stringValue = editorParamRef.Name;
|
|||
|
p.FindPropertyRelative("Value").floatValue = editorParamRef.Default;
|
|||
|
});
|
|||
|
|
|||
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
|||
|
RefreshMissingParameterLists();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void DeleteInitialParameterValue(string name)
|
|||
|
{
|
|||
|
serializedObject.Update();
|
|||
|
|
|||
|
int index = parametersProperty.FindArrayIndex("Name", p => p.stringValue == name);
|
|||
|
|
|||
|
if (index >= 0)
|
|||
|
{
|
|||
|
DeleteInitialParameterValue(index);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void DeleteInitialParameterValue(int index)
|
|||
|
{
|
|||
|
serializedObject.Update();
|
|||
|
|
|||
|
parametersProperty.DeleteArrayElementAtIndex(index);
|
|||
|
|
|||
|
serializedObject.ApplyModifiedProperties();
|
|||
|
RefreshMissingParameterLists();
|
|||
|
}
|
|||
|
|
|||
|
private void AddParameterAutomation(string name)
|
|||
|
{
|
|||
|
serializedObject.Update();
|
|||
|
|
|||
|
if (!ParameterLinkExists(name))
|
|||
|
{
|
|||
|
int slot = -1;
|
|||
|
|
|||
|
for (int i = 0; i < AutomatableSlots.Count; ++i)
|
|||
|
{
|
|||
|
if (!parameterLinksProperty.ArrayContains("Slot", p => p.intValue == i))
|
|||
|
{
|
|||
|
slot = i;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (slot >= 0)
|
|||
|
{
|
|||
|
DeleteInitialParameterValue(name);
|
|||
|
|
|||
|
parameterLinksProperty.ArrayAdd(p => {
|
|||
|
p.FindPropertyRelative("Name").stringValue = name;
|
|||
|
p.FindPropertyRelative("Slot").intValue = slot;
|
|||
|
});
|
|||
|
|
|||
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
|||
|
RefreshMissingParameterLists();
|
|||
|
RefreshTimelineEditor();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void DeleteParameterAutomation(string name)
|
|||
|
{
|
|||
|
serializedObject.Update();
|
|||
|
|
|||
|
int index = parameterLinksProperty.FindArrayIndex("Name", p => p.stringValue == name);
|
|||
|
|
|||
|
if (index >= 0)
|
|||
|
{
|
|||
|
DeleteParameterAutomation(index);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void DeleteParameterAutomation(int index)
|
|||
|
{
|
|||
|
serializedObject.Update();
|
|||
|
|
|||
|
if (eventPlayable.OwningClip.hasCurves)
|
|||
|
{
|
|||
|
SerializedProperty linkProperty = parameterLinksProperty.GetArrayElementAtIndex(index);
|
|||
|
SerializedProperty slotProperty = linkProperty.FindPropertyRelative("Slot");
|
|||
|
|
|||
|
AnimationClip curvesClip = eventPlayable.OwningClip.curves;
|
|||
|
|
|||
|
Undo.RecordObject(curvesClip, string.Empty);
|
|||
|
AnimationUtility.SetEditorCurve(curvesClip, GetParameterCurveBinding(slotProperty.intValue), null);
|
|||
|
}
|
|||
|
|
|||
|
parameterLinksProperty.DeleteArrayElementAtIndex(index);
|
|||
|
|
|||
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
|||
|
RefreshMissingParameterLists();
|
|||
|
|
|||
|
RefreshTimelineEditor();
|
|||
|
}
|
|||
|
|
|||
|
private static EditorCurveBinding GetParameterCurveBinding(int index)
|
|||
|
{
|
|||
|
EditorCurveBinding result = new EditorCurveBinding() {
|
|||
|
path = string.Empty,
|
|||
|
type = typeof(FMODEventPlayable),
|
|||
|
propertyName = string.Format("parameterAutomation.slot{0:D2}", index),
|
|||
|
};
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
private static void RefreshTimelineEditor()
|
|||
|
{
|
|||
|
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|