using System.Collections.Generic; using System.Linq; using UnityEditor.EditorTools; using UnityEditor.Rendering.Universal.Path2D; using UnityEngine; using UnityEngine.Rendering.Universal; namespace UnityEditor.Rendering.Universal { [CustomEditor(typeof(Light2D))] [CanEditMultipleObjects] internal class Light2DEditor : PathComponentEditor { [EditorTool("Edit Freeform Shape", typeof(Light2D))] class FreeformShapeTool : PathEditorTool { const string k_ShapePath = "m_ShapePath"; public override bool IsAvailable() { var light = target as Light2D; if (light == null) return false; else return base.IsAvailable() && light.lightType == Light2D.LightType.Freeform; } protected override IShape GetShape(Object target) { return (target as Light2D).shapePath.ToPolygon(false); } protected override void SetShape(ScriptablePath shapeEditor, SerializedObject serializedObject) { serializedObject.Update(); var pointsProperty = serializedObject.FindProperty(k_ShapePath); pointsProperty.arraySize = shapeEditor.pointCount; for (var i = 0; i < shapeEditor.pointCount; ++i) pointsProperty.GetArrayElementAtIndex(i).vector3Value = shapeEditor.GetPoint(i).position; ((Light2D)(serializedObject.targetObject)).UpdateMesh(); // This is untracked right now... serializedObject.ApplyModifiedProperties(); } } private struct ToggleFoldoutResult { public bool foldoutState; public bool toggleState; } private static class Styles { public static readonly GUIContent InnerOuterSpotAngle = EditorGUIUtility.TrTextContent("Inner / Outer Spot Angle", "Adjusts the inner / outer angles of this light to change the angle ranges of this Spot Light’s beam."); public static Texture lightCapTopRight = Resources.Load("LightCapTopRight"); public static Texture lightCapTopLeft = Resources.Load("LightCapTopLeft"); public static Texture lightCapBottomLeft = Resources.Load("LightCapBottomLeft"); public static Texture lightCapBottomRight = Resources.Load("LightCapBottomRight"); public static Texture lightCapUp = Resources.Load("LightCapUp"); public static Texture lightCapDown = Resources.Load("LightCapDown"); public static GUIContent lightTypeFreeform = new GUIContent("Freeform", Resources.Load("InspectorIcons/FreeformLight") as Texture); public static GUIContent lightTypeSprite = new GUIContent("Sprite", Resources.Load("InspectorIcons/SpriteLight") as Texture); public static GUIContent lightTypePoint = new GUIContent("Spot", Resources.Load("InspectorIcons/PointLight") as Texture); public static GUIContent lightTypeGlobal = new GUIContent("Global", Resources.Load("InspectorIcons/GlobalLight") as Texture); public static GUIContent[] lightTypeOptions = new GUIContent[] { lightTypeFreeform, lightTypeSprite, lightTypePoint, lightTypeGlobal }; public static GUIContent blendingSettingsFoldout = EditorGUIUtility.TrTextContent("Blending", "Options used for blending"); public static GUIContent shadowsSettingsFoldout = EditorGUIUtility.TrTextContent("Shadows", "Options used for shadows"); public static GUIContent volumetricSettingsFoldout = EditorGUIUtility.TrTextContent("Volumetric", "Options used for volumetric lighting"); public static GUIContent normalMapsSettingsFoldout = EditorGUIUtility.TrTextContent("Normal Maps", "Options used for normal maps"); public static GUIContent generalLightType = EditorGUIUtility.TrTextContent("Light Type", "Select the light type. \n\nGlobal Light: For ambient light. \nSpot Light: For a spot light / point light. \nFreeform Light: For a custom shape light. \nSprite Light: For a custom light cookie using Sprites."); public static GUIContent generalFalloffSize = EditorGUIUtility.TrTextContent("Falloff", "Adjusts the falloff area of this light. The higher the falloff value, the larger area the falloff spans."); public static GUIContent generalFalloffIntensity = EditorGUIUtility.TrTextContent("Falloff Strength", "Adjusts the falloff curve to control the softness of this light’s edges. The higher the falloff strength, the softer the edges of this light."); public static GUIContent generalLightColor = EditorGUIUtility.TrTextContent("Color", "Adjusts this light’s color."); public static GUIContent generalLightIntensity = EditorGUIUtility.TrTextContent("Intensity", "Adjusts this light’s color intensity by using multiply to brighten the Sprite beyond its original color."); public static GUIContent generalVolumeIntensity = EditorGUIUtility.TrTextContent("Intensity", "Adjusts the intensity of this additional light volume that's additively blended on top of this light. To enable the Volumetric Shadow Strength, increase this Intensity to be greater than 0."); public static GUIContent generalBlendStyle = EditorGUIUtility.TrTextContent("Blend Style", "Adjusts how this light blends with the Sprites on the Target Sorting Layers. Different Blend Styles can be customized in the 2D Renderer Data Asset."); public static GUIContent generalLightOverlapOperation = EditorGUIUtility.TrTextContent("Overlap Operation", "Determines how this light blends with the other lights either through additive or alpha blending."); public static GUIContent generalLightOrder = EditorGUIUtility.TrTextContent("Light Order", "Determines the relative order in which lights of the same Blend Style get rendered. Lights with lower values are rendered first."); public static GUIContent generalShadowIntensity = EditorGUIUtility.TrTextContent("Strength", "Adjusts the amount of light occlusion from the Shadow Caster 2D component(s) when blocking this light.The higher the value, the more opaque the shadow becomes."); public static GUIContent generalShadowSoftness = EditorGUIUtility.TrTextContent("Softness", "Adjusts the amount of softness at the edge of the shadow."); public static GUIContent generalShadowSoftnessFalloffIntensity = EditorGUIUtility.TrTextContent("Falloff Strength", "Adjusts the falloff curve to control the softness of the shadow edges. The higher the falloff strength, the softer the edges of this shadow."); public static GUIContent generalShadowVolumeIntensity = EditorGUIUtility.TrTextContent("Shadow Strength", "Adjusts the amount of volume light occlusion from the Shadow Caster 2D component(s) when blocking this light."); public static GUIContent generalSortingLayerPrefixLabel = EditorGUIUtility.TrTextContent("Target Sorting Layers", "Determines which layers this light affects. To optimize performance, minimize the number of layers this light affects."); public static GUIContent generalLightNoLightEnabled = EditorGUIUtility.TrTextContentWithIcon("No valid blend styles are enabled.", MessageType.Error); public static GUIContent generalNormalMapZDistance = EditorGUIUtility.TrTextContent("Distance", "Adjusts the z-axis distance of this light and the lit Sprite(s). Do note that this distance does not Transform the position of this light in the Scene."); public static GUIContent generalNormalMapLightQuality = EditorGUIUtility.TrTextContent("Quality", "Determines the accuracy of the lighting calculations when normal map is used. To optimize for performance, select Fast."); public static GUIContent pointLightRadius = EditorGUIUtility.TrTextContent("Radius", "Adjusts the inner / outer radius of this light to change the size of this light."); public static GUIContent pointLightInner = EditorGUIUtility.TrTextContent("Inner", "Specify the inner radius of the light"); public static GUIContent pointLightOuter = EditorGUIUtility.TrTextContent("Outer", "Specify the outer radius of the light"); public static GUIContent pointLightSprite = EditorGUIUtility.TrTextContent("Sprite", "Specify the sprite (deprecated)"); public static GUIContent shapeLightSprite = EditorGUIUtility.TrTextContent("Sprite", "Assign a Sprite which acts as a mask to create a light cookie."); public static GUIContent deprecatedParametricLightWarningSingle = EditorGUIUtility.TrTextContentWithIcon("Parametic Lights have been deprecated. To continue, upgrade your Parametric Light to a Freeform Light to enjoy similar light functionality.", MessageType.Warning); public static GUIContent deprecatedParametricLightWarningMulti = EditorGUIUtility.TrTextContentWithIcon("Parametic Lights have been deprecated. To continue, upgrade your Parametric Lights to Freeform Lights to enjoy similar light functionality.", MessageType.Warning); public static GUIContent deprecatedParametricLightInstructions = EditorGUIUtility.TrTextContent("Alternatively, you may choose to upgrade from the menu. Window > Rendering > Render Pipeline Converter > URP 2D Converters"); public static GUIContent deprecatedParametricLightButtonSingle = EditorGUIUtility.TrTextContent("Upgrade Parametric Light"); public static GUIContent deprecatedParametricLightButtonMulti = EditorGUIUtility.TrTextContent("Upgrade Parametric Lights"); public static GUIContent renderPipelineUnassignedWarning = EditorGUIUtility.TrTextContentWithIcon("Universal scriptable renderpipeline asset must be assigned in Graphics Settings or Quality Settings.", MessageType.Warning); public static GUIContent asset2DUnassignedWarning = EditorGUIUtility.TrTextContentWithIcon("2D renderer data must be assigned to your universal render pipeline asset or camera.", MessageType.Warning); public static string deprecatedParametricLightDialogTextSingle = "The upgrade will convert the selected parametric light into a freeform light. You can't undo this operation."; public static string deprecatedParametricLightDialogTextMulti = "The upgrade will convert the selected parametric lights into freeform lights. You can't undo this operation."; public static string deprecatedParametricLightDialogTitle = "Parametric Light Upgrader"; public static string deprecatedParametricLightDialogProceed = "Proceed"; public static string deprecatedParametricLightDialogCancel = "Cancel"; } const float k_GlobalLightGizmoSize = 1.2f; const float k_AngleCapSize = 0.16f * k_GlobalLightGizmoSize; const float k_AngleCapOffset = 0.08f * k_GlobalLightGizmoSize; const float k_AngleCapOffsetSecondary = -0.05f; const float k_RangeCapSize = 0.025f * k_GlobalLightGizmoSize; const float k_InnerRangeCapSize = 0.08f * k_GlobalLightGizmoSize; SerializedProperty m_LightType; SerializedProperty m_LightColor; SerializedProperty m_LightIntensity; SerializedProperty m_UseNormalMap; SerializedProperty m_ShadowsEnabled; SerializedProperty m_ShadowIntensity; SerializedProperty m_ShadowSoftness; SerializedProperty m_ShadowSoftnessFalloffIntensity; SerializedProperty m_ShadowVolumeIntensity; SerializedProperty m_ShadowVolumeIntensityEnabled; SerializedProperty m_ApplyToSortingLayers; SerializedProperty m_VolumetricIntensity; SerializedProperty m_VolumetricEnabled; SerializedProperty m_BlendStyleIndex; SerializedProperty m_FalloffIntensity; SerializedProperty m_NormalMapZDistance; SerializedProperty m_NormalMapQuality; SerializedProperty m_LightOrder; SerializedProperty m_OverlapOperation; // Point Light Properties SerializedProperty m_PointInnerAngle; SerializedProperty m_PointOuterAngle; SerializedProperty m_PointInnerRadius; SerializedProperty m_PointOuterRadius; SerializedProperty m_DeprecatedPointLightSprite; // Shape Light Properties SerializedProperty m_ShapeLightParametricRadius; SerializedProperty m_ShapeLightFalloffSize; SerializedProperty m_ShapeLightParametricSides; SerializedProperty m_ShapeLightSprite; SavedBool m_BlendingSettingsFoldout; SavedBool m_ShadowsSettingsFoldout; SavedBool m_VolumetricSettingsFoldout; SavedBool m_NormalMapsSettingsFoldout; int[] m_BlendStyleIndices; GUIContent[] m_BlendStyleNames; bool m_AnyBlendStyleEnabled = false; SortingLayerDropDown m_SortingLayerDropDown; Light2D lightObject => target as Light2D; Analytics.Renderer2DAnalytics m_Analytics; HashSet m_ModifiedLights; private void AnalyticsTrackChanges(SerializedObject serializedObject) { if (serializedObject.hasModifiedProperties) { foreach (Object targetObj in serializedObject.targetObjects) { Light2D light2d = (Light2D)targetObj; if (!m_ModifiedLights.Contains(light2d)) m_ModifiedLights.Add(light2d); } } } private ToggleFoldoutResult DrawHeaderFoldoutWithToggle(GUIContent title, bool foldoutState, bool toggleState, string documentationURL = "") { ToggleFoldoutResult foldoutResult = new ToggleFoldoutResult(); const float height = 17f; var backgroundRect = GUILayoutUtility.GetRect(0, 0); float xMin = backgroundRect.xMin; var labelRect = backgroundRect; labelRect.yMax += height; labelRect.xMin += 16f; labelRect.xMax -= 20f; foldoutResult.toggleState = GUI.Toggle(labelRect, toggleState, " "); // Needs a space because the checkbox won't have a proper outline if we don't make a space here foldoutResult.foldoutState = CoreEditorUtils.DrawHeaderFoldout("", foldoutState); labelRect.xMin += 20; EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel); return foldoutResult; } void OnEnable() { m_Analytics = Analytics.Renderer2DAnalytics.instance; m_ModifiedLights = new HashSet(); m_SortingLayerDropDown = new SortingLayerDropDown(); m_BlendingSettingsFoldout = new SavedBool($"{target.GetType()}.2DURPBlendingSettingsFoldout", false); m_ShadowsSettingsFoldout = new SavedBool($"{target.GetType()}.2DURPShadowsSettingsFoldout", false); m_VolumetricSettingsFoldout = new SavedBool($"{target.GetType()}.2DURPVolumetricSettingsFoldout", false); m_NormalMapsSettingsFoldout = new SavedBool($"{target.GetType()}.2DURPNormalMapsSettingsFoldout", false); m_LightType = serializedObject.FindProperty("m_LightType"); m_LightColor = serializedObject.FindProperty("m_Color"); m_LightIntensity = serializedObject.FindProperty("m_Intensity"); m_UseNormalMap = serializedObject.FindProperty("m_UseNormalMap"); m_ShadowsEnabled = serializedObject.FindProperty("m_ShadowsEnabled"); m_ShadowIntensity = serializedObject.FindProperty("m_ShadowIntensity"); m_ShadowSoftness = serializedObject.FindProperty("m_ShadowSoftness"); m_ShadowSoftnessFalloffIntensity = serializedObject.FindProperty("m_ShadowSoftnessFalloffIntensity"); m_ShadowVolumeIntensity = serializedObject.FindProperty("m_ShadowVolumeIntensity"); m_ShadowVolumeIntensityEnabled = serializedObject.FindProperty("m_ShadowVolumeIntensityEnabled"); m_ApplyToSortingLayers = serializedObject.FindProperty("m_ApplyToSortingLayers"); m_VolumetricIntensity = serializedObject.FindProperty("m_LightVolumeIntensity"); m_VolumetricEnabled = serializedObject.FindProperty("m_LightVolumeEnabled"); m_BlendStyleIndex = serializedObject.FindProperty("m_BlendStyleIndex"); m_FalloffIntensity = serializedObject.FindProperty("m_FalloffIntensity"); m_NormalMapZDistance = serializedObject.FindProperty("m_NormalMapDistance"); m_NormalMapQuality = serializedObject.FindProperty("m_NormalMapQuality"); m_LightOrder = serializedObject.FindProperty("m_LightOrder"); m_OverlapOperation = serializedObject.FindProperty("m_OverlapOperation"); // Point Light m_PointInnerAngle = serializedObject.FindProperty("m_PointLightInnerAngle"); m_PointOuterAngle = serializedObject.FindProperty("m_PointLightOuterAngle"); m_PointInnerRadius = serializedObject.FindProperty("m_PointLightInnerRadius"); m_PointOuterRadius = serializedObject.FindProperty("m_PointLightOuterRadius"); m_DeprecatedPointLightSprite = serializedObject.FindProperty("m_DeprecatedPointLightCookieSprite"); // Shape Light m_ShapeLightParametricRadius = serializedObject.FindProperty("m_ShapeLightParametricRadius"); m_ShapeLightFalloffSize = serializedObject.FindProperty("m_ShapeLightFalloffSize"); m_ShapeLightParametricSides = serializedObject.FindProperty("m_ShapeLightParametricSides"); m_ShapeLightSprite = serializedObject.FindProperty("m_LightCookieSprite"); m_AnyBlendStyleEnabled = false; var blendStyleIndices = new List(); var blendStyleNames = new List(); var rendererData = Light2DEditorUtility.GetRenderer2DData(); if (rendererData != null) { for (int i = 0; i < rendererData.lightBlendStyles.Length; ++i) { blendStyleIndices.Add(i); ref var blendStyle = ref rendererData.lightBlendStyles[i]; if (blendStyle.maskTextureChannel == Light2DBlendStyle.TextureChannel.None) blendStyleNames.Add(blendStyle.name); else { var name = string.Format("{0} ({1})", blendStyle.name, blendStyle.maskTextureChannel); blendStyleNames.Add(name); } m_AnyBlendStyleEnabled = true; } } else { for (int i = 0; i < 4; ++i) { blendStyleIndices.Add(i); blendStyleNames.Add("Operation" + i); } } m_BlendStyleIndices = blendStyleIndices.ToArray(); m_BlendStyleNames = blendStyleNames.Select(x => new GUIContent(x)).ToArray(); m_SortingLayerDropDown.OnEnable(serializedObject, "m_ApplyToSortingLayers"); } internal void SendModifiedAnalytics(Analytics.Renderer2DAnalytics analytics, Light2D light) { Analytics.LightDataAnalytic lightData = new Analytics.LightDataAnalytic(light.GetInstanceID(), false, light.lightType); Analytics.Renderer2DAnalytics.instance.SendData(lightData); } void OnDestroy() { if (m_ModifiedLights != null && m_ModifiedLights.Count > 0) { foreach (Light2D light in m_ModifiedLights) { SendModifiedAnalytics(m_Analytics, light); } } } void DrawBlendingGroup() { CoreEditorUtils.DrawSplitter(false); m_BlendingSettingsFoldout.value = CoreEditorUtils.DrawHeaderFoldout(Styles.blendingSettingsFoldout, m_BlendingSettingsFoldout.value); if (m_BlendingSettingsFoldout.value) { if (!m_AnyBlendStyleEnabled) EditorGUILayout.HelpBox(Styles.generalLightNoLightEnabled); else EditorGUILayout.IntPopup(m_BlendStyleIndex, m_BlendStyleNames, m_BlendStyleIndices, Styles.generalBlendStyle); EditorGUILayout.PropertyField(m_LightOrder, Styles.generalLightOrder); EditorGUILayout.PropertyField(m_OverlapOperation, Styles.generalLightOverlapOperation); } } void DrawShadowsGroup() { CoreEditorUtils.DrawSplitter(false); ToggleFoldoutResult result = DrawHeaderFoldoutWithToggle(Styles.shadowsSettingsFoldout, m_ShadowsSettingsFoldout.value, m_ShadowsEnabled.boolValue); m_ShadowsEnabled.boolValue = result.toggleState; m_ShadowsSettingsFoldout.value = result.foldoutState; if (m_ShadowsSettingsFoldout.value) { EditorGUI.indentLevel++; EditorGUI.BeginDisabledGroup(!m_ShadowsEnabled.boolValue); EditorGUILayout.PropertyField(m_ShadowIntensity, Styles.generalShadowIntensity); EditorGUILayout.PropertyField(m_ShadowSoftness, Styles.generalShadowSoftness); EditorGUILayout.PropertyField(m_ShadowSoftnessFalloffIntensity,Styles.generalShadowSoftnessFalloffIntensity); EditorGUI.EndDisabledGroup(); EditorGUI.indentLevel--; } } void DrawVolumetricGroup() { CoreEditorUtils.DrawSplitter(false); ToggleFoldoutResult result = DrawHeaderFoldoutWithToggle(Styles.volumetricSettingsFoldout, m_VolumetricSettingsFoldout.value, m_VolumetricEnabled.boolValue); m_VolumetricSettingsFoldout.value = result.foldoutState; m_VolumetricEnabled.boolValue = result.toggleState; if (m_VolumetricSettingsFoldout.value) { EditorGUI.indentLevel++; EditorGUI.BeginDisabledGroup(!m_VolumetricEnabled.boolValue); EditorGUILayout.PropertyField(m_VolumetricIntensity, Styles.generalVolumeIntensity); if (m_VolumetricIntensity.floatValue < 0) m_VolumetricIntensity.floatValue = 0; DrawToggleProperty(Styles.generalShadowVolumeIntensity, m_ShadowVolumeIntensityEnabled, m_ShadowVolumeIntensity); EditorGUI.EndDisabledGroup(); EditorGUI.indentLevel--; } } void DrawNormalMapGroup() { CoreEditorUtils.DrawSplitter(false); m_NormalMapsSettingsFoldout.value = CoreEditorUtils.DrawHeaderFoldout(Styles.normalMapsSettingsFoldout, m_NormalMapsSettingsFoldout.value); if (m_NormalMapsSettingsFoldout.value) { EditorGUILayout.PropertyField(m_NormalMapQuality, Styles.generalNormalMapLightQuality); EditorGUI.BeginDisabledGroup(m_NormalMapQuality.intValue == (int)Light2D.NormalMapQuality.Disabled); EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_NormalMapZDistance, Styles.generalNormalMapZDistance); if (EditorGUI.EndChangeCheck()) m_NormalMapZDistance.floatValue = Mathf.Max(0.0f, m_NormalMapZDistance.floatValue); EditorGUI.EndDisabledGroup(); } } void DrawFoldouts() { DrawBlendingGroup(); DrawShadowsGroup(); DrawVolumetricGroup(); DrawNormalMapGroup(); } void DrawRadiusProperties(GUIContent label, SerializedProperty innerRadius, GUIContent content1, SerializedProperty outerRadius, GUIContent content2) { GUIStyle style = GUI.skin.box; float savedLabelWidth = EditorGUIUtility.labelWidth; int savedIndentLevel = EditorGUI.indentLevel; EditorGUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel(label); EditorGUILayout.BeginHorizontal(); EditorGUI.indentLevel = 0; EditorGUIUtility.labelWidth = style.CalcSize(content1).x; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(innerRadius, content1); if (EditorGUI.EndChangeCheck()) { if (innerRadius.floatValue > outerRadius.floatValue) innerRadius.floatValue = outerRadius.floatValue; else if (innerRadius.floatValue < 0) innerRadius.floatValue = 0; } EditorGUIUtility.labelWidth = style.CalcSize(content2).x; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(outerRadius, content2); if (EditorGUI.EndChangeCheck() && outerRadius.floatValue < innerRadius.floatValue) outerRadius.floatValue = innerRadius.floatValue; EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal(); EditorGUIUtility.labelWidth = savedLabelWidth; EditorGUI.indentLevel = savedIndentLevel; } void DrawToggleProperty(GUIContent label, SerializedProperty boolProperty, SerializedProperty property) { int savedIndentLevel = EditorGUI.indentLevel; float savedLabelWidth = EditorGUIUtility.labelWidth; const int kCheckboxWidth = 20; EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(boolProperty, GUIContent.none, GUILayout.MaxWidth(kCheckboxWidth)); EditorGUIUtility.labelWidth = EditorGUIUtility.labelWidth - kCheckboxWidth; EditorGUI.BeginDisabledGroup(!boolProperty.boolValue); EditorGUILayout.PropertyField(property, label); EditorGUI.EndDisabledGroup(); EditorGUILayout.EndHorizontal(); EditorGUI.indentLevel = savedIndentLevel; EditorGUIUtility.labelWidth = savedLabelWidth; } public void DrawInnerAndOuterSpotAngle(SerializedProperty minProperty, SerializedProperty maxProperty, GUIContent label) { float textFieldWidth = 45f; float min = minProperty.floatValue; float max = maxProperty.floatValue; var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight); // This widget is a little bit of a special case. // The right hand side of the min max slider will control the reset of the max value // The left hand side of the min max slider will control the reset of the min value // The label itself will not have a right click and reset value. rect = EditorGUI.PrefixLabel(rect, label); EditorGUI.BeginProperty(new Rect(rect) { width = rect.width * 0.5f }, label, minProperty); EditorGUI.BeginProperty(new Rect(rect) { xMin = rect.x + rect.width * 0.5f }, GUIContent.none, maxProperty); var minRect = new Rect(rect) { width = textFieldWidth }; var maxRect = new Rect(rect) { xMin = rect.xMax - textFieldWidth }; var sliderRect = new Rect(rect) { xMin = minRect.xMax + 4, xMax = maxRect.xMin - 4 }; EditorGUI.BeginChangeCheck(); EditorGUI.DelayedFloatField(minRect, minProperty, GUIContent.none); if (EditorGUI.EndChangeCheck()) { if (minProperty.floatValue > maxProperty.floatValue) minProperty.floatValue = maxProperty.floatValue; else if (minProperty.floatValue < 0) minProperty.floatValue = 0; } EditorGUI.BeginChangeCheck(); EditorGUI.MinMaxSlider(sliderRect, ref min, ref max, 0f, 360f); if (EditorGUI.EndChangeCheck()) { minProperty.floatValue = min; maxProperty.floatValue = max; } EditorGUI.BeginChangeCheck(); EditorGUI.DelayedFloatField(maxRect, m_PointOuterAngle, GUIContent.none); if (EditorGUI.EndChangeCheck()) { if (minProperty.floatValue > maxProperty.floatValue) maxProperty.floatValue = minProperty.floatValue; else if (maxProperty.floatValue > 360) maxProperty.floatValue = 360; } EditorGUI.EndProperty(); EditorGUI.EndProperty(); } void DrawGlobalLight(SerializedObject serializedObject) { m_SortingLayerDropDown.OnTargetSortingLayers(serializedObject, targets, Styles.generalSortingLayerPrefixLabel, AnalyticsTrackChanges); DrawBlendingGroup(); } void DrawParametricDeprecated(SerializedObject serializedObject) { GUIContent buttonText = targets.Length > 1 ? Styles.deprecatedParametricLightButtonMulti : Styles.deprecatedParametricLightButtonSingle; GUIContent helpText = targets.Length > 1 ? Styles.deprecatedParametricLightWarningMulti : Styles.deprecatedParametricLightWarningSingle; string dialogText = targets.Length > 1 ? Styles.deprecatedParametricLightDialogTextMulti : Styles.deprecatedParametricLightDialogTextSingle; EditorGUILayout.HelpBox(helpText); EditorGUILayout.Space(); if (GUILayout.Button(buttonText)) { if (EditorUtility.DisplayDialog(Styles.deprecatedParametricLightDialogTitle, dialogText, Styles.deprecatedParametricLightDialogProceed, Styles.deprecatedParametricLightDialogCancel)) { for (int i = 0; i < targets.Length; i++) { Light2D light = (Light2D)targets[i]; if (light.lightType == (Light2D.LightType)Light2D.DeprecatedLightType.Parametric) ParametricToFreeformLightUpgrader.UpgradeParametricLight(light); } } } EditorGUILayout.Space(); EditorGUILayout.HelpBox(Styles.deprecatedParametricLightInstructions); } bool DrawLightCommon() { var meshChanged = false; Rect lightTypeRect = EditorGUILayout.GetControlRect(); EditorGUI.BeginProperty(lightTypeRect, GUIContent.none, m_LightType); EditorGUI.BeginChangeCheck(); int newLightType = EditorGUI.Popup(lightTypeRect, Styles.generalLightType, m_LightType.intValue - 1, Styles.lightTypeOptions); // -1 is a bit hacky its to support compatibiltiy. We need something better. if (EditorGUI.EndChangeCheck()) { m_LightType.intValue = newLightType + 1; // -1 is a bit hacky its to support compatibiltiy. We need something better. meshChanged = true; } EditorGUI.EndProperty(); // Color and intensity EditorGUILayout.PropertyField(m_LightColor, Styles.generalLightColor); EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_LightIntensity, Styles.generalLightIntensity); if (EditorGUI.EndChangeCheck()) m_LightIntensity.floatValue = Mathf.Max(m_LightIntensity.floatValue, 0); return meshChanged; } void DrawSpotLight(SerializedObject serializedObject) { DrawRadiusProperties(Styles.pointLightRadius, m_PointInnerRadius, Styles.pointLightInner, m_PointOuterRadius, Styles.pointLightOuter); DrawInnerAndOuterSpotAngle(m_PointInnerAngle, m_PointOuterAngle, Styles.InnerOuterSpotAngle); EditorGUILayout.Slider(m_FalloffIntensity, 0, 1, Styles.generalFalloffIntensity); if (m_DeprecatedPointLightSprite.objectReferenceValue != null) EditorGUILayout.PropertyField(m_DeprecatedPointLightSprite, Styles.pointLightSprite); m_SortingLayerDropDown.OnTargetSortingLayers(serializedObject, targets, Styles.generalSortingLayerPrefixLabel, AnalyticsTrackChanges); DrawFoldouts(); } void DrawSpriteLight(SerializedObject serializedObject) { EditorGUILayout.PropertyField(m_ShapeLightSprite, Styles.shapeLightSprite); m_SortingLayerDropDown.OnTargetSortingLayers(serializedObject, targets, Styles.generalSortingLayerPrefixLabel, AnalyticsTrackChanges); DrawFoldouts(); } void DrawShapeLight(SerializedObject serializedObject) { EditorGUILayout.PropertyField(m_ShapeLightFalloffSize, Styles.generalFalloffSize); if (m_ShapeLightFalloffSize.floatValue < 0) m_ShapeLightFalloffSize.floatValue = 0; EditorGUILayout.Slider(m_FalloffIntensity, 0, 1, Styles.generalFalloffIntensity); m_SortingLayerDropDown.OnTargetSortingLayers(serializedObject, targets, Styles.generalSortingLayerPrefixLabel, AnalyticsTrackChanges); if (m_LightType.intValue == (int)Light2D.LightType.Freeform) { DoEditButton(PathEditorToolContents.icon, "Edit Shape"); DoPathInspector(); DoSnappingInspector(); } DrawFoldouts(); } Vector3 DrawAngleSlider2D(Transform transform, Quaternion rotation, float radius, float offset, Handles.CapFunction capFunc, float capSize, bool leftAngle, bool drawLine, bool useCapOffset, ref float angle) { float oldAngle = angle; float angleBy2 = (angle / 2) * (leftAngle ? -1.0f : 1.0f); Vector3 trcwPos = Quaternion.AngleAxis(angleBy2, -transform.forward) * (transform.up); Vector3 cwPos = transform.position + trcwPos * (radius + offset); float direction = leftAngle ? 1 : -1; // Offset the handle float size = .25f * capSize; Vector3 handleOffset = useCapOffset ? rotation * new Vector3(direction * size, 0, 0) : Vector3.zero; EditorGUI.BeginChangeCheck(); var id = GUIUtility.GetControlID("AngleSlider".GetHashCode(), FocusType.Passive); Vector3 cwHandle = Handles.Slider2D(id, cwPos, handleOffset, Vector3.forward, rotation * Vector3.up, rotation * Vector3.right, capSize, capFunc, Vector3.zero); if (EditorGUI.EndChangeCheck()) { Vector3 toCwHandle = (transform.position - cwHandle).normalized; angle = 360 - 2 * Quaternion.Angle(Quaternion.FromToRotation(transform.up, toCwHandle), Quaternion.identity); angle = Mathf.Round(angle * 100) / 100f; float side = Vector3.Dot(direction * transform.right, toCwHandle); if (side < 0) { if (oldAngle < 180) angle = 0; else angle = 360; } } if (drawLine) Handles.DrawLine(transform.position, cwHandle); return cwHandle; } private float DrawAngleHandle(Transform transform, float radius, float offset, Handles.CapFunction capLeft, Handles.CapFunction capRight, ref float angle) { float old = angle; float handleOffset = HandleUtility.GetHandleSize(transform.position) * offset; float handleSize = HandleUtility.GetHandleSize(transform.position) * k_AngleCapSize; Quaternion rotLt = Quaternion.AngleAxis(-angle / 2, -transform.forward) * transform.rotation; DrawAngleSlider2D(transform, rotLt, radius, handleOffset, capLeft, handleSize, true, true, true, ref angle); Quaternion rotRt = Quaternion.AngleAxis(angle / 2, -transform.forward) * transform.rotation; DrawAngleSlider2D(transform, rotRt, radius, handleOffset, capRight, handleSize, false, true, true, ref angle); return angle - old; } private void DrawRadiusArc(Transform transform, float radius, float angle, int steps, Handles.CapFunction capFunc, float capSize, bool even) { Handles.DrawWireArc(transform.position, transform.forward, Quaternion.AngleAxis(180 - angle / 2, transform.forward) * -transform.up, angle, radius); } Handles.CapFunction GetCapFunc(Texture texture, bool isAngleHandle) { return (controlID, position, rotation, size, eventType) => Light2DEditorUtility.GUITextureCap(controlID, texture, position, rotation, size, eventType, isAngleHandle); } private void DrawAngleHandles(Light2D light) { var oldColor = Handles.color; Handles.color = Color.yellow; float outerAngle = light.pointLightOuterAngle; float diff = DrawAngleHandle(light.transform, light.pointLightOuterRadius, k_AngleCapOffset, GetCapFunc(Styles.lightCapTopRight, true), GetCapFunc(Styles.lightCapBottomRight, true), ref outerAngle); light.pointLightOuterAngle = outerAngle; if (diff != 0.0f) light.pointLightInnerAngle = Mathf.Max(0.0f, light.pointLightInnerAngle + diff); float innerAngle = light.pointLightInnerAngle; diff = DrawAngleHandle(light.transform, light.pointLightOuterRadius, -k_AngleCapOffset, GetCapFunc(Styles.lightCapTopLeft, true), GetCapFunc(Styles.lightCapBottomLeft, true), ref innerAngle); light.pointLightInnerAngle = innerAngle; if (diff != 0.0f) light.pointLightInnerAngle = light.pointLightInnerAngle < light.pointLightOuterAngle ? light.pointLightInnerAngle : light.pointLightOuterAngle; light.pointLightInnerAngle = Mathf.Min(light.pointLightInnerAngle, light.pointLightOuterAngle); Handles.color = oldColor; } private void DrawRangeHandles(Light2D light) { var dummy = 0.0f; bool radiusChanged = false; Vector3 handlePos = Vector3.zero; Quaternion rotLeft = Quaternion.AngleAxis(0, -light.transform.forward) * light.transform.rotation; float handleOffset = HandleUtility.GetHandleSize(light.transform.position) * k_AngleCapOffsetSecondary; float handleSize = HandleUtility.GetHandleSize(light.transform.position) * k_AngleCapSize; var oldColor = Handles.color; Handles.color = Color.yellow; float outerRadius = light.pointLightOuterRadius; EditorGUI.BeginChangeCheck(); Vector3 returnPos = DrawAngleSlider2D(light.transform, rotLeft, outerRadius, -handleOffset, GetCapFunc(Styles.lightCapUp, false), handleSize, false, false, false, ref dummy); if (EditorGUI.EndChangeCheck()) { var vec = (returnPos - light.transform.position).normalized; light.transform.up = new Vector3(vec.x, vec.y, 0); outerRadius = (returnPos - light.transform.position).magnitude; outerRadius = outerRadius + handleOffset; radiusChanged = true; } DrawRadiusArc(light.transform, light.pointLightOuterRadius, light.pointLightOuterAngle, 0, Handles.DotHandleCap, k_RangeCapSize, false); Handles.color = Color.gray; float innerRadius = light.pointLightInnerRadius; EditorGUI.BeginChangeCheck(); returnPos = DrawAngleSlider2D(light.transform, rotLeft, innerRadius, handleOffset, GetCapFunc(Styles.lightCapDown, false), handleSize, true, false, false, ref dummy); if (EditorGUI.EndChangeCheck()) { innerRadius = (returnPos - light.transform.position).magnitude; innerRadius = innerRadius - handleOffset; radiusChanged = true; } DrawRadiusArc(light.transform, light.pointLightInnerRadius, light.pointLightOuterAngle, 0, Handles.SphereHandleCap, k_InnerRangeCapSize, false); Handles.color = oldColor; if (radiusChanged) { light.pointLightInnerRadius = (outerRadius < innerRadius) ? outerRadius : innerRadius; light.pointLightOuterRadius = (innerRadius > outerRadius) ? innerRadius : outerRadius; } } void OnSceneGUI() { var light = target as Light2D; if (light == null) return; Transform t = light.transform; switch (light.lightType) { case Light2D.LightType.Point: { Undo.RecordObject(light.transform, "Edit Point Light Transform"); Undo.RecordObject(light, "Edit Point Light"); DrawRangeHandles(light); DrawAngleHandles(light); if (GUI.changed) EditorUtility.SetDirty(light); } break; case Light2D.LightType.Sprite: { var cookieSprite = light.lightCookieSprite; if (cookieSprite != null) { Vector3 min = cookieSprite.bounds.min; Vector3 max = cookieSprite.bounds.max; Vector3 v0 = t.TransformPoint(new Vector3(min.x, min.y)); Vector3 v1 = t.TransformPoint(new Vector3(max.x, min.y)); Vector3 v2 = t.TransformPoint(new Vector3(max.x, max.y)); Vector3 v3 = t.TransformPoint(new Vector3(min.x, max.y)); Handles.DrawLine(v0, v1); Handles.DrawLine(v1, v2); Handles.DrawLine(v2, v3); Handles.DrawLine(v3, v0); } } break; case Light2D.LightType.Freeform: { // Draw the falloff shape's outline List falloffShape = light.GetFalloffShape(); Handles.color = Color.white; for (int i = 0; i < falloffShape.Count - 1; ++i) { Handles.DrawLine(t.TransformPoint(falloffShape[i]), t.TransformPoint(falloffShape[i + 1])); } if (falloffShape.Count > 0) Handles.DrawLine(t.TransformPoint(falloffShape[falloffShape.Count - 1]), t.TransformPoint(falloffShape[0])); for (int i = 0; i < light.shapePath.Length - 1; ++i) { Handles.DrawLine(t.TransformPoint(light.shapePath[i]), t.TransformPoint(light.shapePath[i + 1])); } if (light.shapePath.Length > 0) Handles.DrawLine(t.TransformPoint(light.shapePath[light.shapePath.Length - 1]), t.TransformPoint(light.shapePath[0])); } break; } } public override void OnInspectorGUI() { var meshChanged = false; serializedObject.Update(); UniversalRenderPipelineAsset asset = UniversalRenderPipeline.asset; if (asset != null) { if (!Light2DEditorUtility.IsUsing2DRenderer()) { EditorGUILayout.HelpBox(Styles.asset2DUnassignedWarning); } else { if (m_LightType.intValue != (int)Light2D.DeprecatedLightType.Parametric) meshChanged = DrawLightCommon(); switch (m_LightType.intValue) { case (int)Light2D.LightType.Point: { DrawSpotLight(serializedObject); } break; case (int)Light2D.LightType.Freeform: { DrawShapeLight(serializedObject); } break; case (int)Light2D.LightType.Sprite: { DrawSpriteLight(serializedObject); } break; case (int)Light2D.LightType.Global: { DrawGlobalLight(serializedObject); } break; case (int)Light2D.DeprecatedLightType.Parametric: { DrawParametricDeprecated(serializedObject); } break; } AnalyticsTrackChanges(serializedObject); if (serializedObject.ApplyModifiedProperties()) { if (meshChanged) lightObject.UpdateMesh(); } } } else { EditorGUILayout.HelpBox(Styles.renderPipelineUnassignedWarning); if (meshChanged) lightObject.UpdateMesh(); } } } internal class Light2DPostProcess : AssetPostprocessor { void OnPostprocessSprites(Texture2D texture, Sprite[] sprites) { var lights = Resources.FindObjectsOfTypeAll().Where(x => x.lightType == Light2D.LightType.Sprite && x.lightCookieSprite == null); foreach (var light in lights) light.MarkForUpdate(); } } }