using System.Collections.Generic; using UnityEngine; namespace IndianOceanAssets.Engine2_5D { /// /// 黑暗遮罩管理器 v2 —— 乘法混合 + 编辑器开关(不含回声)。 /// 回声描边已独立到 EchoSystem + SpriteEchoOutline shader。 /// /// 单例模式:收集场景中所有 LightSource,每帧把位置/半径/强度 /// 写入 DarknessOverlay 材质的 _LightData 数组。 /// /// 使用方式: /// 1. 在场景中创建一个空物体,挂上此脚本 /// 2. 创建一个大平面(覆盖可视区域),Y 稍高于精灵 /// 3. 给平面分配使用 DarknessOverlay shader 的材质 /// 4. 把该材质拖到此脚本的 darknessMaterial 字段 /// 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("遮罩平面跟随目标(留空则自动查找 Player 标签)。\n跟随玩家而非摄像机,确保光照精度始终一致。")] [SerializeField] private Transform followTarget; [SerializeField] private Transform maskPlane; [SerializeField] private Vector3 maskOffset = new Vector3(0f, 5f, 0f); // 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 _lights = new List(); 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(); // 自动查找玩家作为跟随目标 if (followTarget == null) { var player = GameObject.FindWithTag("Player"); if (player != null) followTarget = player.transform; } // 确保遮罩平面在所有Sprite之后渲染 if (maskPlane != null) { Renderer renderer = maskPlane.GetComponent(); if (renderer != null) { renderer.sortingOrder = 9999; if (renderer is MeshRenderer && darknessMaterial != null) { darknessMaterial.renderQueue = 3100; } } } } private void LateUpdate() { // 在 FixedUpdate 之后读取光源位置,避免物理移动与光照更新时序不同步导致的抖动 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 (maskPlane == null) return; // 优先跟随玩家,回退到摄像机 Transform target = followTarget != null ? followTarget : _mainCamera?.transform; if (target == null) return; maskPlane.position = new Vector3( target.position.x + maskOffset.x, maskOffset.y, target.position.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); } /// /// 快捷切换黑暗开关(右键组件 > Toggle Darkness) /// [ContextMenu("Toggle Darkness (Debug)")] private void ToggleDarknessDebug() { debugShowAll = !debugShowAll; OnValidate(); Debug.Log($"[LightMaskSystem] Darkness {(debugShowAll ? "OFF (debug)" : "ON")}"); } // ===== 运行时动态调整 ===== /// /// 动态调整最小亮度(白天/黑夜切换、Q键过场点亮等) /// 0 = 纯黑(完全看不见),0.3 = 能模糊看到东西 /// public void SetMinBrightness(float value) { minBrightness = Mathf.Clamp01(value); if (darknessMaterial != null && !debugShowAll) darknessMaterial.SetFloat(MinBrightnessID, minBrightness); } /// /// 动态调整黑暗底色 /// public void SetDarknessColor(Color color) { darknessColor = color; if (darknessMaterial != null) darknessMaterial.SetColor(DarknessColorID, darknessColor); } } }