This commit is contained in:
JA
2026-06-27 03:36:46 +08:00
parent aec4e97d27
commit 7a8d4a5d83
1215 changed files with 48271 additions and 146052 deletions

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: W3gcvSivBi1oTGd80Lv6CVuiGVtnr2kZBHGZCHcSZmKJh1zzbiCU3xU=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,106 @@
using UnityEngine;
namespace IndianOceanAssets.Engine2_5D
{
/// <summary>
/// 光源组件方案B用—— 挂到任何需要发光的物体上。
/// 向 LightMaskSystem 注册自己的世界坐标、半径、强度。
/// 不创建真实的 Point Light而是由 DarknessOverlay shader 在遮罩层挖洞。
/// </summary>
public class LightSource : MonoBehaviour
{
[Header("光源参数")]
[SerializeField] private float radius = 4f; // 光照半径(世界单位)
[SerializeField] private float intensity = 1f; // 光照强度 (0~1)
[SerializeField] private bool registerOnStart = true;
[Header("可选:目标跟随(留空则跟随自身)")]
[SerializeField] private Transform followTarget;
[Header("闪烁效果")]
[SerializeField] private bool enableFlicker = false;
[SerializeField] private float flickerSpeed = 5f;
[SerializeField] private float flickerAmount = 0.2f;
private float baseIntensity;
private float noiseSeed;
private void Start()
{
baseIntensity = intensity;
noiseSeed = Random.Range(0f, 100f);
if (registerOnStart && LightMaskSystem.Instance != null)
LightMaskSystem.Instance.RegisterLight(this);
}
private void Update()
{
if (enableFlicker)
{
float noise = Mathf.PerlinNoise(Time.time * flickerSpeed, noiseSeed);
intensity = baseIntensity + (noise - 0.5f) * 2f * flickerAmount;
intensity = Mathf.Clamp01(intensity);
}
}
/// <summary>
/// 返回光源在世界坐标的位置XZ 平面)
/// </summary>
public Vector2 GetWorldPositionXZ()
{
Transform t = followTarget != null ? followTarget : transform;
return new Vector2(t.position.x, t.position.z);
}
public float Radius => radius;
public float Intensity => intensity;
public void SetIntensity(float value)
{
baseIntensity = Mathf.Clamp01(value);
intensity = baseIntensity;
}
public void SetRadius(float value)
{
radius = Mathf.Max(0.1f, value);
}
/// <summary>
/// 平滑过渡半径(如 Q 键过场时光圈逐渐扩大)
/// </summary>
public void LerpRadius(float target, float speed)
{
radius = Mathf.Lerp(radius, target, Time.deltaTime * speed);
radius = Mathf.Max(0.1f, radius);
}
/// <summary>
/// 平滑过渡强度(如玩家受伤时光变暗、靠近篝火时光变亮)
/// </summary>
public void LerpIntensity(float target, float speed)
{
baseIntensity = Mathf.Lerp(baseIntensity, Mathf.Clamp01(target), Time.deltaTime * speed);
intensity = baseIntensity;
}
private void OnEnable()
{
if (LightMaskSystem.Instance != null)
LightMaskSystem.Instance.RegisterLight(this);
}
private void OnDisable()
{
if (LightMaskSystem.Instance != null)
LightMaskSystem.Instance.UnregisterLight(this);
}
private void OnDestroy()
{
if (LightMaskSystem.Instance != null)
LightMaskSystem.Instance.UnregisterLight(this);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: DXwasiiqUCpQtkGljnadQ+RnnuWuF/epuKhbMn0neaJ+FsgFuxBdjWM=
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: IndianOcean_DarknessOverlay
m_Shader: {fileID: 4800000, guid: 207cc8c905f607746a3a339a9e6d57c6, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs: []
m_Ints: []
m_Floats:
- _DarknessAlpha: 0.98
- _EchoInnerGlow: 0.12
- _EchoIntensity: 0
- _EchoRadius: 39.41617
- _EchoWidth: 2.5
- _LightSoftness: 0.5
- _MinBrightness: 0
m_Colors:
- _DarknessColor: {r: 0.01, g: 0.01, b: 0.02, a: 1}
- _EchoCenter: {r: -2.4399998, g: -1.5000057, b: 0, a: 0}
m_BuildTextureStacks: []

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: CS5LtCL5UXhUjMHSunwr/VifpRn2vjkqduXuKDXYrjVkWKTIF9e3950=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: IndianOcean_SpriteEchoOutline
m_Shader: {fileID: 4800000, guid: 15a387d57aa9cac4d85fb36945615daf, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _OutlineWidth: 2
m_Colors:
- _OutlineColor: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: DntKtn6lB33AwMEAocK58HaVURACxdbjx82TK/bFBiFpzdzdXvm9V5k=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant: