Files
Dino_Love_Simulation/Assets/02_Scripts/UI/Communication/DialogHud.cs
2026-07-09 12:28:11 +09:00

93 lines
4.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using TMPro;
using UnityEngine;
// 화자(NPC) 옆에 떠 있는 World-space 대사 HUD 싱글턴.
// DialogPlayer가 대사 노드를 재생할 때 Show()로 화자 이름 + 대사를 표시한다.
//
// Placement(화자 옆 배치) 로직을 담당한다. 원래 ChoiceHud에 있던 로직을 이리로 옮겼다.
// 카메라(플레이어)가 아니라 화자(NPC)의 회전을 기준으로 배치/회전한다 —
// NPC가 LookAtPlayer 등으로 돌면 HUD도 같이 돈다. 플레이어 위치에는 영향받지 않는다.
// 자기 자신(transform)을 화자 옆으로 옮기므로, ChoiceHud를 이 오브젝트의 자식으로 두면 함께 따라온다.
// 주의: 이 오브젝트(GO)는 항상 활성 상태여야 한다(LateUpdate가 돌아야 하므로).
// 보이기/숨기기는 자식 패널(_panel)만 토글한다.
public class DialogHud : MonoBehaviour
{
public static DialogHud Instance { get; private set; }
[Header("Refs")]
[SerializeField] private GameObject _panel; // 대사 패널(토글 대상). 보통 이 오브젝트의 자식.
[SerializeField] private TMP_Text _speakerName;
[SerializeField] private TMP_Text _dialogueText;
[Header("Placement (기본값 — Show에서 오프셋을 안 넘길 때 폴백)")]
[SerializeField] private float _chestHeight = 1.2f; // 화자 발 기준 가슴 높이
[SerializeField] private float _forwardOffset = 0.5f; // 화자가 바라보는 방향으로 띄울 거리
[SerializeField] private float _lateralOffset = 0f; // 좌우 오프셋 (+ 화자와 마주보는 시점 오른쪽)
[SerializeField] private Vector3 _rotationOffset = Vector3.zero; // 화자 회전 기준 추가 회전 (+α, 오일러 각)
private Transform _speakerTransform;
private float _activeChestHeight;
private float _activeForwardOffset;
private float _activeLateralOffset;
private Vector3 _activeRotationOffset;
private void Awake()
{
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
Hide();
}
private void OnDestroy()
{
if (Instance == this) Instance = null;
}
// DialogHud 자체 기본 오프셋 사용
public void Show(CharacterData speaker, string text)
=> Show(speaker, text, _chestHeight, _forwardOffset, _lateralOffset, _rotationOffset);
// 배치 오프셋을 직접 넘겨 사용 (DialogPlayer가 NPC/씬별 값 전달)
public void Show(CharacterData speaker, string text, float chestHeight, float forwardOffset, float lateralOffset, Vector3 rotationOffset)
{
_speakerTransform = speaker != null ? CharacterVoiceObject.Find(speaker)?.transform : null;
_activeChestHeight = chestHeight;
_activeForwardOffset = forwardOffset;
_activeLateralOffset = lateralOffset;
_activeRotationOffset = rotationOffset;
if (_speakerName != null)
_speakerName.text = speaker != null ? DialogVariables.Format(speaker.Name) : string.Empty;
if (_dialogueText != null)
_dialogueText.text = DialogVariables.Format(text); // {key} 토큰 치환
if (_panel != null) _panel.SetActive(true);
}
public void Hide()
{
if (_dialogueText != null) _dialogueText.text = string.Empty;
if (_speakerName != null) _speakerName.text = string.Empty;
if (_panel != null) _panel.SetActive(false);
_speakerTransform = null;
}
private void LateUpdate()
{
if (_speakerTransform == null) return;
// 화자(NPC)가 바라보는 수평 방향 (yaw만) — LookAtPlayer 등으로 NPC가 돌면 HUD도 같이 돈다
Vector3 dir = _speakerTransform.forward;
dir.y = 0f;
if (dir.sqrMagnitude < 0.0001f) return;
dir.Normalize();
Vector3 right = Vector3.Cross(dir, Vector3.up); // 화자와 마주보는 시점 기준 오른쪽(수평)
Vector3 chestWorld = _speakerTransform.position + Vector3.up * _activeChestHeight;
transform.position = chestWorld + dir * _activeForwardOffset + right * _activeLateralOffset;
// 읽는 면(-Z)이 화자가 바라보는 쪽을 향하도록 놓고, 그 위에 추가 회전(+α)을 곱한다
transform.rotation = Quaternion.LookRotation(-dir) * Quaternion.Euler(_activeRotationOffset);
}
}