using System.Collections.Generic; using UnityEngine; public class CharacterVoiceObject : MonoBehaviour { public CharacterData Character; public AudioSource VoiceSource; private static readonly Dictionary _registry = new(); // Enter Play Mode에서 도메인 리로드를 꺼도 이전 세션의 등록이 남지 않게 // (DialogPlayer·HiddenBranchResolver의 static과 같은 이유) [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void ResetStatics() => _registry.Clear(); // Character를 비워 두면 Dictionary 널 키로 예외가 나므로 등록하지 않는다. // (화자 없는 대화는 CharacterVoiceObject 자체를 붙이지 않는 씬 플레이어로 처리한다) private void OnEnable() { if (Character == null) { Debug.LogWarning($"[CharacterVoiceObject] Character가 비어 있어 등록하지 않음: {name}"); return; } // 같은 캐릭터의 오브젝트가 둘 이상 켜져 있으면 나중 것이 등록을 가져간다. // 조용히 덮으면 "포즈·보이스가 엉뚱한 오브젝트로 간다"는 재현 어려운 버그가 되므로 알린다. if (_registry.TryGetValue(Character, out var existing) && existing != null && existing != this) Debug.LogWarning($"[CharacterVoiceObject] {Character.name} 오브젝트가 둘 이상 활성 상태 — " + $"'{existing.name}' 대신 '{name}'이 등록된다. 한쪽을 꺼 둘 것"); _registry[Character] = this; } private void OnDisable() { // 내가 등록돼 있을 때만 지운다. // 같은 캐릭터의 오브젝트가 둘일 때 무조건 지우면, 먼저 꺼지는 쪽이 살아 있는 다른 쪽의 // 등록까지 날려서 Find가 null을 반환하게 된다 — 포즈도 보이스도 조용히 죽는 상태다. if (Character != null && _registry.TryGetValue(Character, out var registered) && registered == this) _registry.Remove(Character); } // data가 null이면 Dictionary가 예외를 던지므로 먼저 막는다 (화자 없는 대사 경로에서 올 수 있다) public static CharacterVoiceObject Find(CharacterData data) => data != null && _registry.TryGetValue(data, out var obj) ? obj : null; public void Play(AudioClip clip) => VoiceSource.PlayOneShot(clip); }