GCS Initialization & Update
GCS Initialization & Update
在介绍GCS在运行时如何去使用配置的CameraDirector, CameraRig和Transition内容前,先简单回顾下上一节中介绍的 IGameplayCameraSystemHost 。
IGameplayCameraSystemHost 是虚幻引擎 Gameplay Camera System (GCS) 的核心接口,GCS 的启动依赖于至少存在一个实现此接口的宿主对象。该接口定义了摄像机系统宿主必须遵守的行为和访问规则,负责管理和维护自身的摄像机评估器实例(FCameraSystemEvaluator)。
系统在运行时会根据 PlayerController 的 PlayerCameraManager 或当前 ViewTarget,动态选定唯一一个激活的宿主作为摄像机系统的主体控制者,确保摄像机评估和视角切换由该集中化宿主统一管理。
对原版摄像机系统的主要影响包括:
- 若支持 GCS 的 PlayerCameraManager 存在,实现了接口的摄像机组件由该 PlayerCameraManager 统一管理和调度ViewTarget变化, 以及GCS的更新;
- 若无支持 GCS 的 PlayerCameraManager,实现了接口的摄像机组件将独立维护和管理自身摄像机系统;
- 支持 GCS 的 PlayerCameraManager会确保原版的摄像机流程在 GCS 环境下也能正常工作。
Info
“支持GCS 的 PlayerCameraManager" 是指项目配置了实现 IGameplayCameraSystemHost 接口的 AGameplayCamerasPlayerCameraManager(及其派生类)作为 PlayerCameraManager
GCS 的初始化
为了方便理解GCS各种子系统之间的关系,这里主要以 UGameplayCameraComponent 来展开。
仅存在 UGameplayCameraComponent
- 当前环境下,实现IGameplayCameraSystemHost 的 Host 只有UGameplayCameraComponent。
- UGameplayCameraComponent 会在组件初始化的时候先触发Activate,然后通过Beginplay进行初始化。
- 在执行 ActivateCameraEvaluationContext 方法时,组件自身的摄像机评估上下文会被注册并加入到 IGameplayCameraSystemHost 持有的评估器 FCameraSystemEvaluator 中。
在PlayerController中配置GCS的PlayerCameraManager
- TeardownCameraSystemHost 的函数实际上不会做任何事, 因为它上面的GCS并不会初始化。
- 和之前的区别是,整个内容的更新是在GCS的PlayerCameraManager的SetViewTarget(APawn) 时处理的。
GCS 的运行核心
FCameraSystemEvaluator 是 GCS 的运行核心,负责驱动摄像机系统的状态更新与计算。它根据时间变化中的影响推动摄像机各节点的动态行为,实现摄像机视角的平滑切换及动态调整。
IGameplayCameraSystemHost 持有一个共享指针,管理对该评估器的访问:
/** 摄像机系统评估器 */
TSharedPtr<FCameraSystemEvaluator> CameraSystemEvaluator;
摄像机评估器的初始化过程在宿主对象中简单明了:
void IGameplayCameraSystemHost::InitializeCameraSystem()
{
FCameraSystemEvaluatorCreateParams Params;
Params.Owner = GetAsObject(); // 将宿主对象作为评估器的Owner
CameraSystemEvaluator = MakeShared<FCameraSystemEvaluator>();
CameraSystemEvaluator->Initialize(Params); // 内部进行完整初始化
}
GCS通过每帧调用宿主接口,驱动评估器刷新摄像机系统状态:
void IGameplayCameraSystemHost::UpdateCameraSystem(float DeltaTime)
{
if (CameraSystemEvaluator)
{
FCameraSystemEvaluationParams Params;
Params.DeltaTime = DeltaTime; // 传递帧时间增量
CameraSystemEvaluator->Update(Params); // 基于时间更新摄像机状态
}
}
由此完成了摄像机逻辑的时序驱动。
FCameraSystemEvaluator
为了帮助理解,以下是 FCameraSystemEvaluator 初始化流程的简化示例代码:
// 简化版本
void FCameraSystemEvaluator::Initialize(...)
{
WeakOwner = IGameplayCameraSystemHost; // 弱指针
Role = Game; // ECameraSystemEvaluatorRole
RootNode = NewObject<UDefaultRootCameraNode>(Owner, TEXT("RootNode")); // 创建摄像机节点树根节点
ContextStack.Initialize(*this); // 初始化摄像机评估上下文堆栈
// 构造根摄像机节点评估器,对应RootNode节点
FCameraNodeEvaluatorTreeBuildParams BuildParams;
BuildParams.RootCameraNode = RootNode;
RootEvaluator = static_cast<FRootCameraNodeEvaluator*>(RootEvaluatorStorage.BuildEvaluatorTree(BuildParams));
// ... 可在此注册扩展服务 ...
// 初始化根评估器,构建完整评估器树结构
FCameraNodeEvaluatorInitializeParams InitParams;
InitParams.Evaluator = this;
RootEvaluator->Initialize(InitParams, RootNodeResult);
}
- RootNode 是摄像机节点树的根,是所有摄像机节点的父节点。
- ContextStack 管理多个运行时的摄像机评估上下文,支持灵活切换。
- RootEvaluator 负责对整棵摄像机评估器树进行构建,并执行评估流程。
- 初始化阶段完成摄像机的评估树准备,待Tick调用进行更新计算。
FCameraEvaluationContext
FCameraEvaluationContext 提供了CameraRig运行时的上下文数据,该上下文对象会存储在 FCameraSystemEvaluator 的 ContextStack 中,便于摄像机状态在多上下文间切换及存储。
参考代码片段:
class FCameraEvaluationContext : public TSharedFromThis<FCameraEvaluationContext>
{
protected:
TWeakObjectPtr<> WeakOwner;
TWeakObjectPtr<APlayerController> WeakPlayerController;
/** 当前上下文托管的摄像机资产 */
TObjectPtr<const UCameraAsset> CameraAsset;
/** CameraAsset中所有CameraRig初步计算结果,用于获得摄像机位置等信息 */
FCameraNodeEvaluationResult InitialResult;
}
void FCameraEvaluationContext::Initialize(const FCameraEvaluationContextInitializeParams& Params)
{
WeakOwner = Params.Owner;
CameraAsset = Params.CameraAsset;
WeakPlayerController = Params.PlayerController;
if (CameraAsset)
{
const FCameraAssetAllocationInfo& AllocationInfo = CameraAsset->GetAllocationInfo();
InitialResult.VariableTable.Initialize(AllocationInfo.VariableTableInfo);
InitialResult.ContextDataTable.Initialize(AllocationInfo.ContextDataTableInfo);
}
}
- CameraAsset 是运行时摄像机配置的资产,是GCS的核心数据之一。
- InitialResult 提供了摄像机Rig的初步评估数据,是后续动态计算依据。
FGameplayCameraComponentEvaluationContext
FCameraEvaluationContext 的派生类,UGameplayCameraComponent 的私有数据。
UGameplayCameraComponent 运行时会根据配置的 CameraAsset 构建 FCameraEvaluationContext ,交由 FCameraSystemEvaluator 处理。
class UGameplayCameraComponent
{
TSharedPtr<FGameplayCameraComponentEvaluationContext> EvaluationContext;
// TryCreateCameraEvaluationContext(..)
EvaluationContext = MakeShared<FGameplayCameraComponentEvaluationContext>();
FCameraEvaluationContextInitializeParams InitParams;
InitParams.Owner = this;
InitParams.CameraAsset = CameraAsset; // 配置的CameraAsset
InitParams.PlayerController = PlayerController;
EvaluationContext->Initialize(InitParams);
}
/**
* 针对Gameplay摄像机组件的评估上下文,继承自FCameraEvaluationContext
*/
class FGameplayCameraComponentEvaluationContext : public FCameraEvaluationContext;
FActorCameraEvaluationContext
FCameraEvaluationContext 的派生类,专门用于将非GameplayCameraSystem体系下的摄像机数据(通常来自Actor或CameraComponent)封装为GCS可以识别和计算的上下文环境。
它主要完成的工作是:
- 接收AActor或UCameraComponent实例,
- 程序化地构造一个简化的CameraAsset(仅含有复制Actor摄像机属性的节点),
- 提供静态辅助函数完成视图信息到评估结果的转换。
// 参考代码
UCameraAsset* FActorCameraEvaluationContext::MakeCalcCameraActorCameraAsset(ViewTargetActor)
{
UCalcCameraActorCameraNode* WrapperCameraNode = NewObject<UCalcCameraActorCameraNode>(ViewTargetActor, NAME_None, RF_Transient);
UCameraRigAsset* CameraRig = NewObject<UCameraRigAsset>(ViewTargetActor, NAME_None, RF_Transient);
CameraRig->RootNode = WrapperCameraNode;
USingleCameraDirector* SingleDirector = NewObject<USingleCameraDirector>(ViewTargetActor, NAME_None, RF_Transient);
SingleDirector->CameraRig = CameraRig;
UCameraAsset* CameraAsset = NewObject<UCameraAsset>(ViewTargetActor, NAME_None, RF_Transient);
CameraAsset->SetCameraDirector(SingleDirector);
return CameraAsset;
}
//AGameplayCamerasPlayerCameraManager::SetViewTarget
TSharedRef<FActorCameraEvaluationContext> NewContext = MakeShared<FActorCameraEvaluationContext>(ViewTargetActor);
CameraSystemEvaluator->PushEvaluationContext(NewContext);
ViewTargetContexts.Add(NewContext);
使得非GCS的ViewTarget Actor能够被Gameplay Cameras系统统一处理和计算,使旧有摄像机系统或自定义视角能顺利过渡至GCS框架内,提供向下兼容能力,提高系统整合度和灵活性。
GCS 的更新流程
下面介绍GCS的更新过程中关键节点
// 简化版本
void FCameraSystemEvaluator::UpdateImpl(float DeltaTime, ECameraNodeEvaluationType EvaluationType)
{
// 预先调用各类服务的更新(如摄像机震动系统等),方便提前处理状态
PreUpdateServices(DeltaTime, ECameraEvaluationServiceFlags::None);
// 获取当前活动的摄像机评估上下文
TSharedPtr<FCameraEvaluationContext> ActiveContext = ContextStack.GetActiveContext();
// 执行摄像机导演(Director)逻辑,激活导演返回需要切换的Camera Rig
FCameraDirectorEvaluator* ActiveDirectorEvaluator = ActiveContext->GetDirectorEvaluator();
UpdateCameraDirector(DeltaTime, ActiveDirectorEvaluator);
// 运算摄像机节点树,计算当前摄像机最终输出结果
{
FCameraNodeEvaluationParams NodeParams;
NodeParams.Evaluator = this;
NodeParams.DeltaTime = DeltaTime;
NodeParams.EvaluationType = EvaluationType;
RootNodeResult.Reset();
RootEvaluator->Run(NodeParams, RootNodeResult);
RootNodeResult.bIsValid = true; // 标记结果有效
}
// 后处理所有更新服务,比如清理和状态同步等.
PostUpdateServices(DeltaTime, ECameraEvaluationServiceFlags::None);
// 收集并保存最终摄像机结果,用于后续渲染或逻辑使用
PreVisualResult.Reset(RootEvaluator->GetPreVisualLayerResult());
Result.Reset(RootNodeResult);
}
UpdateCameraDirector 的主要职责是:
- 执行摄像机导演(Director)评估器,获得摄像机导演的输出指令。
- 处理导演返回的摄像机Rig激活和反激活请求,分层执行这些请求以保证摄像机切换的合理性。
// 运行摄像机导演评估器,获取CameraRig的变化请求
CameraDirectorEvaluator->Run(DirectorParams, DirectorResult);
// 在除“Main”层以外的所有层级执行激活/反激活请求
RootEvaluator->ExecuteCameraDirectorRequest(OtherLayerRequest);
// 对“Main”层的摄像机Rig激活请求进行合并处理后执行
GetCombinedCameraRigRequest(MainLayerActivations, CombinedRequest);
RootEvaluator->ExecuteCameraDirectorRequest(CombinedRequest);
// 对“Main”层的摄像机Rig反激活请求同样进行合并后执行
GetCombinedCameraRigRequest(MainLayerDeactivations, CombinedRequest);
RootEvaluator->ExecuteCameraDirectorRequest(CombinedRequest);
CameraNode & CameraNodeEvaluator


