109 lines
4.6 KiB
C#
109 lines
4.6 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace WL.Player
|
|
{
|
|
/// <summary>
|
|
/// 물 판정기 (PD #704 범위확정 1 — 수영).
|
|
///
|
|
/// 씬에 물 볼륨/트리거 콜라이더를 새로 깔지 않고, 이미 있는 물 오브젝트의 렌더러 경계만 읽어서 판정한다.
|
|
/// (실측: BigIsland 의 물 = Ocean 1개 + 강 LOD 4개 뿐이라 이 방식이 가장 싸고 씬을 건드리지 않는다)
|
|
///
|
|
/// 판정 = 발밑 좌표가 어떤 물 오브젝트의 XZ 범위 안에 있고, 그 수면보다 아래로 내려간 깊이가
|
|
/// PlayerMoveSettings.swimEnterDepth 이상이면 수영. 물이 겹치면(바다 위에 강) 더 높은 수면을 쓴다.
|
|
///
|
|
/// 셰이더 이름·판정 수치는 모두 외부화한다(코드 상수 금지 · C45).
|
|
/// </summary>
|
|
[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<Renderer> _water = new List<Renderer>();
|
|
|
|
/// <summary>찾은 물 오브젝트 수. 0 이면 수영 판정이 절대 발생하지 않는다.</summary>
|
|
public int WaterCount { get { return _water.Count; } }
|
|
|
|
private void Awake() { Rescan(); }
|
|
|
|
/// <summary>물 오브젝트 목록을 다시 수집한다. 씬에 물을 추가·제거했다면 호출한다.</summary>
|
|
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<Renderer>(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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이 좌표의 XZ 를 덮는 물이 있으면 그 수면 높이를 돌려준다(겹치면 가장 높은 수면).
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>발밑(feetY)이 수면 아래로 잠긴 깊이(m). 물이 없거나 물 위면 음수/0.</summary>
|
|
public float GetSubmergeDepth(Vector3 feetPos)
|
|
{
|
|
float surface;
|
|
string zone;
|
|
if (!TryGetSurfaceY(feetPos, out surface, out zone)) return float.NegativeInfinity;
|
|
return surface - feetPos.y;
|
|
}
|
|
}
|
|
}
|