以《怪物猎人》为例:深入解析 UE5 GameplayCamera 相机系统

文档版本: 1.0 | 更新日期: 2026-02-25


目录


1. 怪物猎人相机需求分析

1.1 游戏中的相机场景

《怪物猎人》拥有极其复杂的相机系统,我们来看几个典型场景:

┌─────────────────────────────────────────────────────────────────────────────┐
│                    怪物猎人相机场景概览                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐       │
│  │   探索模式       │     │   战斗模式       │     │   骑乘模式       │       │
│  │                 │     │                 │     │                 │       │
│  │ • 第三人称跟随  │     │ • 锁敌追踪      │     │ • 第一人称骑乘  │       │
│  │ • 自由视角旋转  │     │ • 侧闪避相机    │     │ • 怪物背部视角  │       │
│  │ • 距离可调      │     │ • 武器特写      │     │ • 特殊攻击镜头  │       │
│  └─────────────────┘     └─────────────────┘     └─────────────────┘       │
│           │                      │                      │                  │
│           └──────────────────────┼──────────────────────┘                  │
│                                  ▼                                          │
│                    ┌─────────────────────────────┐                         │
│                    │        效果叠加层            │                         │
│                    │                             │                         │
│                    │ • 受击震动                  │                         │
│                    │ • 药水喝药视角偏移          │                         │
│                    │ • 蹲伏时的低视角            │                         │
│                    │ • 斩味系统视觉反馈          │                         │
│                    └─────────────────────────────┘                         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

1.2 具体场景拆解

场景 A:大剑蓄力攻击

时间线:
T=0s      T=0.5s      T=1.0s      T=1.5s      T=2.0s
  │          │           │           │           │
  ▼          ▼           ▼           ▼           ▼
┌───┐    ┌───────┐   ┌───────┐   ┌───────┐   ┌───────┐
│普通│───▶│蓄力中 │──▶│蓄力满 │──▶│释放  │──▶│恢复   │
│视角│    │相机拉近│   │微震动 │   │镜头抖动│   │普通   │
└───┘    └───────┘   └───────┘   └───────┘   └───────┘
           FOV变化     特效叠加    大震动

场景 B:从探索到锁敌战斗

时间线:
T=0s      T=0.3s      T=0.5s      T=0.8s      T=1.0s
  │          │           │           │           │
  ▼          ▼           ▼           ▼           ▼
┌───┐    ┌───────┐   ┌───────┐   ┌───────┐   ┌───────┐
│探索│───▶│探索→战斗│──▶│+锁敌  │──▶│+受击 │──▶│战斗   │
│相机│    │混合中  │   │效果   │   │震动   │   │相机   │
└───┘    └───────┘   └───────┘   └───────┘   └───────┘
          30%进度     混合继续    多效果叠加

场景 C:骑乘怪物的复杂相机链

时间线:
T=0s      T=0.5s      T=1.0s      T=1.5s      T=2.0s
  │          │           │           │           │
  ▼          ▼           ▼           ▼           ▼
┌───┐    ┌───────┐   ┌───────┐   ┌───────┐   ┌───────┐
│战斗│───▶│战斗→骑乘│──▶│骑乘→特殊│──▶│特殊→骑乘│──▶│骑乘   │
│相机│    │混合中  │   │攻击    │   │攻击    │   │相机   │
└───┘    └───────┘   └───────┘   └───────┘   └───────┘
          混合被打断   新混合开始   又被打断

2. GameplayCamera 核心架构回顾

2.1 四层架构

GameplayCamera 使用四层架构来组织相机逻辑:

┌─────────────────────────────────────────────────────────────────────────────┐
│                         GameplayCamera 四层架构                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │ Layer 4: Visual Layer (视觉层)                                      │  │
│   │ ┌─────────────────────────────────────────────────────────────────┐ │  │
│   │ │ • 后处理效果                                                    │ │  │
│   │ │ • 视觉反馈 (如低血量红色边框)                                   │ │  │
│   │ │ • 始终叠加的镜头偏移                                           │ │  │
│   │ │ Example: 喝药时的视角微调                                      │ │  │
│   │ └─────────────────────────────────────────────────────────────────┘ │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                    ▲ 叠加                                   │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │ Layer 3: Global Layer (全局层)                                      │  │
│   │ ┌─────────────────────────────────────────────────────────────────┐ │  │
│   │ │ • 全局效果 (震动、冲击)                                         │ │  │
│   │ │ • 持久性效果                                                    │ │  │
│   │ │ Example: 锁敌追踪、受击震动                                     │ │  │
│   │ └─────────────────────────────────────────────────────────────────┘ │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                    ▲ 叠加                                   │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │ Layer 2: Main Layer (主层)                                          │  │
│   │ ┌─────────────────────────────────────────────────────────────────┐ │  │
│   │ │ • 游戏玩法相机                                                  │ │  │
│   │ │ • 混合栈 (Blend Stack)                                         │ │  │
│   │ │ Example: 探索相机 ↔ 战斗相机 ↔ 骑乘相机                        │ │  │
│   │ └─────────────────────────────────────────────────────────────────┘ │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                    ▲ 叠加                                   │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │ Layer 1: Base Layer (基础层)                                        │  │
│   │ ┌─────────────────────────────────────────────────────────────────┐ │  │
│   │ │ • 附加到玩家/目标                                              │ │  │
│   │ │ • 基础跟随逻辑                                                  │ │  │
│   │ │ Example: 相机始终跟随玩家位置                                   │ │  │
│   │ └─────────────────────────────────────────────────────────────────┘ │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│   执行顺序:Base → Main → Global → Visual                                  │
│   每一层的结果叠加到前一层之上                                              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

2.2 关键概念

概念 说明 怪物猎人对应场景
Camera Rig 相机装备,包含一组节点定义行为 探索Rig、战斗Rig、骑乘Rig
Camera Node 单一功能节点(数据定义) 跟随节点、瞄准节点、震动节点
Node Evaluator 节点评估器(逻辑实现) 计算位置、计算旋转、应用震动
Camera Pose 相机姿态(位置+旋转+镜头参数) 每帧计算的最终结果
Blend Stack 混合栈,管理相机过渡 武器切换时的平滑过渡
Layer 层,组织效果叠加顺序 基础跟随 → 战斗相机 → 锁敌 → 震动

2.3 评估流程

// 每帧的评估流程
void FCameraSystemEvaluator::Evaluate(float DeltaTime)
{
    // 1. 准备参数
    FCameraNodeEvaluationParams Params;
    Params.DeltaTime = DeltaTime;
    Params.VariableTable = &VariableTable;
  
    // 2. 初始化结果
    FCameraNodeEvaluationResult Result;
    Result.CameraPose = FCameraPose::Identity;
  
    // 3. 按层顺序评估
    // Base Layer
    if (BaseLayerEvaluator)
    {
        BaseLayerEvaluator->Run(Params, Result);
        // Result 现在包含基础跟随的位置
    }
  
    // Main Layer (包含混合栈)
    if (MainLayerEvaluator)
    {
        MainLayerEvaluator->Run(Params, Result);
        // Result 现在包含主相机的结果(可能是混合中的状态)
    }
  
    // Global Layer (锁敌、震动等)
    if (GlobalLayerEvaluator)
    {
        GlobalLayerEvaluator->Run(Params, Result);
        // Result 现在叠加了全局效果
    }
  
    // Visual Layer (后处理、视觉反馈)
    if (VisualLayerEvaluator)
    {
        VisualLayerEvaluator->Run(Params, Result);
        // Result 现在叠加了视觉效果
    }
  
    // 4. 应用到实际相机
    ApplyResultToCamera(Result);
}

3. 场景一:武器切换时的相机过渡

3.1 场景描述

玩家从大剑切换到双刀:

T=0s                    T=0.5s                 T=1.0s
  │                       │                      │
  ▼                       ▼                      ▼
┌─────────┐          ┌────────────┐         ┌─────────┐
│大剑相机  │─────────▶│  混合中    │────────▶│双刀相机  │
│         │          │ (50%进度)  │         │         │
│• 距离远  │          │            │         │• 距离近  │
│• FOV窄   │          │            │         │• FOV宽   │
│• 侧后方  │          │            │         │• 更靠后  │
└─────────┘          └────────────┘         └─────────┘

3.2 Main Layer 的混合栈机制

┌─────────────────────────────────────────────────────────────────────────────┐
│                         Main Layer Blend Stack                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  === 初始状态 ===                                                           │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Blend Stack: [GreatSwordRig]                                        │   │
│  │                                                                     │   │
│  │ 激活相机: GreatSwordRig                                             │   │
│  │ 结果状态: GreatSwordRig 计算的姿态                                  │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  === 请求切换到双刀 (T=0s) ===                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Blend Stack: [GreatSwordRig ←──Blend──→ DualBladesRig]             │   │
│  │                      CamA         混合中      CamB                  │   │
│  │                                                                     │   │
│  │ 混合时间: 1.0 秒                                                    │   │
│  │ 混合曲线: EaseInOut                                                 │   │
│  │ 当前权重: 0% (完全 CamA)                                            │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  === 混合进行中 (T=0.5s) ===                                                │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Blend Stack: [GreatSwordRig ←──Blend(50%)──→ DualBladesRig]        │   │
│  │                                                                     │   │
│  │ 当前权重: 50%                                                       │   │
│  │ 结果状态: Lerp(GreatSword.State, DualBlades.State, 0.5)            │   │
│  │                                                                     │   │
│  │ 位置: 大剑位置和双刀位置的中间                                      │   │
│  │ 旋转: 大剑朝向和双刀朝向的球面插值                                  │   │
│  │ FOV: Lerp(65, 75, 0.5) = 70                                        │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  === 混合完成 (T=1.0s) ===                                                  │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Blend Stack: [DualBladesRig]                                        │   │
│  │                                                                     │   │
│  │ 激活相机: DualBladesRig                                             │   │
│  │ GreatSwordRig 已从栈中移除                                          │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

3.3 内部代码实现

// ========================================
// FTransientBlendStackCameraNodeEvaluator
// 管理 Main Layer 的混合栈
// ========================================

