You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1539 lines
77 KiB
1539 lines
77 KiB
#define WORKAROUND_TIMELINE
|
|
|
|
using System;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.VFX;
|
|
|
|
using UnityEditor.Overlays;
|
|
using UnityEditor.VFX.UI;
|
|
|
|
using UnityObject = UnityEngine.Object;
|
|
|
|
namespace UnityEditor.VFX
|
|
{
|
|
static class VisualEffectControl
|
|
{
|
|
public static void ControlStop(this VisualEffect effect)
|
|
{
|
|
effect.Reinit();
|
|
effect.pause = true;
|
|
}
|
|
|
|
public static void ControlPlayPause(this VisualEffect effect)
|
|
{
|
|
effect.pause = !effect.pause;
|
|
}
|
|
|
|
public static void ControlStep(this VisualEffect effect)
|
|
{
|
|
effect.pause = true;
|
|
effect.AdvanceOneFrame();
|
|
}
|
|
|
|
public static void ControlRestart(this VisualEffect effect)
|
|
{
|
|
effect.Reinit();
|
|
effect.pause = false;
|
|
}
|
|
|
|
public const float minSlider = 1;
|
|
public const float maxSlider = 4000;
|
|
|
|
public const float playRateToValue = 100.0f;
|
|
public const float valueToPlayRate = 1.0f / playRateToValue;
|
|
|
|
public const float sliderPower = 10;
|
|
|
|
public static readonly int[] setPlaybackValues = new int[] { 1, 10, 50, 100, 200, 500, 1000, 4000 };
|
|
}
|
|
|
|
|
|
class VisualEffectEditor : Editor
|
|
{
|
|
const string kGeneralFoldoutStatePreferenceName = "VFX.VisualEffectEditor.Foldout.General";
|
|
const string kRendererFoldoutStatePreferenceName = "VFX.VisualEffectEditor.Foldout.Renderer";
|
|
const string kInstancingFoldoutStatePreferenceName = "VFX.VisualEffectEditor.Foldout.Instancing";
|
|
const string kPropertyFoldoutStatePreferenceName = "VFX.VisualEffectEditor.Foldout.Properties";
|
|
|
|
bool showGeneralCategory;
|
|
bool showRendererCategory;
|
|
bool showInstancingCategory;
|
|
bool showPropertyCategory;
|
|
|
|
protected SerializedProperty m_VisualEffectAsset;
|
|
SerializedProperty m_ReseedOnPlay;
|
|
SerializedProperty m_InitialEventName;
|
|
SerializedProperty m_InitialEventNameOverriden;
|
|
SerializedProperty m_RandomSeed;
|
|
SerializedProperty m_VFXPropertySheet;
|
|
SerializedProperty m_AllowInstancing;
|
|
|
|
RendererEditor m_RendererEditor;
|
|
|
|
static SerializedObject s_FakeObjectSerializedCache;
|
|
|
|
static readonly List<VisualEffectEditor> s_AllEditors = new List<VisualEffectEditor>();
|
|
|
|
public static void RepaintAllEditors()
|
|
{
|
|
foreach (var ed in s_AllEditors)
|
|
{
|
|
ed.Repaint();
|
|
}
|
|
}
|
|
|
|
SerializedObject m_SingleSerializedObject;
|
|
SerializedObject[] m_OtherSerializedObjects;
|
|
|
|
protected void OnEnable()
|
|
{
|
|
m_SingleSerializedObject = targets.Length == 1 ? serializedObject : new SerializedObject(targets[0]);
|
|
showPropertyCategory = EditorPrefs.GetBool(kPropertyFoldoutStatePreferenceName, true);
|
|
showRendererCategory = EditorPrefs.GetBool(kRendererFoldoutStatePreferenceName, true);
|
|
showInstancingCategory = EditorPrefs.GetBool(kInstancingFoldoutStatePreferenceName, true);
|
|
showGeneralCategory = EditorPrefs.GetBool(kGeneralFoldoutStatePreferenceName, true);
|
|
|
|
m_OtherSerializedObjects = targets.Skip(1).Select(x => new SerializedObject(x)).ToArray();
|
|
s_AllEditors.Add(this);
|
|
m_RandomSeed = serializedObject.FindProperty("m_StartSeed");
|
|
m_ReseedOnPlay = serializedObject.FindProperty("m_ResetSeedOnPlay");
|
|
m_InitialEventName = serializedObject.FindProperty("m_InitialEventName");
|
|
m_InitialEventNameOverriden = serializedObject.FindProperty("m_InitialEventNameOverriden");
|
|
m_VisualEffectAsset = serializedObject.FindProperty("m_Asset");
|
|
m_VFXPropertySheet = m_SingleSerializedObject.FindProperty("m_PropertySheet");
|
|
m_AllowInstancing = serializedObject.FindProperty("m_AllowInstancing");
|
|
|
|
var renderers = targets.Cast<Component>().Select(t => t.GetComponent<VFXRenderer>()).ToArray();
|
|
m_RendererEditor = new RendererEditor(renderers);
|
|
|
|
s_FakeObjectSerializedCache = new SerializedObject(targets[0]);
|
|
s_EffectUi = this;
|
|
}
|
|
|
|
protected void OnDisable()
|
|
{
|
|
OnDisableWithoutResetting();
|
|
if (s_EffectUi == this)
|
|
s_EffectUi = null;
|
|
}
|
|
|
|
protected void OnDisableWithoutResetting()
|
|
{
|
|
if (s_EffectUi == this)
|
|
s_EffectUi = null;
|
|
s_AllEditors.Remove(this);
|
|
}
|
|
|
|
private static bool GenerateMultipleField(ref VFXParameterInfo parameter, SerializedProperty property)
|
|
{
|
|
if (property.propertyType == SerializedPropertyType.Vector4 && parameter.realType != typeof(Color).Name)
|
|
{
|
|
return true;
|
|
}
|
|
else if (property.propertyType == SerializedPropertyType.Vector3 || property.propertyType == SerializedPropertyType.Vector2)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DisplayProperty(ref VFXParameterInfo parameter, GUIContent nameContent, SerializedProperty overridenProperty, SerializedProperty valueProperty, bool overrideMixed, bool valueMixed, out bool overriddenChanged)
|
|
{
|
|
if (parameter.realType == typeof(Matrix4x4).Name
|
|
|| parameter.realType == typeof(GraphicsBuffer).Name)
|
|
{
|
|
overriddenChanged = false;
|
|
return false;
|
|
}
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
var height = 18f;
|
|
if (!EditorGUIUtility.wideMode && GenerateMultipleField(ref parameter, valueProperty))
|
|
{
|
|
height *= 2.0f;
|
|
height += 1;
|
|
}
|
|
|
|
var rect = EditorGUILayout.GetControlRect(false, height);
|
|
|
|
var toggleRect = rect;
|
|
toggleRect.x += EditorGUI.indentLevel * 16;
|
|
toggleRect.yMin += 2.0f;
|
|
toggleRect.width = 18;
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUI.BeginProperty(toggleRect, GUIContent.none, overridenProperty);
|
|
bool newOverriden = EditorGUI.Toggle(toggleRect, overrideMixed ? false : overridenProperty.boolValue, overrideMixed ? Styles.toggleMixedStyle : Styles.toggleStyle);
|
|
EditorGUI.EndProperty();
|
|
overriddenChanged = EditorGUI.EndChangeCheck();
|
|
if (overriddenChanged)
|
|
{
|
|
overridenProperty.boolValue = newOverriden;
|
|
}
|
|
rect.xMin += Styles.overrideWidth + EditorGUI.indentLevel * 16;
|
|
|
|
int saveIndent = EditorGUI.indentLevel; // since we already applied the indentLevel to the rect reset it to zero.
|
|
EditorGUI.indentLevel = 0;
|
|
bool changed = false;
|
|
if (!valueMixed)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUI.BeginProperty(rect, nameContent, valueProperty);
|
|
|
|
if (parameter.min != Mathf.NegativeInfinity && parameter.max != Mathf.Infinity)
|
|
{
|
|
if (valueProperty.propertyType == SerializedPropertyType.Float)
|
|
EditorGUI.Slider(rect, valueProperty, parameter.min, parameter.max, nameContent);
|
|
else
|
|
EditorGUI.IntSlider(rect, valueProperty, (int)parameter.min, (int)parameter.max, nameContent);
|
|
}
|
|
else if (parameter.enumValues != null && parameter.enumValues.Count > 0)
|
|
{
|
|
long currentValue = valueProperty.longValue;
|
|
int newIndex = EditorGUI.Popup(rect, nameContent, (int)currentValue, parameter.enumValues.ToArray());
|
|
if (newIndex != currentValue)
|
|
{
|
|
valueProperty.longValue = newIndex;
|
|
}
|
|
}
|
|
else if (parameter.realType == typeof(Color).Name)
|
|
{
|
|
Vector4 vVal = valueProperty.vector4Value;
|
|
Color c = new Color(vVal.x, vVal.y, vVal.z, vVal.w);
|
|
EditorGUI.BeginChangeCheck();
|
|
c = EditorGUI.ColorField(rect, nameContent, c, true, true, true);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
valueProperty.vector4Value = new Vector4(c.r, c.g, c.b, c.a);
|
|
}
|
|
else if (parameter.realType == typeof(Gradient).Name)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
Gradient newGradient = EditorGUI.GradientField(rect, nameContent, valueProperty.gradientValue, true, ColorSpace.Linear);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
valueProperty.gradientValue = newGradient;
|
|
}
|
|
else if (valueProperty.propertyType == SerializedPropertyType.Vector4)
|
|
{
|
|
SerializedProperty copy = valueProperty.Copy();
|
|
copy.Next(true);
|
|
EditorGUI.MultiPropertyField(rect, new GUIContent[] { new GUIContent("X"), new GUIContent("Y"), new GUIContent("Z"), new GUIContent("W") }, copy, nameContent);
|
|
}
|
|
else if (valueProperty.propertyType == SerializedPropertyType.ObjectReference)
|
|
{
|
|
Type objTyp = typeof(UnityObject);
|
|
if (!string.IsNullOrEmpty(parameter.realType))
|
|
{
|
|
if (parameter.realType.StartsWith("Texture") || parameter.realType.StartsWith("Cubemap"))
|
|
{
|
|
objTyp = typeof(Texture);
|
|
}
|
|
else if (parameter.realType == "Mesh")
|
|
{
|
|
objTyp = typeof(Mesh);
|
|
}
|
|
else if (parameter.realType == "SkinnedMeshRenderer")
|
|
{
|
|
objTyp = typeof(SkinnedMeshRenderer);
|
|
}
|
|
}
|
|
EditorGUI.ObjectField(rect, valueProperty, objTyp, nameContent);
|
|
}
|
|
else
|
|
{
|
|
EditorGUI.PropertyField(rect, valueProperty, nameContent, true);
|
|
}
|
|
EditorGUI.indentLevel = saveIndent;
|
|
EditorGUI.EndProperty();
|
|
changed = EditorGUI.EndChangeCheck();
|
|
}
|
|
else
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
switch (valueProperty.propertyType)
|
|
{
|
|
case SerializedPropertyType.Vector4:
|
|
if (parameter.realType == typeof(Color).Name)
|
|
{
|
|
Vector4 vVal = valueProperty.vector4Value;
|
|
Color c = new Color(vVal.x, vVal.y, vVal.z, vVal.w);
|
|
EditorGUI.BeginChangeCheck();
|
|
c = EditorGUI.ColorField(rect, nameContent, c, true, true, true);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.vector4Value = new Vector4(c.r, c.g, c.b, c.a);
|
|
changed = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector4 result = EditorGUI.Vector4Field(rect, nameContent, Vector4.zero);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.vector4Value = result;
|
|
changed = true;
|
|
}
|
|
}
|
|
break;
|
|
case SerializedPropertyType.Vector3:
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector3 result = EditorGUI.Vector3Field(rect, nameContent, Vector3.zero);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.vector3Value = result;
|
|
changed = true;
|
|
}
|
|
}
|
|
break;
|
|
case SerializedPropertyType.Vector2:
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector2 result = EditorGUI.Vector2Field(rect, nameContent, Vector2.zero);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.vector2Value = result;
|
|
changed = true;
|
|
}
|
|
}
|
|
break;
|
|
case SerializedPropertyType.Boolean:
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
bool result = EditorGUI.Toggle(rect, nameContent, false);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.boolValue = result;
|
|
changed = true;
|
|
}
|
|
}
|
|
break;
|
|
case SerializedPropertyType.ObjectReference:
|
|
{
|
|
Type objTyp = typeof(UnityObject);
|
|
if (!string.IsNullOrEmpty(parameter.realType))
|
|
{
|
|
if (parameter.realType.StartsWith("Texture") || parameter.realType.StartsWith("Cubemap"))
|
|
{
|
|
objTyp = typeof(Texture);
|
|
}
|
|
else if (parameter.realType == "Mesh")
|
|
{
|
|
objTyp = typeof(Mesh);
|
|
}
|
|
}
|
|
EditorGUI.BeginChangeCheck();
|
|
UnityObject result = EditorGUI.ObjectField(rect, nameContent, null, objTyp, false);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.objectReferenceValue = result;
|
|
changed = true;
|
|
}
|
|
}
|
|
break;
|
|
case SerializedPropertyType.Float:
|
|
if (parameter.min != Mathf.NegativeInfinity && parameter.max != Mathf.Infinity)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
float value = EditorGUI.Slider(rect, nameContent, 0, parameter.min, parameter.max);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.floatValue = value;
|
|
changed = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
float value = EditorGUI.FloatField(rect, nameContent, 0);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.floatValue = value;
|
|
changed = true;
|
|
}
|
|
}
|
|
break;
|
|
case SerializedPropertyType.Integer:
|
|
if (parameter.min != Mathf.NegativeInfinity && parameter.max != Mathf.Infinity)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
int value = EditorGUI.IntSlider(rect, nameContent, 0, (int)parameter.min, (int)parameter.max);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.intValue = value;
|
|
changed = true;
|
|
}
|
|
}
|
|
else if (parameter.enumValues != null && parameter.enumValues.Count > 0)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
int newIndex = EditorGUI.Popup(rect, nameContent, (int)0, parameter.enumValues.ToArray());
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.intValue = newIndex;
|
|
changed = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
int value = EditorGUI.IntField(rect, nameContent, 0);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.intValue = value;
|
|
changed = true;
|
|
}
|
|
}
|
|
break;
|
|
case SerializedPropertyType.AnimationCurve:
|
|
EditorGUI.BeginChangeCheck();
|
|
AnimationCurve animationCurve = EditorGUI.CurveField(rect, nameContent, new AnimationCurve());
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.animationCurveValue = animationCurve;
|
|
changed = true;
|
|
}
|
|
break;
|
|
case SerializedPropertyType.Gradient:
|
|
EditorGUI.BeginChangeCheck();
|
|
Gradient newGradient = EditorGUI.GradientField(rect, nameContent, s_DefaultGradient, true);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
valueProperty.gradientValue = newGradient;
|
|
changed = true;
|
|
}
|
|
break;
|
|
default:
|
|
Debug.Assert(parameter.realType != typeof(Gradient).Name);
|
|
break;
|
|
}
|
|
EditorGUI.showMixedValue = false;
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
return changed;
|
|
}
|
|
|
|
static readonly Gradient s_DefaultGradient = new Gradient();
|
|
|
|
protected static object GetObjectValue(SerializedProperty prop)
|
|
{
|
|
switch (prop.propertyType)
|
|
{
|
|
case SerializedPropertyType.Float:
|
|
return prop.floatValue;
|
|
case SerializedPropertyType.Vector3:
|
|
return prop.vector3Value;
|
|
case SerializedPropertyType.Vector2:
|
|
return prop.vector2Value;
|
|
case SerializedPropertyType.Vector4:
|
|
return prop.vector4Value;
|
|
case SerializedPropertyType.ObjectReference:
|
|
return prop.objectReferenceValue;
|
|
case SerializedPropertyType.Integer:
|
|
return prop.intValue;
|
|
case SerializedPropertyType.Boolean:
|
|
return prop.boolValue;
|
|
case SerializedPropertyType.Gradient:
|
|
return prop.gradientValue;
|
|
case SerializedPropertyType.AnimationCurve:
|
|
return prop.animationCurveValue;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected static void SetObjectValue(SerializedProperty prop, object value)
|
|
{
|
|
switch (prop.propertyType)
|
|
{
|
|
case SerializedPropertyType.Float:
|
|
prop.floatValue = (float)value;
|
|
return;
|
|
case SerializedPropertyType.Vector3:
|
|
prop.vector3Value = (Vector3)value;
|
|
return;
|
|
case SerializedPropertyType.Vector2:
|
|
prop.vector2Value = (Vector2)value;
|
|
return;
|
|
case SerializedPropertyType.Vector4:
|
|
if (value is Color)
|
|
prop.vector4Value = (Vector4)(Color)value;
|
|
else
|
|
prop.vector4Value = (Vector4)value;
|
|
return;
|
|
case SerializedPropertyType.ObjectReference:
|
|
prop.objectReferenceValue = (UnityEngine.Object)value;
|
|
return;
|
|
case SerializedPropertyType.Integer:
|
|
if (value is uint)
|
|
prop.longValue = (int)(uint)value;
|
|
else
|
|
prop.intValue = (int)value;
|
|
return;
|
|
case SerializedPropertyType.Boolean:
|
|
prop.boolValue = (bool)value;
|
|
return;
|
|
case SerializedPropertyType.Gradient:
|
|
prop.gradientValue = (Gradient)value;
|
|
return;
|
|
case SerializedPropertyType.AnimationCurve:
|
|
prop.animationCurveValue = (AnimationCurve)value;
|
|
return;
|
|
}
|
|
}
|
|
|
|
protected virtual void SceneViewGUICallback()
|
|
{
|
|
var effects = targets
|
|
.OfType<VisualEffect>()
|
|
.Where(x => x != null)
|
|
.ToList();
|
|
if (effects.Count == 0)
|
|
return;
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if (GUILayout.Button(Contents.GetIcon(Contents.Icon.Stop), Contents.sceneViewButtonWidth))
|
|
{
|
|
effects.ForEach(x => x.ControlStop());
|
|
}
|
|
if (effects.All(x => x.pause))
|
|
{
|
|
if (GUILayout.Button(Contents.GetIcon(Contents.Icon.Play), Contents.sceneViewButtonWidth))
|
|
{
|
|
effects.ForEach(x => x.ControlPlayPause());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GUILayout.Button(Contents.GetIcon(Contents.Icon.Pause), Contents.sceneViewButtonWidth))
|
|
{
|
|
effects.ForEach(x => x.ControlPlayPause());
|
|
}
|
|
}
|
|
|
|
|
|
if (GUILayout.Button(Contents.GetIcon(Contents.Icon.Step), Contents.sceneViewButtonWidth))
|
|
{
|
|
effects.ForEach(x => x.ControlStep());
|
|
}
|
|
if (GUILayout.Button(Contents.GetIcon(Contents.Icon.Restart), Contents.sceneViewButtonWidth))
|
|
{
|
|
effects.ForEach(x => x.ControlRestart());
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
float playRate = effects[0].playRate;
|
|
bool mixedValues = false;
|
|
for (int i = 1; i < effects.Count; i++)
|
|
{
|
|
if (Math.Abs(effects[i].playRate - playRate) > 1e-5)
|
|
{
|
|
mixedValues = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
float playRateValue = playRate * VisualEffectControl.playRateToValue;
|
|
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label(Contents.playRate, Contents.playRateWidth);
|
|
EditorGUI.showMixedValue = mixedValues;
|
|
EditorGUI.BeginChangeCheck();
|
|
var newPlayRateVal = EditorGUILayout.PowerSlider("", (float)Math.Round(playRateValue), VisualEffectControl.minSlider, VisualEffectControl.maxSlider, VisualEffectControl.sliderPower, Contents.powerSliderWidth);
|
|
EditorGUI.showMixedValue = false;
|
|
bool playRateChanged = EditorGUI.EndChangeCheck();
|
|
if (playRateChanged && playRate >= 0)
|
|
{
|
|
effects.ForEach(x => x.playRate = newPlayRateVal * VisualEffectControl.valueToPlayRate);
|
|
}
|
|
|
|
var eventType = Event.current.type;
|
|
if (EditorGUILayout.DropdownButton(Contents.setPlayRate, FocusType.Passive, Contents.playRateDropdownWidth))
|
|
{
|
|
GenericMenu menu = new GenericMenu();
|
|
foreach (var value in VisualEffectControl.setPlaybackValues)
|
|
{
|
|
menu.AddItem(EditorGUIUtility.TextContent(string.Format("{0}%", value)), false, SetPlayRate, value);
|
|
}
|
|
var savedEventType = Event.current.type;
|
|
Event.current.type = eventType;
|
|
Rect buttonRect = GUILayoutUtility.GetLastRect();
|
|
Event.current.type = savedEventType;
|
|
menu.DropDown(buttonRect);
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label("Show Bounds", Contents.showToggleLabelsWidth);
|
|
VisualEffectUtility.renderBounds = EditorGUILayout.Toggle(VisualEffectUtility.renderBounds, Contents.showToggleWidth);
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label("Show Event Tester", Contents.showToggleLabelsWidth);
|
|
VFXEventTesterWindow.visible = EditorGUILayout.Toggle(VFXEventTesterWindow.visible, Contents.showToggleWidth);
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if (GUILayout.Button(Contents.play))
|
|
effects.ForEach(x => x.Play());
|
|
if (GUILayout.Button(Contents.stop))
|
|
effects.ForEach(x => x.Stop());
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
void SetPlayRate(object value)
|
|
{
|
|
float rate = (int)value * VisualEffectControl.valueToPlayRate;
|
|
foreach (var visualEffect in targets.OfType<VisualEffect>())
|
|
{
|
|
visualEffect.playRate = rate;
|
|
}
|
|
}
|
|
|
|
static VisualEffectEditor s_EffectUi;
|
|
|
|
[Overlay(typeof(SceneView), k_OverlayId, k_DisplayName)]
|
|
class SceneViewVFXSlotContainerOverlay : IMGUIOverlay, ITransientOverlay
|
|
{
|
|
const string k_OverlayId = "Scene View/Visual Effect";
|
|
const string k_DisplayName = "Visual Effect";
|
|
|
|
public bool visible => s_EffectUi != null;
|
|
|
|
public override void OnGUI()
|
|
{
|
|
if (s_EffectUi == null)
|
|
return;
|
|
|
|
s_EffectUi.SceneViewGUICallback();
|
|
}
|
|
}
|
|
|
|
private VFXGraph m_graph;
|
|
|
|
protected struct NameNTooltip
|
|
{
|
|
public string name;
|
|
public string tooltip;
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
if (name == null)
|
|
return 0;
|
|
|
|
if (tooltip == null)
|
|
return name.GetHashCode();
|
|
|
|
return name.GetHashCode() ^ (tooltip.GetHashCode() << sizeof(int) * 4);
|
|
}
|
|
}
|
|
|
|
|
|
static Dictionary<NameNTooltip, GUIContent> s_ContentCache = new Dictionary<NameNTooltip, GUIContent>();
|
|
|
|
|
|
protected GUIContent GetGUIContent(string name, string tooltip = null)
|
|
{
|
|
GUIContent result = null;
|
|
var nnt = new NameNTooltip { name = name, tooltip = tooltip };
|
|
if (!s_ContentCache.TryGetValue(nnt, out result))
|
|
{
|
|
s_ContentCache[nnt] = result = new GUIContent(name, tooltip);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected virtual void EmptyLineControl(string name, string tooltip, VFXSpace? space, int depth, VisualEffectResource resource)
|
|
{
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Space(Styles.overrideWidth);
|
|
|
|
if (space != null && space != VFXSpace.None)
|
|
name += $" (in {space} Space)";
|
|
|
|
var guiContent = GetGUIContent(name, tooltip);
|
|
EditorGUILayout.LabelField(guiContent, EditorStyles.label);
|
|
GUILayout.FlexibleSpace();
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
protected virtual void EditorModeInspectorButton()
|
|
{
|
|
}
|
|
|
|
public static bool ShowHeader(GUIContent nameContent, bool displayFoldout, bool foldoutState, string preferenceName = null)
|
|
{
|
|
float height = Styles.categoryHeader.CalcHeight(nameContent, 4000) + 3;
|
|
|
|
Rect rect = GUILayoutUtility.GetRect(1, height - 1);
|
|
|
|
rect.width += rect.x;
|
|
rect.x = 0;
|
|
|
|
if (Event.current.type == EventType.Repaint)
|
|
Styles.categoryHeader.Draw(rect, nameContent, false, true, true, false);
|
|
|
|
bool result = false;
|
|
if (displayFoldout)
|
|
{
|
|
rect.x += 14;
|
|
rect.width -= 2;
|
|
result = EditorGUI.Toggle(rect, foldoutState, Styles.foldoutStyle);
|
|
}
|
|
|
|
EditorGUI.indentLevel = result ? 1 : 0;
|
|
|
|
if (preferenceName != null && result != foldoutState)
|
|
{
|
|
EditorPrefs.SetBool(preferenceName, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected virtual void AssetField(VisualEffectResource resource)
|
|
{
|
|
EditorGUILayout.PropertyField(m_VisualEffectAsset, Contents.assetPath);
|
|
}
|
|
|
|
void SeedField()
|
|
{
|
|
using (new GUILayout.HorizontalScope())
|
|
{
|
|
using (new EditorGUI.DisabledGroupScope(m_ReseedOnPlay.boolValue || m_ReseedOnPlay.hasMultipleDifferentValues))
|
|
{
|
|
EditorGUILayout.PropertyField(m_RandomSeed, Contents.randomSeed);
|
|
if (GUILayout.Button(Contents.setRandomSeed, EditorStyles.miniButton, Styles.MiniButtonWidth))
|
|
{
|
|
foreach (VisualEffect ve in targets)
|
|
{
|
|
var singleSerializedObject = new SerializedObject(ve);
|
|
var singleProperty = singleSerializedObject.FindProperty("m_StartSeed");
|
|
singleProperty.intValue = UnityEngine.Random.Range(0, int.MaxValue);
|
|
singleSerializedObject.ApplyModifiedProperties();
|
|
ve.startSeed = (uint)singleProperty.intValue;
|
|
}
|
|
serializedObject.Update();
|
|
}
|
|
}
|
|
}
|
|
EditorGUILayout.PropertyField(m_ReseedOnPlay, Contents.reseedOnPlay);
|
|
}
|
|
|
|
static readonly GUIContent exampleGUIContent = new GUIContent("Aq");
|
|
|
|
void InitialEventField(VisualEffectResource resource)
|
|
{
|
|
if (m_InitialEventName == null)
|
|
return;
|
|
|
|
bool changed = false;
|
|
using (new GUILayout.HorizontalScope())
|
|
{
|
|
var rect = EditorGUILayout.GetControlRect(false, GUI.skin.textField.CalcHeight(exampleGUIContent, 10000));
|
|
var toggleRect = rect;
|
|
toggleRect.yMin += 2.0f;
|
|
toggleRect.width = Styles.overrideWidth;
|
|
|
|
s_FakeObjectSerializedCache.Update();
|
|
var fakeInitialEventNameField = s_FakeObjectSerializedCache.FindProperty("m_InitialEventName");
|
|
fakeInitialEventNameField.stringValue = resource != null ? resource.initialEventName : "OnPlay";
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUI.BeginProperty(toggleRect, GUIContent.none, m_InitialEventNameOverriden);
|
|
bool resultOverriden = EditorGUI.Toggle(toggleRect, m_InitialEventNameOverriden.boolValue, Styles.toggleStyle);
|
|
EditorGUI.EndProperty();
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
m_InitialEventNameOverriden.boolValue = resultOverriden;
|
|
changed = true;
|
|
}
|
|
|
|
rect.xMin += Styles.overrideWidth;
|
|
var save = EditorGUI.indentLevel;
|
|
EditorGUI.indentLevel = 0;
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
SerializedProperty intialEventName = m_InitialEventNameOverriden.boolValue ? m_InitialEventName : fakeInitialEventNameField;
|
|
|
|
EditorGUI.PropertyField(rect, intialEventName);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
if (!m_InitialEventNameOverriden.boolValue)
|
|
{
|
|
m_InitialEventNameOverriden.boolValue = true;
|
|
s_FakeObjectSerializedCache.ApplyModifiedPropertiesWithoutUndo();
|
|
m_InitialEventName.stringValue = intialEventName.stringValue;
|
|
}
|
|
changed = true;
|
|
}
|
|
EditorGUI.indentLevel = save;
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
|
|
bool ShowCategory(GUIContent nameContent, bool foldoutState)
|
|
{
|
|
//bool currentState = EditorGUILayout.Toggle(GUIContent.none, prevState, Styles.foldoutStyle);
|
|
|
|
float height = Styles.foldoutStyle.CalcHeight(nameContent, 4000) + 3;
|
|
|
|
Rect rect = GUILayoutUtility.GetRect(1, height);
|
|
|
|
rect.width += rect.x;
|
|
rect.x += Styles.foldoutStyle.CalcSize(GUIContent.none).x;
|
|
rect.y += 3;
|
|
|
|
EditorGUI.LabelField(rect, nameContent, EditorStyles.boldLabel);
|
|
|
|
bool result = false;
|
|
rect.x = 14;
|
|
|
|
rect.width -= 2;
|
|
result = EditorGUI.Toggle(rect, foldoutState, Styles.foldoutStyle);
|
|
|
|
return result;
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
GUILayout.Space(6);
|
|
showGeneralCategory = ShowHeader(Contents.headerGeneral, true, showGeneralCategory, kGeneralFoldoutStatePreferenceName);
|
|
|
|
m_SingleSerializedObject.Update();
|
|
if (m_OtherSerializedObjects != null) // copy the set value to all multi selection by hand, because it might not be at the same array index or already present in the property sheet
|
|
{
|
|
foreach (var serobj in m_OtherSerializedObjects)
|
|
{
|
|
serobj.Update();
|
|
}
|
|
}
|
|
|
|
VisualEffectResource resource = null;
|
|
if (!m_VisualEffectAsset.hasMultipleDifferentValues)
|
|
{
|
|
VisualEffect effect = ((VisualEffect)targets[0]);
|
|
var asset = effect.visualEffectAsset;
|
|
if (asset != null)
|
|
{
|
|
resource = asset.GetResource(); //This resource could be null if asset is actually in an AssetBundle
|
|
}
|
|
}
|
|
|
|
if (showGeneralCategory)
|
|
{
|
|
AssetField(resource);
|
|
SeedField();
|
|
}
|
|
|
|
if (!m_VisualEffectAsset.hasMultipleDifferentValues)
|
|
{
|
|
if (showGeneralCategory)
|
|
InitialEventField(resource);
|
|
|
|
DrawRendererProperties();
|
|
DrawInstancingProperties();
|
|
DrawParameters(resource);
|
|
}
|
|
EditorGUI.indentLevel = 0;
|
|
if (serializedObject.ApplyModifiedProperties())
|
|
{
|
|
foreach (var window in VFXViewWindow.GetAllWindows())
|
|
{
|
|
window.OnVisualEffectComponentChanged(targets.Cast<VisualEffect>());
|
|
}
|
|
}
|
|
}
|
|
|
|
Dictionary<string, Dictionary<string, SerializedProperty>> m_PropertyToProp = new Dictionary<string, Dictionary<string, SerializedProperty>>();
|
|
|
|
protected virtual void DrawParameters(VisualEffectResource resource)
|
|
{
|
|
var component = (VisualEffect)target;
|
|
VFXGraph graph = null;
|
|
if (resource != null)
|
|
graph = resource.GetOrCreateGraph();
|
|
|
|
|
|
if (graph == null)
|
|
{
|
|
ShowHeader(Contents.headerProperties, true, showPropertyCategory);
|
|
EditorGUILayout.HelpBox(Contents.graphInBundle.text.ToString(), MessageType.Info, true);
|
|
}
|
|
else
|
|
{
|
|
if (graph.m_ParameterInfo == null)
|
|
{
|
|
graph.BuildParameterInfo();
|
|
}
|
|
|
|
m_PropertyToProp.Clear();
|
|
|
|
foreach (var sheetType in graph.m_ParameterInfo.Select(t => t.sheetType).Where(t => !string.IsNullOrEmpty(t)).Distinct())
|
|
{
|
|
var nameToIndices = new Dictionary<string, SerializedProperty>();
|
|
|
|
var sourceVfxField = m_VFXPropertySheet.FindPropertyRelative(sheetType + ".m_Array");
|
|
for (int i = 0; i < sourceVfxField.arraySize; ++i)
|
|
{
|
|
SerializedProperty sourceProperty = sourceVfxField.GetArrayElementAtIndex(i);
|
|
var nameProperty = sourceProperty.FindPropertyRelative("m_Name").stringValue;
|
|
|
|
nameToIndices[nameProperty] = sourceProperty;
|
|
}
|
|
m_PropertyToProp[sheetType] = nameToIndices;
|
|
}
|
|
|
|
|
|
if (graph.m_ParameterInfo != null)
|
|
{
|
|
showPropertyCategory = ShowHeader(Contents.headerProperties, true, showPropertyCategory, kPropertyFoldoutStatePreferenceName);
|
|
|
|
if (showPropertyCategory)
|
|
{
|
|
var stack = new List<int>();
|
|
int currentCount = graph.m_ParameterInfo.Length;
|
|
if (currentCount == 0)
|
|
{
|
|
GUILayout.Label("No Property exposed in the Visual Effect Graph");
|
|
}
|
|
else
|
|
{
|
|
EditorModeInspectorButton();
|
|
}
|
|
|
|
bool ignoreUntilNextCat = false;
|
|
|
|
foreach (var param in graph.m_ParameterInfo)
|
|
{
|
|
EditorGUI.indentLevel = stack.Count;
|
|
--currentCount;
|
|
|
|
var parameter = param;
|
|
if (parameter.descendantCount > 0)
|
|
{
|
|
stack.Add(currentCount);
|
|
currentCount = parameter.descendantCount;
|
|
}
|
|
|
|
if (currentCount == 0 && stack.Count > 0)
|
|
{
|
|
do
|
|
{
|
|
currentCount = stack.Last();
|
|
stack.RemoveAt(stack.Count - 1);
|
|
}
|
|
while (currentCount == 0);
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(parameter.sheetType))
|
|
{
|
|
if (!string.IsNullOrEmpty(parameter.name))
|
|
{
|
|
if (string.IsNullOrEmpty(parameter.realType)) // This is a category
|
|
{
|
|
bool wasIgnored = ignoreUntilNextCat;
|
|
ignoreUntilNextCat = false;
|
|
var nameContent = GetGUIContent(parameter.name);
|
|
|
|
bool prevState = EditorPrefs.GetBool("VFX-category-" + parameter.name, true);
|
|
bool currentState = ShowCategory(nameContent, prevState);
|
|
|
|
if (currentState != prevState)
|
|
{
|
|
EditorPrefs.SetBool("VFX-category-" + parameter.name, currentState);
|
|
}
|
|
|
|
if (!currentState)
|
|
ignoreUntilNextCat = true;
|
|
}
|
|
else if (!ignoreUntilNextCat)
|
|
EmptyLineControl(parameter.name, parameter.tooltip, parameter.spaceable ? parameter.space : (VFXSpace?)null, stack.Count, resource);
|
|
}
|
|
}
|
|
else if (!ignoreUntilNextCat)
|
|
{
|
|
SerializedProperty sourceProperty = null;
|
|
|
|
m_PropertyToProp[parameter.sheetType].TryGetValue(parameter.path, out sourceProperty);
|
|
|
|
//< Prepare potential indirection
|
|
bool wasNewProperty = false;
|
|
bool wasNotOverriddenProperty = false;
|
|
|
|
SerializedProperty actualDisplayedPropertyValue = null;
|
|
SerializedProperty actualDisplayedPropertyOverridden = null;
|
|
if (sourceProperty == null)
|
|
{
|
|
s_FakeObjectSerializedCache.Update();
|
|
var fakeField = s_FakeObjectSerializedCache.FindProperty("m_PropertySheet." + parameter.sheetType + ".m_Array");
|
|
fakeField.InsertArrayElementAtIndex(fakeField.arraySize);
|
|
var newFakeEntry = fakeField.GetArrayElementAtIndex(fakeField.arraySize - 1);
|
|
newFakeEntry.FindPropertyRelative("m_Name").stringValue = param.path;
|
|
newFakeEntry.FindPropertyRelative("m_Overridden").boolValue = false;
|
|
|
|
actualDisplayedPropertyOverridden = newFakeEntry.FindPropertyRelative("m_Overridden");
|
|
actualDisplayedPropertyValue = newFakeEntry.FindPropertyRelative("m_Value");
|
|
SetObjectValue(actualDisplayedPropertyValue, parameter.defaultValue.Get());
|
|
|
|
wasNewProperty = true;
|
|
}
|
|
else
|
|
{
|
|
actualDisplayedPropertyOverridden = sourceProperty.FindPropertyRelative("m_Overridden");
|
|
actualDisplayedPropertyValue = sourceProperty.FindPropertyRelative("m_Value");
|
|
if (!actualDisplayedPropertyOverridden.boolValue)
|
|
{
|
|
s_FakeObjectSerializedCache.Update();
|
|
|
|
actualDisplayedPropertyOverridden = s_FakeObjectSerializedCache.FindProperty(actualDisplayedPropertyOverridden.propertyPath);
|
|
actualDisplayedPropertyValue = s_FakeObjectSerializedCache.FindProperty(actualDisplayedPropertyValue.propertyPath);
|
|
SetObjectValue(actualDisplayedPropertyValue, parameter.defaultValue.Get());
|
|
|
|
wasNotOverriddenProperty = true;
|
|
}
|
|
}
|
|
|
|
//< Actual display
|
|
GUIContent nameContent = GetGUIContent(parameter.name, parameter.tooltip);
|
|
|
|
bool wasOverriden = actualDisplayedPropertyOverridden.boolValue;
|
|
|
|
bool overrideMixed = false;
|
|
bool valueMixed = false;
|
|
if (m_OtherSerializedObjects != null) // copy the set value to all multi selection by hand, because it might not be at the same array index or already present in the property sheet
|
|
{
|
|
foreach (var otherObject in m_OtherSerializedObjects)
|
|
{
|
|
var otherSourceVfxField = otherObject.FindProperty("m_PropertySheet." + parameter.sheetType + ".m_Array");
|
|
SerializedProperty otherSourceProperty = null;
|
|
for (int i = 0; i < otherSourceVfxField.arraySize; ++i)
|
|
{
|
|
otherSourceProperty = otherSourceVfxField.GetArrayElementAtIndex(i);
|
|
var nameProperty = otherSourceProperty.FindPropertyRelative("m_Name").stringValue;
|
|
if (nameProperty == parameter.path)
|
|
{
|
|
break;
|
|
}
|
|
otherSourceProperty = null;
|
|
}
|
|
|
|
if (otherSourceProperty != null)
|
|
{
|
|
overrideMixed = overrideMixed || (wasOverriden != otherSourceProperty.FindPropertyRelative("m_Overridden").boolValue);
|
|
}
|
|
else
|
|
{
|
|
overrideMixed = overrideMixed || wasOverriden;
|
|
}
|
|
if (overrideMixed)
|
|
break;
|
|
}
|
|
|
|
if (overrideMixed)
|
|
valueMixed = true;
|
|
else
|
|
{
|
|
foreach (var otherObject in m_OtherSerializedObjects)
|
|
{
|
|
var otherSourceVfxField = otherObject.FindProperty("m_PropertySheet." + parameter.sheetType + ".m_Array");
|
|
SerializedProperty otherSourceProperty = null;
|
|
for (int i = 0; i < otherSourceVfxField.arraySize; ++i)
|
|
{
|
|
otherSourceProperty = otherSourceVfxField.GetArrayElementAtIndex(i);
|
|
var nameProperty = otherSourceProperty.FindPropertyRelative("m_Name").stringValue;
|
|
if (nameProperty == parameter.path)
|
|
break;
|
|
otherSourceProperty = null;
|
|
}
|
|
|
|
if (otherSourceProperty != null)
|
|
{
|
|
var otherValue = GetObjectValue(otherSourceProperty.FindPropertyRelative("m_Value"));
|
|
if (otherValue == null)
|
|
valueMixed = valueMixed || GetObjectValue(actualDisplayedPropertyValue) != null;
|
|
else
|
|
valueMixed = valueMixed || !otherValue.Equals(GetObjectValue(actualDisplayedPropertyValue));
|
|
}
|
|
|
|
if (valueMixed)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
bool overridenChanged = false;
|
|
if (DisplayProperty(ref parameter, nameContent, actualDisplayedPropertyOverridden, actualDisplayedPropertyValue, overrideMixed, valueMixed, out overridenChanged) || overridenChanged)
|
|
{
|
|
if (!overridenChanged) // the value has changed
|
|
{
|
|
if (m_OtherSerializedObjects != null) // copy the set value to all multi selection by hand, because it might not be at the same array index or already present in the property sheet
|
|
{
|
|
foreach (var otherObject in m_OtherSerializedObjects)
|
|
{
|
|
var singleSourceVfxField = otherObject.FindProperty("m_PropertySheet." + parameter.sheetType + ".m_Array");
|
|
SerializedProperty singleSourceProperty = null;
|
|
for (int i = 0; i < singleSourceVfxField.arraySize; ++i)
|
|
{
|
|
singleSourceProperty = singleSourceVfxField.GetArrayElementAtIndex(i);
|
|
var nameProperty = singleSourceProperty.FindPropertyRelative("m_Name").stringValue;
|
|
if (nameProperty == parameter.path)
|
|
{
|
|
break;
|
|
}
|
|
singleSourceProperty = null;
|
|
}
|
|
if (singleSourceProperty == null)
|
|
{
|
|
singleSourceVfxField.InsertArrayElementAtIndex(singleSourceVfxField.arraySize);
|
|
var newEntry = singleSourceVfxField.GetArrayElementAtIndex(singleSourceVfxField.arraySize - 1);
|
|
|
|
newEntry.FindPropertyRelative("m_Overridden").boolValue = true;
|
|
SetObjectValue(newEntry.FindPropertyRelative("m_Value"), GetObjectValue(actualDisplayedPropertyValue));
|
|
newEntry.FindPropertyRelative("m_Name").stringValue = param.path;
|
|
PropertyOverrideChanged();
|
|
}
|
|
else
|
|
{
|
|
singleSourceProperty.FindPropertyRelative("m_Overridden").boolValue = true;
|
|
SetObjectValue(singleSourceProperty.FindPropertyRelative("m_Value"), GetObjectValue(actualDisplayedPropertyValue));
|
|
}
|
|
otherObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
}
|
|
if (wasNewProperty)
|
|
{
|
|
var sourceVfxField = m_VFXPropertySheet.FindPropertyRelative(parameter.sheetType + ".m_Array");
|
|
//We start editing a new exposed value which wasn't stored in this Visual Effect Component
|
|
sourceVfxField.InsertArrayElementAtIndex(sourceVfxField.arraySize);
|
|
var newEntry = sourceVfxField.GetArrayElementAtIndex(sourceVfxField.arraySize - 1);
|
|
|
|
newEntry.FindPropertyRelative("m_Overridden").boolValue = true;
|
|
SetObjectValue(newEntry.FindPropertyRelative("m_Value"), GetObjectValue(actualDisplayedPropertyValue));
|
|
newEntry.FindPropertyRelative("m_Name").stringValue = param.path;
|
|
PropertyOverrideChanged();
|
|
}
|
|
else if (wasNotOverriddenProperty && !overridenChanged)
|
|
{
|
|
if (!actualDisplayedPropertyOverridden.boolValue)
|
|
{
|
|
//The value has been directly changed, change overridden state and recopy new value
|
|
SetObjectValue(sourceProperty.FindPropertyRelative("m_Value"), GetObjectValue(actualDisplayedPropertyValue));
|
|
}
|
|
sourceProperty.FindPropertyRelative("m_Overridden").boolValue = true;
|
|
PropertyOverrideChanged();
|
|
}
|
|
else if (wasOverriden != actualDisplayedPropertyOverridden.boolValue)
|
|
{
|
|
sourceProperty.FindPropertyRelative("m_Overridden").boolValue = actualDisplayedPropertyOverridden.boolValue;
|
|
if (m_OtherSerializedObjects != null) // copy the set value to all multi selection by hand, because it might not be at the same array index or already present in the property sheet
|
|
{
|
|
foreach (var otherObject in m_OtherSerializedObjects)
|
|
{
|
|
var otherSourceVfxField = otherObject.FindProperty("m_PropertySheet." + parameter.sheetType + ".m_Array");
|
|
SerializedProperty otherSourceProperty = null;
|
|
for (int i = 0; i < otherSourceVfxField.arraySize; ++i)
|
|
{
|
|
otherSourceProperty = otherSourceVfxField.GetArrayElementAtIndex(i);
|
|
var nameProperty = otherSourceProperty.FindPropertyRelative("m_Name").stringValue;
|
|
if (nameProperty == parameter.path)
|
|
{
|
|
break;
|
|
}
|
|
otherSourceProperty = null;
|
|
}
|
|
if (otherSourceProperty == null)
|
|
{
|
|
if (!wasOverriden)
|
|
{
|
|
otherSourceVfxField.InsertArrayElementAtIndex(otherSourceVfxField.arraySize);
|
|
var newEntry = otherSourceVfxField.GetArrayElementAtIndex(otherSourceVfxField.arraySize - 1);
|
|
|
|
newEntry.FindPropertyRelative("m_Overridden").boolValue = true;
|
|
SetObjectValue(newEntry.FindPropertyRelative("m_Value"), GetObjectValue(actualDisplayedPropertyValue));
|
|
newEntry.FindPropertyRelative("m_Name").stringValue = param.path;
|
|
PropertyOverrideChanged();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
otherSourceProperty.FindPropertyRelative("m_Overridden").boolValue = !wasOverriden;
|
|
if (!wasOverriden)
|
|
{
|
|
SetObjectValue(otherSourceProperty.FindPropertyRelative("m_Value"), GetObjectValue(actualDisplayedPropertyValue));
|
|
}
|
|
PropertyOverrideChanged();
|
|
}
|
|
otherObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
|
|
PropertyOverrideChanged();
|
|
}
|
|
m_SingleSerializedObject.ApplyModifiedProperties();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
GUILayout.Space(1); // Space for the line if the last category is closed.
|
|
}
|
|
|
|
protected virtual void PropertyOverrideChanged() { }
|
|
|
|
private void DrawRendererProperties()
|
|
{
|
|
showRendererCategory = ShowHeader(Contents.headerRenderer, true, showRendererCategory, kRendererFoldoutStatePreferenceName);
|
|
|
|
if (showRendererCategory)
|
|
m_RendererEditor.OnInspectorGUI();
|
|
}
|
|
|
|
private void DrawInstancingProperties()
|
|
{
|
|
showInstancingCategory = ShowHeader(Contents.headerInstancing, true, showInstancingCategory, kInstancingFoldoutStatePreferenceName);
|
|
|
|
if (showInstancingCategory)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
EditorGUILayout.PropertyField(m_AllowInstancing, Contents.allowInstancing);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
foreach (var visualEffect in targets.OfType<VisualEffect>())
|
|
{
|
|
visualEffect.RecreateData();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class RendererEditor
|
|
{
|
|
const string kRendererProbesFoldoutStatePreferenceName = "VFX.VisualEffectEditor.Foldout.Renderer.Probes";
|
|
const string kRendererAdditionnalSettingsFoldoutStatePreferenceName = "VFX.VisualEffectEditor.Foldout.Renderer.AdditionnalSettings";
|
|
|
|
private VFXRenderer[] m_Renderers;
|
|
private SerializedObject m_SerializedRenderers;
|
|
|
|
private SerializedProperty m_RendererPriority;
|
|
private SerializedProperty m_SortingLayerID;
|
|
private SerializedProperty m_SortingOrder;
|
|
private SerializedProperty m_RenderingLayerMask;
|
|
private SerializedProperty m_LightProbeUsage;
|
|
private SerializedProperty m_LightProbeVolumeOverride;
|
|
private SerializedProperty m_ReflectionProbeUsage;
|
|
private SerializedProperty m_ProbeAnchor;
|
|
|
|
private bool m_ShowProbesCategory;
|
|
private bool m_ShowAdditionnalCategory;
|
|
|
|
public RendererEditor(params VFXRenderer[] renderers)
|
|
{
|
|
m_ShowProbesCategory = EditorPrefs.GetBool(kRendererProbesFoldoutStatePreferenceName, true);
|
|
m_ShowAdditionnalCategory = EditorPrefs.GetBool(kRendererAdditionnalSettingsFoldoutStatePreferenceName, true);
|
|
|
|
m_Renderers = renderers;
|
|
m_SerializedRenderers = new SerializedObject(m_Renderers);
|
|
|
|
m_RendererPriority = m_SerializedRenderers.FindProperty("m_RendererPriority");
|
|
m_SortingOrder = m_SerializedRenderers.FindProperty("m_SortingOrder");
|
|
m_SortingLayerID = m_SerializedRenderers.FindProperty("m_SortingLayerID");
|
|
m_RenderingLayerMask = m_SerializedRenderers.FindProperty("m_RenderingLayerMask");
|
|
m_LightProbeUsage = m_SerializedRenderers.FindProperty("m_LightProbeUsage");
|
|
m_LightProbeVolumeOverride = m_SerializedRenderers.FindProperty("m_LightProbeVolumeOverride");
|
|
m_ReflectionProbeUsage = m_SerializedRenderers.FindProperty("m_ReflectionProbeUsage");
|
|
m_ProbeAnchor = m_SerializedRenderers.FindProperty("m_ProbeAnchor");
|
|
}
|
|
|
|
public SerializedObject SerializedRenderers => m_SerializedRenderers;
|
|
|
|
public static readonly Action<GUIContent, SerializedProperty, GUIStyle, GUIStyle> s_fnGetSortingLayerField = GetSortingLayerField();
|
|
|
|
private static Action<GUIContent, SerializedProperty, GUIStyle, GUIStyle> GetSortingLayerField()
|
|
{
|
|
//Find UnityEditor.EditorGUILayout.SortingLayerField by reflection to avoid any breakage due to an API change
|
|
var type = typeof(EditorGUILayout);
|
|
var function = type.GetMethods(BindingFlags.Static | BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public)
|
|
.FirstOrDefault(f => f.Name == "SortingLayerField" && f.GetParameters().Length == 4);
|
|
if (function != null)
|
|
{
|
|
var parameters = function.GetParameters();
|
|
if (parameters[0].ParameterType == typeof(GUIContent)
|
|
&& parameters[1].ParameterType == typeof(SerializedProperty)
|
|
&& parameters[2].ParameterType == typeof(GUIStyle)
|
|
&& parameters[3].ParameterType == typeof(GUIStyle))
|
|
{
|
|
return delegate (GUIContent label, SerializedProperty layerID, GUIStyle style, GUIStyle labelStyle)
|
|
{
|
|
function.Invoke(null, new object[] { label, layerID, style, labelStyle });
|
|
};
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static void SortingLayerField(GUIContent label, SerializedProperty layerID, GUIStyle style, GUIStyle labelStyle)
|
|
{
|
|
if (s_fnGetSortingLayerField == null)
|
|
return;
|
|
s_fnGetSortingLayerField.Invoke(label, layerID, style, labelStyle);
|
|
}
|
|
|
|
static bool HasPrefabOverride(SerializedProperty property)
|
|
{
|
|
return property != null && property.serializedObject.targetObjectsCount == 1 && property.isInstantiatedPrefab && property.prefabOverride;
|
|
}
|
|
|
|
public void OnInspectorGUI()
|
|
{
|
|
m_SerializedRenderers.Update();
|
|
|
|
EditorGUI.indentLevel += 1;
|
|
// Ugly hack to indent the header group because "indentLevel" is not taken into account
|
|
var x = EditorStyles.inspectorDefaultMargins.padding.left;
|
|
EditorStyles.inspectorDefaultMargins.padding.left -= 24;
|
|
bool showProbesCategory = EditorGUILayout.BeginFoldoutHeaderGroup(m_ShowProbesCategory, Contents.probeSettings);
|
|
if (showProbesCategory != m_ShowProbesCategory)
|
|
{
|
|
m_ShowProbesCategory = showProbesCategory;
|
|
EditorPrefs.SetBool(kRendererProbesFoldoutStatePreferenceName, m_ShowProbesCategory);
|
|
}
|
|
|
|
if (m_ShowProbesCategory)
|
|
{
|
|
bool showReflectionProbeUsage = m_ReflectionProbeUsage != null && SupportedRenderingFeatures.active.reflectionProbes;
|
|
|
|
var srpAssetType = GraphicsSettings.currentRenderPipelineAssetType;
|
|
if (srpAssetType is not null && srpAssetType.ToString().Contains("UniversalRenderPipeline"))
|
|
{
|
|
//Reflection Probe Usage option has been removed in URP but the VFXRenderer uses ReflectionProbeUsage.Off by default
|
|
//We are temporarily letting this option reachable until the C++ doesn't change the default value
|
|
showReflectionProbeUsage = m_ReflectionProbeUsage != null;
|
|
}
|
|
|
|
if (showReflectionProbeUsage)
|
|
{
|
|
Rect r = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup);
|
|
EditorGUI.BeginProperty(r, Contents.reflectionProbeUsageStyle, m_ReflectionProbeUsage);
|
|
EditorGUI.BeginChangeCheck();
|
|
var newValue = EditorGUI.EnumPopup(r, Contents.reflectionProbeUsageStyle, (ReflectionProbeUsage)m_ReflectionProbeUsage.intValue);
|
|
if (EditorGUI.EndChangeCheck())
|
|
m_ReflectionProbeUsage.intValue = (int)(ReflectionProbeUsage)newValue;
|
|
EditorGUI.EndProperty();
|
|
}
|
|
|
|
if (m_LightProbeUsage != null)
|
|
{
|
|
Rect r = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight, EditorStyles.popup);
|
|
EditorGUI.BeginProperty(r, Contents.lightProbeUsageStyle, m_LightProbeUsage);
|
|
EditorGUI.BeginChangeCheck();
|
|
var newValue = EditorGUI.EnumPopup(r, Contents.lightProbeUsageStyle, (LightProbeUsage)m_LightProbeUsage.intValue);
|
|
if (EditorGUI.EndChangeCheck())
|
|
m_LightProbeUsage.intValue = (int)(LightProbeUsage)newValue;
|
|
EditorGUI.EndProperty();
|
|
|
|
if (!m_LightProbeUsage.hasMultipleDifferentValues && m_LightProbeUsage.intValue == (int)LightProbeUsage.UseProxyVolume)
|
|
{
|
|
if (!LightProbeProxyVolume.isFeatureSupported || !SupportedRenderingFeatures.active.lightProbeProxyVolumes)
|
|
EditorGUILayout.HelpBox(Contents.lightProbeVolumeUnsupportedNote.text, MessageType.Warning);
|
|
EditorGUILayout.PropertyField(m_LightProbeVolumeOverride, Contents.lightProbeVolumeOverrideStyle);
|
|
}
|
|
}
|
|
|
|
bool useReflectionProbes = m_ReflectionProbeUsage != null && !m_ReflectionProbeUsage.hasMultipleDifferentValues && (ReflectionProbeUsage)m_ReflectionProbeUsage.intValue != ReflectionProbeUsage.Off;
|
|
bool lightProbesEnabled = m_LightProbeUsage != null && !m_LightProbeUsage.hasMultipleDifferentValues && (LightProbeUsage)m_LightProbeUsage.intValue != LightProbeUsage.Off;
|
|
bool needsProbeAnchor = useReflectionProbes || lightProbesEnabled;
|
|
|
|
if (needsProbeAnchor)
|
|
EditorGUILayout.PropertyField(m_ProbeAnchor, Contents.lightProbeAnchorStyle);
|
|
}
|
|
EditorGUILayout.EndFoldoutHeaderGroup();
|
|
|
|
var showAdditionnalCategory = EditorGUILayout.BeginFoldoutHeaderGroup(m_ShowAdditionnalCategory, Contents.otherSettings);
|
|
if (showAdditionnalCategory != m_ShowAdditionnalCategory)
|
|
{
|
|
m_ShowAdditionnalCategory = showAdditionnalCategory;
|
|
EditorPrefs.SetBool(kRendererAdditionnalSettingsFoldoutStatePreferenceName, m_ShowAdditionnalCategory);
|
|
}
|
|
|
|
if (showAdditionnalCategory)
|
|
{
|
|
if (m_RenderingLayerMask != null && GraphicsSettings.isScriptableRenderPipelineEnabled)
|
|
{
|
|
var mask = m_Renderers[0].renderingLayerMask;
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
mask = EditorGUILayout.RenderingLayerMaskField(Contents.renderingLayerMaskStyle, mask);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObjects(m_SerializedRenderers.targetObjects, "Set rendering layer mask");
|
|
for (var i = 0; i < m_SerializedRenderers.targetObjects.Length; i++)
|
|
{
|
|
var r = m_SerializedRenderers.targetObjects[i] as VFXRenderer;
|
|
if (r == null)
|
|
continue;
|
|
r.renderingLayerMask = mask;
|
|
EditorUtility.SetDirty(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_RendererPriority != null && SupportedRenderingFeatures.active.rendererPriority)
|
|
{
|
|
EditorGUILayout.PropertyField(m_RendererPriority, Contents.rendererPriorityStyle);
|
|
}
|
|
|
|
if (m_SortingOrder != null && m_SortingLayerID != null)
|
|
{
|
|
var hasPrefabOverride = HasPrefabOverride(m_SortingLayerID);
|
|
SortingLayerField(Contents.sortingLayerStyle, m_SortingLayerID, hasPrefabOverride ? Contents.boldPopupStyle : EditorStyles.popup, hasPrefabOverride ? EditorStyles.boldLabel : EditorStyles.label);
|
|
EditorGUILayout.PropertyField(m_SortingOrder, Contents.sortingOrderStyle);
|
|
}
|
|
}
|
|
EditorGUILayout.EndFoldoutHeaderGroup();
|
|
EditorStyles.inspectorDefaultMargins.padding.left = x;
|
|
EditorGUI.indentLevel -= 1;
|
|
|
|
m_SerializedRenderers.ApplyModifiedProperties();
|
|
}
|
|
|
|
private static class Contents
|
|
{
|
|
public static readonly GUIContent renderingLayerMaskStyle = EditorGUIUtility.TrTextContent("Rendering Layer Mask", "Mask that can be used with SRP DrawRenderers command to filter renderers outside of the normal layering system.");
|
|
public static readonly GUIContent rendererPriorityStyle = EditorGUIUtility.TrTextContent("Priority", "Priority used for sorting objects on top of material render queue.");
|
|
public static readonly GUIContent lightProbeUsageStyle = EditorGUIUtility.TrTextContent("Light Probes", "Specifies how Light Probes will handle the interpolation of lighting and occlusion.");
|
|
public static readonly GUIContent reflectionProbeUsageStyle = EditorGUIUtility.TrTextContent("Reflection Probes", "Specifies if or how the object is affected by reflections in the Scene. This property cannot be disabled in deferred rendering modes.");
|
|
public static readonly GUIContent lightProbeVolumeOverrideStyle = EditorGUIUtility.TrTextContent("Proxy Volume Override", "If set, the Renderer will use the Light Probe Proxy Volume component from another GameObject.");
|
|
public static readonly GUIContent lightProbeAnchorStyle = EditorGUIUtility.TrTextContent("Anchor Override", "Specifies the Transform position that will be used for sampling the light probes and reflection probes.");
|
|
public static readonly GUIContent lightProbeVolumeUnsupportedNote = EditorGUIUtility.TrTextContent("The Light Probe Proxy Volume feature is unsupported by the current graphics hardware or API configuration. Simple 'Blend Probes' mode will be used instead.");
|
|
|
|
public static readonly GUIContent probeSettings = EditorGUIUtility.TrTextContent("Probes");
|
|
public static readonly GUIContent otherSettings = EditorGUIUtility.TrTextContent("Additional Settings");
|
|
|
|
public static readonly GUIContent sortingLayerStyle = EditorGUIUtility.TrTextContent("Sorting Layer", "Name of the Renderer's sorting layer");
|
|
public static readonly GUIContent sortingOrderStyle = EditorGUIUtility.TrTextContent("Order in Layer", "Renderer's order within a sorting layer");
|
|
|
|
public static readonly GUIStyle boldPopupStyle = new GUIStyle(EditorStyles.popup) { fontStyle = FontStyle.Bold };
|
|
}
|
|
}
|
|
|
|
protected static class Contents
|
|
{
|
|
public static readonly GUIContent headerPlayControls = EditorGUIUtility.TrTextContent("Play Controls");
|
|
public static readonly GUIContent headerGeneral = EditorGUIUtility.TrTextContent("General");
|
|
public static readonly GUIContent headerProperties = EditorGUIUtility.TrTextContent("Properties");
|
|
public static readonly GUIContent headerRenderer = EditorGUIUtility.TrTextContent("Renderer");
|
|
public static readonly GUIContent headerInstancing = EditorGUIUtility.TrTextContent("Instancing");
|
|
|
|
public static readonly GUIContent assetPath = EditorGUIUtility.TrTextContent("Asset Template", "Sets the Visual Effect Graph asset to be used in this component.");
|
|
public static readonly GUIContent randomSeed = EditorGUIUtility.TrTextContent("Random Seed", "Sets the value used when determining the randomness of the graph. Using the same seed will make the Visual Effect play identically each time.");
|
|
public static readonly GUIContent reseedOnPlay = EditorGUIUtility.TrTextContent("Reseed on play", "When enabled, a new random seed value will be used each time the effect is played. Enable to randomize the look of this Visual Effect.");
|
|
public static readonly GUIContent openEditor = EditorGUIUtility.TrTextContent("Edit", "Opens the currently assigned template for editing within the Visual Effect Graph window.");
|
|
public static readonly GUIContent createAsset = EditorGUIUtility.TrTextContent("New", "Creates a new Visual Effect Graph and opens it for editing within the Visual Effect Graph window.");
|
|
public static readonly GUIContent setRandomSeed = EditorGUIUtility.TrTextContent("Reseed", "When clicked, if ‘Reseed on play’ is disabled a new random seed will be generated.");
|
|
public static readonly GUIContent resetInitialEvent = EditorGUIUtility.TrTextContent("Default");
|
|
public static readonly GUIContent setPlayRate = EditorGUIUtility.TrTextContent("Set");
|
|
public static readonly GUIContent playRate = EditorGUIUtility.TrTextContent("Rate");
|
|
public static readonly GUILayoutOption playRateWidth = GUILayout.Width(46);
|
|
public static readonly GUILayoutOption showToggleLabelsWidth = GUILayout.Width(192);
|
|
public static readonly GUILayoutOption showToggleWidth = GUILayout.Width(18);
|
|
public static readonly GUILayoutOption powerSliderWidth = GUILayout.Width(124);
|
|
public static readonly GUILayoutOption sceneViewButtonWidth = GUILayout.Width(52);
|
|
public static readonly GUILayoutOption playRateDropdownWidth = GUILayout.Width(40);
|
|
|
|
public static readonly GUIContent allowInstancing = EditorGUIUtility.TrTextContent("Allow Instancing", "When enabled, the effect will try to be batched with other of the same type.");
|
|
|
|
public static readonly GUIContent graphInBundle = EditorGUIUtility.TrTextContent("Exposed properties are hidden in the Inspector when Visual Effect Assets are stored in Asset Bundles.");
|
|
public static readonly GUIContent play = new GUIContent("Play()");
|
|
public static readonly GUIContent stop = new GUIContent("Stop()");
|
|
|
|
static readonly GUIContent[] m_Icons;
|
|
|
|
public enum Icon
|
|
{
|
|
Pause,
|
|
Play,
|
|
Restart,
|
|
Step,
|
|
Stop
|
|
}
|
|
static Contents()
|
|
{
|
|
m_Icons = new GUIContent[1 + (int)Icon.Stop];
|
|
for (int i = 0; i <= (int)Icon.Stop; ++i)
|
|
{
|
|
Icon icon = (Icon)i;
|
|
string name = icon.ToString();
|
|
|
|
//TODO replace with editor default resource call when going to trunk
|
|
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(VisualEffectGraphPackageInfo.assetPackagePath + "/Editor/SceneWindow/Textures/" + name + ".png");
|
|
if (texture == null)
|
|
{
|
|
Debug.LogError("Can't find icon for " + name + " in Styles");
|
|
continue;
|
|
}
|
|
m_Icons[i] = new GUIContent(texture);
|
|
}
|
|
}
|
|
|
|
public static GUIContent GetIcon(Icon icon)
|
|
{
|
|
return m_Icons[(int)icon];
|
|
}
|
|
}
|
|
|
|
public static GUISkin GetCurrentSkin()
|
|
{
|
|
return EditorGUIUtility.isProSkin ? EditorGUIUtility.GetBuiltinSkin(EditorSkin.Scene) : EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector);
|
|
}
|
|
|
|
protected static class Styles
|
|
{
|
|
public static readonly GUIStyle foldoutStyle;
|
|
public static readonly GUIStyle toggleStyle;
|
|
public static readonly GUIStyle toggleMixedStyle;
|
|
|
|
public static readonly GUIStyle categoryHeader;
|
|
|
|
public static readonly GUILayoutOption MiniButtonWidth = GUILayout.Width(56);
|
|
public static readonly GUILayoutOption PlayControlsHeight = GUILayout.Height(24);
|
|
public const float overrideWidth = 16;
|
|
|
|
static Styles()
|
|
{
|
|
var builtInSkin = GetCurrentSkin();
|
|
foldoutStyle = new GUIStyle(EditorStyles.foldout);
|
|
foldoutStyle.fontStyle = FontStyle.Bold;
|
|
|
|
toggleStyle = new GUIStyle(builtInSkin.GetStyle("ShurikenToggle"));
|
|
|
|
toggleMixedStyle = new GUIStyle(builtInSkin.GetStyle("ShurikenCheckMarkMixed"));
|
|
categoryHeader = new GUIStyle(builtInSkin.label);
|
|
categoryHeader.fontStyle = FontStyle.Bold;
|
|
categoryHeader.border.left = 2;
|
|
categoryHeader.padding.left = 32;
|
|
categoryHeader.padding.top = 2;
|
|
categoryHeader.border.right = 2;
|
|
|
|
//TODO change to editor resources calls
|
|
categoryHeader.normal.background = (Texture2D)AssetDatabase.LoadAssetAtPath<Texture2D>(VisualEffectAssetEditorUtility.editorResourcesPath + (EditorGUIUtility.isProSkin ? "/VFX/cat-background-dark.png" : "/VFX/cat-background-light.png"));
|
|
}
|
|
}
|
|
}
|
|
}
|