구닥다리 제거

This commit is contained in:
2026-07-30 10:49:41 +09:00
parent fc4093ee93
commit 28f014152c
8 changed files with 108 additions and 66 deletions

View File

@@ -3,67 +3,88 @@
// UI Toolkit 버전 대사 HUD. DialogUI.uxml의 요소(#SpeakerName / #DialogText / #DialogField)를 잡아
// 화자 이름 + 대사를 표시한다.
// 공개 API(Instance / Show / Hide)는 기존 uGUI 버전과 동일 — DialogPlayer는 수정 없이 그대로 쓴다.
[RequireComponent(typeof(UIDocument))]
// 공개 API(Instance / Show / Hide)는 기존과 동일 — DialogPlayer는 수정 없이 그대로 쓴다.
//
// UIDocument의 후속인 PanelRenderer를 쓴다. 요소 참조는 UI 리로드 콜백으로 받는다:
// - rootVisualElement가 준비됐는지 매번 확인하던 지연 초기화(EnsureRefs)가 사라진다.
// - 플레이 중 UXML을 수정해 UI가 리로드돼도 참조가 자동으로 다시 연결된다.
[RequireComponent(typeof(PanelRenderer))]
public class DialogHud : MonoBehaviour
{
public static DialogHud Instance { get; private set; }
private UIDocument _document;
private PanelRenderer _panelRenderer;
private VisualElement _panel; // 대사 패널(#DialogField) — 토글 대상
private Label _speakerName;
private Label _dialogText;
private bool _ready;
// 현재 표시 상태. 리로드로 요소가 새로 만들어졌을 때 이 값으로 복원한다.
private bool _visible;
private string _speakerText = string.Empty;
private string _bodyText = string.Empty;
// 같은 버전으로 콜백이 중복 호출될 때 헛일을 막는다 (Unity 권장 패턴)
private int _uiVersion = -1;
private void Awake()
{
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
_document = GetComponent<UIDocument>();
}
// 시작 시 숨김 (이 시점엔 UIDocument의 visual tree가 준비돼 있다)
private void Start() => Hide();
_panelRenderer = GetComponent<PanelRenderer>();
// root가 이미 준비돼 있으면 즉시 호출되고, 이후 UI가 리로드될 때마다 다시 호출된다
_panelRenderer.RegisterUIReloadCallback(OnUIReload);
}
private void OnDestroy()
{
if (_panelRenderer != null)
_panelRenderer.UnregisterUIReloadCallback(OnUIReload);
if (Instance == this) Instance = null;
}
// rootVisualElement가 준비된 뒤 한 번만 요소를 캐싱한다.
// (스크립트 실행 순서상 UIDocument보다 먼저 OnEnable이 돌 수 있어 지연 초기화로 안전하게 처리)
private bool EnsureRefs()
// UI가 (재)구성될 때마다 요소를 다시 잡고 현재 표시 상태를 그대로 되돌린다.
private void OnUIReload(PanelRenderer panelRenderer, VisualElement root, int version)
{
if (_ready) return true;
var root = _document != null ? _document.rootVisualElement : null;
if (root == null) return false;
if (_uiVersion == version) return;
_uiVersion = version;
_panel = root.Q<VisualElement>("DialogField");
_speakerName = root.Q<Label>("SpeakerName");
_dialogText = root.Q<Label>("DialogText");
_ready = true;
return true;
ApplyState(); // 첫 호출에선 _visible=false라 숨김 상태로 시작한다
}
// 화자 이름 + 대사 표시.
// - speakerNameOverride가 비어있지 않으면 CharacterData.Name 대신 그 이름을 표시한다 (예: "???")
public void Show(CharacterData speaker, string text, string speakerNameOverride = null)
{
if (!EnsureRefs()) return;
string speakerName = !string.IsNullOrEmpty(speakerNameOverride) ? speakerNameOverride
: speaker != null ? speaker.Name : string.Empty;
if (_speakerName != null) _speakerName.text = DialogVariables.Format(speakerName); // {key} 토큰 치환
if (_dialogText != null) _dialogText.text = DialogVariables.Format(text);
if (_panel != null) _panel.style.display = DisplayStyle.Flex;
_speakerText = DialogVariables.Format(speakerName); // {key} 토큰 치환
_bodyText = DialogVariables.Format(text);
_visible = true;
ApplyState();
}
public void Hide()
{
if (!EnsureRefs()) return;
if (_speakerName != null) _speakerName.text = string.Empty;
if (_dialogText != null) _dialogText.text = string.Empty;
if (_panel != null) _panel.style.display = DisplayStyle.None;
_speakerText = string.Empty;
_bodyText = string.Empty;
_visible = false;
ApplyState();
}
// 캐시된 상태를 실제 요소에 반영한다.
// 요소가 아직 없으면(리로드 콜백 전) 조용히 넘어가고, 콜백이 오면 같은 함수로 복원된다.
private void ApplyState()
{
if (_speakerName != null) _speakerName.text = _speakerText;
if (_dialogText != null) _dialogText.text = _bodyText;
if (_panel != null)
_panel.style.display = _visible ? DisplayStyle.Flex : DisplayStyle.None;
}
}