using System.Collections.Generic; using UnityEngine; namespace Cielonos.MainGame.Characters { public partial class OcclusionFadeSubmodule { // --- 内部类:用于管理正在褪色的物体状态 --- private class FadingObject { public Renderer renderer; public MaterialPropertyBlock propBlock; public float currentAlpha; public float lastHitTime; public FadingObject(Renderer r) { renderer = r; propBlock = new MaterialPropertyBlock(); // 初始获取当前可能的Fade值,默认为1 currentAlpha = 1.0f; lastHitTime = Time.time; } } } public partial class OcclusionFadeSubmodule : SubmoduleBase { [Header("References")] [Tooltip("主角头部的Transform")] public Transform playerHead; [Tooltip("主角中心点的Transform")] public Transform playerCenter; [Tooltip("主角脚部的Transform")] public Transform playerFeet; [Tooltip("主摄像机,如果不填则自动获取MainCamera")] public Camera targetCamera; [Header("Settings")] [Tooltip("遮挡层级:只检测这些层级的物体(例如 Wall, Default)")] public LayerMask obstructionLayer; [Header("Fading Settings")] [Tooltip("物体变透明的速度 (建议快一点,比如 10-15)")] public float fadeOutSpeed = 5f; [Tooltip("物体恢复实体的速度 (建议慢一点,比如 2-5)")] public float fadeInSpeed = 2f; [Tooltip("被遮挡时的目标透明度")] [Range(0f, 1f)] public float targetAlpha = 0.5f; [Header("Stability")] [Tooltip("防抖动缓冲时间:当物体不再遮挡视线后,延迟多少秒才开始恢复实体?")] public float recoveryDelay = 0.1f; [Tooltip("射线球体半径:使用SphereCast代替Raycast可以让检测更宽容,防止细小缝隙穿帮")] public float checkRadius = 0.1f; // 字典:Renderer -> 状态对象 private Dictionary _fadingObjects = new Dictionary(); // 缓存 ID private static readonly int FadeID = Shader.PropertyToID("_Fade"); // 射线检测缓存数组 (NonAlloc优化,避免每帧GC) private RaycastHit[] _hitsBuffer = new RaycastHit[20]; private List _debugLines = new List(); public OcclusionFadeSubmodule(PlayerViewSubcontroller owner) : base(owner) { obstructionLayer = LayerMask.GetMask("FadableEnvironment"); targetCamera = owner.playerCamera; playerHead = owner.player.bodyPartsSc.head; playerCenter = owner.player.bodyPartsSc.flexibleCenterPoint; playerFeet = owner.player.bodyPartsSc.footPoint; } public void Update() { if (playerHead == null || playerCenter == null || playerFeet == null || targetCamera == null) return; // 2. 执行射线检测 Vector3 cameraPos = targetCamera.transform.position; // 检测头部路径 CastRayAndMark(cameraPos, playerHead.position); // 检测中心路径 CastRayAndMark(cameraPos, playerCenter.position); // 检测脚部路径 CastRayAndMark(cameraPos, playerFeet.position); // 3. 处理淡入淡出逻辑 ProcessFading(); } private void CastRayAndMark(Vector3 start, Vector3 end) { Vector3 direction = end - start; float distance = direction.magnitude; int hitCount = Physics.SphereCastNonAlloc( start, checkRadius, direction.normalized, _hitsBuffer, distance, obstructionLayer, QueryTriggerInteraction.Ignore ); for (int i = 0; i < hitCount; i++) { RaycastHit hit = _hitsBuffer[i]; Renderer r = hit.collider.GetComponent(); if (r == null || hit.transform.root == playerHead.root) continue; // 如果是新物体,加入字典 if (!_fadingObjects.ContainsKey(r)) { if (r.sharedMaterial != null && r.sharedMaterial.HasProperty(FadeID)) { _fadingObjects.Add(r, new FadingObject(r)); } } // 更新击中时间 if (_fadingObjects.TryGetValue(r, out FadingObject obj)) { obj.lastHitTime = Time.time; // 刷新时间戳 } } } private void ProcessFading() { List keysToRemove = new List(); float currentTime = Time.time; foreach (var kvp in _fadingObjects) { FadingObject obj = kvp.Value; Renderer r = obj.renderer; if (r == null) { keysToRemove.Add(kvp.Key); continue; } // --- 核心逻辑修改 --- // 判断是否处于"遮挡状态"或"缓冲期" // 只要当前时间 < 最后击中时间 + 延迟时间,就认为它还应该保持透明 bool isObstructedOrBuffered = currentTime < (obj.lastHitTime + recoveryDelay); float target = isObstructedOrBuffered ? targetAlpha : 1.0f; // 根据目标选择速度:变透明用快速度,恢复用慢速度 float speed = isObstructedOrBuffered ? fadeOutSpeed : fadeInSpeed; // 执行插值 obj.currentAlpha = Mathf.MoveTowards(obj.currentAlpha, target, Time.deltaTime * speed); // 应用属性 r.GetPropertyBlock(obj.propBlock); obj.propBlock.SetFloat(FadeID, obj.currentAlpha); r.SetPropertyBlock(obj.propBlock); // 清理逻辑:只有当完全恢复,且早已过了缓冲期才移除 if (!isObstructedOrBuffered && Mathf.Approximately(obj.currentAlpha, 1.0f)) { keysToRemove.Add(r); } } foreach (var key in keysToRemove) { _fadingObjects.Remove(key); } } } }