多轮审查和修复
This commit is contained in:
31
Assets/Scripts/Animation/AnimationEventBinder.cs
Normal file
31
Assets/Scripts/Animation/AnimationEventBinder.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Animancer;
|
||||
|
||||
namespace BaseGames.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// 静态工具类:将 AnimationEventConfigSO 中声明的事件注入 Animancer ClipTransition。
|
||||
/// 使用 Animancer Pro API:ClipTransition.Events.SetCallback(normalizedTime, Action)。
|
||||
/// 调用时机:MonoBehaviour.Awake(在 Animancer 播放前完成绑定)。
|
||||
/// </summary>
|
||||
public static class AnimationEventBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// 将 config 中的所有事件回调注入到 clip 的 Animancer 事件系统。
|
||||
/// </summary>
|
||||
/// <param name="clip">目标 Animancer ClipTransition。</param>
|
||||
/// <param name="config">事件配置资产(null 时静默跳过)。</param>
|
||||
/// <param name="receiver">事件接收者(通常是同一 MonoBehaviour)。</param>
|
||||
public static void Bind(ClipTransition clip, AnimationEventConfigSO config, IAnimationEventHandler receiver)
|
||||
{
|
||||
if (clip == null || config == null || receiver == null) return;
|
||||
|
||||
foreach (var entry in config.SortedEvents)
|
||||
{
|
||||
// 捕获循环变量,避免闭包陷阱
|
||||
var captured = entry;
|
||||
clip.Events.Add(captured.normalizedTime, () =>
|
||||
receiver.HandleEvent(captured.eventType, captured.data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14d8a3e3d7371e54eb25a5d4dded4645
|
||||
guid: e9c16d7d7a6978a4c98a745261fbbf4f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
64
Assets/Scripts/Animation/AnimationEventConfigSO.cs
Normal file
64
Assets/Scripts/Animation/AnimationEventConfigSO.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// 动画事件配置资产(架构 §AnimationModule)。
|
||||
/// 为指定 AnimationClip 声明一组事件时机,由 AnimationEventBinder 在运行时注入 Animancer 回调。
|
||||
///
|
||||
/// 使用方式:
|
||||
/// 1. 在 Inspector 中创建此资产(菜单:Animation/EventConfig)。
|
||||
/// 2. 配置 TargetClip 和 Events 列表。
|
||||
/// 3. 在 MonoBehaviour.Awake 中:AnimationEventBinder.Bind(clip, config, this)。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Animation/EventConfig")]
|
||||
public class AnimationEventConfigSO : ScriptableObject
|
||||
{
|
||||
[Serializable]
|
||||
public struct EventEntry
|
||||
{
|
||||
public AnimationEventType eventType;
|
||||
|
||||
[Range(0f, 1f)]
|
||||
[Tooltip("事件在动画中的归一化时间(0 = 开始,1 = 结束)。")]
|
||||
public float normalizedTime;
|
||||
|
||||
[Tooltip("附加字符串数据(可空)。用于 payload 参数。")]
|
||||
public string data;
|
||||
}
|
||||
|
||||
[Header("绑定的动画片段")]
|
||||
public AnimationClip targetClip;
|
||||
|
||||
[Header("事件时机列表(归一化时间)")]
|
||||
public EventEntry[] events;
|
||||
|
||||
/// <summary>
|
||||
/// 用于编辑器工具验证:记录创建 Config 时 Clip 的实际帧数,
|
||||
/// 若 Clip 被替换(长度漂移 >5 帧)则 EventConfigEditor 显示警告。
|
||||
/// </summary>
|
||||
[HideInInspector] public float ExpectedClipLength = -1f;
|
||||
|
||||
// ── 运行时查询 ───────────────────────────────────────────────────
|
||||
|
||||
/// <summary>按归一化时间升序返回所有事件(保证 SetCallback 顺序稳定)。</summary>
|
||||
public IEnumerable<EventEntry> SortedEvents =>
|
||||
events != null
|
||||
? events.OrderBy(e => e.normalizedTime)
|
||||
: Enumerable.Empty<EventEntry>();
|
||||
|
||||
/// <summary>
|
||||
/// 返回第一个匹配类型的归一化时间;未找到时返回 -1。
|
||||
/// </summary>
|
||||
public float GetNormalizedTime(AnimationEventType eventType)
|
||||
{
|
||||
if (events == null) return -1f;
|
||||
foreach (var e in events)
|
||||
if (e.eventType == eventType) return e.normalizedTime;
|
||||
return -1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Animation/AnimationEventConfigSO.cs.meta
Normal file
11
Assets/Scripts/Animation/AnimationEventConfigSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3dc3625236c5f8747ae7352e78c01137
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
49
Assets/Scripts/Animation/AnimationEventType.cs
Normal file
49
Assets/Scripts/Animation/AnimationEventType.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace BaseGames.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// 动画事件类型枚举(架构 §AnimationModule)。
|
||||
/// 用作 AnimationEventConfigSO 中事件时机的分类标签,
|
||||
/// 与 AnimationEventBinder 配合将 Animancer ClipTransition 回调路由到 IAnimationEventHandler。
|
||||
/// </summary>
|
||||
public enum AnimationEventType
|
||||
{
|
||||
// ── 命中判定 ──────────────────────────────────────────────────────
|
||||
EnableHitBox,
|
||||
DisableHitBox,
|
||||
AttackImpact, // 攻击命中瞬间反馈(不依赖特定 HitBox)
|
||||
|
||||
// ── 招架窗口 ──────────────────────────────────────────────────────
|
||||
EnableParryWindow,
|
||||
DisableParryWindow,
|
||||
|
||||
// ── 无敌帧 ────────────────────────────────────────────────────────
|
||||
EnableIFrame,
|
||||
DisableIFrame,
|
||||
|
||||
// ── 移动反馈 ──────────────────────────────────────────────────────
|
||||
Footstep,
|
||||
LandImpact,
|
||||
JumpLaunch,
|
||||
|
||||
// ── 交互 ──────────────────────────────────────────────────────────
|
||||
EnableInteract,
|
||||
|
||||
// ── 通用反馈 ──────────────────────────────────────────────────────
|
||||
TriggerFeedback, // payload:FeedbackPreset 名称
|
||||
PlaySFX, // payload:SFX Id
|
||||
|
||||
// ── 敌方专用 ──────────────────────────────────────────────────────
|
||||
SpawnProjectile, // payload:弹幕配置 Id
|
||||
RoarStart,
|
||||
RoarEnd,
|
||||
PhaseTwoStart,
|
||||
|
||||
// ── 玩家取消窗口 ──────────────────────────────────────────────────
|
||||
CancelWindowOpen,
|
||||
CancelWindowClose,
|
||||
|
||||
// ── 状态机钩子 ────────────────────────────────────────────────────
|
||||
StateTransition, // payload:目标状态 Id
|
||||
AnimationComplete, // payload:上下文字符串(可空)
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Animation/AnimationEventType.cs.meta
Normal file
11
Assets/Scripts/Animation/AnimationEventType.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97412b5f50276484fa226d97b8c3769b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -9,7 +9,12 @@
|
||||
"rootNamespace": "BaseGames.Animation",
|
||||
"references": [
|
||||
"BaseGames.Core.Events",
|
||||
"Kybernetik.Animancer"
|
||||
"Kybernetik.Animancer",
|
||||
"BaseGames.Combat",
|
||||
"BaseGames.Parry",
|
||||
"BaseGames.Feedback",
|
||||
"BaseGames.Player",
|
||||
"BaseGames.Enemies"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"overrideReferences": false,
|
||||
|
||||
111
Assets/Scripts/Animation/EnemyAnimationEvents.cs
Normal file
111
Assets/Scripts/Animation/EnemyAnimationEvents.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using Animancer;
|
||||
using BaseGames.Combat;
|
||||
using BaseGames.Enemies;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人动画事件接收器(架构 §AnimationModule)。
|
||||
/// 挂载于敌人 Prefab 根节点。负责将 Animancer 动画时间点回调路由到
|
||||
/// HitBox 激活、弹幕生成、嘶吼状态、二阶段切换等系统。
|
||||
///
|
||||
/// 使用方式:
|
||||
/// 1. 在 Inspector 中填充 _hitBoxes、_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 EnemyBase _enemy;
|
||||
|
||||
[Header("事件绑定(每个 Clip 对应一个配置资产)")]
|
||||
[SerializeField] private EventBinding[] _bindings;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_enemy == null)
|
||||
_enemy = GetComponentInParent<EnemyBase>();
|
||||
|
||||
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.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.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Animation/EnemyAnimationEvents.cs.meta
Normal file
11
Assets/Scripts/Animation/EnemyAnimationEvents.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13003b40ed7bd164d8d90eb58f0548ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
17
Assets/Scripts/Animation/IAnimationEventHandler.cs
Normal file
17
Assets/Scripts/Animation/IAnimationEventHandler.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace BaseGames.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// 动画事件接收接口(架构 §AnimationModule)。
|
||||
/// 由 PlayerAnimationEvents / EnemyAnimationEvents 实现。
|
||||
/// AnimationEventBinder 将 Animancer 回调路由至此接口,避免硬耦合。
|
||||
/// </summary>
|
||||
public interface IAnimationEventHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理由 Animancer ClipTransition 触发的动画事件。
|
||||
/// </summary>
|
||||
/// <param name="type">事件类型。</param>
|
||||
/// <param name="payload">附加字符串数据(可为 null 或空)。</param>
|
||||
void HandleEvent(AnimationEventType type, string payload);
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Animation/IAnimationEventHandler.cs.meta
Normal file
11
Assets/Scripts/Animation/IAnimationEventHandler.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2c57ff98093620448d1f7c8ce6d0511
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
149
Assets/Scripts/Animation/PlayerAnimationEvents.cs
Normal file
149
Assets/Scripts/Animation/PlayerAnimationEvents.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using Animancer;
|
||||
using BaseGames.Combat;
|
||||
using BaseGames.Feedback;
|
||||
using BaseGames.Parry;
|
||||
using BaseGames.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// 玩家动画事件接收器(架构 §AnimationModule)。
|
||||
/// 挂载于玩家 Prefab 根节点。负责将 Animancer 动画时间点回调路由到
|
||||
/// HitBox 激活、招架窗口、无敌帧、移动取消窗口、反馈等系统。
|
||||
///
|
||||
/// 使用方式:
|
||||
/// 1. 在 Inspector 中填充 _hitBoxes、_hurtBox、_mover、_parrySystem。
|
||||
/// 2. 将每条 ClipTransition + AnimationEventConfigSO 配对添加到 _bindings。
|
||||
/// 3. Awake 中 AnimationEventBinder.Bind 自动完成注入。
|
||||
/// </summary>
|
||||
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<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.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;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 私有辅助 ───────────────────────────────────────────────────────
|
||||
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Animation/PlayerAnimationEvents.cs.meta
Normal file
11
Assets/Scripts/Animation/PlayerAnimationEvents.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e3a90eca47d12a49a07be7f38a376e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,3 +0,0 @@
|
||||
// Placeholder to prevent asmdef-no-scripts warning.
|
||||
namespace BaseGames.Animation { }
|
||||
|
||||
Reference in New Issue
Block a user