void FTransientBlendStackCameraNodeEvaluator::OnRun(
    const FCameraNodeEvaluationParams& Params,
    FCameraNodeEvaluationResult& OutResult)
{
    // 1. 更新所有活动的混合
    UpdateActiveBlends(Params.DeltaTime);
  
    // 2. 移除已完成的混合
    RemoveCompletedBlends();
  
    // 3. 计算最终结果
    if (BlendStack.IsEmpty())
    {
        // 没有活动相机
        return;
    }
  
    if (BlendStack.Num() == 1)
    {
        // 只有一个相机,直接评估
        BlendStack[0].CameraEvaluator->Run(Params, OutResult);
    }
    else
    {
        // 有混合,从栈底到栈顶依次混合
        FCameraPose AccumulatedPose = FCameraPose::Identity;
      
        for (int32 i = 0; i < BlendStack.Num(); ++i)
        {
            FBlendEntry& Entry = BlendStack[i];
          
            // 评估当前相机
            FCameraNodeEvaluationResult TempResult;
            TempResult.CameraPose = AccumulatedPose;
            Entry.CameraEvaluator->Run(Params, TempResult);
          
            // 如果是第一个,直接使用
            if (i == 0)
            {
                AccumulatedPose = TempResult.CameraPose;
            }
            else
            {
                // 与之前的结果混合
                float Weight = Entry.BlendWeight;
                AccumulatedPose = FCameraPose::Lerp(
                    AccumulatedPose, 
                    TempResult.CameraPose, 
                    Weight);
            }
        }
      
        OutResult.CameraPose = AccumulatedPose;
    }
}

void FTransientBlendStackCameraNodeEvaluator::PushNewCamera(
    FCameraNodeEvaluator* NewCameraEvaluator,
    float BlendTime,
    ECameraBlendType BlendType)
{
    // 创建新的混合条目
    FBlendEntry NewEntry;
    NewEntry.CameraEvaluator = NewCameraEvaluator;
    NewEntry.BlendWeight = 0.0f;
    NewEntry.TargetWeight = 1.0f;
    NewEntry.BlendTimeRemaining = BlendTime;
  
    // 添加到栈顶
    BlendStack.Add(NewEntry);
  
    // 将之前所有条目的目标权重设为 0
    for (int32 i = 0; i < BlendStack.Num() - 1; ++i)
    {
        BlendStack[i].TargetWeight = 0.0f;
    }
}

void FTransientBlendStackCameraNodeEvaluator::UpdateActiveBlends(float DeltaTime)
{
    for (FBlendEntry& Entry : BlendStack)
    {
        if (Entry.BlendTimeRemaining > 0)
        {
            // 更新混合权重
            float BlendProgress = 1.0f - (Entry.BlendTimeRemaining / Entry.TotalBlendTime);
            Entry.BlendWeight = EvaluateBlendCurve(BlendProgress);
            Entry.BlendTimeRemaining -= DeltaTime;
        }
        else
        {
            Entry.BlendWeight = Entry.TargetWeight;
        }
    }
}

3.4 相机混合曲线

// 不同的混合曲线类型
enum class ECameraBlendType : uint8
{
    Linear,       // 线性混合
    EaseIn,       // 慢入快出
    EaseOut,      // 快入慢出
    EaseInOut,    // 两头慢中间快
    Cubic,        // 三次曲线
};

float EvaluateBlendCurve(float Progress, ECameraBlendType BlendType)
{
    // Progress: 0.0 ~ 1.0
    // 返回: 0.0 ~ 1.0 (权重)
  
    switch (BlendType)
    {
        case ECameraBlendType::Linear:
            return Progress;
          
        case ECameraBlendType::EaseIn:
            // 缓入:开始慢,后面快
            return Progress * Progress;
          
        case ECameraBlendType::EaseOut:
            // 缓出:开始快,后面慢
            return 1.0f - (1.0f - Progress) * (1.0f - Progress);
          
        case ECameraBlendType::EaseInOut:
            // 两头慢中间快(SmoothStep)
            return Progress * Progress * (3.0f - 2.0f * Progress);
          
        case ECameraBlendType::Cubic:
            // 三次曲线,更平滑
            return Progress * Progress * Progress * (Progress * (6.0f * Progress - 15.0f) + 10.0f);
          
        default:
            return Progress;
    }
}

混合曲线可视化

权重 (Weight)
1.0 ┤                              ╭────── EaseOut
    │                         ╭────╯
    │                    ╭────╯          EaseInOut
0.8 ┤              ╭─────╯
    │         ╭────╯
    │    ╭────╯                          Linear
0.6 ┤╭───╯
    │╯
0.5 ┤     ╭───────╮
    │    ╱         ╲
0.4 ┤   ╱           ╲
    │  ╱             ╲                      EaseIn
0.2 ┤ ╱               ╲
    │╱                  ╲
0.0 ┼────────────────────╲────────────────
    0    0.2   0.4   0.6   0.8   1.0
              Progress (进度)

4. 场景二:锁敌系统与效果叠加

4.1 场景描述

怪物猎人锁敌系统:

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│     相机                                                                    │
│      ●                                                                      │
│       \                                                                     │
│        \  锁敌视线                                                          │
│         \                                                                   │
│    玩家   ▼                           怪物                                  │
│      ● ─────────────────────────────▶ ▲                                    │
│                                        │                                    │
│                                        │ 锁敌点 (通常是头部)                │
│                                     ┌──┴──┐                                 │
│                                     │     │                                 │
│                                     └─────┘                                 │
│                                                                             │
│  锁敌效果:                                                                  │
│  1. 相机自动转向锁敌点                                                      │
│  2. 保持一定程度的玩家控制                                                  │
│  3. 可以切换锁敌部位                                                        │
│  4. 怪物移动时平滑跟随                                                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

4.2 使用 Global Layer 实现锁敌

┌─────────────────────────────────────────────────────────────────────────────┐
│                         锁敌效果的层级结构                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Visual Layer (空)                                                          │
│       ▲                                                                     │
│       │ 叠加                                                                │
│  Global Layer: LockOnRig                                                    │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ LockOnAimNode: 修改相机旋转,指向锁敌目标                           │   │
│  │                                                                     │   │
│  │ 输入: Main Layer 的结果 (相机位置已经确定)                          │   │
│  │ 处理: 计算从相机位置到锁敌点的方向                                  │   │
│  │ 输出: 修改后的旋转                                                  │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│       ▲                                                                     │
│       │ 叠加                                                                │
│  Main Layer: CombatRig (战斗相机)                                           │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ OrbitalFollow: 相机绕玩家旋转                                       │   │
│  │ ThirdPersonAim: 基础瞄准                                            │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│       ▲                                                                     │
│       │ 叠加                                                                │
│  Base Layer: AttachRig                                                      │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ AttachToPlayer: 相机跟随玩家移动                                    │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

4.3 锁敌节点实现

// ========================================
// LockOnAimCameraNode.h
// 锁敌瞄准节点
// ========================================

#pragma once

#include "Core/CameraNode.h"
#include "LockOnAimCameraNode.generated.h"

/**
 * 锁敌瞄准节点
 * 让相机自动转向锁定的目标
 * 放在 Global Layer 使用
 */
UCLASS(MinimalAPI, meta=(CameraNodeCategories="Aim,Combat"))
class ULockOnAimCameraNode : public UCameraNode
{
    GENERATED_BODY()
  
public:
    ULockOnAimCameraNode(const FObjectInitializer& ObjInit);
  
protected:
    virtual FCameraNodeEvaluatorPtr OnBuildEvaluator(FCameraNodeEvaluatorBuilder& Builder) const override;
  
public:
    // === 锁敌设置 ===
  
    // 阻尼强度:值越大跟随越快
    UPROPERTY(EditAnywhere, Category="Lock On")
    FFloatCameraParameter DampingStrength = 10.0f;
  
    // 目标偏移:相对于锁定部位的偏移
    UPROPERTY(EditAnywhere, Category="Lock On")
    FVector3dCameraParameter TargetOffset = FVector3d(0, 0, 50);  // 默认瞄准头部上方
  
    // 最大旋转速度(度/秒)
    UPROPERTY(EditAnywhere, Category="Lock On")
    FFloatCameraParameter MaxRotationSpeed = 180.0f;
  
    // 是否保持玩家控制
    // 如果为 true,玩家输入可以覆盖部分锁敌旋转
    UPROPERTY(EditAnywhere, Category="Lock On")
    bool bAllowPlayerControl = true;
  
    // 玩家控制权重 (0-1)
    // 0 = 完全锁敌,1 = 完全玩家控制
    UPROPERTY(EditAnywhere, Category="Lock On")
    FFloatCameraParameter PlayerControlWeight = 0.3f;
  
    // === 距离限制 ===
  
    // 最大锁敌距离
    UPROPERTY(EditAnywhere, Category="Limits")
    FFloatCameraParameter MaxLockDistance = 5000.0f;
  
    // 最小锁敌距离(太近时解除锁定)
    UPROPERTY(EditAnywhere, Category="Limits")
    FFloatCameraParameter MinLockDistance = 100.0f;
  
    // === 视角限制 ===
  
    // 最大仰角
    UPROPERTY(EditAnywhere, Category="Limits")
    FFloatCameraParameter MaxPitchAngle = 80.0f;
  
    // 最小俯角
    UPROPERTY(EditAnywhere, Category="Limits")
    FFloatCameraParameter MinPitchAngle = -45.0f;
};
// ========================================
// LockOnAimCameraNodeEvaluator.h
// 锁敌瞄准节点评估器
// ========================================

#pragma once

#include "Core/CameraNodeEvaluator.h"

class FLockOnAimCameraNodeEvaluator : public FCameraNodeEvaluator
{
    UE_DECLARE_CAMERA_NODE_EVALUATOR(FLockOnAimCameraNodeEvaluator)
  
public:
    // 设置锁敌目标
    void SetLockOnTarget(AActor* Target, FName BoneName = NAME_None);
  
    // 清除锁敌目标
    void ClearLockOnTarget();
  
    // 是否正在锁敌
    bool IsLockOnActive() const { return LockOnTarget.IsValid(); }
  
protected:
    virtual void OnRun(const FCameraNodeEvaluationParams& Params, 
                       FCameraNodeEvaluationResult& OutResult) override;
  
private:
    // 锁敌目标
    TWeakObjectPtr<AActor> LockOnTarget;
    FName LockOnBoneName;
  
    // 当前旋转(用于平滑过渡)
    FRotator3d CurrentAimRotation;
  
    // 上一帧的目标位置(用于预测)
    FVector3d PreviousTargetLocation;
    FVector3d TargetVelocity;
  
    // 获取锁敌点的世界位置
    FVector3d GetLockOnLocation() const;
  
    // 计算期望的相机旋转
    FRotator3d CalculateDesiredRotation(
        const FVector3d& CameraLocation,
        const FVector3d& TargetLocation,
        const FVector3d& UpVector) const;
};
// ========================================
// LockOnAimCameraNodeEvaluator.cpp
// ========================================