- 构建CameraRig的各种摄像机节点都由对应的评估器用于解析节点的数据,对CameraPos产生影响。
- 除了抽象基类 UCameraNode,派生的摄像机节点在重写OnBuildEvaluator中指定匹配的评估器。
- 根节点的评估器 RootEvaluator 会遍历激活的CameraRig中节点解析器,并将结果叠加生成最终的CameraPos信息。整个过程在FCameraSystemEvaluator的Update中完成。
- 摄像机评估器和摄像机节点一一对应,且根据CameraRig的激活状态进行刷新。
CameraNodeEvaluator 工作流程
// 初始化评估器,构建评估器完成后调用
void FCollisionPushCameraNodeEvaluator::OnInitialize(const FCameraNodeEvaluatorInitializeParams& Params, FCameraNodeEvaluationResult& OutResult)
{
// 设置节点评估器的标记,这里无特殊标记
SetNodeEvaluatorFlags(ECameraNodeEvaluatorFlags::None);
// 获取本节点对应的 UCollisionPushCameraNode 数据对象
const UCollisionPushCameraNode* CollisionPushNode = GetCameraNodeAs<UCollisionPushCameraNode>();
// 初始化各个参数读取器,从对应CameraNode配置中读取相关属性,支持动态绑定和读取
EnableCollisionReader.Initialize(CollisionPushNode->EnableCollision, true); // 碰撞启用开关
CustomSafePositionReader.Initialize(CollisionPushNode->CustomSafePosition); // 自定义安全位置
CollisionSphereRadiusReader.Initialize(CollisionPushNode->CollisionSphereRadius); // 碰撞半径
SafePositionOffsetReader.Initialize(CollisionPushNode->SafePositionOffset); // 安全位置偏移量
// 初始化推挤和平拉的插值器,若预设了插值器则使用其构建的双精度插值器,否则创建默认插值器
PushInterpolator = CollisionPushNode->PushInterpolator ?
CollisionPushNode->PushInterpolator->BuildDoubleInterpolator() :
MakeUnique<TPopValueInterpolator<double>>();
PullInterpolator = CollisionPushNode->PullInterpolator ?
CollisionPushNode->PullInterpolator->BuildDoubleInterpolator() :
MakeUnique<TPopValueInterpolator<double>>();
}
// 执行碰撞检测及摄像机位置的调整,保证摄像机视角不会穿墙或发生穿透
void FCollisionPushCameraNodeEvaluator::OnRun(const FCameraNodeEvaluationParams& Params, FCameraNodeEvaluationResult& OutResult)
{
UWorld* World = Params.EvaluationContext->GetWorld();
APlayerController* PlayerController = Params.EvaluationContext->GetPlayerController();
// 获取安全位置,如果无法获取则返回空值
TOptional<FVector3d> SafePosition = GetFinalSafePosition(Params, OutResult);
// 判断状态区的碰撞功能是否启用,若未启用,调用处理函数直接使用安全位置并返回
const bool bEnableCollision = EnableCollisionReader.Get(OutResult.VariableTable);
if (!bEnableCollision)
{
HandleDisabledCollision(SafePosition.GetValue(), Params, OutResult);
return;
}
// 如果当前评估类型非标准(如IK或无状态更新),跳过新碰撞检测逻辑
// 向前推摄像机位置与上一次推挤状态相结合,实现平滑效果
if (Params.EvaluationType != ECameraNodeEvaluationType::Standard)
{
if (SafePosition.IsSet())
{
const FVector3d CameraPoseLocation = OutResult.CameraPose.GetLocation();
const FVector3d PushedLocation = CameraPoseLocation + (SafePosition.GetValue() - CameraPoseLocation) * LastDampedPushFactor;
OutResult.CameraPose.SetLocation(PushedLocation);
}
return;
}
// 标准评估类型下,真正执行异步碰撞检测结果处理和新的碰撞检测逻辑
HandleAsyncCollisionTraceResult(World, SafePosition.GetValue(), Params, OutResult);
RunCollisionTrace(World, PlayerController, SafePosition.GetValue(), Params, OutResult);
}
- 如果要理解其他的摄像机节点的运作方式,需要结合对应的评估器去调试。
GCS 获取结果
最后的CameraPos 由更新的 RootNodeResult来生成,参考如下:
void FCameraSystemEvaluator::GetEvaluatedCameraView(FMinimalViewInfo& DesiredView)
{
const FCameraPose& CameraPose = RootNodeResult.CameraPose;
DesiredView.Location = CameraPose.GetLocation();
DesiredView.Rotation = CameraPose.GetRotation();
DesiredView.FOV = CameraPose.GetEffectiveFieldOfView();
DesiredView.DesiredFOV = DesiredView.FOV;
DesiredView.AspectRatio = CameraPose.GetSensorAspectRatio();
// 略
}
GCS蓝图接口:动态激活Camera Rig
GCS为蓝图提供了功能丰富的静态函数库 UActivateCameraRigFunctions,能够在不同层级(Base, Global, Visual)动态激活新的摄像机Rig。其核心实现依赖于挂载在 APlayerController 上的组件 UControllerGameplayCameraEvaluationComponent。示例代码如下:
void UActivateCameraRigFunctions::ActivateCameraRigImpl(UObject* WorldContextObject, APlayerController* PlayerController, UCameraRigAsset* CameraRig, ECameraRigLayer EvaluationLayer)
{
// 查询当前摄像机系统宿主
if (IGameplayCameraSystemHost* FoundHost = IGameplayCameraSystemHost::FindActiveHost(PlayerController))
{
UObject* FoundHostObject = FoundHost->GetAsObject();
AActor* HostOwningActor = Cast<AActor>(FoundHostObject);
bool bComponentCreated = false;
CameraEvaluationComponent = UControllerGameplayCameraEvaluationComponent::FindOrAddComponent(HostOwningActor, &bComponentCreated);
if (bComponentCreated)
{
CameraEvaluationComponent->Initialize(FoundHost->GetAsScriptInterface(), PlayerController);
}
}
if (CameraEvaluationComponent)
{
CameraEvaluationComponent->ActivateCameraRig(CameraRig, EvaluationLayer);
}
}