项目背景:一个看似简单的需求
那是去年秋天,我们团队接了一个武侠风格的动作游戏项目,我负责角色战斗模块。需求文档上写着:”角色在释放特定技能时,需要触发武器拖尾特效、音效、以及伤害判定。” 看起来很简单对吧?用动画通知(Anim Notify)嘛。但真正做起来,才发现里面全是坑。今天我就以这个项目为线索,把从需求分析到最终交付的全过程,包括踩过的坑和背后的原理,一次讲透。

技术选型:为什么是Anim Notify?
接到需求,我第一反应是:在动画资产里添加通知(Notify)呗。但团队里有个新同事问:”为什么不直接在技能蓝图里用定时器?” 这是个好问题。咱们得先理解动画通知的底层逻辑:它是挂在动画序列(Anim Sequence)或蒙太奇(Montage)上的事件标记,由动画系统在播放到特定时间点时精准触发。这比外部定时器靠谱得多——因为定时器无法感知动画是否被打断、是否变速,而动画通知是随着动画播放状态走的,天然同步。
所以,第一步技术选型:武器拖尾和音效用Anim Notify(一次性事件),伤害判定用Anim Notify State(持续状态,比如持续0.2秒的判定窗口)。这里要强调:Notify适合”瞬间”事件,Notify State适合”时间段”事件,这是很多新手容易混淆的。
蓝图还是C++?
我们项目是蓝图为主,但为了性能和复用,我决定用C++写一个基类通知。为什么?因为蓝图通知类每次触发都有一定的调用开销,而且在大量敌人同屏时,性能会吃紧。用C++实现,底层就是虚函数重载,效率高。当然,如果项目不大,纯蓝图也行,但咱们得为长远考虑。
于是,我创建了UAnimNotify_WeaponAction,继承自UAnimNotify,重写Notify()函数;以及UAnimNotifyState_WeaponDamage,继承自UAnimNotifyState,重写NotifyBegin、NotifyTick、NotifyEnd。蓝图里只需要选对类并设置参数即可。
开发中的坑:第一个坑——通知没触发?
满怀信心地加好通知,测试,发现:有时候触发,有时候不触发。排查半天,发现是动画蓝图的更新时机问题。原来,动画通知的触发依赖于动画评估(Evaluation)是否发生。如果你的角色处于VisibilityBasedAnimTickOption为AlwaysTickPoseAndRefreshBones还好,但如果是OnlyTickPoseWhenRendered,当角色在屏幕外或相机裁剪掉时,动画就不更新,通知自然不触发。
技巧提示:确定你的动画更新选项。如果需要离屏也触发(比如AI行为),务必设置为AlwaysTickPoseAndRefreshBones。但这会增加CPU开销,所以只对需要离屏更新的Mesh设置。
第二个坑:蒙太奇里的通知和普通动画不一样?
我们把技能做成蒙太奇,结果发现通知在编辑器里看着有,但运行时就是没反应。后来排查,原来蒙太奇里默认的通知触发模式和序列不同。检查Montage属性里的Notify Trigger Mode,默认是All,但如果被设为None,就全不触发。再者,蒙太奇有Rate Scale(播放速率),如果你在蓝图中动态改了这个值,通知的时间点可能偏移,甚至错过。通知其实是在动画时间轴上的点,变速时它会在正确的帧触发,但如果你跳帧(比如从第10帧直接跳到第50帧),通知就会因为采样而遗漏。
解决方法是:使用根骨骼运动(Root Motion)时要注意,通知位置是基于动画自身的播放时间,而不是世界空间。如果遇到跳跃触发,可以用AnimNotify_Jump类似的技巧,但更稳定的做法是确保动画流畅播放,不要用SetPosition硬跳。
核心实操:自己动手写一个通用的通知类
下面是我在项目中用的C++类,包含了主要的逻辑。首先,我们定义UAnimNotify_WeaponAction:
// WeaponActionNotify.h
UCLASS()
class MYGAME_API UAnimNotify_WeaponAction : public UAnimNotify
{
GENERATED_BODY()
public:
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
// 定义要触发的事件类型
UPROPERTY(EditAnywhere)
EWeaponNotifyType NotifyType;
};
实现中,通过MeshComp->GetOwner()拿到角色,然后调用角色接口(比如IWeaponInterface)。这样做的好处是解耦,通知只关心”通知谁”,不关心具体逻辑。
void UAnimNotify_WeaponAction::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
if (MeshComp && MeshComp->GetOwner())
{
if (MeshComp->GetOwner()->Implements<UWeaponInterface>())
{
IWeaponInterface::Execute_OnWeaponNotify(MeshComp->GetOwner(), NotifyType);
}
}
}
而对于AnimNotifyState,因为要处理持续判定,我用了NotifyTick来每帧做检测,但性能开销一定要注意。这里有个优化技巧:用TimerManager替代每帧Tick?但动画通知的Tick是精确到帧的,如果要真实判定,还是得用Tick,但可以只在敌人入侵时开启。
注意事项:NotifyTick每帧调用,如果在里面做复杂的碰撞检测,会拖垮性能。建议只在NotifyBegin里开启碰撞检测,NotifyEnd里关闭,中间用Tick检测重叠或距离。
那么,如何将动画通知和伤害判定窗口同步?在蒙太奇里,我把伤害判定的Notify State放在攻击帧的前后0.1秒处,这样在视觉上武器挥出到击中目标的时间窗口,与判定时间窗口重合。为了调试,我加了Debug输出,可以看到Notify Begin/End的时机。
下面是一个示例配置:
| 通知类型 | 触发时机 | 用途 |
|---|---|---|
| Notify | 00:00.5 | 播放挥砍音效 |
| Notify State | 00:00.8 ~ 00:01.1 | 伤害判定窗口 |
| Notify | 00:01.2 | 生成武器拖尾特效 |

