diff --git a/Assets/_Game/Scripts/Player/ObstacleTransparencyController.cs b/Assets/_Game/Scripts/Player/ObstacleTransparencyController.cs new file mode 100644 index 00000000..e6536005 --- /dev/null +++ b/Assets/_Game/Scripts/Player/ObstacleTransparencyController.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; + +using UnityEngine; + +namespace Colosseum.Player +{ + /// + /// 카메라와 타겟 사이의 장애물을 숨기는 컨트롤러 + /// + public class ObstacleTransparencyController : MonoBehaviour + { + [Header("Occlusion Settings")] + [Min(0.01f)] [SerializeField] private float checkRadius = 0.3f; + + private Transform cameraTransform; + private Transform targetTransform; + private LayerMask occlusionMask; + private readonly HashSet hiddenRenderers = new(); + + public void Initialize(Transform target, LayerMask mask) + { + cameraTransform = GetComponent().transform; + targetTransform = target; + occlusionMask = mask; + } + + private void LateUpdate() + { + if (cameraTransform == null || targetTransform == null) return; + + Vector3 origin = cameraTransform.position; + Vector3 direction = targetTransform.position - origin; + float maxDistance = direction.magnitude; + + if (maxDistance < 0.01f) return; + + direction.Normalize(); + + HashSet currentHits = CollectHits(origin, direction, maxDistance); + + foreach (Renderer renderer in currentHits) + { + if (!hiddenRenderers.Contains(renderer)) + { + renderer.enabled = false; + hiddenRenderers.Add(renderer); + } + } + + List toShow = null; + foreach (Renderer renderer in hiddenRenderers) + { + if (renderer == null || !currentHits.Contains(renderer)) + { + if (renderer != null) + renderer.enabled = true; + (toShow ??= new List()).Add(renderer); + } + } + + if (toShow != null) + { + foreach (Renderer renderer in toShow) + hiddenRenderers.Remove(renderer); + } + } + + private HashSet CollectHits(Vector3 origin, Vector3 direction, float maxDistance) + { + var hits = new HashSet(); + RaycastHit[] rayHits = Physics.SphereCastAll(origin, checkRadius, direction, maxDistance, occlusionMask, QueryTriggerInteraction.Ignore); + + float targetY = targetTransform.position.y; + + foreach (RaycastHit hit in rayHits) + { + if (hit.point.y <= targetY) continue; + + Renderer renderer = hit.collider.GetComponent(); + if (renderer == null) continue; + if (renderer is ParticleSystemRenderer || renderer is TrailRenderer) continue; + + hits.Add(renderer); + } + + return hits; + } + + public void ClearHidden() + { + foreach (Renderer renderer in hiddenRenderers) + { + if (renderer != null) + renderer.enabled = true; + } + + hiddenRenderers.Clear(); + } + + private void OnDestroy() + { + ClearHidden(); + } + } +} diff --git a/Assets/_Game/Scripts/Player/ObstacleTransparencyController.cs.meta b/Assets/_Game/Scripts/Player/ObstacleTransparencyController.cs.meta new file mode 100644 index 00000000..c7e35de4 --- /dev/null +++ b/Assets/_Game/Scripts/Player/ObstacleTransparencyController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2e94a9a820149c5589b02955f59d465e \ No newline at end of file diff --git a/Assets/_Game/Scripts/Player/PlayerCamera.cs b/Assets/_Game/Scripts/Player/PlayerCamera.cs index fc025a3d..94aafa82 100644 --- a/Assets/_Game/Scripts/Player/PlayerCamera.cs +++ b/Assets/_Game/Scripts/Player/PlayerCamera.cs @@ -17,9 +17,7 @@ namespace Colosseum.Player [SerializeField] private float maxPitch = 60f; [Header("Collision")] - [SerializeField] private float collisionRadius = 0.3f; [SerializeField] private LayerMask collisionMask; - [SerializeField] private float minDistance = 0.5f; [SerializeField] private float minHeightAboveGround = 0.3f; // 바닥 위 최소 카메라 높이 [Header("Smooth Distance")] @@ -87,6 +85,12 @@ namespace Colosseum.Player currentDistance = distance; distanceSmoothVelocity = 0f; + // 장애물 반투명 컨트롤러 연동 + var transparencyController = cameraInstance.gameObject.GetComponent(); + if (transparencyController == null) + transparencyController = cameraInstance.gameObject.AddComponent(); + transparencyController.Initialize(target, collisionMask); + // 카메라 위치를 즉시 타겟 위치로 초기화 SnapToTarget(); } @@ -177,19 +181,8 @@ namespace Colosseum.Player Vector3 pivot = target.position + Vector3.up * height * 0.5f; Vector3 desiredPos = target.position + offset; - // pivot → desiredPos 방향으로 SphereCast해서 지형 충돌 감지 - Vector3 dir = desiredPos - pivot; - float maxDist = dir.magnitude; - float targetDistance = distance; - if (Physics.SphereCast(pivot, collisionRadius, dir.normalized, out RaycastHit hit, maxDist, collisionMask, QueryTriggerInteraction.Ignore)) - { - // 충돌 지점에서 collisionRadius만큼 pivot 쪽으로 당긴 거리 - targetDistance = Mathf.Max(minDistance, hit.distance - collisionRadius); - } - // 부드러운 거리 보간 - currentDistance = Mathf.SmoothDamp(currentDistance, targetDistance, ref distanceSmoothVelocity, distanceSmoothTime); - currentDistance = Mathf.Max(minDistance, currentDistance); + currentDistance = Mathf.SmoothDamp(currentDistance, distance, ref distanceSmoothVelocity, distanceSmoothTime); // 보간된 거리로 최종 오프셋 재계산 offset = rotation * new Vector3(0f, 0f, -currentDistance);