Files
FORT-BO/Assets/Plugins/VolumetricLightBeam/Editor/EditorExtensions.cs
T
AsanoRema ffa1064063 Squashed commit of the following:
commit 428243f4a6
Author: AsanoRema <asanorema2036@gmail.com>
Date:   Sat Aug 29 13:43:13 2026 +0300

    Squashed commit of the following:

    commit 68e78fa464
    Author: AsanoRema <asanorema2036@gmail.com>
    Date:   Sat Aug 29 13:41:49 2026 +0300

        Updated .gitignore

commit 9f87122a34
Author: AsanoRema <asanorema2036@gmail.com>
Date:   Sat Aug 29 13:34:57 2026 +0300

    Added Dohya

commit f6a7547e84
Author: AsanoRema <asanorema2036@gmail.com>
Date:   Fri Aug 21 17:59:13 2026 +0300

    Fixed

commit b98901c366
Author: AsanoRema <asanorema2036@gmail.com>
Date:   Tue Aug 18 17:37:22 2026 +0300

    Added new particle Package

commit bb2f4a3b16
Author: AsanoRema <asanorema2036@gmail.com>
Date:   Tue Aug 18 17:17:48 2026 +0300

    Map mehanic ready to multiplayer

commit c01ff800f6
Author: AsanoRema <asanorema2036@gmail.com>
Date:   Tue Aug 18 16:16:19 2026 +0300

    Squashed commit of the following:

    commit 173bbb165c
    Author: henjkey <henjkey@gmail.com>
    Date:   Fri Aug 14 18:51:23 2026 +0500

        Mappp

    commit d559ab338a
    Author: henjkey <henjkey@gmail.com>
    Date:   Thu Aug 13 18:42:41 2026 +0500

        Map

        Controller
        Coordinates
        Target System
        Crosshair System
        Radar System
2026-08-29 13:45:55 +03:00

297 lines
15 KiB
C#