#include "LockOnAimCameraNodeEvaluator.h"
#include "LockOnAimCameraNode.h"
#include "GameFramework/Character.h"
#include "Components/SkeletalMeshComponent.h"

UE_DEFINE_CAMERA_NODE_EVALUATOR(FLockOnAimCameraNodeEvaluator)

void FLockOnAimCameraNodeEvaluator::SetLockOnTarget(AActor* Target, FName BoneName)
{
    LockOnTarget = Target;
    LockOnBoneName = BoneName;
    PreviousTargetLocation = GetLockOnLocation();
    TargetVelocity = FVector3d::ZeroVector;
}

void FLockOnAimCameraNodeEvaluator::ClearLockOnTarget()
{
    LockOnTarget.Reset();
    LockOnBoneName = NAME_None;
    TargetVelocity = FVector3d::ZeroVector;
}

FVector3d FLockOnAimCameraNodeEvaluator::GetLockOnLocation() const
{
    if (!LockOnTarget.IsValid())
    {
        return FVector3d::ZeroVector;
    }
  
    AActor* Target = LockOnTarget.Get();
    FVector3d BaseLocation = Target->GetActorLocation();
  
    // 如果指定了骨骼,获取骨骼位置
    if (LockOnBoneName != NAME_None)
    {
        if (ACharacter* Character = Cast<ACharacter>(Target))
        {
            if (USkeletalMeshComponent* Mesh = Character->GetMesh())
            {
                BaseLocation = Mesh->GetSocketLocation(LockOnBoneName);
            }
        }
    }
  
    return BaseLocation;
}

FRotator3d FLockOnAimCameraNodeEvaluator::CalculateDesiredRotation(
    const FVector3d& CameraLocation,
    const FVector3d& TargetLocation,
    const FVector3d& UpVector) const
{
    // 计算从相机到目标的方向
    FVector3d Direction = TargetLocation - CameraLocation;
    double Distance = Direction.Length();
  
    if (Distance < KINDA_SMALL_NUMBER)
    {
        return CurrentAimRotation;
    }
  
    Direction.Normalize();
  
    // 转换为旋转
    FRotator3d DesiredRotation = Direction.Rotation();
  
    return DesiredRotation;
}

void FLockOnAimCameraNodeEvaluator::OnRun(
    const FCameraNodeEvaluationParams& Params,
    FCameraNodeEvaluationResult& OutResult)
{
    const ULockOnAimCameraNode* NodeData = GetCameraNodeAs<ULockOnAimCameraNode>();
  
    // 检查是否有有效的锁敌目标
    if (!LockOnTarget.IsValid())
    {
        // 没有锁敌目标,不修改相机
        return;
    }
  
    // 获取参数值
    const float Damping = NodeData->DampingStrength.GetValue(OutResult.VariableTable);
    const float MaxSpeed = NodeData->MaxRotationSpeed.GetValue(OutResult.VariableTable);
    const float MaxDistance = NodeData->MaxLockDistance.GetValue(OutResult.VariableTable);
    const float MinDistance = NodeData->MinLockDistance.GetValue(OutResult.VariableTable);
    const FVector3d Offset = NodeData->TargetOffset.GetValue(OutResult.VariableTable);
    const float MaxPitch = NodeData->MaxPitchAngle.GetValue(OutResult.VariableTable);
    const float MinPitch = NodeData->MinPitchAngle.GetValue(OutResult.VariableTable);
  
    // 获取相机当前位置
    const FVector3d CameraLocation = OutResult.CameraPose.GetLocation();
    const FVector3d UpVector = OutResult.CameraPose.GetReferenceUp();
  
    // 获取锁敌目标位置
    FVector3d TargetLocation = GetLockOnLocation() + Offset;
  
    // 计算距离
    double Distance = FVector3d::Distance(CameraLocation, TargetLocation);
  
    // 距离检查
    if (Distance > MaxDistance || Distance < MinDistance)
    {
        // 超出范围,解除锁定
        ClearLockOnTarget();
        return;
    }
  
    // 预测目标移动
    if (Params.DeltaTime > 0)
    {
        FVector3d CurrentVelocity = (TargetLocation - PreviousTargetLocation) / Params.DeltaTime;
        // 平滑速度估计
        TargetVelocity = FMath::VInterpTo(TargetVelocity, CurrentVelocity, Params.DeltaTime, 5.0f);
      
        // 预测未来位置
        TargetLocation += TargetVelocity * Params.DeltaTime * 0.5f;
      
        PreviousTargetLocation = TargetLocation;
    }
  
    // 计算期望旋转
    FRotator3d DesiredRotation = CalculateDesiredRotation(CameraLocation, TargetLocation, UpVector);
  
    // 限制俯仰角
    DesiredRotation.Pitch = FMath::Clamp(DesiredRotation.Pitch, MinPitch, MaxPitch);
  
    // 平滑过渡到目标旋转
    if (Params.DeltaTime > 0)
    {
        // 使用阻尼插值
        CurrentAimRotation = FMath::RInterpTo(
            CurrentAimRotation,
            DesiredRotation,
            Params.DeltaTime,
            Damping);
      
        // 限制最大旋转速度
        FRotator3d DeltaRotation = CurrentAimRotation - OutResult.CameraPose.GetRotation();
        DeltaRotation.Normalize();
      
        float MaxDeltaDegrees = MaxSpeed * Params.DeltaTime;
        DeltaRotation.Pitch = FMath::Clamp(DeltaRotation.Pitch, -MaxDeltaDegrees, MaxDeltaDegrees);
        DeltaRotation.Yaw = FMath::Clamp(DeltaRotation.Yaw, -MaxDeltaDegrees, MaxDeltaDegrees);
        DeltaRotation.Roll = 0;
      
        CurrentAimRotation = OutResult.CameraPose.GetRotation() + DeltaRotation;
    }
    else
    {
        CurrentAimRotation = DesiredRotation;
    }
  
    // 应用旋转
    OutResult.CameraPose.SetRotation(CurrentAimRotation);
  
    // 更新参考 LookAt 点
    OutResult.CameraPose.SetReferenceLookAt(TargetLocation);
}

4.4 锁敌 Rig 配置

// ========================================
// LockOnRig 资产配置
// ========================================

/*
 * Camera Rig: LockOnRig
 * Layer: Global
 * 
 * RootNode: Sequence
 *   [0] LockOnAimNode
 */

// 在游戏代码中激活锁敌
void AMonsterHunterPlayerController::ActivateLockOn(AActor* Target, FName BoneName)
{
    if (!Target) return;
  
    // 获取锁敌 Rig 的评估器
    if (UGameplayCameraComponent* CameraComponent = GetGameplayCameraComponent())
    {
        // 设置锁敌目标
        // 这通常通过 VariableTable 或直接访问评估器实现
        SetLockOnTarget(Target, BoneName);
    }
  
    // 激活锁敌 Rig
    FActivateCameraRigParams Params;
    Params.CameraRig = LockOnRig;
    Params.Layer = ECameraRigLayer::Global;
  
    LockOnRigInstanceID = UActivateCameraRigFunctions::ActivateCameraRig(Params);
  
    bLockOnActive = true;
}

void AMonsterHunterPlayerController::DeactivateLockOn()
{
    if (!bLockOnActive) return;
  
    // 清除锁敌目标
    ClearLockOnTarget();
  
    // 停用锁敌 Rig
    FDeactivateCameraRigParams Params;
    Params.CameraRig = LockOnRig;
    Params.Layer = ECameraRigLayer::Global;
  
    UActivateCameraRigFunctions::DeactivateCameraRig(Params);
  
    bLockOnActive = false;
}

// 切换锁敌部位
void AMonsterHunterPlayerController::SwitchLockOnBone(FName NewBoneName)
{
    if (!bLockOnActive || !LockOnTarget.IsValid()) return;
  
    SetLockOnTarget(LockOnTarget.Get(), NewBoneName);
}

4.5 锁敌与武器切换同时发生

┌─────────────────────────────────────────────────────────────────────────────┐
│           场景:武器切换混合进行中时,玩家按下锁敌键                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  T=0s      T=0.3s          T=0.5s           T=0.8s          T=1.3s         │
│    │         │               │                │                │            │
│    ▼         ▼               ▼                ▼                ▼            │
│  ┌───┐   ┌─────────┐    ┌─────────────┐  ┌─────────────┐  ┌─────────┐     │
│  │大剑│──▶│大剑→双刀│───▶│大剑→双刀   │──▶│双刀         │──▶│双刀     │     │
│  │   │   │ (30%)   │    │+ 锁敌激活  │   │+ 锁敌      │   │+ 锁敌   │     │
│  └───┘   └─────────┘    └─────────────┘  └─────────────┘  └─────────┘     │
│                             混合继续        混合完成                        │
│                                                                             │
│  层级状态:                                                                  │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Visual Layer: (空)                                                  │   │
│  │ Global Layer: [LockOnRig] ← T=0.5s 激活                            │   │
│  │ Main Layer: [大剑Rig ←Blend(30%→80%)→ 双刀Rig]                     │   │
│  │ Base Layer: [AttachRig]                                             │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  关键点:                                                                   │
│  • Main Layer 的混合不受 Global Layer 影响                                │
│  • 锁敌效果叠加在混合结果之上                                              │
│  • 最终结果 = Lerp(大剑, 双刀, weight) 然后应用锁敌旋转                   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

5. 场景三:连续相机切换请求

5.1 场景描述

怪物猎人经典场景:骑乘怪物

T=0s      T=0.5s      T=1.0s      T=1.2s      T=1.5s      T=2.0s
  │          │           │           │           │           │
  ▼          ▼           ▼           ▼           ▼           ▼
┌───┐    ┌───────┐   ┌───────┐   ┌───────┐   ┌───────┐   ┌───────┐
│战斗│───▶│战斗→骑乘│──▶│骑乘→特殊│──▶│特殊→骑乘│──▶│骑乘→终结│──▶│骑乘   │
│相机│    │混合中  │   │攻击    │   │攻击    │   │技     │   │相机   │
└───┘    └───────┘   └───────┘   └───────┘   └───────┘   └───────┘
          50%进度     混合被打断   新混合      又被打断
                      新请求到来   开始        终结技请求

5.2 GameplayCamera 的处理方式:快照机制