进一步:多个动画资产的通知管理
项目里角色有几十个攻击动画,如果每个资产手动添加通知,累死且易错。我们的做法是:用一个数据驱动的方式,将通知配置在DataTable中,然后写一个编辑器工具批量添加。这里用到了蓝图或Python脚本,但底层原理是:用UAnimSequence::AddAnimNotifyAPI动态添加通知。这样方便策划配置,程序员不用天天改动画资源。
我记得当时写了一个编辑器工具,读取Excel配置,一键给所有动画添加通知。但要注意,通知一旦添加,如果动画被修改,通知位置可能偏移,所以更合适锁定动画资产或使用源控制。我建议在动画资产里,把通知与动画曲线(Curve)结合,比如用一条float曲线来控制伤害启用,当曲线值大于0时判定,这比手动设置时间点更灵活。
技术花絮:在大型项目里,更高级的做法是使用动画通知槽(AnimNotify Slot),在Montage里预留槽位,动态替换通知。但一般项目用我上面的方法就足够了。
实战测试与性能分析
功能完成后,我们做了性能测试。使用Unreal Insights,我观察到通知触发时的CPU峰值。由于我使用了C++,每个通知的调用开销几乎可忽略,但Notify State的Tick确实占用一些。在100个敌人同时攻击时,如果每个敌人有判定窗口,每帧都要执行一次碰撞检测,那会卡。所以,我把判定做成:只在攻击发生的那几帧里检测,且只对攻击目标范围内最近的几个敌人检测。
最终,我们交付的版本流畅运行,玩家反馈打击感很好,音效特效和伤害同步精准。复盘一下,其实就是扎实理解了动画通知的机制,并用C++实现了可控的通知类,规避了蓝图的性能坑。

总结与心得
这次实战,我深刻体会到:动画通知的核心是”同步”与”事件”,它让美术动画与游戏逻辑无缝衔接。作为技术美术或游戏程序员,你不仅要会用编辑器添加,更要理解其触发原理,以及如何在性能与灵活性之间做权衡。
最后提醒一句:动画通知不是万能的,如果事件逻辑复杂、条件判断多,不如让动画只发一个简单信号,具体逻辑放在角色蓝图/状态机里处理,保持清晰。
以上是我基于这次项目写下的复盘,希望能帮到正在UE5路上进阶的你。如果你也遇到过类似的坑,欢迎交流。
关于火星人教育
在火星人教育,我们正是以这种”真实项目复盘”的教学方式来带学员,让大家少走弯路。火星人教育创办24年来,专注数字艺术与AIGC实战职业技能培训,拥有北京、深圳双校区,开设AI漫剧创作、AIGC视觉设计、UE5游戏特效、室内全屋定制、MAYA三维动画、C4D动态设计、工业设计、NUKE影视后期、摄影摄像实训等课程。我们面向美术生、在校大学生及意向转行的职场人群,以项目化实训教学,夯实专业技能,助力就业与职业提升。如果你对UE5或相关方向感兴趣,欢迎咨询:010-65918670。








评论(0)