창 올라타기

This commit is contained in:
2026-08-26 11:01:56 +09:00
parent 65024110df
commit cab0d772b1
18 changed files with 1335 additions and 31 deletions

View File

@@ -52,6 +52,14 @@ public class HeadLookAt : MonoBehaviour
[Tooltip("대상을 놓쳤을 때 정면으로 돌아가기까지의 유예 시간(초)")]
[SerializeField] float returnDelay = 1.5f;
[Header("주의 범위")]
[Tooltip("이 거리 안에서는 완전히 쳐다본다. 캐릭터의 화면상 키를 1 로 하는 배수. " +
"픽셀이 아니라 배수라서 캐릭터 크기나 해상도가 달라져도 체감이 유지된다")]
[SerializeField] float attentionRadius = 0.8f;
[Tooltip("반경 바깥으로 이만큼 더 멀어지는 동안 서서히 정면으로 돌아간다")]
[SerializeField] float attentionFalloff = 0.7f;
Transform head;
Transform neck;
Transform root;
@@ -59,6 +67,9 @@ public class HeadLookAt : MonoBehaviour
// 기준(정지) 자세. 회전 누적을 막기 위해 매 프레임 여기로 되돌린 뒤 적용한다.
Quaternion restHead, restNeck;
// 주의 반경의 기준 단위. 캐릭터가 화면에서 차지하는 높이(픽셀).
float characterScreenHeight = 200f;
Vector2 currentAngles; // x = yaw, y = pitch
float lastSeenTime = -999f;
@@ -93,6 +104,14 @@ bool TryResolveBones()
{
restHead = head.localRotation;
if (neck != null) restNeck = neck.localRotation;
// 캐릭터는 고정 깊이 평면 위에 있으므로 화면상 크기가 변하지 않는다. 한 번만 잰다.
if (viewCamera != null &&
CharacterScreenBounds.TryMeasure(viewCamera, root, out Vector2 offMin, out Vector2 offMax))
{
characterScreenHeight = Mathf.Max(1f, offMax.y - offMin.y);
}
resolved = true;
return true;
}
@@ -112,10 +131,10 @@ void LateUpdate()
if (head == null) return;
Vector2 desired;
if (TryGetTargetPosition(out Vector3 targetPos))
if (TryGetTargetPosition(out Vector3 targetPos, out float attention))
{
lastSeenTime = Time.time;
desired = ComputeAngles(targetPos);
desired = ComputeAngles(targetPos) * attention;
}
else if (Time.time - lastSeenTime < returnDelay)
{
@@ -133,9 +152,11 @@ void LateUpdate()
ApplyRotation();
}
bool TryGetTargetPosition(out Vector3 worldPos)
/// <param name="attention">0 = 관심 없음(정면), 1 = 완전히 쳐다봄</param>
bool TryGetTargetPosition(out Vector3 worldPos, out float attention)
{
worldPos = default;
attention = 1f;
if (!followCursor)
{
@@ -149,6 +170,14 @@ bool TryGetTargetPosition(out Vector3 worldPos)
System.IntPtr hwnd = window != null ? window.Hwnd : System.IntPtr.Zero;
if (!DesktopCursor.TryGetScreenPosition(hwnd, out Vector2 screenPos)) return false;
// 커서가 얼마나 가까운지로 관심도를 정한다. 멀면 굳이 쳐다보지 않는다.
Vector2 headScreen = viewCamera.WorldToScreenPoint(head.position);
float distance = Vector2.Distance(screenPos, headScreen);
float inner = attentionRadius * characterScreenHeight;
float outer = inner + Mathf.Max(1f, attentionFalloff * characterScreenHeight);
attention = 1f - Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(inner, outer, distance));
// 머리와 같은 깊이 평면에 커서를 투영한다.
Vector3 camForward = viewCamera.transform.forward;
float depth = Vector3.Dot(head.position - viewCamera.transform.position, camForward);