┌─────────────────────────────────────────────────────────────────────────────┐
│                    连续混合的快照处理机制                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  === T=0s: 初始状态 ===                                                     │
│  Main Layer Stack: [CombatRig]                                              │
│  Result = CombatRig.State                                                   │
│                                                                             │
│  === T=0s: 请求切换到骑乘 ===                                               │
│  Main Layer Stack: [CombatRig ←Blend(0%)→ MountRig]                        │
│  BlendTime = 1.0s                                                           │
│                                                                             │
│  === T=0.5s: 混合进行中 (50%) ===                                           │
│  Main Layer Stack: [CombatRig ←Blend(50%)→ MountRig]                       │
│  CurrentState = Lerp(Combat.State, Mount.State, 0.5)                       │
│                                                                             │
│  === T=0.5s: 新请求!切换到特殊攻击 ===                                     │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 关键操作:创建当前状态的快照                                        │   │
│  │                                                                     │   │
│  │ SnapshotState = Lerp(Combat.State, Mount.State, 0.5)              │   │
│  │ SnapshotRig = CreateSnapshotRig(SnapshotState)                     │   │
│  │                                                                     │   │
│  │ 新的混合栈:                                                        │   │
│  │ Main Layer Stack: [SnapshotRig ←Blend(0%)→ SpecialAttackRig]      │   │
│  │                                                                     │   │
│  │ CombatRig 和 MountRig 被移除,它们的混合状态被"压缩"到快照中       │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  === T=0.7s: 新混合进行中 (20%) ===                                         │
│  Main Layer Stack: [SnapshotRig ←Blend(20%)→ SpecialAttackRig]             │
│  CurrentState = Lerp(SnapshotState, SpecialAttack.State, 0.2)              │
│                                                                             │
│  注意:SnapshotState 是静态的,不会继续从 Combat 向 Mount 混合             │
│                                                                             │
│  === T=0.8s: 又一个新请求!切换回骑乘 ===                                   │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 再次创建快照:                                                      │   │
│  │                                                                     │   │
│  │ NewSnapshotState = Lerp(SnapshotState, SpecialAttack.State, 0.4)  │   │
│  │ NewSnapshotRig = CreateSnapshotRig(NewSnapshotState)              │   │
│  │                                                                     │   │
│  │ 新的混合栈:                                                        │   │
│  │ Main Layer Stack: [NewSnapshotRig ←Blend(0%)→ MountRig]           │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

5.3 快照的数学原理

┌─────────────────────────────────────────────────────────────────────────────┐
│                         快照混合的数学表示                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  假设:                                                                     │
│  • CombatRig 的状态为 C                                                     │
│  • MountRig 的状态为 M                                                      │
│  • SpecialAttackRig 的状态为 S                                              │
│                                                                             │
│  T=0.5s 时请求 SpecialAttack:                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Snapshot1 = Lerp(C, M, 0.5) = 0.5*C + 0.5*M                        │   │
│  │                                                                     │   │
│  │ T=0.7s 时的状态:                                                   │   │
│  │ State = Lerp(Snapshot1, S, 0.2)                                    │   │
│  │      = 0.8 * Snapshot1 + 0.2 * S                                   │   │
│  │      = 0.8 * (0.5*C + 0.5*M) + 0.2 * S                             │   │
│  │      = 0.4*C + 0.4*M + 0.2*S                                       │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  T=0.8s 时再次请求 MountRig:                                               │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Snapshot2 = Lerp(Snapshot1, S, 0.4)                                │   │
│  │          = 0.6 * Snapshot1 + 0.4 * S                               │   │
│  │          = 0.6 * (0.5*C + 0.5*M) + 0.4 * S                         │   │
│  │          = 0.3*C + 0.3*M + 0.4*S                                   │   │
│  │                                                                     │   │
│  │ 最终混合完成后:                                                    │   │
│  │ State = MountRig.State (M)                                         │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  可视化权重变化:                                                           │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 权重                                                                │   │
│  │ 1.0 ┤     M ────────────────────────●━━━━━━━━━━━━━━ M              │   │
│  │     │    ╱                          ╱                              │   │
│  │ 0.5 ┤   ╱   Snapshot1 ●━━━━━━━━━━━╱                               │   │
│  │     │  ╱               ╲         ╱                                 │   │
│  │     │ ╱                 ╲       ╱                                  │   │
│  │ 0.0 ┤●───────────────────╲━━━━━╱──────────────────────────── S     │   │
│  │     │ C                   ╲   ╱                                    │   │
│  │     │                      ●                                       │   │
│  │     └────┬────┬────┬────┬────┬────┬────┬────→ Time                │   │
│  │          0   0.2  0.4  0.6 0.8 1.0 1.2 1.4                        │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

5.4 内部实现代码

// ========================================
// FTransientBlendStackCameraNodeEvaluator
// 处理连续混合请求的核心逻辑
// ========================================

void FTransientBlendStackCameraNodeEvaluator::PushCamera(
    FCameraNodeEvaluator* NewCameraEvaluator,
    float BlendTime,
    ECameraBlendType BlendType)
{
    // 检查当前是否正在混合
    if (BlendStack.Num() > 1)
    {
        // 当前有混合正在进行
        // 需要创建快照
      
        CreateBlendSnapshot();
    }
  
    // 创建新的混合条目
    FBlendEntry NewEntry;
    NewEntry.CameraEvaluator = NewCameraEvaluator;
    NewEntry.BlendWeight = 0.0f;
    NewEntry.TargetWeight = 1.0f;
    NewEntry.TotalBlendTime = BlendTime;
    NewEntry.BlendTimeRemaining = BlendTime;
    NewEntry.BlendType = BlendType;
  
    // 清空栈,只保留快照和新相机
    BlendStack.Empty();
    BlendStack.Add(NewEntry);
}

void FTransientBlendStackCameraNodeEvaluator::CreateBlendSnapshot()
{
    // 1. 计算当前的混合状态
    FCameraNodeEvaluationParams Params;
    FCameraNodeEvaluationResult SnapshotResult;
  
    // 评估当前所有活动相机的混合结果
    for (const FBlendEntry& Entry : BlendStack)
    {
        FCameraNodeEvaluationResult EntryResult;
        Entry.CameraEvaluator->Run(Params, EntryResult);
      
        // 根据权重混合
        SnapshotResult.CameraPose = FCameraPose::Lerp(
            SnapshotResult.CameraPose,
            EntryResult.CameraPose,
            Entry.BlendWeight);
    }
  
    // 2. 创建快照 Rig
    // 快照 Rig 是一个特殊的 Rig,它的状态固定为当前混合结果
    TSharedPtr<FSnapshotCameraNodeEvaluator> SnapshotEvaluator = 
        MakeShared<FSnapshotCameraNodeEvaluator>(SnapshotResult.CameraPose);
  
    // 3. 替换混合栈
    BlendStack.Empty();
  
    FBlendEntry SnapshotEntry;
    SnapshotEntry.CameraEvaluator = SnapshotEvaluator;
    SnapshotEntry.BlendWeight = 1.0f;
    SnapshotEntry.TargetWeight = 0.0f;  // 快照的权重会递减
    BlendStack.Add(SnapshotEntry);
}

// ========================================
// FSnapshotCameraNodeEvaluator
// 快照评估器:保持固定的相机状态
// ========================================

class FSnapshotCameraNodeEvaluator : public FCameraNodeEvaluator
{
public:
    FSnapshotCameraNodeEvaluator(const FCameraPose& InSnapshotPose)
        : SnapshotPose(InSnapshotPose)
    {}
  
protected:
    virtual void OnRun(
        const FCameraNodeEvaluationParams& Params,
        FCameraNodeEvaluationResult& OutResult) override
    {
        // 直接返回快照的状态
        OutResult.CameraPose = SnapshotPose;
    }
  
private:
    FCameraPose SnapshotPose;
};

5.5 与 Cinemachine 的对比

┌─────────────────────────────────────────────────────────────────────────────┐
│                    连续混合:GPC vs Cinemachine                              │
├─────────────────────────────┬───────────────────────────────────────────────┤
│      GameplayCamera         │            Cinemachine                        │
├─────────────────────────────┼───────────────────────────────────────────────┤
│                             │                                               │
│  【处理方式】                │  【处理方式】                                 │
│  创建静态快照                │  创建 NestedBlendSource                      │
│  快照状态固定不变            │  内部混合继续更新                             │
│                             │                                               │
│  【视觉效果】                │  【视觉效果】                                 │
│  原混合被"打断"             │  原混合继续进行                               │
│  从当前状态开始新过渡        │  平滑接力到新相机                             │
│                             │                                               │
│  【优点】                    │  【优点】                                     │
│  实现简单                    │  过渡更自然流畅                               │
│  性能好                      │  视觉效果更好                                 │
│  行为可预测                  │  符合电影剪辑直觉                             │
│                             │                                               │
│  【缺点】                    │  【缺点】                                     │
│  过渡可能不自然              │  实现复杂                                     │
│  可能有轻微跳跃感            │  嵌套层次深时有性能开销                       │
│                             │                                               │
└─────────────────────────────┴───────────────────────────────────────────────┘

视觉对比

GameplayCamera (Snapshot):
================================
T=0.5s 请求新相机时:

快照定格在此刻:Lerp(C, M, 0.5)
新混合从快照开始

T=0.6s:
Result = Lerp(快照, 新相机, 0.1)
       = Lerp(Lerp(C, M, 0.5), 新相机, 0.1)
                    ↑ 永远是 0.5

视觉上:原混合被打断,开始新的过渡


Cinemachine (NestedBlendSource):
================================
T=0.5s 请求新相机时:

包装当前混合为 NestedBlendSource
内部混合继续更新

T=0.6s:
NestedBlend.State = Lerp(C, M, 0.6)  // 继续更新!
Result = Lerp(NestedBlend.State, 新相机, 0.1)
       = Lerp(Lerp(C, M, 0.6), 新相机, 0.1)
                    ↑ 变成 0.6 了

视觉上:原混合继续完成,同时向新相机过渡

6. 场景四:骑乘怪物时的多相机协同

6.1 场景描述

┌─────────────────────────────────────────────────────────────────────────────┐
│                    骑乘怪物的相机系统                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                        怪物                                                 │
│                     ┌─────────┐                                            │
│                     │  头部   │ ← 可攻击部位                                │
│                     │    ●    │                                            │
│                     │    │    │                                            │
│         玩家位置 ───┼────●────┼─── 相机                                     │
│                     │    │    │      ●                                     │
│                     │  身体   │     ╱│                                      │
│                     │    ●────┼───╱ │ 看向头部                              │
│                     │         │  ╱                                         │
│                     │  尾巴   │ ╱                                           │
│                     └────●────┘                                            │
│                                                                             │
│  相机需求:                                                                  │
│  1. 相机附着在怪物背上(跟随怪物移动)                                      │
│  2. 相机可以旋转观察周围                                                    │
│  3. 攻击时镜头会特写攻击部位                                                │
│  4. 怪物挣扎时相机震动                                                      │
│  5. 终结技时有特殊镜头效果                                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

