Montage 与 AnimNotify
自动关联目录:Montage 与 AnimNotify
1. 定位
Montage 解决的是"在基础动画之上叠加一段一次性动作":挥剑、受击、翻滚、处决。它是 AnimBP 之外的独立轨道,通过 Slot 混入最终姿势。
一句话定位:Montage 管"播什么、怎么打断",AnimNotify 管"播到某一点时通知代码"。两者一起构成了动画与逻辑的时间轴接口。
2. Montage 结构
Montage
├── Slot(决定混在哪一层,如 UpperBody / FullBody)
├── Section(分段:Start / Loop / End)
│ └── Section 之间可定义过渡
├── 若干动画片段(可来自多个 AnimSequence)
├── AnimNotify / AnimNotifyState(时间轴上的标记)
└── 曲线(Curve)| 概念 | 作用 |
|---|---|
| Slot | 混入的位置;AnimBP 里要有对应的 Slot 节点,否则不会生效 |
| Section | 分段,支持"播放到某段循环""跳到下一段" |
| Blend In / Blend Out | 混入/混出时间 |
| Curve | 随动画变化的值(可用于材质、移动修正) |
AnimBP 里必须有对应的 Slot 节点——这是最常见的"Montage 播了但没效果"的原因。
3. 播放与控制
// 播放
UAnimInstance* Anim = Mesh->GetAnimInstance();
float Duration = Anim->Montage_Play(Montage, /*InPlayRate=*/1.f);
// 跳到某个 Section
Anim->Montage_JumpToSection(FName("Loop"), Montage);
// 停止
Anim->Montage_Stop(0.25f, Montage);
// 查询
Anim->Montage_IsPlaying(Montage);
Anim->GetCurrentActiveMontage();| API | 用途 |
|---|---|
Montage_Play | 播放,返回时长 |
Montage_Stop | 带混出时间停止 |
Montage_JumpToSection | 跳段 |
Montage_SetNextSection | 指定下一段(做"循环直到停止") |
Montage_GetPosition / SetPosition | 定位 |
Montage_GetIsStopped | 是否已停 |
"蓄力循环直到松手"的经典写法:Montage_Play 后 Montage_SetNextSection("Start", "Loop"),松手时 Montage_SetNextSection("Loop", "End")。
4. 打断与混出
| 机制 | 说明 |
|---|---|
Montage_Stop(BlendOutTime) | 主动停止,带混出 |
StopAllMontages | 全部停止 |
| AnimBP 的 Slot 权重 | 权重为 0 时 Montage 不生效 |
| 同 Slot 互斥 | 同一 Slot 上的新 Montage 会覆盖旧的 |
Montage_SetPlayRate | 变速 |
同一 Slot 上同时只能有一个 Montage 生效——想让上半身挥剑、下半身跑步,就要分两个 Slot。
5. AnimNotify
| 类型 | 时机 | 用途 |
|---|---|---|
| AnimNotify | 一个时间点 | 播放音效、生成特效、判定命中帧 |
| AnimNotifyState | 一段时间(有 Begin/Tick/End) | 开启武器碰撞、开启霸体、持续特效 |
// C++ 实现 Notify
UCLASS()
class UAnimNotify_MyEffect : public UAnimNotify
{
GENERATED_BODY()
public:
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimBase* Animation,
const FAnimNotifyEventReference& EventReference) override;
};
// NotifyState
UCLASS()
class UAnimNotifyState_WeaponActive : public UAnimNotifyState
{
virtual void NotifyBegin(...) override; // 开启碰撞
virtual void NotifyTick(...) override;
virtual void NotifyEnd(...) override; // 关闭碰撞
};武器判定的标准做法是 NotifyState:Begin 开碰撞、End 关碰撞,而不是在 Notify 里做一次射线。
6. 网络与可靠性
| 要点 | 说明 |
|---|---|
| Notify 不保证复制 | 它只在播 Montage 的那端触发 |
| 服务端要表现 | 走 GameplayCue 或 RPC |
| Montage 复制 | 通过 属性复制 或 RPC 在远端播 |
| 不要在 Notify 里做权威判定 | 应发意图给服务端 |
最常见的错误:在 Notify 里直接扣血。Notify 在客户端和服务端各自触发,且时机可能不同——正确做法是 Notify 里发"命中帧到了",由服务端做判定。
7. 代价与权衡
| 设计 | 收益 | 代价 | 什么时候不该用 |
|---|---|---|---|
| Montage | 不侵入 AnimBP 状态机 | 需要 Slot 配套 | 常驻循环动作用状态机更合适 |
| 多 Slot | 上下半身独立 | 层数多时混出复杂 | 简单角色一个 FullBody 即可 |
| Notify | 时间轴与代码解耦 | 不保证网络同步 | 权威逻辑不能放这里 |
| NotifyState | 有明确起止,适合开/关碰撞 | 需要正确配对 | 一次性事件用 Notify |
| Section 跳转 | 循环/分支播放 | 逻辑复杂 | 简单播放不必分段 |
8. 踩坑与排查
| 坑 | 现象 | 怎么验证 |
|---|---|---|
| AnimBP 里没有对应 Slot | Montage 播了没效果 | 检查 AnimBP 的 Slot 节点名 |
| 两个 Montage 互相覆盖 | 只看到后播的 | 确认是否同 Slot;需要则分 Slot |
忘了 Montage_Stop | 动画停不下来 | 检查停止路径 |
| 在 Notify 里做权威判定 | 两端不一致 | 应发给服务端 |
| Notify 在服务端没触发 | 服务端没播该 Montage | 需要复制播放 |
| NotifyState 没配对 | 碰撞一直开着 | 确认 End 被调用(打断时也会调) |
| 打断时状态没清理 | 霸体残留 | NotifyState 的 End 里清理 |
| Montage 播不出来 | Anim Class 不对 / Montage 的骨架不匹配 | 检查骨架 |
| 混出太生硬 | Blend Out 时间为 0 | 给混出时间 |
9. 排查顺序
1. 完全没效果?→ AnimBP 里有没有对应 Slot
2. 被覆盖了?→ 是否同 Slot
3. 停不下来?→ Montage_Stop 是否被调用
4. Notify 没触发?→ 播 Montage 的那端才触发;服务端是否需要复制播放
5. 状态残留?→ NotifyState 的 End 是否清理
6. 判定不一致?→ 权威逻辑是否写在服务端参考
许可协议:CC BY
作者:Davids
本文链接:https://hustjjd.github.io/c51d8002.html
更新于:2026年10月10日