using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.UIElements; // 증거품창. 가진 항목을 좌우 화살표로 넘겨 본다. // // 하위 분류가 둘이고 Evidence 버튼 하나로 순환한다: 닫힘 → 소지품 → 인물 정보 → 닫힘. // 두 분류는 같은 창·같은 레이아웃을 쓰고, 보고 있는 분류의 항목만 목록에 오른다. // // 제시(들이대기): 대화 노드가 히든 분기로 무장돼 있는 동안 그림을 클릭하면 // HiddenBranchResolver.Fire(항목 Id)를 쏜다 — DialogTouchZone의 부위 키와 완전히 같은 통로라서, // 노드에 Key = 항목 Id 인 히든 분기를 달아두면 그 분기가 열린다. 발동하면 창은 자동으로 닫힌다. // 소지품과 인물 모두 제시할 수 있다. 무장돼 있지 않으면(평소) 클릭해도 아무 일도 없다. // // 목록은 StoryManager의 획득 이력(Id)을 EvidenceDatabase로 복원해 만든다. 획득 순서 = 표시 순서. [RequireComponent(typeof(PanelRenderer))] public class EvidenceHud : MonoBehaviour { public static EvidenceHud Instance { get; private set; } [Tooltip("Id → EvidenceData 복원용 전체 목록 (소지품·인물 모두 포함, 게임에 하나)")] [SerializeField] private EvidenceDatabase _database; [Tooltip("창 상단에 표시할 소지품 분류 이름")] [SerializeField] private string _itemCategoryLabel = "소지품"; [Tooltip("창 상단에 표시할 인물 정보 분류 이름")] [SerializeField] private string _profileCategoryLabel = "인물 정보"; [Tooltip("가진 소지품이 없을 때 설명 칸에 표시할 문구")] [SerializeField] private string _emptyItemText = "가지고 있는 소지품이 없습니다."; [Tooltip("등록된 인물이 없을 때 설명 칸에 표시할 문구")] [SerializeField] private string _emptyProfileText = "등록된 인물이 없습니다."; [Tooltip("제시가 실제로 발동했을 때 호출 (연출·사운드용). 발동하지 않은 클릭에는 불리지 않는다")] [SerializeField] private UnityEvent _onPresented; [Header("Slide")] [Tooltip("아이템 전환 슬라이드 전체 시간(초). 0이면 연출 없이 즉시 교체")] [Min(0f)] [SerializeField] private float _slideDuration = 0.16f; [Tooltip("슬라이드로 밀려나는 거리(px)")] [Min(0f)] [SerializeField] private float _slideDistance = 500f; private PanelRenderer _panelRenderer; private VisualElement _root; // 전체 토글 대상(#Body) private VisualElement _itemNode; // 슬라이드 대상 (.EvidenceNode — 그림+설명 묶음) private Label _categoryLabel; // 창 상단 분류 이름 (#EvidenceCategory) private Image _image; private Label _label; // Step마다 증가 — 연타하면 진행 중이던 슬라이드가 즉시 물러나고 새 슬라이드가 이어받는다 // (Typewriter의 세대 패턴과 동일) private int _slideGen; // 같은 버전으로 콜백이 중복 호출될 때 헛일을 막는다 (Unity 권장 패턴) private int _uiVersion = -1; // 현재 표시 상태. 리로드로 요소가 새로 만들어져도 이 값으로 복원된다. private bool _visible; private EvidenceCategory _category = EvidenceCategory.Item; // 지금 보고 있는 분류 private int _index; private readonly List _owned = new(); // 창이 떠 있는가 (DialogTouchZone이 창 뒤 월드 클릭을 무시하는 데 쓴다) public bool IsOpen => _visible; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; _panelRenderer = GetComponent(); _panelRenderer.RegisterUIReloadCallback(OnUIReload); } private void Start() { // 창이 열려 있는 동안 아이템을 얻으면 목록이 바로 갱신되게 if (StoryManager.Instance != null) StoryManager.Instance.Changed += OnStoryChanged; // 증거품창 버튼 (Evidence 액션) — 입력 해석은 InputManager가, 여닫는 행동은 창 자신이 맡는다 if (InputManager.Instance != null) { InputManager.Instance.OnEvidence_Event += Toggle; InputManager.Instance.OnEvidencePrev_Event += OnPrevPressed; InputManager.Instance.OnEvidenceNext_Event += OnNextPressed; } } private void OnDestroy() { if (StoryManager.Instance != null) StoryManager.Instance.Changed -= OnStoryChanged; if (InputManager.Instance != null) { InputManager.Instance.OnEvidence_Event -= Toggle; InputManager.Instance.OnEvidencePrev_Event -= OnPrevPressed; InputManager.Instance.OnEvidenceNext_Event -= OnNextPressed; } 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; _root = root.Q("Body"); _itemNode = root.Q(className: "EvidenceNode"); _categoryLabel = root.Q