#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
namespace VLB
{
public static class EditorExtensions
{
public static GameObject NewBeam()
{
return new GameObject("Volumetric Light Beam", typeof(VolumetricLightBeam));
}
public static GameObject NewBeam2D()
{
var gao = new GameObject("Volumetric Light Beam (2D)", typeof(VolumetricLightBeam));
gao.GetComponent<VolumetricLightBeam>().dimensions = Dimensions.Dim2D;
return gao;
}
public static GameObject NewBeamAndDust()
{
return new GameObject("Volumetric Light Beam + Dust", typeof(VolumetricLightBeam), typeof(VolumetricDustParticles));
}
public static GameObject NewSpotLightAndBeam()
{
var light = Utils.NewWithComponent<Light>("Spotlight and Beam");
light.type = LightType.Spot;
var gao = light.gameObject;
gao.AddComponent<VolumetricLightBeam>();
return gao;
}
static void OnNewGameObjectCreated(GameObject gao)
{
if (Selection.activeGameObject)
gao.transform.SetParent(Selection.activeGameObject.transform);
Selection.activeGameObject = gao;
}
[MenuItem("GameObject/Light/Volumetric Beam (3D)", false, 100)]
static void Menu_CreateNewBeam()
{
OnNewGameObjectCreated(NewBeam());
}
[MenuItem("GameObject/Light/Volumetric Beam (3D) and Spotlight", false, 101)]
static void Menu_CreateSpotLightAndBeam()
{
OnNewGameObjectCreated(NewSpotLightAndBeam());
}
[MenuItem("GameObject/Light/Volumetric Beam (2D)", false, 102)]
static void Menu_CreateNewBeam2D()
{
OnNewGameObjectCreated(NewBeam2D());
}
const string kAddVolumetricBeam = "CONTEXT/Light/Attach a Volumetric Beam";
static bool CanAddVolumetricBeam(Light light) { return !Application.isPlaying && light != null && light.type == LightType.Spot && light.GetComponent<VolumetricLightBeam>() == null; }
[MenuItem(kAddVolumetricBeam, false)]
static void Menu_AttachBeam_Command(MenuCommand menuCommand)
{
var light = menuCommand.context as Light;
if (CanAddVolumetricBeam(light))
Undo.AddComponent<VolumetricLightBeam>(light.gameObject);
}
[MenuItem(kAddVolumetricBeam, true)]
static bool Menu_AttachBeam_Validate() { return CanAddVolumetricBeam(GetActiveLight()); }
[MenuItem("CONTEXT/" + VolumetricLightBeam.ClassName + "/Documentation")]
static void Menu_Beam_Doc(MenuCommand menuCommand) { Application.OpenURL(Consts.Help.UrlBeam); }
[MenuItem("CONTEXT/" + VolumetricDustParticles.ClassName + "/Documentation")]
static void Menu_DustParticles_Doc(MenuCommand menuCommand) { Application.OpenURL(Consts.Help.UrlDustParticles); }
[MenuItem("CONTEXT/" + DynamicOcclusionRaycasting.ClassName + "/Documentation")]
static void Menu_DynamicOcclusionRaycasting_Doc(MenuCommand menuCommand) { Application.OpenURL(Consts.Help.UrlDynamicOcclusionRaycasting); }
[MenuItem("CONTEXT/" + DynamicOcclusionDepthBuffer.ClassName + "/Documentation")]
static void Menu_DynamicOcclusionDepthBuffer_Doc(MenuCommand menuCommand) { Application.OpenURL(Consts.Help.UrlDynamicOcclusionDepthBuffer); }
[MenuItem("CONTEXT/" + TriggerZone.ClassName + "/Documentation")]
static void Menu_TriggerZone_Doc(MenuCommand menuCommand) { Application.OpenURL(Consts.Help.UrlTriggerZone); }
[MenuItem("CONTEXT/" + EffectFlicker.ClassName + "/Documentation")]
static void Menu_EffectFlicker_Doc(MenuCommand menuCommand) { Application.OpenURL(Consts.Help.UrlEffectFlicker); }
[MenuItem("CONTEXT/" + EffectPulse.ClassName + "/Documentation")]
static void Menu_EffectPulse_Doc(MenuCommand menuCommand) { Application.OpenURL(Consts.Help.UrlEffectPulse); }
[MenuItem("CONTEXT/" + Config.ClassName + "/Documentation")]
static void Menu_Config_Doc(MenuCommand menuCommand) { Application.OpenURL(Consts.Help.UrlConfig); }
[MenuItem("CONTEXT/" + VolumetricLightBeam.ClassName + "/Open Global Config")]
[MenuItem("CONTEXT/" + VolumetricDustParticles.ClassName + "/Open Global Config")]
[MenuItem("CONTEXT/" + DynamicOcclusionAbstractBase.ClassName + "/Open Global Config")]
[MenuItem("CONTEXT/" + TriggerZone.ClassName + "/Open Global Config")]
[MenuItem("CONTEXT/" + EffectAbstractBase.ClassName + "/Open Global Config")]
public static void Menu_Beam_Config(MenuCommand menuCommand) { Config.EditorSelectInstance(); }
const string kAddParticles = "CONTEXT/" + VolumetricLightBeam.ClassName + "/Add Dust Particles";
[MenuItem(kAddParticles, false)] static void Menu_AddDustParticles_Command(MenuCommand menuCommand) { AddComponentFromEditor<VolumetricDustParticles>(menuCommand.context as VolumetricLightBeam); }
[MenuItem(kAddParticles, true)] static bool Menu_AddDustParticles_Validate() { return CanAddComponentFromEditor<VolumetricDustParticles>(GetActiveVolumetricLightBeam()); }
const string kAddDynamicOcclusionRaycasting = "CONTEXT/" + VolumetricLightBeam.ClassName + "/Add Dynamic Occlusion (Raycasting)";
[MenuItem(kAddDynamicOcclusionRaycasting, false)] static void Menu_AddDynamicOcclusionRaycasting_Command(MenuCommand menuCommand) { AddComponentFromEditor<DynamicOcclusionRaycasting>(menuCommand.context as VolumetricLightBeam); }
[MenuItem(kAddDynamicOcclusionRaycasting, true)] static bool Menu_AddDynamicOcclusionRaycasting_Validate() { return Config.Instance.featureEnabledDynamicOcclusion && CanAddComponentFromEditor<DynamicOcclusionAbstractBase>(GetActiveVolumetricLightBeam()); }
const string kAddDynamicOcclusionDepthBuffer = "CONTEXT/" + VolumetricLightBeam.ClassName + "/Add Dynamic Occlusion (Depth Buffer)";
[MenuItem(kAddDynamicOcclusionDepthBuffer, false)] static void Menu_AddDynamicOcclusionDepthBuffer_Command(MenuCommand menuCommand) { AddComponentFromEditor<DynamicOcclusionDepthBuffer>(menuCommand.context as VolumetricLightBeam); }
[MenuItem(kAddDynamicOcclusionDepthBuffer, true)] static bool Menu_AddDynamicOcclusionDepthBuffer_Validate() { return Config.Instance.featureEnabledDynamicOcclusion && CanAddComponentFromEditor<DynamicOcclusionAbstractBase>(GetActiveVolumetricLightBeam()); }
const string kAddTriggerZone = "CONTEXT/" + VolumetricLightBeam.ClassName + "/Add Trigger Zone";
[MenuItem(kAddTriggerZone, false)] static void Menu_AddTriggerZone_Command(MenuCommand menuCommand) { AddComponentFromEditor<TriggerZone>(menuCommand.context as VolumetricLightBeam); }
[MenuItem(kAddTriggerZone, true)] static bool Menu_AddTriggerZone_Validate() { return CanAddComponentFromEditor<TriggerZone>(GetActiveVolumetricLightBeam()); }
const string kAddEffectFlicker = "CONTEXT/" + VolumetricLightBeam.ClassName + "/Add Effect Flicker";
[MenuItem(kAddEffectFlicker, false)] static void Menu_EffectFlicker_Command(MenuCommand menuCommand) { AddComponentFromEditor<EffectFlicker>(menuCommand.context as VolumetricLightBeam); }
[MenuItem(kAddEffectFlicker, true)] static bool Menu_EffectFlicker_Validate() { return CanAddComponentFromEditor<EffectAbstractBase>(GetActiveVolumetricLightBeam()); }
const string kAddEffectPulse = "CONTEXT/" + VolumetricLightBeam.ClassName + "/Add Effect Pulse";
[MenuItem(kAddEffectPulse, false)] static void Menu_EffectPulse_Command(MenuCommand menuCommand) { AddComponentFromEditor<EffectPulse>(menuCommand.context as VolumetricLightBeam); }
[MenuItem(kAddEffectPulse, true)] static bool Menu_EffectPulse_Validate() { return CanAddComponentFromEditor<EffectAbstractBase>(GetActiveVolumetricLightBeam()); }
static Light GetActiveLight() { return Selection.activeGameObject != null ? Selection.activeGameObject.GetComponent<Light>() : null; }
static VolumetricLightBeam GetActiveVolumetricLightBeam() { return Selection.activeGameObject != null ? Selection.activeGameObject.GetComponent<VolumetricLightBeam>() : null; }
static bool CanAddComponentFromEditor<TComp>(VolumetricLightBeam self) where TComp : Component
{
return !Application.isPlaying && self != null && self.GetComponent<TComp>() == null;
}
public static void AddComponentFromEditor<TComp>(VolumetricLightBeam self) where TComp : Component
{
if (CanAddComponentFromEditor<TComp>(self))
{
/*var comp =*/ Undo.AddComponent<TComp>(self.gameObject);
}
}
[MenuItem("Edit/Volumetric Light Beam Config", false, 20001)]
static void Menu_EditOpenConfig()
{
Config.EditorSelectInstance();
}
/// <summary>
/// Add a EditorGUILayout.ToggleLeft which properly handles multi-object editing
/// </summary>
public static void ToggleLeft(this SerializedProperty prop, GUIContent label, params GUILayoutOption[] options)
{
ToggleLeft(prop, label, prop.boolValue, options);
}
public static void ToggleLeft(this SerializedProperty prop, GUIContent label, bool forcedValue, params GUILayoutOption[] options)
{
EditorGUI.BeginChangeCheck();
EditorGUI.showMixedValue = prop.hasMultipleDifferentValues;
var newValue = EditorGUILayout.ToggleLeft(label, forcedValue, options);
EditorGUI.showMixedValue = false;
if (EditorGUI.EndChangeCheck())
prop.boolValue = newValue;
}
public static bool HasAtLeastOneValue(this SerializedProperty prop, bool value)
{
return (prop.boolValue == value) || prop.hasMultipleDifferentValues;
}
/// <summary>
/// Create a EditorGUILayout.Slider which properly handles multi-object editing
/// We apply the 'convIn' conversion to the SerializedProperty value before exposing it as a Slider.
/// We apply the 'convOut' conversion to the Slider value to get the SerializedProperty back.
/// </summary>
/// <param name="prop">The value the slider shows.</param>
/// <param name="label">Label in front of the slider.</param>
/// <param name="leftValue">The value at the left end of the slider.</param>
/// <param name="rightValue">The value at the right end of the slider.</param>
/// <param name="convIn">Conversion applied on the SerializedProperty to get the Slider value</param>
/// <param name="convOut">Conversion applied on the Slider value to get the SerializedProperty</param>
public static bool FloatSlider(
this SerializedProperty prop,
GUIContent label,
float leftValue, float rightValue,
System.Func<float, float> convIn,
System.Func<float, float> convOut,
params GUILayoutOption[] options)
{
var floatValue = convIn(prop.floatValue);
EditorGUI.BeginChangeCheck();
{
EditorGUI.showMixedValue = prop.hasMultipleDifferentValues;
{
floatValue = EditorGUILayout.Slider(label, floatValue, leftValue, rightValue, options);
}
EditorGUI.showMixedValue = false;
}
if (EditorGUI.EndChangeCheck())
{
prop.floatValue = convOut(floatValue);
return true;
}
return false;
}
public static bool FloatSlider(
this SerializedProperty prop,
GUIContent label,
float leftValue, float rightValue,
params GUILayoutOption[] options)
{
var floatValue = prop.floatValue;
EditorGUI.BeginChangeCheck();
{
EditorGUI.showMixedValue = prop.hasMultipleDifferentValues;
{
floatValue = EditorGUILayout.Slider(label, floatValue, leftValue, rightValue, options);
}
EditorGUI.showMixedValue = false;
}
if (EditorGUI.EndChangeCheck())
{
prop.floatValue = floatValue;
return true;
}
return false;
}
/*
public static void ToggleFromLight(this SerializedProperty prop)
{
ToggleLeft(
prop,
new GUIContent("From Spot", "Get the value from the Light Spot"),
GUILayout.MaxWidth(80.0f));
}
*/
public static void ToggleUseGlobalNoise(this SerializedProperty prop)
{
ToggleLeft(
prop,
new GUIContent("Global", "Get the value from the Global 3D Noise"),
GUILayout.MaxWidth(55.0f));
}
public static void CustomEnum<EnumType>(this SerializedProperty prop, GUIContent content, string[] descriptions = null)
{
if(descriptions == null)
descriptions = System.Enum.GetNames(typeof(EnumType));
Debug.Assert(System.Enum.GetNames(typeof(EnumType)).Length == descriptions.Length, string.Format("Enum '{0}' and the description array don't have the same size", typeof(EnumType)));
int enumValueIndex = prop.enumValueIndex;
EditorGUI.BeginChangeCheck();
{
EditorGUI.showMixedValue = prop.hasMultipleDifferentValues;
{
#if UNITY_2018_1_OR_NEWER
enumValueIndex = EditorGUILayout.Popup(content, enumValueIndex, descriptions);
#else
enumValueIndex = EditorGUILayout.Popup(content.text, enumValueIndex, descriptions);
#endif
}
EditorGUI.showMixedValue = false;
}
if (EditorGUI.EndChangeCheck())
prop.enumValueIndex = enumValueIndex;
}
public static void CustomMask<EnumType>(this SerializedProperty prop, GUIContent content, string[] descriptions = null)
{
if (descriptions == null)
descriptions = System.Enum.GetNames(typeof(EnumType));
Debug.Assert(System.Enum.GetNames(typeof(EnumType)).Length == descriptions.Length, string.Format("Enum '{0}' and the description array don't have the same size", typeof(EnumType)));
int intValue = prop.intValue;
EditorGUI.BeginChangeCheck();
{
EditorGUI.showMixedValue = prop.hasMultipleDifferentValues;
{
intValue = EditorGUILayout.MaskField(content, intValue, descriptions);
}
EditorGUI.showMixedValue = false;
}
if (EditorGUI.EndChangeCheck())
prop.intValue = intValue;
}
}
}
#endif