483 lines
21 KiB
C#
483 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace FMODUnity
|
|
{
|
|
[CustomEditor(typeof(StudioEventEmitter))]
|
|
[CanEditMultipleObjects]
|
|
public class StudioEventEmitterEditor : Editor
|
|
{
|
|
ParameterValueView parameterValueView;
|
|
|
|
public void OnEnable()
|
|
{
|
|
parameterValueView = new ParameterValueView(serializedObject);
|
|
}
|
|
|
|
public void OnSceneGUI()
|
|
{
|
|
var emitter = target as StudioEventEmitter;
|
|
|
|
EditorEventRef editorEvent = EventManager.EventFromPath(emitter.Event);
|
|
if (editorEvent != null && editorEvent.Is3D)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
float minDistance = emitter.OverrideAttenuation ? emitter.OverrideMinDistance : editorEvent.MinDistance;
|
|
float maxDistance = emitter.OverrideAttenuation ? emitter.OverrideMaxDistance : editorEvent.MaxDistance;
|
|
minDistance = Handles.RadiusHandle(Quaternion.identity, emitter.transform.position, minDistance);
|
|
maxDistance = Handles.RadiusHandle(Quaternion.identity, emitter.transform.position, maxDistance);
|
|
if (EditorGUI.EndChangeCheck() && emitter.OverrideAttenuation)
|
|
{
|
|
Undo.RecordObject(emitter, "Change Emitter Bounds");
|
|
emitter.OverrideMinDistance = Mathf.Clamp(minDistance, 0, emitter.OverrideMaxDistance);
|
|
emitter.OverrideMaxDistance = Mathf.Max(emitter.OverrideMinDistance, maxDistance);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
var begin = serializedObject.FindProperty("PlayEvent");
|
|
var end = serializedObject.FindProperty("StopEvent");
|
|
var tag = serializedObject.FindProperty("CollisionTag");
|
|
var ev = serializedObject.FindProperty("Event");
|
|
var fadeout = serializedObject.FindProperty("AllowFadeout");
|
|
var once = serializedObject.FindProperty("TriggerOnce");
|
|
var preload = serializedObject.FindProperty("Preload");
|
|
var overrideAtt = serializedObject.FindProperty("OverrideAttenuation");
|
|
var minDistance = serializedObject.FindProperty("OverrideMinDistance");
|
|
var maxDistance = serializedObject.FindProperty("OverrideMaxDistance");
|
|
|
|
EditorGUILayout.PropertyField(begin, new GUIContent("Play Event"));
|
|
EditorGUILayout.PropertyField(end, new GUIContent("Stop Event"));
|
|
|
|
if ((begin.enumValueIndex >= (int)EmitterGameEvent.TriggerEnter && begin.enumValueIndex <= (int)EmitterGameEvent.TriggerExit2D) ||
|
|
(end.enumValueIndex >= (int)EmitterGameEvent.TriggerEnter && end.enumValueIndex <= (int)EmitterGameEvent.TriggerExit2D))
|
|
{
|
|
tag.stringValue = EditorGUILayout.TagField("Collision Tag", tag.stringValue);
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
EditorGUILayout.PropertyField(ev, new GUIContent("Event"));
|
|
|
|
EditorEventRef editorEvent = EventManager.EventFromPath(ev.stringValue);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
EditorUtils.UpdateParamsOnEmitter(serializedObject, ev.stringValue);
|
|
if (editorEvent != null)
|
|
{
|
|
overrideAtt.boolValue = false;
|
|
minDistance.floatValue = editorEvent.MinDistance;
|
|
maxDistance.floatValue = editorEvent.MaxDistance;
|
|
}
|
|
}
|
|
|
|
// Attenuation
|
|
if (editorEvent != null)
|
|
{
|
|
{
|
|
EditorGUI.BeginDisabledGroup(editorEvent == null || !editorEvent.Is3D);
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PrefixLabel("Override Attenuation");
|
|
EditorGUI.BeginChangeCheck();
|
|
overrideAtt.boolValue = EditorGUILayout.Toggle(overrideAtt.boolValue, GUILayout.Width(20));
|
|
if (EditorGUI.EndChangeCheck() ||
|
|
(minDistance.floatValue == -1 && maxDistance.floatValue == -1) // never been initialiased
|
|
)
|
|
{
|
|
minDistance.floatValue = editorEvent.MinDistance;
|
|
maxDistance.floatValue = editorEvent.MaxDistance;
|
|
}
|
|
EditorGUI.BeginDisabledGroup(!overrideAtt.boolValue);
|
|
EditorGUIUtility.labelWidth = 30;
|
|
minDistance.floatValue = EditorGUILayout.FloatField("Min", minDistance.floatValue);
|
|
minDistance.floatValue = Mathf.Clamp(minDistance.floatValue, 0, maxDistance.floatValue);
|
|
maxDistance.floatValue = EditorGUILayout.FloatField("Max", maxDistance.floatValue);
|
|
maxDistance.floatValue = Mathf.Max(minDistance.floatValue, maxDistance.floatValue);
|
|
EditorGUIUtility.labelWidth = 0;
|
|
EditorGUI.EndDisabledGroup();
|
|
EditorGUILayout.EndHorizontal();
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
|
|
parameterValueView.OnGUI(editorEvent, !ev.hasMultipleDifferentValues);
|
|
|
|
fadeout.isExpanded = EditorGUILayout.Foldout(fadeout.isExpanded, "Advanced Controls");
|
|
if (fadeout.isExpanded)
|
|
{
|
|
EditorGUILayout.PropertyField(preload, new GUIContent("Preload Sample Data"));
|
|
EditorGUILayout.PropertyField(fadeout, new GUIContent("Allow Fadeout When Stopping"));
|
|
EditorGUILayout.PropertyField(once, new GUIContent("Trigger Once"));
|
|
}
|
|
}
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
|
|
private class ParameterValueView
|
|
{
|
|
// The "Params" property from the SerializedObject we're editing in the inspector,
|
|
// so we can expand/collapse it or revert to prefab.
|
|
private SerializedProperty paramsProperty;
|
|
|
|
// This holds one SerializedObject for each object in the current selection.
|
|
private List<SerializedObject> serializedTargets = new List<SerializedObject>();
|
|
|
|
// A mapping from EditorParamRef to the initial parameter value properties in the
|
|
// current selection that have the same name.
|
|
// We need this because some objects may be missing some properties, and properties with
|
|
// the same name may be at different array indices in different objects.
|
|
private class PropertyRecord
|
|
{
|
|
public string name { get { return paramRef.Name; } }
|
|
public EditorParamRef paramRef;
|
|
public List<SerializedProperty> valueProperties;
|
|
}
|
|
|
|
// Mappings from EditorParamRef to initial parameter value property for all properties
|
|
// found in the current selection.
|
|
private List<PropertyRecord> propertyRecords = new List<PropertyRecord>();
|
|
|
|
// Any parameters that are in the current event but are missing from some objects in
|
|
// the current selection, so we can put them in the "Add" menu.
|
|
private List<EditorParamRef> missingParameters = new List<EditorParamRef>();
|
|
|
|
public ParameterValueView(SerializedObject serializedObject)
|
|
{
|
|
paramsProperty = serializedObject.FindProperty("Params");
|
|
|
|
foreach (UnityEngine.Object target in serializedObject.targetObjects)
|
|
{
|
|
serializedTargets.Add(new SerializedObject(target));
|
|
}
|
|
}
|
|
|
|
// Rebuilds the propertyRecords and missingParameters collections.
|
|
private void RefreshPropertyRecords(EditorEventRef eventRef)
|
|
{
|
|
propertyRecords.Clear();
|
|
|
|
foreach (SerializedObject serializedTarget in serializedTargets)
|
|
{
|
|
SerializedProperty paramsProperty = serializedTarget.FindProperty("Params");
|
|
|
|
foreach (SerializedProperty parameterProperty in paramsProperty)
|
|
{
|
|
string name = parameterProperty.FindPropertyRelative("Name").stringValue;
|
|
SerializedProperty valueProperty = parameterProperty.FindPropertyRelative("Value");
|
|
|
|
PropertyRecord record = propertyRecords.Find(r => r.name == name);
|
|
|
|
if (record != null)
|
|
{
|
|
record.valueProperties.Add(valueProperty);
|
|
}
|
|
else
|
|
{
|
|
EditorParamRef paramRef = eventRef.Parameters.Find(p => p.Name == name);
|
|
|
|
if (paramRef != null)
|
|
{
|
|
propertyRecords.Add(
|
|
new PropertyRecord() {
|
|
paramRef = paramRef,
|
|
valueProperties = new List<SerializedProperty>() { valueProperty },
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only sort if there is a multi-selection. If there is only one object selected,
|
|
// the user can revert to prefab, and the behaviour depends on the array order,
|
|
// so it's helpful to show the true order.
|
|
if (serializedTargets.Count > 1)
|
|
{
|
|
propertyRecords.Sort((a, b) => EditorUtility.NaturalCompare(a.name, b.name));
|
|
}
|
|
|
|
missingParameters.Clear();
|
|
missingParameters.AddRange(eventRef.Parameters.Where(
|
|
p => {
|
|
PropertyRecord record = propertyRecords.Find(r => r.name == p.Name);
|
|
return record == null || record.valueProperties.Count < serializedTargets.Count;
|
|
}));
|
|
}
|
|
|
|
public void OnGUI(EditorEventRef eventRef, bool matchingEvents)
|
|
{
|
|
foreach (SerializedObject serializedTarget in serializedTargets)
|
|
{
|
|
serializedTarget.Update();
|
|
}
|
|
|
|
if (Event.current.type == EventType.Layout)
|
|
{
|
|
RefreshPropertyRecords(eventRef);
|
|
}
|
|
|
|
DrawHeader(matchingEvents);
|
|
|
|
if (paramsProperty.isExpanded)
|
|
{
|
|
if (matchingEvents)
|
|
{
|
|
DrawValues();
|
|
}
|
|
else
|
|
{
|
|
GUILayout.Box("Cannot change parameters when different events are selected", GUILayout.ExpandWidth(true));
|
|
}
|
|
}
|
|
|
|
foreach (SerializedObject serializedTarget in serializedTargets)
|
|
{
|
|
serializedTarget.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
|
|
private void DrawHeader(bool enableAddButton)
|
|
{
|
|
Rect controlRect = EditorGUILayout.GetControlRect();
|
|
|
|
Rect titleRect = controlRect;
|
|
titleRect.width = EditorGUIUtility.labelWidth;
|
|
|
|
// Let the user revert the whole Params array to prefab by context-clicking the title.
|
|
EditorGUI.BeginProperty(titleRect, GUIContent.none, paramsProperty);
|
|
|
|
paramsProperty.isExpanded = EditorGUI.Foldout(titleRect, paramsProperty.isExpanded,
|
|
"Initial Parameter Values");
|
|
|
|
EditorGUI.EndProperty();
|
|
|
|
Rect buttonRect = controlRect;
|
|
buttonRect.xMin = titleRect.xMax;
|
|
|
|
EditorGUI.BeginDisabledGroup(!enableAddButton);
|
|
|
|
DrawAddButton(buttonRect);
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
|
|
private void DrawAddButton(Rect position)
|
|
{
|
|
EditorGUI.BeginDisabledGroup(missingParameters.Count == 0);
|
|
|
|
if (EditorGUI.DropdownButton(position, new GUIContent("Add"), FocusType.Passive))
|
|
{
|
|
GenericMenu menu = new GenericMenu();
|
|
menu.AddItem(new GUIContent("All"), false, () =>
|
|
{
|
|
foreach (EditorParamRef parameter in missingParameters)
|
|
{
|
|
AddParameter(parameter);
|
|
}
|
|
});
|
|
|
|
menu.AddSeparator(string.Empty);
|
|
|
|
foreach (EditorParamRef parameter in missingParameters)
|
|
{
|
|
menu.AddItem(new GUIContent(parameter.Name), false,
|
|
(userData) =>
|
|
{
|
|
AddParameter(userData as EditorParamRef);
|
|
},
|
|
parameter);
|
|
}
|
|
|
|
menu.DropDown(position);
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
|
|
private void DrawValues()
|
|
{
|
|
// We use this to defer deletion so we don't mess with arrays while using
|
|
// SerializedProperties that refer to array elements, as this can throw exceptions.
|
|
string parameterToDelete = null;
|
|
|
|
foreach (PropertyRecord record in propertyRecords)
|
|
{
|
|
if (record.valueProperties.Count == serializedTargets.Count)
|
|
{
|
|
bool delete;
|
|
DrawValue(record, out delete);
|
|
|
|
if (delete)
|
|
{
|
|
parameterToDelete = record.name;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parameterToDelete != null)
|
|
{
|
|
DeleteParameter(parameterToDelete);
|
|
}
|
|
}
|
|
|
|
private void DrawValue(PropertyRecord record, out bool delete)
|
|
{
|
|
delete = false;
|
|
|
|
GUIContent removeLabel = new GUIContent("Remove");
|
|
|
|
Rect position = EditorGUILayout.GetControlRect();
|
|
|
|
Rect nameLabelRect = position;
|
|
nameLabelRect.width = EditorGUIUtility.labelWidth;
|
|
|
|
Rect removeButtonRect = position;
|
|
removeButtonRect.width = EditorStyles.miniButton.CalcSize(removeLabel).x;
|
|
removeButtonRect.x = position.xMax - removeButtonRect.width;
|
|
|
|
Rect sliderRect = position;
|
|
sliderRect.xMin = nameLabelRect.xMax;
|
|
sliderRect.xMax = removeButtonRect.xMin - EditorStyles.miniButton.margin.left;
|
|
|
|
GUIContent nameLabel = new GUIContent(record.name);
|
|
|
|
float value = 0;
|
|
bool mixedValues = false;
|
|
|
|
// We use EditorGUI.BeginProperty when there is a single object selected, so
|
|
// the user can revert the value to prefab by context-clicking the name.
|
|
// We handle multi-selections ourselves, so that we can deal with
|
|
// mismatched arrays nicely.
|
|
if (record.valueProperties.Count == 1)
|
|
{
|
|
value = record.valueProperties[0].floatValue;
|
|
EditorGUI.BeginProperty(position, nameLabel, record.valueProperties[0]);
|
|
}
|
|
else
|
|
{
|
|
bool first = true;
|
|
|
|
foreach (SerializedProperty property in record.valueProperties)
|
|
{
|
|
if (first)
|
|
{
|
|
value = property.floatValue;
|
|
first = false;
|
|
}
|
|
else if (property.floatValue != value)
|
|
{
|
|
mixedValues = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.LabelField(nameLabelRect, nameLabel);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
EditorGUI.showMixedValue = mixedValues;
|
|
|
|
float newValue = EditorGUI.Slider(sliderRect, value, record.paramRef.Min, record.paramRef.Max);
|
|
|
|
EditorGUI.showMixedValue = false;
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
foreach (SerializedProperty property in record.valueProperties)
|
|
{
|
|
property.floatValue = newValue;
|
|
}
|
|
}
|
|
|
|
delete = GUI.Button(removeButtonRect, removeLabel, EditorStyles.miniButton);
|
|
|
|
if (record.valueProperties.Count == 1)
|
|
{
|
|
EditorGUI.EndProperty();
|
|
}
|
|
else
|
|
{
|
|
// Context menu to set all values from one object in the multi-selection.
|
|
if (mixedValues && Event.current.type == EventType.ContextClick
|
|
&& nameLabelRect.Contains(Event.current.mousePosition))
|
|
{
|
|
GenericMenu menu = new GenericMenu();
|
|
|
|
foreach (SerializedProperty sourceProperty in record.valueProperties)
|
|
{
|
|
UnityEngine.Object targetObject = sourceProperty.serializedObject.targetObject;
|
|
|
|
menu.AddItem(new GUIContent(string.Format("Set to Value of '{0}'", targetObject.name)), false,
|
|
(userData) => CopyValueToAll(userData as SerializedProperty, record.valueProperties),
|
|
sourceProperty);
|
|
}
|
|
|
|
menu.DropDown(position);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy the value from the source property to all target properties.
|
|
private void CopyValueToAll(SerializedProperty sourceProperty, List<SerializedProperty> targetProperties)
|
|
{
|
|
foreach (SerializedProperty targetProperty in targetProperties)
|
|
{
|
|
if (targetProperty != sourceProperty)
|
|
{
|
|
targetProperty.floatValue = sourceProperty.floatValue;
|
|
targetProperty.serializedObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add an initial value for the given parameter to all selected objects that don't have one.
|
|
private void AddParameter(EditorParamRef parameter)
|
|
{
|
|
foreach (SerializedObject serializedTarget in serializedTargets)
|
|
{
|
|
StudioEventEmitter emitter = serializedTarget.targetObject as StudioEventEmitter;
|
|
|
|
if (Array.FindIndex(emitter.Params, p => p.Name == parameter.Name) < 0)
|
|
{
|
|
SerializedProperty paramsProperty = serializedTarget.FindProperty("Params");
|
|
|
|
int index = paramsProperty.arraySize;
|
|
paramsProperty.InsertArrayElementAtIndex(index);
|
|
|
|
SerializedProperty arrayElement = paramsProperty.GetArrayElementAtIndex(index);
|
|
|
|
arrayElement.FindPropertyRelative("Name").stringValue = parameter.Name;
|
|
arrayElement.FindPropertyRelative("Value").floatValue = parameter.Default;
|
|
|
|
serializedTarget.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete initial parameter values for the given name from all selected objects.
|
|
private void DeleteParameter(string name)
|
|
{
|
|
foreach (SerializedObject serializedTarget in serializedTargets)
|
|
{
|
|
SerializedProperty paramsProperty = serializedTarget.FindProperty("Params");
|
|
|
|
foreach (SerializedProperty child in paramsProperty)
|
|
{
|
|
if (child.FindPropertyRelative("Name").stringValue == name)
|
|
{
|
|
child.DeleteCommand();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |