#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 s_AllEditors = new List(); 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().Select(t => t.GetComponent()).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() .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.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 s_ContentCache = new Dictionary(); 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()); } } } Dictionary> m_PropertyToProp = new Dictionary>(); 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(); 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 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.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 s_fnGetSortingLayerField = GetSortingLayerField(); private static Action 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(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(VisualEffectAssetEditorUtility.editorResourcesPath + (EditorGUIUtility.isProSkin ? "/VFX/cat-background-dark.png" : "/VFX/cat-background-light.png")); } } } }