6.2 多层架构设计

┌─────────────────────────────────────────────────────────────────────────────┐
│                    骑乘相机的层级结构                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   Visual Layer                                                              │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │ MountAttackFOVNode: 攻击时 FOV 变化                                 │  │
│   │ MountAttackShakeNode: 攻击命中时的微震动                           │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                    ▲                                        │
│   Global Layer                                                              │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │ MountStruggleShakeNode: 怪物挣扎时的震动                           │  │
│   │ MountLockOnNode: 攻击部位的锁定瞄准                                 │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                    ▲                                        │
│   Main Layer                                                                │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │ MountOrbitalCamera: 玩家可旋转的骑乘相机                           │  │
│   │ MountAttackCamera: 攻击时的特写镜头                                 │  │
│   │ MountFinisherCamera: 终结技的特殊镜头                               │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                    ▲                                        │
│   Base Layer                                                                │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │ AttachToMonsterNode: 相机跟随怪物移动                               │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

6.3 Base Layer:附着到怪物

// ========================================
// AttachToMonsterCameraNode.h
// 相机附着到怪物背上
// ========================================

UCLASS(MinimalAPI, meta=(CameraNodeCategories="Transform"))
class UAttachToMonsterCameraNode : public UCameraNode
{
    GENERATED_BODY()
  
public:
    // 附着偏移(相对于怪物骨骼)
    UPROPERTY(EditAnywhere, Category="Attachment")
    FVector3dCameraParameter AttachmentOffset = FVector3d(0, 0, 100);
  
    // 附着骨骼名称
    UPROPERTY(EditAnywhere, Category="Attachment")
    FName AttachBoneName = NAME_None;
  
    // 是否跟随怪物旋转
    UPROPERTY(EditAnywhere, Category="Attachment")
    bool bFollowRotation = false;
  
    // 位置阻尼
    UPROPERTY(EditAnywhere, Category="Damping")
    FVector3dCameraParameter PositionDamping = FVector3d(5, 5, 5);
};
// AttachToMonsterCameraNodeEvaluator.cpp
void FAttachToMonsterCameraNodeEvaluator::OnRun(
    const FCameraNodeEvaluationParams& Params,
    FCameraNodeEvaluationResult& OutResult)
{
    if (!AttachedMonster.IsValid())
    {
        return;
    }
  
    ACharacter* Monster = AttachedMonster.Get();
    const UAttachToMonsterCameraNode* NodeData = GetCameraNodeAs<UAttachToMonsterCameraNode>();
  
    // 获取附着点位置
    FVector3d TargetLocation;
    if (NodeData->AttachBoneName != NAME_None)
    {
        // 使用骨骼位置
        if (USkeletalMeshComponent* Mesh = Monster->GetMesh())
        {
            TargetLocation = Mesh->GetSocketLocation(NodeData->AttachBoneName);
        }
    }
    else
    {
        TargetLocation = Monster->GetActorLocation();
    }
  
    // 应用偏移
    FVector3d Offset = NodeData->AttachmentOffset.GetValue(OutResult.VariableTable);
    if (NodeData->bFollowRotation)
    {
        Offset = Monster->GetActorRotation().RotateVector(Offset);
    }
    TargetLocation += Offset;
  
    // 应用阻尼
    FVector3d Damping = NodeData->PositionDamping.GetValue(OutResult.VariableTable);
    CurrentLocation = FMath::VInterpTo(CurrentLocation, TargetLocation, Params.DeltaTime, Damping.X);
  
    // 设置相机位置
    OutResult.CameraPose.SetLocation(CurrentLocation);
  
    // 如果跟随旋转
    if (NodeData->bFollowRotation)
    {
        OutResult.CameraPose.SetRotation(Monster->GetActorRotation());
    }
}

6.4 Main Layer:骑乘相机与攻击相机切换

// ========================================
// 骑乘相机管理器
// ========================================

UCLASS()
class UMountCameraManager : public UObject
{
    GENERATED_BODY()
  
public:
    // Camera Rig 资产
    UPROPERTY(EditDefaultsOnly, Category="Rigs")
    UCameraRigAsset* MountBaseRig;        // 基础骑乘相机
  
    UPROPERTY(EditDefaultsOnly, Category="Rigs")
    UCameraRigAsset* MountAttackRig;      // 攻击特写相机
  
    UPROPERTY(EditDefaultsOnly, Category="Rigs")
    UCameraRigAsset* MountFinisherRig;    // 终结技相机
  
    // 过渡设置
    UPROPERTY(EditDefaultsOnly, Category="Transitions")
    UCameraRigTransition* ToAttackTransition;
  
    UPROPERTY(EditDefaultsOnly, Category="Transitions")
    UCameraRigTransition* FromAttackTransition;
  
    UPROPERTY(EditDefaultsOnly, Category="Transitions")
    UCameraRigTransition* ToFinisherTransition;
  
    // 切换到攻击相机
    void EnterAttackMode(FName TargetBone)
    {
        // 设置攻击目标
        CurrentAttackTarget = TargetBone;
      
        // 激活攻击 Rig
        FActivateCameraRigParams Params;
        Params.CameraRig = MountAttackRig;
        Params.Layer = ECameraRigLayer::Main;
        Params.TransitionOverride = ToAttackTransition;
      
        AttackRigInstanceID = UActivateCameraRigFunctions::ActivateCameraRig(Params);
      
        bInAttackMode = true;
    }
  
    // 退出攻击模式
    void ExitAttackMode()
    {
        if (!bInAttackMode) return;
      
        // 激活基础骑乘 Rig(会自动混合出攻击 Rig)
        FActivateCameraRigParams Params;
        Params.CameraRig = MountBaseRig;
        Params.Layer = ECameraRigLayer::Main;
        Params.TransitionOverride = FromAttackTransition;
      
        UActivateCameraRigFunctions::ActivateCameraRig(Params);
      
        bInAttackMode = false;
    }
  
    // 执行终结技
    void ExecuteFinisher()
    {
        // 激活终结技 Rig
        FActivateCameraRigParams Params;
        Params.CameraRig = MountFinisherRig;
        Params.Layer = ECameraRigLayer::Main;
        Params.TransitionOverride = ToFinisherTransition;
      
        FinisherRigInstanceID = UActivateCameraRigFunctions::ActivateCameraRig(Params);
      
        bInFinisher = true;
    }
  
private:
    FCameraRigInstanceID AttackRigInstanceID;
    FCameraRigInstanceID FinisherRigInstanceID;
  
    bool bInAttackMode = false;
    bool bInFinisher = false;
  
    FName CurrentAttackTarget;
};

6.5 完整的骑乘相机流程

┌─────────────────────────────────────────────────────────────────────────────┐
│                    骑乘相机完整流程                                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  时间线:                                                                   │
│                                                                             │
│  T=0s           T=1s           T=2s           T=3s           T=4s          │
│    │              │              │              │              │            │
│    ▼              ▼              ▼              ▼              ▼            │
│  ┌─────┐      ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐       │
│  │进入 │─────▶│骑乘中   │──▶│攻击特写 │──▶│终结技   │──▶│退出     │       │
│  │骑乘 │      │玩家旋转 │   │锁定部位 │   │特殊镜头 │   │骑乘     │       │
│  └─────┘      └─────────┘   └─────────┘   └─────────┘   └─────────┘       │
│                                                                             │
│  Layer 状态:                                                               │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Visual Layer:                                                       │   │
│  │   T=2s: FOV 变化 + 命中震动                                         │   │
│  │   T=3s: 终结技 FOV 特效                                             │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Global Layer:                                                       │   │
│  │   T=1s~: 怪物挣扎震动                                               │   │
│  │   T=2s: 锁定攻击部位                                                │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Main Layer:                                                         │   │
│  │   T=0s: MountBaseRig 激活                                           │   │
│  │   T=2s: MountBaseRig → MountAttackRig 混合                         │   │
│  │   T=3s: MountAttackRig → MountFinisherRig 混合                     │   │
│  │   T=4s: MountFinisherRig → CombatRig 混合                          │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ Base Layer:                                                         │   │
│  │   T=0s~4s: 始终附着在怪物背上                                       │   │
│  │   T=4s: 切换回附着到玩家                                           │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7. 场景五:受击震动与视觉冲击

7.1 场景描述

怪物猎人受击系统:

┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│  轻击:相机轻微抖动                                                         │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 震动强度: 0.3                                                        │   │
│  │ 持续时间: 0.2s                                                       │   │
│  │ 衰减: 快速                                                           │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  重击:相机明显抖动 + FOV 微变                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 震动强度: 0.7                                                        │   │
│  │ 持续时间: 0.5s                                                       │   │
│  │ FOV 变化: -5°                                                        │   │
│  │ 衰减: 中等                                                           │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  大招/龙吼:强烈震动 + 时间减速 + FOV 大变                                  │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ 震动强度: 1.0                                                        │   │
│  │ 持续时间: 1.0s                                                       │   │
│  │ FOV 变化: -15°                                                       │   │
│  │ 时间减速: 0.3x                                                       │   │
│  │ 径向模糊: 启用                                                       │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7.2 震动服务

GameplayCamera 使用 Evaluation Service 机制实现震动:

// ========================================
// CameraShakeService.h
// 相机震动服务
// ========================================

UCLASS(MinimalAPI)
class UCameraShakeService : public UCameraEvaluationService
{
    GENERATED_BODY()
  
public:
    // 添加震动
    FCameraShakeHandle AddShake(
        const FCameraShakeParams& Params);
  
    // 移除震动
    void RemoveShake(FCameraShakeHandle Handle);
  
    // 移除所有震动
    void RemoveAllShakes();
  
protected:
    // 服务执行点
    virtual ECameraServiceExecutionPoint GetExecutionPoint() const override
    {
        return ECameraServiceExecutionPoint::PostEvaluation;
    }
  
    // 服务执行
    virtual void ExecuteService(
        const FCameraServiceParams& Params,
        FCameraServiceResult& OutResult) override;
  
private:
    // 活动的震动列表
    TArray<TSharedPtr<FCameraShakeInstance>> ActiveShakes;
};

// 震动参数
USTRUCT(BlueprintType)
struct FCameraShakeParams
{
    GENERATED_BODY()
  
    // 震动模式
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FVector3f PositionAmplitude = FVector3f(1, 1, 1);
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FVector3f PositionFrequency = FVector3f(10, 10, 10);
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FRotator3f RotationAmplitude = FRotator3f(1, 1, 1);
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FRotator3f RotationFrequency = FRotator3f(10, 10, 10);
  
