using System.Collections.Generic; using UnityEngine; using BaseGames.Localization; namespace BaseGames.Dialogue { /// /// (架构 14_NarrativeModule §3)。 /// 将 NPC 的显示名、头像、对话气泡颜色集中在一处管理。 /// DialogueLine.actor 引用此 SO,修改头像/名称只需改一个资产, /// 无需批量编辑所有对话行。 /// /// 资产路径:Assets/_Game/Data/Dialogue/Actors/Actor_{actorId}.asset /// [CreateAssetMenu(menuName = "BaseGames/Dialogue/DialogueActor")] public class DialogueActorSO : ScriptableObject, ILocalizableAsset { [Header("标识")] [Tooltip("唯一 ID,如 \"NPC_Elder\",供 DialogueLine 引用")] public string actorId; [Header("显示")] [Tooltip("本地化 Key,格式如 \"NPC_Elder_Name\"")] public string nameKey; [Tooltip("对话 UI 中显示的头像")] public Sprite portrait; [Tooltip("对话气泡/说话人名称的主题颜色(可选)")] public Color accentColor = Color.white; [Tooltip("是否为玩家角色(影响对话 UI 排版方向)")] public bool isPlayer; #if UNITY_EDITOR // actorId → 资产路径,5 秒 TTL,跨所有 DialogueActorSO.OnValidate 共用。 // 与 QuestSO / DialogueSequenceSO 保持一致的 O(1) 重复检测策略。 private static System.Collections.Generic.Dictionary s_actorIdToPath; private static double s_actorIdsCacheTime = -10.0; private static System.Collections.Generic.Dictionary GetActorIdCache() { double now = UnityEditor.EditorApplication.timeSinceStartup; if (s_actorIdToPath != null && now - s_actorIdsCacheTime < 5.0) return s_actorIdToPath; s_actorIdToPath = new System.Collections.Generic.Dictionary(System.StringComparer.Ordinal); string[] guids = UnityEditor.AssetDatabase.FindAssets("t:DialogueActorSO"); foreach (var guid in guids) { var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid); var actor = UnityEditor.AssetDatabase.LoadAssetAtPath(path); if (actor != null && !string.IsNullOrEmpty(actor.actorId) && !s_actorIdToPath.ContainsKey(actor.actorId)) s_actorIdToPath[actor.actorId] = path; } s_actorIdsCacheTime = now; return s_actorIdToPath; } private void OnValidate() { if (string.IsNullOrWhiteSpace(actorId)) { Debug.LogWarning($"[DialogueActorSO] '{name}' 缺少 actorId,保存前请填写。", this); return; } // 检测重复 actorId:缓存路径 vs 自身路径比对(O(1)),5 秒内无需重扫。 var cache = GetActorIdCache(); string myPath = UnityEditor.AssetDatabase.GetAssetPath(this); if (!string.IsNullOrEmpty(myPath) && cache.TryGetValue(actorId, out var existingPath) && existingPath != myPath) { Debug.LogError( $"[DialogueActorSO] actorId '{actorId}' 与 " + $"'{System.IO.Path.GetFileNameWithoutExtension(existingPath)}' 重复!请修改其中一个。", this); s_actorIdsCacheTime = -10.0; } } #endif public IEnumerable GetLocalizationKeys() { if (!string.IsNullOrEmpty(nameKey)) yield return new LocalizationKeyRef(nameKey, "Dialogue", nameof(nameKey)); } } }