using System; using Animancer; using BaseGames.Combat; using BaseGames.Feedback; using BaseGames.Parry; using BaseGames.Player; using UnityEngine; namespace BaseGames.Animation { /// /// 玩家动画事件接收器(架构 §AnimationModule)。 /// 挂载于玩家 Prefab 根节点。负责将 Animancer 动画时间点回调路由到 /// HitBox 激活、招架窗口、无敌帧、移动取消窗口、反馈等系统。 /// /// 使用方式: /// 1. 在 Inspector 中填充 _hitBoxes、_hurtBox、_mover、_parrySystem。 /// 2. 将每条 ClipTransition + AnimationEventConfigSO 配对添加到 _bindings。 /// 3. Awake 中 AnimationEventBinder.Bind 自动完成注入。 /// public class PlayerAnimationEvents : MonoBehaviour, IAnimationEventHandler { [Serializable] public struct EventBinding { [Tooltip("Animancer ClipTransition(需与动画组件中同一引用绑定)。")] public ClipTransition clip; [Tooltip("对应的 AnimationEventConfigSO 资产。")] public AnimationEventConfigSO config; } [Header("子系统引用")] [SerializeField] private HitBox[] _hitBoxes; [SerializeField] private HurtBox _hurtBox; [SerializeField] private PlayerMovement _mover; [SerializeField] private ParrySystem _parrySystem; [Header("事件绑定(每个 Clip 对应一个配置资产)")] [SerializeField] private EventBinding[] _bindings; private IFeedbackPlayer _feedback; private void Awake() { // IFeedbackPlayer 由父层或同层实现(PlayerFeedback / NullFeedbackPlayer) _feedback = GetComponentInParent() ?? NullFeedbackPlayer.Instance; foreach (var b in _bindings) AnimationEventBinder.Bind(b.clip, b.config, this); } // ── IAnimationEventHandler ───────────────────────────────────────── public void HandleEvent(AnimationEventType type, string payload) { switch (type) { // ── 命中判定 ────────────────────────────────────────────── case AnimationEventType.EnableHitBox: SetHitBoxActive(payload, true); break; case AnimationEventType.DisableHitBox: SetHitBoxActive(payload, false); break; case AnimationEventType.AttackImpact: _feedback.PlayAttackWhoosh(); break; // ── 招架窗口 ────────────────────────────────────────────── case AnimationEventType.EnableParryWindow: _parrySystem?.OpenParryWindow(); break; case AnimationEventType.DisableParryWindow: _parrySystem?.CloseParryWindow(); break; // ── 无敌帧 ──────────────────────────────────────────────── case AnimationEventType.EnableIFrame: _hurtBox?.SetInvincible(true); break; case AnimationEventType.DisableIFrame: _hurtBox?.SetInvincible(false); break; // ── 移动反馈 ────────────────────────────────────────────── case AnimationEventType.LandImpact: _feedback.PlayLandImpact(); break; case AnimationEventType.JumpLaunch: _feedback.PlayJumpLaunch(); break; case AnimationEventType.Footstep: _feedback.PlayFootstep(); break; // ── 取消窗口 ────────────────────────────────────────────── case AnimationEventType.CancelWindowOpen: _mover?.SetCancelWindowOpen(true); break; case AnimationEventType.CancelWindowClose: _mover?.SetCancelWindowOpen(false); break; // ── 通用反馈 ────────────────────────────────────────────── case AnimationEventType.TriggerFeedback: if (!string.IsNullOrEmpty(payload)) _feedback.TriggerPreset(payload); break; case AnimationEventType.PlaySFX: if (!string.IsNullOrEmpty(payload)) _feedback.PlaySFXById(payload); break; } } // ── 私有辅助 ─────────────────────────────────────────────────────── /// /// 按 Id 激活或停用 HitBox。 /// payload 为空时操作所有 HitBox;非空时仅操作 Id 匹配的 HitBox。 /// private void SetHitBoxActive(string id, bool active) { if (_hitBoxes == null) return; bool matchAll = string.IsNullOrEmpty(id); foreach (var hb in _hitBoxes) { if (hb == null) continue; if (matchAll || hb.Id == id) { if (active) hb.Activate(); else hb.Deactivate(); } } } } }