    // 时间包络
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float AttackTime = 0.1f;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float SustainTime = 0.2f;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float DecayTime = 0.3f;
  
    // 整体强度缩放
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float Scale = 1.0f;
  
    // 震动通道
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 Channel = 1;
};
// CameraShakeService.cpp
void UCameraShakeService::ExecuteService(
    const FCameraServiceParams& Params,
    FCameraServiceResult& OutResult)
{
    FVector3d TotalPositionOffset = FVector3d::ZeroVector;
    FRotator3d TotalRotationOffset = FRotator3d::ZeroRotator;
  
    // 遍历所有活动震动
    for (int32 i = ActiveShakes.Num() - 1; i >= 0; --i)
    {
        TSharedPtr<FCameraShakeInstance> Shake = ActiveShakes[i];
      
        // 检查是否过期
        if (Shake->IsExpired())
        {
            ActiveShakes.RemoveAt(i);
            continue;
        }
      
        // 更新震动
        Shake->Update(Params.DeltaTime);
      
        // 累加偏移
        TotalPositionOffset += Shake->GetPositionOffset();
        TotalRotationOffset += Shake->GetRotationOffset();
    }
  
    // 应用到结果
    OutResult.CameraPose.SetLocation(
        OutResult.CameraPose.GetLocation() + TotalPositionOffset);
    OutResult.CameraPose.SetRotation(
        OutResult.CameraPose.GetRotation() + TotalRotationOffset);
}

7.3 震动的层级位置

┌─────────────────────────────────────────────────────────────────────────────┐
│                    震动在层级中的位置                                        │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  震动作为 Service 存在,在评估完成后执行:                                  │
│                                                                             │
│  评估流程:                                                                 │
│  1. Base Layer 评估                                                        │
│  2. Main Layer 评估                                                        │
│  3. Global Layer 评估                                                      │
│  4. Visual Layer 评估                                                      │
│  5. PostEvaluation Services 执行 ← 震动在这里!                            │
│     • CameraShakeService                                                   │
│     • CameraConfinerService                                                │
│                                                                             │
│  这样设计的好处:                                                           │
│  • 震动不影响任何层的计算                                                   │
│  • 震动在所有层计算完成后叠加                                               │
│  • 震动不会参与混合计算                                                     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7.4 使用示例

// ========================================
// 怪物猎人受击处理
// ========================================

void AMonsterHunterCharacter::OnTakeDamage(
    float Damage, 
    AActor* DamageCauser,
    const FHitResult& HitInfo)
{
    // 根据伤害计算震动强度
    float ShakeIntensity = CalculateShakeIntensity(Damage);
  
    // 创建震动参数
    FCameraShakeParams ShakeParams;
    ShakeParams.Scale = ShakeIntensity;
  
    if (Damage < 20)
    {
        // 轻击
        ShakeParams.PositionAmplitude = FVector3f(2, 2, 1);
        ShakeParams.RotationAmplitude = FRotator3f(0.5f, 0.5f, 0);
        ShakeParams.AttackTime = 0.05f;
        ShakeParams.DecayTime = 0.15f;
    }
    else if (Damage < 50)
    {
        // 重击
        ShakeParams.PositionAmplitude = FVector3f(5, 5, 3);
        ShakeParams.RotationAmplitude = FRotator3f(2, 2, 0.5f);
        ShakeParams.AttackTime = 0.1f;
        ShakeParams.DecayTime = 0.4f;
      
        // FOV 效果
        ApplyFOVEffect(-5, 0.3f);
    }
    else
    {
        // 大招
        ShakeParams.PositionAmplitude = FVector3f(10, 10, 5);
        ShakeParams.RotationAmplitude = FRotator3f(5, 5, 1);
        ShakeParams.AttackTime = 0.15f;
        ShakeParams.DecayTime = 0.8f;
      
        // FOV 效果
        ApplyFOVEffect(-15, 0.8f);
      
        // 时间减速
        ApplyTimeDilation(0.3f, 1.0f);
    }
  
    // 添加震动
    if (UGameplayCameraComponent* CameraComp = GetGameplayCameraComponent())
    {
        CameraComp->AddCameraShake(ShakeParams);
    }
}

float AMonsterHunterCharacter::CalculateShakeIntensity(float Damage)
{
    // 非线性映射
    // Damage: 0-100 -> Intensity: 0-1
    return FMath::Clamp(FMath::Pow(Damage / 100.0f, 0.5f), 0.0f, 1.0f);
}

8. 完整实现:怪物猎人风格相机系统

8.1 系统架构

┌─────────────────────────────────────────────────────────────────────────────┐
│                    怪物猎人风格相机系统架构                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                    AMonsterHunterCameraManager                      │  │
│   │                                                                     │  │
│   │  职责:                                                             │  │
│   │  • 管理所有 Camera Rig 的激活/停用                                 │  │
│   │  • 处理相机模式切换                                                 │  │
│   │  • 协调锁敌系统                                                     │  │
│   │  • 触发震动和视觉效果                                               │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                    │                                        │
│                                    ▼                                        │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                    UGameplayCameraComponent                         │  │
│   │                                                                     │  │
│   │  内部结构:                                                         │  │
│   │  ┌─────────────────────────────────────────────────────────────┐   │  │
│   │  │ FCameraSystemEvaluator                                      │   │  │
│   │  │                                                             │   │  │
│   │  │   Base Layer    Main Layer     Global Layer   Visual Layer │   │  │
│   │  │       │             │              │              │        │   │  │
│   │  │       ▼             ▼              ▼              ▼        │   │  │
│   │  │   AttachRig     [混合栈]      [效果栈]       [视觉栈]     │   │  │
│   │  │                 • ExploreRig  • LockOnRig    • FOVRig     │   │  │
│   │  │                 • CombatRig   • ShakeRig     • VignetteRig│   │  │
│   │  │                 • MountRig    • ImpactRig                  │   │  │
│   │  └─────────────────────────────────────────────────────────────┘   │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

8.2 完整代码

// ========================================
// MonsterHunterCameraManager.h
// ========================================

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MonsterHunterCameraManager.generated.h"

// 相机模式
UENUM(BlueprintType)
enum class EMHCameraMode : uint8
{
    Explore,        // 探索模式
    Combat,         // 战斗模式
    Mount,          // 骑乘模式
    Cinematic,      // 过场动画
    Dead            // 死亡
};

// 武器类型(影响相机距离和 FOV)
UENUM(BlueprintType)
enum class EMHWeaponType : uint8
{
    GreatSword,     // 大剑
    DualBlades,     // 双刀
    LongSword,      // 太刀
    Hammer,         // 大锤
    Bow,            // 弓箭
    HeavyBowgun,    // 重弩
    InsectGlaive,   // 操虫棍
    ChargeBlade,    // 充能斧
    SwitchAxe,      // 斩击斧
    Lance,          // 长枪
    Gunlance,       // 铳枪
    HuntingHorn,    // 狩猎笛
    LightBowgun,    // 轻弩
    SwordAndShield  // 单手剑
};

// 相机 Rig 资产配置
USTRUCT(BlueprintType)
struct FMHCameraRigConfig
{
    GENERATED_BODY()
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigAsset* ExploreRig;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigAsset* CombatBaseRig;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TMap<EMHWeaponType, UCameraRigAsset*> WeaponSpecificRigs;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigAsset* MountBaseRig;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigAsset* MountAttackRig;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigAsset* MountFinisherRig;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigAsset* LockOnRig;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigAsset* ShakeRig;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigAsset* FOVEffectRig;
};

// 过渡配置
USTRUCT(BlueprintType)
struct FMHTransitionConfig
{
    GENERATED_BODY()
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigTransition* ExploreToCombat;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigTransition* CombatToExplore;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigTransition* ToMount;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigTransition* FromMount;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigTransition* ToLockOn;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigTransition* FromLockOn;
  
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCameraRigTransition* WeaponSwitch;
};

UCLASS()
class AMonsterHunterCameraManager : public AActor
{
    GENERATED_BODY()
  
public:
    AMonsterHunterCameraManager();
  
    // ========== 配置 ==========
  
    UPROPERTY(EditDefaultsOnly, Category="Config")
    FMHCameraRigConfig RigConfig;
  
    UPROPERTY(EditDefaultsOnly, Category="Config")
    FMHTransitionConfig TransitionConfig;
  
    // ========== 相机模式 ==========
  
    // 设置相机模式
    UFUNCTION(BlueprintCallable, Category="Camera")
    void SetCameraMode(EMHCameraMode NewMode);
  
    // 获取当前相机模式
    UFUNCTION(BlueprintPure, Category="Camera")
    EMHCameraMode GetCameraMode() const { return CurrentMode; }
  
    // ========== 武器系统 ==========
  
    // 切换武器
    UFUNCTION(BlueprintCallable, Category="Camera|Weapon")
    void SetWeaponType(EMHWeaponType WeaponType);
  
    // ========== 锁敌系统 ==========
  
    // 激活锁敌
    UFUNCTION(BlueprintCallable, Category="Camera|LockOn")
    void ActivateLockOn(AActor* Target, FName BoneName = NAME_None);
  
    // 取消锁敌
    UFUNCTION(BlueprintCallable, Category="Camera|LockOn")
    void DeactivateLockOn();
  
    // 切换锁敌部位
    UFUNCTION(BlueprintCallable, Category="Camera|LockOn")
    void SwitchLockOnTarget(AActor* NewTarget, FName BoneName = NAME_None);
  
    // 是否正在锁敌
    UFUNCTION(BlueprintPure, Category="Camera|LockOn")
    bool IsLockOnActive() const { return bLockOnActive; }
  
    // ========== 骑乘系统 ==========
  
    // 进入骑乘
    UFUNCTION(BlueprintCallable, Category="Camera|Mount")
    void EnterMountMode(AActor* Monster);
  
    // 退出骑乘
    UFUNCTION(BlueprintCallable, Category="Camera|Mount")
    void ExitMountMode();
  
    // 进入攻击模式
    UFUNCTION(BlueprintCallable, Category="Camera|Mount")
    void EnterMountAttack(FName TargetBone);
  
    // 退出攻击模式
    UFUNCTION(BlueprintCallable, Category="Camera|Mount")
    void ExitMountAttack();
  
    // 执行终结技
    UFUNCTION(BlueprintCallable, Category="Camera|Mount")
    void ExecuteFinisher();
  
    // ========== 震动和效果 ==========
  
    // 添加受击震动
    UFUNCTION(BlueprintCallable, Category="Camera|Effects")
    void AddHitShake(float Intensity, float Duration);
  
    // 添加攻击震动
    UFUNCTION(BlueprintCallable, Category="Camera|Effects")
    void AddAttackShake(float Intensity);
  
