using System; using System.Collections.Generic; using Cielonos.Core.Interaction; using Cielonos.MainGame.Narrative; using Sirenix.OdinInspector; using SLSUtilities.FunctionalAnimation; using SLSUtilities.Narrative; using UnityEngine; namespace Cielonos.MainGame.Interactions { /// /// NPC 交互基类。 /// 场景中的 NPC 挂载此脚本,交互时会自动将自身的 storyId 投递给 StoryDirector。 /// public class NpcBase : InteractableObjectBase, IFuncAnimExecutor { #region Fields & Properties [SerializeField] protected CharacterData characterData; [TitleGroup("Story Settings", "剧情与对话路由配置", Alignment = TitleAlignments.Centered)] [SerializeField] [Tooltip("对应 NarrativeEntry 剧情路由表中的 storyId (如 'OldMan')")] protected string storyId; /// NPC 的唯一故事 ID。 public string StoryId => storyId; [TitleGroup("Components & References", "组件与动作数据引用", Alignment = TitleAlignments.Centered)] public Animator animator; public AnimatorOverrideController animatorOverride; public FuncAnimDataCollection fullBodyFuncAnims; public VFXData vfxData; /// NPC 专属动画播放子模块 [HideInInspector] public NpcFuncAnimSubmodule funcAnimSm; /// 全局活跃 NPC 查找注册表 (通过 StoryId 和 GameObject 名字进行双重映射查找) #endregion #region IFuncAnimExecutor Implementation public Transform Transform => transform; public Vector3 CenterPosition => transform.position; public Animator Animator => animator; #endregion #region Lifecycle Callbacks protected virtual void OnEnable() { StoryDirector.ActiveNpcs[characterData.nameKey] = this; } protected virtual void OnDisable() { StoryDirector.ActiveNpcs.Remove(characterData.nameKey); } private void Start() { funcAnimSm = new NpcFuncAnimSubmodule(this); if (fullBodyFuncAnims != null && fullBodyFuncAnims.animDataList != null) { foreach (FuncAnimData animData in fullBodyFuncAnims.animDataList) { funcAnimSm.Add(animData); } } } protected virtual void Update() { // 推进时间、检测非循环动作结束以及执行重置 funcAnimSm?.Update(Time.deltaTime); } protected virtual void LateUpdate() { // 在物理表现计算完毕后分发每帧的特效/音效等事件 funcAnimSm?.UpdateEvents(); } #endregion #region Interaction Logic protected override void InitializeChoices() { choices.Add(new InteractionChoice("Talk", Talk)); } /// /// 触发对话。NPC 并不感知具体播放哪个 Yarn 节点,完全委托给 StoryDirector 进行状态评估。 /// public virtual void Talk() { if (string.IsNullOrEmpty(storyId)) { Debug.LogWarning($"[NpcBase] {gameObject.name} 未配置 storyId,无法启动对话。"); return; } if (StoryDirector.Instance == null) { Debug.LogError("[NpcBase] 启动对话失败:场景中未找到 StoryDirector 实例!"); return; } StoryDirector.Instance.PlayStory(storyId); } #endregion } }