Files
zeling_v2/Assets/_Game/Scripts/Animation/EnemyAnimationEvents.cs
T
joywayerandClaude Opus 5 c05583a365 feat(animation): 敌人侧动画事件补接无敌帧与反馈四件
EnableIFrame/DisableIFrame → HurtBox.SetInvincible(与玩家侧同形态,
无敌时长由动画时间轴上两事件间距表达);TriggerFeedback/PlaySFX →
IFeedbackPlayer,用于音效预警。此前敌人侧只接了 7/20 个事件类型。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 14:20:47 +08:00

142 lines
6.1 KiB
C#

using System;
using Animancer;
using BaseGames.Combat;
using BaseGames.Enemies;
using BaseGames.Feedback;
using UnityEngine;
namespace BaseGames.Animation
{
/// <summary>
/// 敌人动画事件接收器(架构 §AnimationModule)。
/// 挂载于敌人 Prefab 根节点。负责将 Animancer 动画时间点回调路由到
/// HitBox 激活、弹幕生成、嘶吼状态、二阶段切换、无敌帧、反馈等系统。
///
/// 使用方式:
/// 1. 在 Inspector 中填充 _hitBoxes、_hurtBox、_enemy。
/// 2. 将每条 ClipTransition + AnimationEventConfigSO 配对添加到 _bindings。
/// 3. Awake 中 AnimationEventBinder.Bind 自动完成注入。
/// </summary>
public class EnemyAnimationEvents : 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 EnemyBase _enemy;
[Header("事件绑定(每个 Clip 对应一个配置资产)")]
[SerializeField] private EventBinding[] _bindings;
private IFeedbackPlayer _feedback;
private void Awake()
{
if (_enemy == null)
_enemy = GetComponentInParent<EnemyBase>();
// IFeedbackPlayer 由父层或同层实现(EnemyFeedback / NullFeedbackPlayer),与玩家侧同写法
_feedback = GetComponentInParent<IFeedbackPlayer>()
?? 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.EnableIFrame:
_hurtBox?.SetInvincible(true);
break;
case AnimationEventType.DisableIFrame:
_hurtBox?.SetInvincible(false);
break;
// ── 弹幕 / 技能 ───────────────────────────────────────────
case AnimationEventType.SpawnProjectile:
_enemy?.SpawnProjectile(payload);
break;
// ── 嘶吼 ──────────────────────────────────────────────────
case AnimationEventType.RoarStart:
_enemy?.SetRoaring(true);
break;
case AnimationEventType.RoarEnd:
_enemy?.SetRoaring(false);
break;
// ── 二阶段切换 ────────────────────────────────────────────
case AnimationEventType.PhaseTwoStart:
_enemy?.TriggerPhaseTwo();
break;
// ── 通用反馈 ──────────────────────────────────────────────
// 音效预警:在起手帧播一段可辨识的音,让玩家提前缩小招式可能性。
case AnimationEventType.TriggerFeedback:
if (!string.IsNullOrEmpty(payload))
_feedback.TriggerPreset(payload);
break;
case AnimationEventType.PlaySFX:
if (!string.IsNullOrEmpty(payload))
_feedback.PlaySFXById(payload);
break;
// ── 状态机钩子 ────────────────────────────────────────────
case AnimationEventType.AnimationComplete:
_enemy?.OnAnimationComplete(payload);
break;
}
}
// ── 私有辅助 ───────────────────────────────────────────────────────
/// <summary>
/// 按 Id 激活或停用 HitBox。
/// payload 为空时操作所有 HitBox;非空时仅操作 Id 匹配的 HitBox。
/// </summary>
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();
}
}
}
}
}