    // 添加 FOV 效果
    UFUNCTION(BlueprintCallable, Category="Camera|Effects")
    void AddFOVEffect(float FOVOffset, float Duration);
  
    // ========== 状态查询 ==========
  
    UFUNCTION(BlueprintPure, Category="Camera")
    bool IsBlending() const;
  
    UFUNCTION(BlueprintPure, Category="Camera")
    float GetBlendProgress() const;
  
protected:
    virtual void BeginPlay() override;
    virtual void Tick(float DeltaTime) override;
  
private:
    // 当前状态
    EMHCameraMode CurrentMode = EMHCameraMode::Explore;
    EMHWeaponType CurrentWeapon = EMHWeaponType::GreatSword;
  
    // 锁敌状态
    bool bLockOnActive = false;
    TWeakObjectPtr<AActor> LockOnTarget;
    FName LockOnBoneName;
  
    // 骑乘状态
    bool bInMountMode = false;
    bool bInMountAttack = false;
    bool bInFinisher = false;
    TWeakObjectPtr<AActor> MountedMonster;
  
    // Instance IDs
    FCameraRigInstanceID ExploreRigInstanceID;
    FCameraRigInstanceID CombatRigInstanceID;
    FCameraRigInstanceID MountRigInstanceID;
    FCameraRigInstanceID LockOnRigInstanceID;
    FCameraRigInstanceID ShakeRigInstanceID;
    FCameraRigInstanceID FOVRigInstanceID;
  
    // 内部方法
    void TransitionToMode(EMHCameraMode NewMode);
    UCameraRigAsset* GetWeaponSpecificRig() const;
};
// ========================================
// MonsterHunterCameraManager.cpp
// ========================================

#include "MonsterHunterCameraManager.h"
#include "GameFramework/ActivateCameraRigFunctions.h"
#include "GameFramework/GameplayCameraComponent.h"

AMonsterHunterCameraManager::AMonsterHunterCameraManager()
{
    PrimaryActorTick.bCanEverTick = true;
}

void AMonsterHunterCameraManager::BeginPlay()
{
    Super::BeginPlay();
  
    // 初始化:激活探索相机
    FActivateCameraRigParams Params;
    Params.CameraRig = RigConfig.ExploreRig;
    Params.Layer = ECameraRigLayer::Main;
    ExploreRigInstanceID = UActivateCameraRigFunctions::ActivateCameraRig(Params);
}

void AMonsterHunterCameraManager::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
  
    // 更新锁敌目标位置(如果需要)
    if (bLockOnActive && LockOnTarget.IsValid())
    {
        // 可以在这里更新 VariableTable 中的锁敌位置
    }
}

// ==================== 相机模式 ====================

void AMonsterHunterCameraManager::SetCameraMode(EMHCameraMode NewMode)
{
    if (NewMode == CurrentMode) return;
  
    TransitionToMode(NewMode);
    CurrentMode = NewMode;
}

void AMonsterHunterCameraManager::TransitionToMode(EMHCameraMode NewMode)
{
    UCameraRigAsset* TargetRig = nullptr;
    UCameraRigTransition* Transition = nullptr;
  
    switch (NewMode)
    {
        case EMHCameraMode::Explore:
            TargetRig = RigConfig.ExploreRig;
            Transition = TransitionConfig.CombatToExplore;
            break;
          
        case EMHCameraMode::Combat:
            TargetRig = GetWeaponSpecificRig();
            Transition = TransitionConfig.ExploreToCombat;
            break;
          
        case EMHCameraMode::Mount:
            TargetRig = RigConfig.MountBaseRig;
            Transition = TransitionConfig.ToMount;
            break;
          
        default:
            return;
    }
  
    if (TargetRig)
    {
        FActivateCameraRigParams Params;
        Params.CameraRig = TargetRig;
        Params.Layer = ECameraRigLayer::Main;
        Params.TransitionOverride = Transition;
      
        CombatRigInstanceID = UActivateCameraRigFunctions::ActivateCameraRig(Params);
    }
}

// ==================== 武器系统 ====================

void AMonsterHunterCameraManager::SetWeaponType(EMHWeaponType WeaponType)
{
    if (WeaponType == CurrentWeapon) return;
  
    EMHWeaponType OldWeapon = CurrentWeapon;
    CurrentWeapon = WeaponType;
  
    // 如果在战斗模式,切换到对应武器的相机
    if (CurrentMode == EMHCameraMode::Combat)
    {
        UCameraRigAsset* NewRig = GetWeaponSpecificRig();
      
        FActivateCameraRigParams Params;
        Params.CameraRig = NewRig;
        Params.Layer = ECameraRigLayer::Main;
        Params.TransitionOverride = TransitionConfig.WeaponSwitch;
      
        CombatRigInstanceID = UActivateCameraRigFunctions::ActivateCameraRig(Params);
      
        UE_LOG(LogTemp, Log, TEXT("[Camera] Weapon switch: %s -> %s"),
            *UEnum::GetValueAsString(OldWeapon),
            *UEnum::GetValueAsString(WeaponType));
    }
}

UCameraRigAsset* AMonsterHunterCameraManager::GetWeaponSpecificRig() const
{
    if (UCameraRigAsset* const* Found = RigConfig.WeaponSpecificRigs.Find(CurrentWeapon))
    {
        return *Found;
    }
    return RigConfig.CombatBaseRig;
}

// ==================== 锁敌系统 ====================

void AMonsterHunterCameraManager::ActivateLockOn(AActor* Target, FName BoneName)
{
    if (!Target || bLockOnActive) return;
  
    LockOnTarget = Target;
    LockOnBoneName = BoneName;
  
    // 激活锁敌 Rig
    FActivateCameraRigParams Params;
    Params.CameraRig = RigConfig.LockOnRig;
    Params.Layer = ECameraRigLayer::Global;
    Params.TransitionOverride = TransitionConfig.ToLockOn;
  
    LockOnRigInstanceID = UActivateCameraRigFunctions::ActivateCameraRig(Params);
  
    bLockOnActive = true;
  
    UE_LOG(LogTemp, Log, TEXT("[Camera] LockOn activated on %s (bone: %s)"),
        *Target->GetName(), *BoneName.ToString());
}

void AMonsterHunterCameraManager::DeactivateLockOn()
{
    if (!bLockOnActive) return;
  
    FDeactivateCameraRigParams Params;
    Params.CameraRig = RigConfig.LockOnRig;
    Params.Layer = ECameraRigLayer::Global;
    Params.TransitionOverride = TransitionConfig.FromLockOn;
  
    UActivateCameraRigFunctions::DeactivateCameraRig(Params);
  
    LockOnTarget.Reset();
    bLockOnActive = false;
  
    UE_LOG(LogTemp, Log, TEXT("[Camera] LockOn deactivated"));
}

void AMonsterHunterCameraManager::SwitchLockOnTarget(AActor* NewTarget, FName BoneName)
{
    if (!bLockOnActive || !NewTarget) return;
  
    LockOnTarget = NewTarget;
    LockOnBoneName = BoneName;
  
    UE_LOG(LogTemp, Log, TEXT("[Camera] LockOn switched to %s"), *NewTarget->GetName());
}

// ==================== 骑乘系统 ====================

void AMonsterHunterCameraManager::EnterMountMode(AActor* Monster)
{
    if (!Monster || bInMountMode) return;
  
    MountedMonster = Monster;
    bInMountMode = true;
  
    // 切换到骑乘模式
    SetCameraMode(EMHCameraMode::Mount);
  
    UE_LOG(LogTemp, Log, TEXT("[Camera] Entered mount mode on %s"), *Monster->GetName());
}

void AMonsterHunterCameraManager::ExitMountMode()
{
    if (!bInMountMode) return;
  
    bInMountMode = false;
    bInMountAttack = false;
    bInFinisher = false;
    MountedMonster.Reset();
  
    // 切换回战斗模式
    SetCameraMode(EMHCameraMode::Combat);
  
    UE_LOG(LogTemp, Log, TEXT("[Camera] Exited mount mode"));
}

void AMonsterHunterCameraManager::EnterMountAttack(FName TargetBone)
{
    if (!bInMountMode || bInMountAttack) return;
  
    bInMountAttack = true;
  
    // 激活攻击相机
    FActivateCameraRigParams Params;
    Params.CameraRig = RigConfig.MountAttackRig;
    Params.Layer = ECameraRigLayer::Main;
  
    MountRigInstanceID = UActivateCameraRigFunctions::ActivateCameraRig(Params);
  
    UE_LOG(LogTemp, Log, TEXT("[Camera] Mount attack on bone: %s"), *TargetBone.ToString());
}

void AMonsterHunterCameraManager::ExitMountAttack()
{
    if (!bInMountAttack) return;
  
    bInMountAttack = false;
  
    // 切换回基础骑乘相机
    FActivateCameraRigParams Params;
    Params.CameraRig = RigConfig.MountBaseRig;
    Params.Layer = ECameraRigLayer::Main;
  
    UActivateCameraRigFunctions::ActivateCameraRig(Params);
  
    UE_LOG(LogTemp, Log, TEXT("[Camera] Mount attack ended"));
}

void AMonsterHunterCameraManager::ExecuteFinisher()
{
    if (!bInMountMode || bInFinisher) return;
  
    bInFinisher = true;
  
    // 激活终结技相机
    FActivateCameraRigParams Params;
    Params.CameraRig = RigConfig.MountFinisherRig;
    Params.Layer = ECameraRigLayer::Main;
  
    MountRigInstanceID = UActivateCameraRigFunctions::ActivateCameraRig(Params);
  
    // 添加 FOV 效果
    AddFOVEffect(-10, 1.5f);
  
    UE_LOG(LogTemp, Log, TEXT("[Camera] Finisher executed"));
}

// ==================== 震动和效果 ====================

void AMonsterHunterCameraManager::AddHitShake(float Intensity, float Duration)
{
    FCameraShakeParams ShakeParams;
    ShakeParams.Scale = Intensity;
    ShakeParams.PositionAmplitude = FVector3f(5, 5, 3) * Intensity;
    ShakeParams.RotationAmplitude = FRotator3f(2, 2, 0.5f) * Intensity;
    ShakeParams.AttackTime = 0.05f;
    ShakeParams.SustainTime = Duration * 0.3f;
    ShakeParams.DecayTime = Duration * 0.7f;
  
    // 通过震动服务添加
    // GetGameplayCameraComponent()->AddCameraShake(ShakeParams);
  
    UE_LOG(LogTemp, Log, TEXT("[Camera] Hit shake: intensity=%.2f, duration=%.2f"), 
        Intensity, Duration);
}

