using System.Collections.Generic; using UnityEngine; namespace WL.Player { /// /// 물 판정기 (PD #704 범위확정 1 — 수영). /// /// 씬에 물 볼륨/트리거 콜라이더를 새로 깔지 않고, 이미 있는 물 오브젝트의 렌더러 경계만 읽어서 판정한다. /// (실측: BigIsland 의 물 = Ocean 1개 + 강 LOD 4개 뿐이라 이 방식이 가장 싸고 씬을 건드리지 않는다) /// /// 판정 = 발밑 좌표가 어떤 물 오브젝트의 XZ 범위 안에 있고, 그 수면보다 아래로 내려간 깊이가 /// PlayerMoveSettings.swimEnterDepth 이상이면 수영. 물이 겹치면(바다 위에 강) 더 높은 수면을 쓴다. /// /// 셰이더 이름·판정 수치는 모두 외부화한다(코드 상수 금지 · C45). /// [DisallowMultipleComponent] public class WaterProbe : MonoBehaviour { [Header("물 인식")] [Tooltip("이 셰이더를 쓰는 머티리얼이 붙은 렌더러를 물로 본다")] [SerializeField] private string waterShaderName = "Shader Graphs/ToonWaterU"; [Tooltip("비워 두면 씬 전체에서 자동으로 찾는다. 직접 지정하면 그 렌더러만 물로 본다")] [SerializeField] private Renderer[] explicitWaterRenderers; [Tooltip("시작할 때 찾은 물 목록을 콘솔에 남긴다")] [SerializeField] private bool logDiscovery = true; private readonly List _water = new List(); /// 찾은 물 오브젝트 수. 0 이면 수영 판정이 절대 발생하지 않는다. public int WaterCount { get { return _water.Count; } } private void Awake() { Rescan(); } /// 물 오브젝트 목록을 다시 수집한다. 씬에 물을 추가·제거했다면 호출한다. public void Rescan() { _water.Clear(); if (explicitWaterRenderers != null && explicitWaterRenderers.Length > 0) { foreach (var r in explicitWaterRenderers) if (r != null) _water.Add(r); } else { var all = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); foreach (var r in all) { var m = r.sharedMaterial; if (m == null || m.shader == null) continue; if (m.shader.name != waterShaderName) continue; _water.Add(r); } } if (logDiscovery) { var sb = new System.Text.StringBuilder(); sb.Append("[WaterProbe] 물 오브젝트 ").Append(_water.Count).Append("개 인식"); for (int i = 0; i < _water.Count; i++) { var b = _water[i].bounds; sb.Append(" | ").Append(_water[i].gameObject.name) .Append(" 수면y=").Append(b.max.y.ToString("F2")) .Append(" XZ=").Append(b.min.x.ToString("F0")).Append("~").Append(b.max.x.ToString("F0")) .Append(",").Append(b.min.z.ToString("F0")).Append("~").Append(b.max.z.ToString("F0")); } Debug.Log(sb.ToString(), this); } } /// /// 이 좌표의 XZ 를 덮는 물이 있으면 그 수면 높이를 돌려준다(겹치면 가장 높은 수면). /// public bool TryGetSurfaceY(Vector3 worldPos, out float surfaceY, out string zoneName) { surfaceY = 0f; zoneName = null; bool found = false; for (int i = 0; i < _water.Count; i++) { var r = _water[i]; if (r == null) continue; var b = r.bounds; if (worldPos.x < b.min.x || worldPos.x > b.max.x) continue; if (worldPos.z < b.min.z || worldPos.z > b.max.z) continue; if (!found || b.max.y > surfaceY) { surfaceY = b.max.y; zoneName = r.gameObject.name; found = true; } } return found; } /// 발밑(feetY)이 수면 아래로 잠긴 깊이(m). 물이 없거나 물 위면 음수/0. public float GetSubmergeDepth(Vector3 feetPos) { float surface; string zone; if (!TryGetSurfaceY(feetPos, out surface, out zone)) return float.NegativeInfinity; return surface - feetPos.y; } } }