2
This commit is contained in:
180
unity/Assets/Light/scripts/LightMaskSystem.cs
Normal file
180
unity/Assets/Light/scripts/LightMaskSystem.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace IndianOceanAssets.Engine2_5D
|
||||
{
|
||||
/// <summary>
|
||||
/// 黑暗遮罩管理器 v2 —— 乘法混合 + 编辑器开关(不含回声)。
|
||||
/// 回声描边已独立到 EchoSystem + SpriteEchoOutline shader。
|
||||
///
|
||||
/// 单例模式:收集场景中所有 LightSource,每帧把位置/半径/强度
|
||||
/// 写入 DarknessOverlay 材质的 _LightData 数组。
|
||||
///
|
||||
/// 使用方式:
|
||||
/// 1. 在场景中创建一个空物体,挂上此脚本
|
||||
/// 2. 创建一个大平面(覆盖可视区域),Y 稍高于精灵
|
||||
/// 3. 给平面分配使用 DarknessOverlay shader 的材质
|
||||
/// 4. 把该材质拖到此脚本的 darknessMaterial 字段
|
||||
/// </summary>
|
||||
public class LightMaskSystem : MonoBehaviour
|
||||
{
|
||||
public static LightMaskSystem Instance { get; private set; }
|
||||
|
||||
[Header("遮罩材质(使用 DarknessOverlay shader)")]
|
||||
[SerializeField] private Material darknessMaterial;
|
||||
|
||||
[Header("编辑器开关")]
|
||||
[Tooltip("勾选后关闭黑暗,所有物件完全可见,方便摆放场景。\n取消勾选恢复黑暗效果。")]
|
||||
[SerializeField] private bool debugShowAll = false;
|
||||
|
||||
[Header("黑暗设置")]
|
||||
[Tooltip("最小亮度:0=纯黑(精灵完全看不见),0.05=微微看到轮廓")]
|
||||
[SerializeField, Range(0f, 0.3f)] private float minBrightness = 0f;
|
||||
|
||||
[Tooltip("黑暗底色:夜间微光色调,接近纯黑")]
|
||||
[SerializeField] private Color darknessColor = new Color(0.01f, 0.01f, 0.02f, 1f);
|
||||
|
||||
[Tooltip("光线边缘柔和度,0 = 硬边,1 = 最柔")]
|
||||
[SerializeField, Range(0.01f, 1f)] private float lightSoftness = 0.35f;
|
||||
|
||||
[Header("遮罩平面跟随设置")]
|
||||
[Tooltip("如果勾选,遮罩平面会跟随摄像机移动(适合大地图)")]
|
||||
[SerializeField] private bool followCamera = true;
|
||||
[SerializeField] private Transform maskPlane;
|
||||
[SerializeField] private Vector3 maskOffset = new Vector3(0f, 5f, 0f);
|
||||
[SerializeField] private float planeSize = 50f;
|
||||
|
||||
// Shader Property IDs
|
||||
private static readonly int LightDataID = Shader.PropertyToID("_LightData");
|
||||
private static readonly int LightCountID = Shader.PropertyToID("_LightCount");
|
||||
private static readonly int DarknessColorID = Shader.PropertyToID("_DarknessColor");
|
||||
private static readonly int MinBrightnessID = Shader.PropertyToID("_MinBrightness");
|
||||
private static readonly int LightSoftnessID = Shader.PropertyToID("_LightSoftness");
|
||||
|
||||
private readonly List<LightSource> _lights = new List<LightSource>();
|
||||
private Vector4[] _lightData = new Vector4[64];
|
||||
private Camera _mainCamera;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_mainCamera = Camera.main;
|
||||
UpdateMaterialProperties();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
UpdateLightData();
|
||||
UpdateMaskPosition();
|
||||
}
|
||||
|
||||
private void UpdateMaterialProperties()
|
||||
{
|
||||
if (darknessMaterial == null) return;
|
||||
darknessMaterial.SetColor(DarknessColorID, darknessColor);
|
||||
darknessMaterial.SetFloat(LightSoftnessID, lightSoftness);
|
||||
|
||||
if (debugShowAll)
|
||||
darknessMaterial.SetFloat(MinBrightnessID, 1.0f);
|
||||
else
|
||||
darknessMaterial.SetFloat(MinBrightnessID, minBrightness);
|
||||
}
|
||||
|
||||
private void UpdateLightData()
|
||||
{
|
||||
if (darknessMaterial == null) return;
|
||||
|
||||
int writeIndex = 0;
|
||||
for (int i = 0; i < _lights.Count && writeIndex < 64; i++)
|
||||
{
|
||||
var src = _lights[i];
|
||||
if (src == null || !src.gameObject.activeInHierarchy)
|
||||
continue;
|
||||
|
||||
Vector2 pos = src.GetWorldPositionXZ();
|
||||
_lightData[writeIndex] = new Vector4(pos.x, pos.y, src.Radius, src.Intensity);
|
||||
writeIndex++;
|
||||
}
|
||||
|
||||
darknessMaterial.SetVectorArray(LightDataID, _lightData);
|
||||
darknessMaterial.SetInt(LightCountID, writeIndex);
|
||||
}
|
||||
|
||||
private void UpdateMaskPosition()
|
||||
{
|
||||
if (!followCamera || maskPlane == null || _mainCamera == null) return;
|
||||
Vector3 camPos = _mainCamera.transform.position;
|
||||
maskPlane.position = new Vector3(camPos.x + maskOffset.x, maskOffset.y, camPos.z + maskOffset.z);
|
||||
}
|
||||
|
||||
// ===== 光源注册/注销 =====
|
||||
|
||||
public void RegisterLight(LightSource source)
|
||||
{
|
||||
if (!_lights.Contains(source))
|
||||
_lights.Add(source);
|
||||
}
|
||||
|
||||
public void UnregisterLight(LightSource source)
|
||||
{
|
||||
_lights.Remove(source);
|
||||
}
|
||||
|
||||
// ===== Inspector 同步 =====
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (darknessMaterial == null) return;
|
||||
darknessMaterial.SetColor(DarknessColorID, darknessColor);
|
||||
darknessMaterial.SetFloat(LightSoftnessID, lightSoftness);
|
||||
|
||||
if (debugShowAll)
|
||||
darknessMaterial.SetFloat(MinBrightnessID, 1.0f);
|
||||
else
|
||||
darknessMaterial.SetFloat(MinBrightnessID, minBrightness);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快捷切换黑暗开关(右键组件 > Toggle Darkness)
|
||||
/// </summary>
|
||||
[ContextMenu("Toggle Darkness (Debug)")]
|
||||
private void ToggleDarknessDebug()
|
||||
{
|
||||
debugShowAll = !debugShowAll;
|
||||
OnValidate();
|
||||
Debug.Log($"[LightMaskSystem] Darkness {(debugShowAll ? "OFF (debug)" : "ON")}");
|
||||
}
|
||||
|
||||
// ===== 运行时动态调整 =====
|
||||
|
||||
/// <summary>
|
||||
/// 动态调整最小亮度(白天/黑夜切换、Q键过场点亮等)
|
||||
/// 0 = 纯黑(完全看不见),0.3 = 能模糊看到东西
|
||||
/// </summary>
|
||||
public void SetMinBrightness(float value)
|
||||
{
|
||||
minBrightness = Mathf.Clamp01(value);
|
||||
if (darknessMaterial != null && !debugShowAll)
|
||||
darknessMaterial.SetFloat(MinBrightnessID, minBrightness);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态调整黑暗底色
|
||||
/// </summary>
|
||||
public void SetDarknessColor(Color color)
|
||||
{
|
||||
darknessColor = color;
|
||||
if (darknessMaterial != null)
|
||||
darknessMaterial.SetColor(DarknessColorID, darknessColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user