void AMonsterHunterCameraManager::AddAttackShake(float Intensity)
{
    FCameraShakeParams ShakeParams;
    ShakeParams.Scale = Intensity;
    ShakeParams.PositionAmplitude = FVector3f(1, 1, 0.5f) * Intensity;
    ShakeParams.RotationAmplitude = FRotator3f(0.3f, 0.3f, 0.1f) * Intensity;
    ShakeParams.AttackTime = 0.02f;
    ShakeParams.SustainTime = 0.05f;
    ShakeParams.DecayTime = 0.1f;
  
    // GetGameplayCameraComponent()->AddCameraShake(ShakeParams);
}

void AMonsterHunterCameraManager::AddFOVEffect(float FOVOffset, float Duration)
{
    // 激活 FOV 效果 Rig
    FActivateCameraRigParams Params;
    Params.CameraRig = RigConfig.FOVEffectRig;
    Params.Layer = ECameraRigLayer::Visual;
  
    FOVRigInstanceID = UActivateCameraRigFunctions::ActivateCameraRig(Params);
  
    // 设置 FOV 偏移(通过 VariableTable)
    // GetVariableTable()->SetValue("FOVOffset", FOVOffset);
  
    // 设置定时器移除效果
    FTimerHandle TimerHandle;
    GetWorldTimerManager().SetTimer(TimerHandle, [this]()
    {
        FDeactivateCameraRigParams DeactivateParams;
        DeactivateParams.CameraRig = RigConfig.FOVEffectRig;
        DeactivateParams.Layer = ECameraRigLayer::Visual;
        UActivateCameraRigFunctions::DeactivateCameraRig(DeactivateParams);
    }, Duration, false);
  
    UE_LOG(LogTemp, Log, TEXT("[Camera] FOV effect: offset=%.1f, duration=%.2f"), 
        FOVOffset, Duration);
}

// ==================== 状态查询 ====================

bool AMonsterHunterCameraManager::IsBlending() const
{
    // 查询是否在混合中
    return false; // TODO: 实现查询
}

float AMonsterHunterCameraManager::GetBlendProgress() const
{
    return 0.0f; // TODO: 实现查询
}

9. 性能优化与最佳实践

9.1 性能考虑

┌─────────────────────────────────────────────────────────────────────────────┐
│                    GameplayCamera 性能优化                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. 节点评估器缓存                                                          │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ • 节点评估器在 Rig 首次激活时创建并缓存                              │   │
│  │ • 避免每帧重新创建评估器                                            │   │
│  │ • 使用对象池管理评估器实例                                          │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  2. 条件评估                                                                │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ • 只有激活的 Rig 才会被评估                                         │   │
│  │ • 混合中的 Rig 都会被评估(需要计算混合)                           │   │
│  │ • 停用的 Rig 完全不消耗性能                                         │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  3. 变量表优化                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ • 使用句柄而非字符串访问变量                                        │   │
│  │ • 避免每帧查找变量名                                                │   │
│  │ • 预编译变量访问路径                                                │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  4. 层级裁剪                                                                │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │ • 空层不会被评估                                                    │   │
│  │ • Global/Visual 层可以完全禁用                                     │   │
│  │ • 根据游戏状态动态启用/禁用层                                       │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

9.2 最佳实践

// 1. 使用 Layer 分离关注点
// ✅ 好的做法
Base Layer: 基础跟随(始终运行)
Main Layer: 主相机切换(游戏玩法)
Global Layer: 全局效果(锁敌、震动)
Visual Layer: 视觉反馈(FOV、后处理)

// ❌ 不好的做法
把所有效果都放在一个 Rig 里

// 2. 避免深层嵌套的连续混合
// ✅ 好的做法
设计时就考虑相机切换的流程,减少不必要的切换

// ❌ 不好的做法
在 0.5 秒内连续切换 5 次相机

// 3. 合理使用过渡时间
// ✅ 好的做法
探索→战斗: 1.0s(平滑过渡)
战斗→探索: 0.5s(快速响应)
受击震动: 0.1s(即时反馈)

// ❌ 不好的做法
所有过渡都用 2.0s

// 4. 使用 VariableTable 传递参数
// ✅ 好的做法
// 在蓝图中定义 Camera Variable
// 在代码中设置值
VariableTable->SetValue("LockOnTarget", TargetActor);

// ❌ 不好的做法
// 直接访问评估器内部状态
Evaluator->LockOnTarget = TargetActor;

9.3 调试工具

// 相机调试命令
UCLASS()
class UCameraDebugComponent : public UActorComponent
{
    GENERATED_BODY()
  
public:
    UPROPERTY(EditAnywhere, Category="Debug")
    bool bShowDebugInfo = false;
  
    UPROPERTY(EditAnywhere, Category="Debug")
    bool bShowLayerInfo = false;
  
    UPROPERTY(EditAnywhere, Category="Debug")
    bool bShowBlendStack = false;
  
    UPROPERTY(EditAnywhere, Category="Debug")
    bool bDrawDebugLines = false;
  
protected:
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, 
                               FActorComponentTickFunction* ThisTickFunction) override
    {
        if (!bShowDebugInfo) return;
      
        // 显示当前相机状态
        if (bShowLayerInfo)
        {
            // 显示各层状态
            DrawLayerDebug();
        }
      
        if (bShowBlendStack)
        {
            // 显示混合栈状态
            DrawBlendStackDebug();
        }
      
        if (bDrawDebugLines)
        {
            // 绘制调试线
            DrawDebugLines();
        }
    }
  
    void DrawLayerDebug()
    {
        // 在屏幕上显示:
        // Base Layer: AttachRig (Active)
        // Main Layer: CombatRig (Blending: 50%)
        // Global Layer: LockOnRig (Active)
        // Visual Layer: (Empty)
    }
  
    void DrawBlendStackDebug()
    {
        // 显示混合栈内容:
        // [0] GreatSwordRig (Weight: 0.5)
        // [1] DualBladesRig (Weight: 0.5)
        // Blend Time Remaining: 0.5s
    }
  
    void DrawDebugLines()
    {
        // 绘制:
        // - 相机位置
        // - 相机朝向
        // - 锁敌目标连线
        // - 跟随目标位置
    }
};

10. 与 Cinemachine 的深度对比

10.1 核心差异总结

┌─────────────────────────────────────────────────────────────────────────────┐
│                    GameplayCamera vs Cinemachine                            │
├─────────────────────────────┬───────────────────────────────────────────────┤
│         Cinemachine          │              GameplayCamera                   │
├─────────────────────────────┼───────────────────────────────────────────────┤
│                             │                                               │
│  【架构】                    │  【架构】                                     │
│  组件式                     │  节点-评估器分离                              │
│  Extension 叠加效果          │  Layer 分层效果                               │
│                             │                                               │
│  【混合】                    │  【混合】                                     │
│  NestedBlendSource 嵌套     │  Snapshot 快照                                │
│  混合继续更新               │  混合被打断                                   │
│                             │                                               │
│  【效果叠加】                │  【效果叠加】                                 │
│  Override Stack             │  Layer System                                 │
│  Extension 回调             │  Evaluation Service                           │
│                             │                                               │
│  【配置】                    │  【配置】                                     │
│  Inspector 直接配置          │  资产 + 节点图                                │
│  运行时可修改               │  数据驱动                                     │
│                             │                                               │
│  【扩展】                    │  【扩展】                                     │
│  继承 CinemachineExtension  │  实现自定义节点 + 评估器                      │
│  MonoBehaviour              │  UObject + C++ 类                             │
│                             │                                               │
│  【性能】                    │  【性能】                                     │
│  C# 开销                    │  C++ 原生性能                                 │
│  GC 压力                    │  无 GC                                        │
│                             │                                               │
│  【易用性】                  │  【易用性】                                   │
│  配置简单直观                │  学习曲线陡峭                                 │
│  可视化调试好               │  需要自定义调试工具                           │
│                             │                                               │
└─────────────────────────────┴───────────────────────────────────────────────┘

10.2 选择建议

场景 推荐 原因
快速原型开发 Cinemachine 配置简单,上手快
复杂相机系统 GameplayCamera 分层架构更适合复杂需求
高性能要求 GameplayCamera C++ 原生性能
团队协作 GameplayCamera 数据驱动,版本控制友好
Unity 项目 Cinemachine 原生集成
UE5 项目 GameplayCamera 原生集成

10.3 怪物猎人风格游戏的选择

对于怪物猎人这类复杂相机系统GameplayCamera 更适合

  1. 分层架构:天然支持效果叠加
  2. 数据驱动:武器类型、怪物类型可以对应不同的相机配置
  3. C++ 性能:复杂的锁敌计算、震动计算
  4. 可扩展性:自定义节点实现特殊行为

附录

A. 关键类参考

路径 说明
UCameraRigAsset Camera/Core/CameraRigAsset.h 相机装备资产
UCameraNode Camera/Core/CameraNode.h 相机节点基类
FCameraNodeEvaluator Camera/Core/CameraNodeEvaluator.h 节点评估器基类
FCameraPose Camera/Core/CameraPose.h 相机姿态
FCameraSystemEvaluator Camera/Core/CameraSystemEvaluator.h 系统评估器
FBlendStackCameraNodeEvaluator Camera/Nodes/BlendStackCameraNodeEvaluator.h 混合栈评估器

B. 示例项目结构

/MonsterHunterCamera
├── CameraRigs/
│   ├── Explore/
│   │   └── ExploreRig.uasset
│   ├── Combat/
│   │   ├── GreatSwordRig.uasset
│   │   ├── DualBladesRig.uasset
│   │   └── ...
│   ├── Mount/
│   │   ├── MountBaseRig.uasset
│   │   ├── MountAttackRig.uasset
│   │   └── MountFinisherRig.uasset
│   ├── Effects/
│   │   ├── LockOnRig.uasset
│   │   ├── ShakeRig.uasset
│   │   └── FOVEffectRig.uasset
│   └── Base/
│       └── AttachRig.uasset
├── CameraNodes/
│   ├── LockOnAimCameraNode.uasset
│   ├── AttachToMonsterCameraNode.uasset
│   └── ...
├── CameraTransitions/
│   ├── ExploreToCombat.uasset
│   ├── ToMount.uasset
│   └── ...
└── Blueprint/
    ├── MonsterHunterCameraManager.uasset
    └── CameraDebugComponent.uasset

本文档详细解析了 GameplayCamera 相机系统的使用方法和内部机制
以《怪物猎人》为例,涵盖了相机切换、效果叠加、连续混合等复杂场景
最后更新: 2026-02-25