您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
388 行
14 KiB
388 行
14 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.Assertions;
|
|
using UnityEngine.Experimental.Rendering;
|
|
|
|
namespace UnityEditor.Experimental.Rendering
|
|
{
|
|
[CustomEditor(typeof(Volume))]
|
|
public sealed class VolumeEditor : Editor
|
|
{
|
|
SerializedProperty m_IsGlobal;
|
|
SerializedProperty m_BlendRadius;
|
|
SerializedProperty m_Weight;
|
|
SerializedProperty m_Priority;
|
|
SerializedProperty m_Components;
|
|
|
|
Dictionary<Type, Type> m_EditorTypes; // Component type => Editor type
|
|
List<VolumeComponentEditor> m_Editors;
|
|
|
|
static VolumeComponent s_ClipboardContent;
|
|
|
|
Volume actualTarget
|
|
{
|
|
get { return target as Volume; }
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
var o = new PropertyFetcher<Volume>(serializedObject);
|
|
m_IsGlobal = o.Find(x => x.isGlobal);
|
|
m_BlendRadius = o.Find(x => x.blendDistance);
|
|
m_Weight = o.Find(x => x.weight);
|
|
m_Priority = o.Find(x => x.priority);
|
|
m_Components = o.Find(x => x.components);
|
|
|
|
m_EditorTypes = new Dictionary<Type, Type>();
|
|
m_Editors = new List<VolumeComponentEditor>();
|
|
|
|
// Gets the list of all available component editors
|
|
var editorTypes = AppDomain.CurrentDomain.GetAssemblies()
|
|
.SelectMany(
|
|
a => a.GetTypes()
|
|
.Where(
|
|
t => t.IsSubclassOf(typeof(VolumeComponentEditor))
|
|
&& t.IsDefined(typeof(VolumeComponentEditorAttribute), false)
|
|
)
|
|
).ToList();
|
|
|
|
// Map them to their corresponding component type
|
|
foreach (var editorType in editorTypes)
|
|
{
|
|
var attribute = (VolumeComponentEditorAttribute)editorType.GetCustomAttributes(typeof(VolumeComponentEditorAttribute), false)[0];
|
|
m_EditorTypes.Add(attribute.componentType, editorType);
|
|
}
|
|
|
|
// Create editors for existing components
|
|
var components = actualTarget.components;
|
|
for (int i = 0; i < components.Count; i++)
|
|
CreateEditor(components[i], m_Components.GetArrayElementAtIndex(i));
|
|
|
|
// Keep track of undo/redo to redraw the inspector when that happens
|
|
Undo.undoRedoPerformed += OnUndoRedoPerformed;
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
Undo.undoRedoPerformed -= OnUndoRedoPerformed;
|
|
|
|
if (m_Editors == null)
|
|
return; // Hasn't been inited yet
|
|
|
|
foreach (var editor in m_Editors)
|
|
editor.OnDisable();
|
|
|
|
m_Editors.Clear();
|
|
m_EditorTypes.Clear();
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
serializedObject.Update();
|
|
|
|
if (actualTarget.isDirty)
|
|
{
|
|
RefreshEditors();
|
|
actualTarget.isDirty = false;
|
|
}
|
|
|
|
using (var scope = new EditorGUILayout.VerticalScope())
|
|
{
|
|
EditorGUILayout.PropertyField(m_IsGlobal);
|
|
|
|
if (!m_IsGlobal.boolValue) // Blend radius is not needed for global volumes
|
|
{
|
|
EditorGUILayout.PropertyField(m_BlendRadius);
|
|
m_BlendRadius.floatValue = Mathf.Max(m_BlendRadius.floatValue, 0f);
|
|
}
|
|
|
|
EditorGUILayout.PropertyField(m_Weight);
|
|
EditorGUILayout.PropertyField(m_Priority);
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
// Component list
|
|
for (int i = 0; i < m_Editors.Count; i++)
|
|
{
|
|
var editor = m_Editors[i];
|
|
string title = editor.GetDisplayTitle();
|
|
int id = i; // Needed for closure capture below
|
|
|
|
CoreEditorUtils.DrawSplitter();
|
|
bool displayContent = CoreEditorUtils.DrawHeaderToggle(
|
|
title,
|
|
editor.baseProperty,
|
|
editor.activeProperty,
|
|
pos => OnContextClick(pos, editor.target, id)
|
|
);
|
|
|
|
if (displayContent)
|
|
{
|
|
using (new EditorGUI.DisabledScope(!editor.activeProperty.boolValue))
|
|
editor.OnInternalInspectorGUI();
|
|
}
|
|
}
|
|
|
|
if (m_Editors.Count > 0)
|
|
CoreEditorUtils.DrawSplitter();
|
|
else
|
|
EditorGUILayout.HelpBox("No override set on this volume.", MessageType.Info);
|
|
|
|
using (new EditorGUILayout.HorizontalScope())
|
|
{
|
|
GUILayout.FlexibleSpace();
|
|
|
|
//if (GUILayout.Button(CoreEditorUtils.GetContent("Add Component"), GUILayout.Width(230f), GUILayout.Height(24f)))
|
|
//{
|
|
|
|
//}
|
|
|
|
GUILayout.FlexibleSpace();
|
|
}
|
|
|
|
// Handle components drag'n'drop
|
|
var e = Event.current;
|
|
if (e.type == EventType.DragUpdated)
|
|
{
|
|
if (IsDragValid(scope.rect, e.mousePosition))
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
|
|
e.Use();
|
|
}
|
|
else
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
|
|
}
|
|
}
|
|
else if (e.type == EventType.DragPerform)
|
|
{
|
|
if (IsDragValid(scope.rect, e.mousePosition))
|
|
{
|
|
DragAndDrop.AcceptDrag();
|
|
|
|
var objs = DragAndDrop.objectReferences;
|
|
foreach (var o in objs)
|
|
{
|
|
var compType = ((MonoScript)o).GetClass();
|
|
AddComponent(compType);
|
|
}
|
|
|
|
e.Use();
|
|
}
|
|
}
|
|
}
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
|
|
bool IsDragValid(Rect rect, Vector2 mousePos)
|
|
{
|
|
if (!rect.Contains(mousePos))
|
|
return false;
|
|
|
|
var objs = DragAndDrop.objectReferences;
|
|
foreach (var o in objs)
|
|
{
|
|
if (o.GetType() != typeof(MonoScript))
|
|
return false;
|
|
|
|
var script = (MonoScript)o;
|
|
var scriptType = script.GetClass();
|
|
|
|
if (!scriptType.IsSubclassOf(typeof(VolumeComponent)))
|
|
return false;
|
|
|
|
if (actualTarget.components.Exists(t => t.GetType() == scriptType))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void RefreshEditors()
|
|
{
|
|
// Disable all editors first
|
|
foreach (var editor in m_Editors)
|
|
editor.OnDisable();
|
|
|
|
// Remove them
|
|
m_Editors.Clear();
|
|
|
|
// Recreate editors for existing settings, if any
|
|
for (int i = 0; i < actualTarget.components.Count; i++)
|
|
CreateEditor(actualTarget.components[i], m_Components.GetArrayElementAtIndex(i));
|
|
}
|
|
|
|
// index is only used when we need to re-create a component in a specific spot (e.g. reset)
|
|
void CreateEditor(VolumeComponent settings, SerializedProperty property, int index = -1)
|
|
{
|
|
var settingsType = settings.GetType();
|
|
Type editorType;
|
|
|
|
if (!m_EditorTypes.TryGetValue(settingsType, out editorType))
|
|
editorType = typeof(VolumeComponentEditor);
|
|
|
|
var editor = (VolumeComponentEditor)Activator.CreateInstance(editorType);
|
|
editor.Init(settings, this);
|
|
editor.baseProperty = property.Copy();
|
|
|
|
if (index < 0)
|
|
m_Editors.Add(editor);
|
|
else
|
|
m_Editors[index] = editor;
|
|
}
|
|
|
|
void AddComponent(Type type)
|
|
{
|
|
serializedObject.Update();
|
|
|
|
var component = (VolumeComponent)CreateInstance(type);
|
|
Undo.RegisterCreatedObjectUndo(component, "Add Volume Component");
|
|
|
|
// Grow the list first, then add - that's how serialized lists work in Unity
|
|
m_Components.arraySize++;
|
|
var effectProp = m_Components.GetArrayElementAtIndex(m_Components.arraySize - 1);
|
|
effectProp.objectReferenceValue = component;
|
|
|
|
// Create & store the internal editor object for this effect
|
|
CreateEditor(component, effectProp);
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
|
|
void RemoveComponent(int id)
|
|
{
|
|
// Huh. Hack to keep foldout state on the next element...
|
|
bool nextFoldoutState = false;
|
|
if (id < m_Editors.Count - 1)
|
|
nextFoldoutState = m_Editors[id + 1].baseProperty.isExpanded;
|
|
|
|
// Remove from the cached editors list
|
|
m_Editors[id].OnDisable();
|
|
m_Editors.RemoveAt(id);
|
|
|
|
serializedObject.Update();
|
|
|
|
var property = m_Components.GetArrayElementAtIndex(id);
|
|
var effect = property.objectReferenceValue;
|
|
|
|
// Unassign it (should be null already but serialization does funky things
|
|
property.objectReferenceValue = null;
|
|
|
|
// ...and remove the array index itself from the list
|
|
m_Components.DeleteArrayElementAtIndex(id);
|
|
|
|
// Finally refresh editor reference to the serialized settings list
|
|
for (int i = 0; i < m_Editors.Count; i++)
|
|
m_Editors[i].baseProperty = m_Components.GetArrayElementAtIndex(i).Copy();
|
|
|
|
// Set the proper foldout state if needed
|
|
if (id < m_Editors.Count)
|
|
m_Editors[id].baseProperty.isExpanded = nextFoldoutState;
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
// Destroy the setting object after ApplyModifiedProperties(). If we do it before, redo
|
|
// actions will be in the wrong order and the reference to the setting object in the
|
|
// list will be lost.
|
|
Undo.DestroyObjectImmediate(effect);
|
|
}
|
|
|
|
// Reset is done by deleting and removing the object from the list and adding a new one in
|
|
// the same spot as it was before
|
|
void ResetComponent(Type type, int id)
|
|
{
|
|
// Remove from the cached editors list
|
|
m_Editors[id].OnDisable();
|
|
m_Editors[id] = null;
|
|
|
|
serializedObject.Update();
|
|
|
|
var property = m_Components.GetArrayElementAtIndex(id);
|
|
var prevSettings = property.objectReferenceValue;
|
|
|
|
// Unassign it but down remove it from the array to keep the index available
|
|
property.objectReferenceValue = null;
|
|
|
|
// Create a new object
|
|
var newEffect = (VolumeComponent)CreateInstance(type);
|
|
Undo.RegisterCreatedObjectUndo(newEffect, "Reset Volume Component");
|
|
|
|
// Put it in the reserved space
|
|
property.objectReferenceValue = newEffect;
|
|
|
|
// Create & store the internal editor object for this effect
|
|
CreateEditor(newEffect, property, id);
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
// Same as RemoveComponent, destroy at the end so it's recreated first on Undo to make
|
|
// sure the GUID exists before undoing the list state
|
|
Undo.DestroyObjectImmediate(prevSettings);
|
|
}
|
|
|
|
void OnContextClick(Vector2 position, VolumeComponent targetComponent, int id)
|
|
{
|
|
var menu = new GenericMenu();
|
|
menu.AddItem(CoreEditorUtils.GetContent("Reset"), false, () => ResetComponent(targetComponent.GetType(), id));
|
|
menu.AddItem(CoreEditorUtils.GetContent("Remove"), false, () => RemoveComponent(id));
|
|
menu.AddSeparator(string.Empty);
|
|
menu.AddItem(CoreEditorUtils.GetContent("Copy Settings"), false, () => CopySettings(targetComponent));
|
|
|
|
if (CanPaste(targetComponent))
|
|
menu.AddItem(CoreEditorUtils.GetContent("Paste Settings"), false, () => PasteSettings(targetComponent));
|
|
else
|
|
menu.AddDisabledItem(CoreEditorUtils.GetContent("Paste Settings"));
|
|
|
|
menu.AddSeparator(string.Empty);
|
|
menu.AddItem(CoreEditorUtils.GetContent("Toggle All"), false, () => m_Editors[id].SetAllOverridesTo(true));
|
|
menu.AddItem(CoreEditorUtils.GetContent("Toggle None"), false, () => m_Editors[id].SetAllOverridesTo(false));
|
|
|
|
menu.DropDown(new Rect(position, Vector2.zero));
|
|
}
|
|
|
|
// Copy/pasting is simply done by creating an in memory copy of the selected component and
|
|
// copying over the serialized data to another; it doesn't use nor affect the OS clipboard
|
|
bool CanPaste(VolumeComponent targetComponent)
|
|
{
|
|
return s_ClipboardContent != null
|
|
&& s_ClipboardContent.GetType() == targetComponent.GetType();
|
|
}
|
|
|
|
void CopySettings(VolumeComponent targetComponent)
|
|
{
|
|
if (s_ClipboardContent != null)
|
|
{
|
|
CoreUtils.Destroy(s_ClipboardContent);
|
|
s_ClipboardContent = null;
|
|
}
|
|
|
|
s_ClipboardContent = (VolumeComponent)CreateInstance(targetComponent.GetType());
|
|
EditorUtility.CopySerializedIfDifferent(targetComponent, s_ClipboardContent);
|
|
}
|
|
|
|
void PasteSettings(VolumeComponent targetComponent)
|
|
{
|
|
Assert.IsNotNull(s_ClipboardContent);
|
|
Assert.AreEqual(s_ClipboardContent.GetType(), targetComponent.GetType());
|
|
|
|
Undo.RecordObject(targetComponent, "Paste Settings");
|
|
EditorUtility.CopySerializedIfDifferent(s_ClipboardContent, targetComponent);
|
|
}
|
|
|
|
void OnUndoRedoPerformed()
|
|
{
|
|
actualTarget.isDirty = true;
|
|
|
|
// Dumb hack to make sure the serialized object is up to date on undo
|
|
serializedObject.Update();
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
// Seems like there's an issue with the inspector not repainting after some undo events
|
|
// This will take care of that
|
|
Repaint();
|
|
}
|
|
}
|
|
}
|