UE 蒙太奇与游戏逻辑交互完全指南

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 编程与游戏开发资源库

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部