Files
2026-07-05 15:33:41 +08:00

210 lines
7.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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("遮罩平面跟随目标(留空则自动查找 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<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();
// 自动查找玩家作为跟随目标
if (followTarget == null)
{
var player = GameObject.FindWithTag("Player");
if (player != null)
followTarget = player.transform;
}
// 确保遮罩平面在所有Sprite之后渲染
if (maskPlane != null)
{
Renderer renderer = maskPlane.GetComponent<Renderer>();
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);
}
/// <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);
}
}
}