UE 蒙太奇与游戏逻辑交互完全指南
前言
Animation Montage(蒙太奇)是 UE 中处理「一次性」动画的核心工具。与 Loop 动画不同,Montage 用于播放受击、翻滚、连击、拾取等有明确开始和结束的动作。更重要的是,Montage 内置的 Notify 系统 充当了动画与游戏逻辑之间的桥梁——动画的某一帧精确触发游戏事件,帧区间内的窗口检测玩家输入。
一、Montage 与 Sequence 的核心区别
| 特性 | Animation Sequence | Animation Montage |
|---|---|---|
| 播放方式 | Loop / One Shot | 播放一次,有明确结束 |
| 混合 | 从头播放到底 | 可分 Section、可打断 |
| 通知 | Track Notify(帧级) | Notify / Notify Window |
| 槽位 | 无 | Slot 机制,支持多层 |
| 控制 | 仅 Play | Play / Pause / Resume / Stop / Jump to Section |
| 蒙太奇曲线 | 无 | Bone / Curve 驱动曲线 |
二、Montage 资产结构
2.1 基本结构
Montage Asset
├── Slot: "FullBody" ← 全身槽
│ ├── Section: "Combo_01" ← 连击第一段
│ ├── Section: "Combo_02" ← 连击第二段
│ └── Section: "Combo_03" ← 连击第三段
├── Slot: "UpperBody" ← 上半身槽
│ └── Section: "Reload" ← 换弹动画
├── Blend Profile: "FullBody_In" ← 全身入混合曲线
├── Blend Profile: "UpperBody_In" ← 上半身入混合曲线
└── Montage Notify
├── Notify_Attack_Hit (帧 12) ← 命中判定
└── NotifyWindow (帧 8~18) ← 输入检测窗口
2.2 Section 机制
Section 是 Montage 中的命名片段,支持:
– 循环播放:Combo_Loop → Combo_Loop 首尾相连
– 跳跃:Notify → Jump to Combo_02
– 组合:Section A → Section B → Section C
// C++ 中控制 Section 跳跃
AnimInstance->Montage_JumpToSection(FName("Combo_02"), Montage);
AnimInstance->Montage_SetNextSection(FName("Combo_01"), FName("Combo_02"), Montage);
2.3 Blend Profile
Blend Profile 定义了入 / 出混合曲线,控制动画切换时的平滑程度:
// Blend Profile 时间曲线
// Linear: 线性过渡(适用于武器切换)
// EaseIn/EaseOut: 缓入缓出(适用于近战攻击)
// Custom: 自定义曲线
// C++ 设置 Montage 混合时间
Montage->BlendIn.SetBlendTime(0.1f);
Montage->BlendOut.SetBlendTime(0.2f);
三、Notify 系统详解
3.1 Notify vs NotifyWindow
| 类型 | 触发方式 | 典型用途 |
|---|---|---|
| Notify | 精确帧,一次性 | 音效、特效、事件 |
| NotifyWindow | 区间内每帧触发 | 连击输入窗口、持续效果 |
3.2 Notify 蓝图 / C++ 实现
// C++ 自定义 Notify
UCLASS()
class UAnimNotify_AttackHit : public UAnimNotify {
GENERATED_BODY()
public:
virtual void Notify(USkeletalMeshComponent* MeshComp,
UAnimSequenceBase* Animation) override {
// 触发游戏逻辑
AMyCharacter* Character = Cast<AMyCharacter>(
MeshComp->GetOwner());
if (Character) {
Character->OnAttackHit();
}
}
};
蓝图中可直接创建 Notify:右键 → Animation → AnimNotify
3.3 NotifyWindow 实现连击
// 监听 NotifyWindow 事件
void AMyCharacter::SetupComboWindow() {
if (!AnimInstance) return;
// 绑定 NotifyWindow 开始/结束
FOnMontageNotifyNotifyBegin ComboBegin;
ComboBegin.BindUObject(this, &AMyCharacter::OnComboWindowBegin);
FOnMontageNotifyNotifyEnd ComboEnd;
ComboEnd.BindUObject(this, &AMyCharacter::OnComboWindowEnd);
AnimInstance->Montage_SetNotifyEndDelegate(
ComboBegin, ComboEnd, ComboMontage);
}
void AMyCharacter::OnComboWindowBegin(
USkeletalMeshComponent* MeshComp, FName NotifyName,
const FX notifications::F Notify&) {
bCanChainCombo = true; // 允许链接下一击
}
void AMyCharacter::OnComboWindowEnd(...) {
bCanChainCombo = false; // 窗口关闭,禁止输入
}
四、近战组合技系统设计
4.1 设计思路
近战 Combo 的核心是一个状态机:
Idle
↓ (按下 Attack)
Combo_01 (0~15帧)
↓ (帧 10 触发 NotifyWindow)
可输入 → Combo_02 (帧 15 Jump to Combo_02)
超时 → Combo_End → Idle
4.2 完整实现代码
// Character.h
UPROPERTY(EditDefaultsOnly, Category = "Combat")
UAnimMontage* ComboMontage;
int32 CurrentComboIndex = 0;
bool bInComboWindow = false;
bool bQueuedCombo = false;
void QueueComboInput();
// Character.cpp
void AMyCharacter::AttackPressed() {
if (bInComboWindow) {
bQueuedCombo = true; // 窗口内输入,缓存
} else {
PlayCombo(1);
}
}
void AMyCharacter::PlayCombo(int32 ComboIndex) {
CurrentComboIndex = ComboIndex;
FName SectionName = FName(*FString::Printf(
TEXT("Combo_%02d"), ComboIndex));
if (ComboMontage) {
AnimInstance->Montage_Play(ComboMontage, 1.0f);
AnimInstance->Montage_JumpToSection(SectionName, ComboMontage);
}
}
void AMyCharacter::OnComboWindowBegin(...) {
bInComboWindow = true;
if (bQueuedCombo) {
bQueuedCombo = false;
PlayCombo(CurrentComboIndex + 1);
}
}
void AMyCharacter::OnComboWindowEnd(...) {
bInComboWindow = false;
}
4.3 Combo UI 输入窗口提示
// 在 UI 中显示连击时机
void UCombatWidget::UpdateComboPrompt(bool bActive) {
if (bActive) {
// 显示"按下攻击连接下一击"
ComboPrompt->SetVisibility(ESlateVisibility::Visible);
} else {
ComboPrompt->SetVisibility(ESlateVisibility::Hidden);
}
}
五、Montage 与网络同步
5.1 Server-Client 架构
Montage 在网络游戏中需要正确同步,否则客户端播放了动画但 Server 不知道:
// 正确做法:通过 Server RPC 播放 Montage
void AMyCharacter::ServerPlayAttackMontage_Implementation() {
MulticastPlayAttackMontage(); // Server 向所有 Client 广播
}
void AMyCharacter::MulticastPlayAttackMontage_Implementation() {
if (GetLocalRole() == ROLE_AutonomousProxy) return; // 本地 Client 跳过
AnimInstance->Montage_Play(AttackMontage);
}
// 伤害判定只在 Server 执行
void AMyCharacter::OnAttackHit_Implementation() {
if (GetLocalRole() != ROLE_Authority) return;
// 只有 Server 执行伤害逻辑
ApplyDamageToTarget();
}
5.2 常见坑
坑 1:Montage 在网络上不同步
→ 确保通过 Server RPC 调用 Montage_Play,而不是本地直接播放
坑 2:Server 和 Client 的 Notify 都被触发
→ 用 GetLocalRole() 判断,只有 ROLE_Authority 执行逻辑
坑 3:Blend Out 导致动画被切断
→ 设置合理的 Blend Out Time(通常 0.1~0.2s),或等 Montage 完全播放完再切换状态
六、Montage 曲线驱动
6.1 Bone Curves
Montage 可以为骨骼曲线(Bone Curves)设置关键帧,AnimBP 中读取当前值:
// 在 Montage 中为 "RootZOffset" 曲线设置关键帧
// (用于跳跃落地时角色高度补偿)
// AnimBP EventGraph:
float RootZ = AnimInstance->GetCurveValue(FName("RootZOffset"));
if (RootZ != 0.0f) {
// 应用垂直偏移
}
6.2 Curve 驱动的动态效果
// 根据 Montage 中的 "Intensity" 曲线驱动特效
float Intensity = AnimInstance->GetCurveValue(FName("EffectIntensity"));
VFXComponent->SetFloatParameter(FName("DustAmount"), Intensity);
AudioComponent->SetFloatParameter(FName("FootstepVolume"), Intensity);
七、总结
Montage 是 UE 动画系统中最接近「游戏逻辑」的模块:
– Section 让 Montage 可分、可跳、可循环
– Notify 让动画帧精确触发游戏事件
– NotifyWindow 让玩家输入与动画时序完美同步
– Slot 让 Montage 兼容分层动画架构
– 网络同步是多人游戏中的关键,确保 Server 为权威节点
把 Combo 系统、武器切换、受击反应这些逻辑吃透,动画与游戏逻辑之间的壁垒就彻底打通了。
本文收录于 Matrix4x4 AI 编程与游戏开发资源库