using UnityEngine; using UnityEngine.UIElements; // UI Toolkit 버전 대사 HUD. DialogUI.uxml의 요소(#SpeakerName / #DialogText / #DialogField)를 잡아 // 화자 이름 + 대사를 표시한다. 대사는 Typewriter로 한 글자씩 드러난다. // // UIDocument의 후속인 PanelRenderer를 쓴다. 요소 참조는 UI 리로드 콜백으로 받는다: // - rootVisualElement가 준비됐는지 매번 확인하던 지연 초기화(EnsureRefs)가 사라진다. // - 플레이 중 UXML을 수정해 UI가 리로드돼도 참조가 자동으로 다시 연결되고, // 타이핑 중이던 대사도 그 지점부터 이어진다 (표시가 ApplyState 한 곳으로 모여 있어서). [RequireComponent(typeof(PanelRenderer))] public class DialogHud : MonoBehaviour { public static DialogHud Instance { get; private set; } [Tooltip("대사의 기본 타이핑 연출. 노드에 Typewriter가 지정돼 있으면 그것이 우선한다. " + "여기까지 비우면 연출 없이 대사가 한 번에 표시된다")] [SerializeField] private TypewriterStyle _defaultStyle; private PanelRenderer _panelRenderer; private VisualElement _panel; // 대사 패널(#DialogField) — 토글 대상 private Label _speakerName; private Label _dialogText; // 현재 표시 상태. 리로드로 요소가 새로 만들어졌을 때 이 값으로 복원한다. private bool _visible; private string _speakerText = string.Empty; private Typewriter _typewriter; // 강조 단어([[...]])의 맥동 연출. 지금 대사에 강조가 있을 때만 매 프레임 다시 그린다. private readonly TextGlow _glow = new(); private bool _hasEmphasis; // 같은 버전으로 콜백이 중복 호출될 때 헛일을 막는다 (Unity 권장 패턴) private int _uiVersion = -1; // 대사가 아직 드러나는 중인가 (DialogPlayer가 진행 입력을 스킵으로 쓸지 판단) public bool IsRevealing => _typewriter != null && _typewriter.IsRevealing; // 남은 글자를 즉시 전부 드러낸다 (진행 입력 1번째) public void SkipReveal() => _typewriter?.SkipToEnd(); private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; _typewriter = new Typewriter(ApplyState); // 진행될 때마다 화면에 반영 _panelRenderer = GetComponent(); // root가 이미 준비돼 있으면 즉시 호출되고, 이후 UI가 리로드될 때마다 다시 호출된다 _panelRenderer.RegisterUIReloadCallback(OnUIReload); } private void OnDestroy() { if (_panelRenderer != null) _panelRenderer.UnregisterUIReloadCallback(OnUIReload); if (Instance == this) Instance = null; } // UI가 (재)구성될 때마다 요소를 다시 잡고 현재 표시 상태를 그대로 되돌린다. private void OnUIReload(PanelRenderer panelRenderer, VisualElement root, int version) { if (_uiVersion == version) return; _uiVersion = version; _panel = root.Q("DialogField"); _speakerName = root.Q