GCS Editor Extensions & IGameplayCameraSystemHost
GCS Editor Extensions & IGameplayCameraSystemHost
第一篇文章中我们简单介绍了GCS怎么激活,以及怎么在编辑器中利用它的工具链去构建对应行为的摄像机。
这篇文章主要介绍工具链中的UCameraAsset是怎么可以创建模版资源的,以及它是怎么指定CameraDirector的。
文章的后面部分我们还会对GCS的核心接口 IGameplayCameraSystemHost 进行介绍。
UPROPERTY(Instanced)
我们首先要介绍的UProperty的Instanced标签,官网对这个标签的介绍是:

简单来说就是当父对象被实例化或复制时,会为该属性自动创建独立的子对象实例。
典型对比示例
// 纹理资产,多个对象共享同一个纹理资源
UPROPERTY(Category=Test, EditDefaultsOnly)
UTexture2D* TestAsset;
// 组件,多个对象需要各自独立的组件实例
UPROPERTY(Category=Test, EditDefaultsOnly)
UStaticMeshComponent* TestComponent;
- TestAsset 指向的是一个“共享的”资源(纹理),多个 actor 可以共享同一个纹理资源指针,内存节省且合理。
- TestComponent 是 actor 的子对象,每个 actor 需要单独的组件实例(对象),否则组件状态会冲突。
这个区别就是通过 Instanced 标签体现的:
纹理属性不需要加 Instanced,就是共享指针。
组件属性则需要加 Instanced,表示每个包含它的对象中,都会实例化不同的组件对象。
/** The main skeletal mesh associated with this Character (optional sub-object). */ UPROPERTY(Category=Character, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true")) TObjectPtr<USkeletalMeshComponent> Mesh; // 上面的Instanced表述主要介绍这个标签的用法,实际上从ACharacter的Mesh组件来看,它是内部控制实例化的,蓝图层面只允许私有只读访问。 // 是否需要添加Instanced标签是根据实际需求来进行的,下面会进行说明。
实例说明
UCLASS(MinimalAPI)
class UCameraAsset : public UObject
{
private:
/** The camera director to use in this camera. */
UPROPERTY(Instanced)
TObjectPtr<UCameraDirector> CameraDirector;
}
UCLASS(MinimalAPI, EditInlineNew)
class UBlueprintCameraDirector : public UCameraDirector
{
public:
/** The blueprint class that we should instantiate and run. */
UPROPERTY(EditAnywhere, Category="Evaluation")
TSubclassOf<UBlueprintCameraDirectorEvaluator> CameraDirectorEvaluatorClass;
}
- 每个CameraAsset实例化的时候都会创建自己的CameraDirector实例。
- CameraDirector的子类添加了EditInlineNew标签,这样它标记为可编辑的属性,可以在蓝图配置中进行剪辑。
编辑器中的实际创建效果如下:

UCameraAssetEditor(UAssetEditor)
UAssetEditor 是编辑器中处理特定资产编辑流程的基类.
- UAssetEditor 管理资产编辑器窗口生命周期、标签页,调用 Toolkit 实现 UI 逻辑
- 当用户点击内容浏览器中的某个资产时,如果有对应的 UAssetEditor,编辑器会创建实例打开该资产。
UCameraAsset 通过自定义的资产编辑器 UCameraAssetEditor 在编辑器中实现了类似 UDataAsset 的资产编辑功能。
/**
* Base class for all asset editors.
*/
UCLASS(Abstract, MinimalAPI)
class UAssetEditor : public UObject, public IAssetEditorInstance;
/**
* Editor for a camera asset.
*/
UCLASS(Transient)
class UCameraAssetEditor : public UAssetEditor
{
public:
void Initialize(TObjectPtr<UCameraAsset> InCameraAsset);
public:
// UAssetEditor interface
virtual void GetObjectsToEdit(TArray<UObject*>& InObjectsToEdit) override;
virtual TSharedPtr<FBaseAssetToolkit> CreateToolkit() override;
private:
UPROPERTY()
TObjectPtr<UCameraAsset> CameraAsset;
};
UCameraAssetEditor 继承自 UAssetEditor,负责CameraAsset的编辑功能。
通过覆写接口,指定了编辑器工具FCameraAssetEditorToolkit, 以及编辑对象 CameraAsset。
FCameraAssetEditorToolkit实现了编辑器 UI 和逻辑:
- 树状视图、属性面板、视窗等编辑界面。
- 通过 DataBinding 和事件机制响应用户操作,修改资产内容。
- 支持资产内嵌组件(比如前面提到的 CameraDirector)的显示与编辑。
重点是:资产编辑器允许你“打开”该资产,像编辑蓝图或材质一样,实时修改其内部结构或参数,做到模板资产对用户透明且可操作。
UCameraAssetFactory(UFactory)
UFactory 是虚幻引擎编辑器中的基础工厂类,用于实现资产(Asset)和对象(Object)的创建和导入功能。
UCameraAssetFactory 继承于UFactory,主要负责 UCameraAsset 资产在编辑器中的创建流程。
/**
* Implements a factory for UCameraAsset objects.
*/
UCLASS(hidecategories=Object)
class UCameraAssetFactory : public UFactory
{
public:
// UFactory Interface
virtual UObject* FactoryCreateNew(UClass* Class, UObject* Parent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
virtual bool ConfigureProperties() override;
private:
/** The type of camera director to create for this asset. */
UPROPERTY(EditAnywhere, Category=Camera)
TSubclassOf<UCameraDirector> CameraDirectorClass;
};
// 重新定义UCameraAsset的创建
UObject* UCameraAssetFactory::FactoryCreateNew(UClass* Class, UObject* Parent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
UCameraAsset* NewCameraAsset = NewObject<UCameraAsset>(Parent, Class, Name, Flags | RF_Transactional);
if (CameraDirectorClass)
{
UCameraDirector* NewCameraDirector = NewObject<UCameraDirector>(NewCameraAsset, CameraDirectorClass, NAME_None, RF_Transactional);
NewCameraAsset->SetCameraDirector(NewCameraDirector);
}
return NewCameraAsset;
}
// 通过用户编辑器窗口选择获取CameraDirector类型
bool UCameraAssetFactory::ConfigureProperties()
{
FCameraDirectorClassPicker Picker;
CameraDirectorClass = nullptr;
TSubclassOf<UCameraDirector> ChosenClass;
const bool bPressedOk = Picker.PickCameraDirectorClass(ChosenClass);
if (bPressedOk)
{
CameraDirectorClass = ChosenClass;
}
return bPressedOk;
}
工作流程
- 用户在编辑器中新建资产时,编辑器会查找所有注册的 UFactory。
- 根据新建资产类型(需匹配 SupportedClass),调用对应的 UFactory::FactoryCreateNew 来创建实例。
- 创建后的资产实例会自动添加到内容浏览器,并打开相关编辑器。
UActorFactory
UActorFactory 不是 UFactory的子类,它用于创建场景中的 Actor 实例的工厂类。
- 聚焦于“世界场景中的对象创建”。
- 在编辑器 Windows->Place Actors 中拖拽资产到关卡时,决定如何将资产转换成 Actor 放置到关卡中.
UCLASS()
class UGameplayCameraActorFactory : public UActorFactory
{
public:
UGameplayCameraActorFactory(const FObjectInitializer& ObjInit);
// UActorFactory interface.
virtual AActor* SpawnActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams) override;
virtual bool CanCreateActorFrom( const FAssetData& AssetData, FText& OutErrorMsg ) override;
};
UGameplayCameraActorFactory::UGameplayCameraActorFactory(const FObjectInitializer& ObjInit)
: Super(ObjInit)
{
// 指定要生产的Actor类型
NewActorClass = AGameplayCameraActor::StaticClass();
}
AActor* UGameplayCameraActorFactory::SpawnActor(UObject* InAsset, ULevel* InLevel, const FTransform& InTransform, const FActorSpawnParameters& InSpawnParams)
{
AGameplayCameraActor* NewActor = Cast<AGameplayCameraActor>(Super::SpawnActor(InAsset, InLevel, InTransform, InSpawnParams));
if (NewActor)
{
// 可以通过UCameraAsset的资源直接生成AGameplayCameraActor
if (UCameraAsset* CameraAsset = Cast<UCameraAsset>(InAsset))
{
UGameplayCameraComponent* NewComponent = NewActor->GetCameraComponent();
NewComponent->CameraReference.SetCameraAsset(CameraAsset);
}
}
return NewActor;
}
bool UGameplayCameraActorFactory::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg)
{
// 在 UPlacementSubsystem 判断是否启用那个ActorFactory时,将UCameraAsset和当前ActorFactory关联起来
if (AssetData.IsValid() && AssetData.IsInstanceOf(UCameraAsset::StaticClass()))
{
return true;
}
}
- UGameplayCameraActorFactory 定义了 AGameplayCameraActor 的创建方式,并扩展支持从UCameraAsset来影响创建的 AGameplayCameraActor。
- 这个扩展非常巧妙,可以将UCameraAsset的配置应用到AGameplayCameraActor 的组件中,这样可以在编辑器中查看受当前CameAsset影响到的AGameplayCameraActor。
- 可以在编辑中拖拽这个Actor, 看到摄像机的轨迹变换。
- 由UPlacementSubsystem(UEditorSubsystem)通过FindAssetFactoryFromAssetData来决策当前资源关联的Factory。
- 相似的实现有UGameplayCameraRigActorFactory
UGameplayCameraComponent
UGameplayCameraComponent 是虚幻引擎 GCS 中用于替代传统 UCameraComponent 的新型摄像机组件。
- 它在自身的 Camera Evaluation context 上运行配置的 CameraAsset,提供灵活且强大的摄像机控制和配置能力。
- 核心摄像机功能由其父类 UGameplayCameraComponentBase负责实现,它主要提供CameraAsset以外部参数的配置功能。
FCameraAssetReference
class UGameplayCameraComponent : public UGameplayCameraComponentBase
{
public:
/** The camera asset to run. */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Camera, meta=(SequencerHideProperty=true))
FCameraAssetReference CameraReference;
}
struct FCameraAssetReference
{
private:
/** The referenced camera asset. */
UPROPERTY(EditAnywhere, Category=Camera)
TObjectPtr<UCameraAsset> CameraAsset;
/** The camera asset's parameters. */
UPROPERTY(EditAnywhere, Category="", meta=(FixedLayout=true, InterpBagProperties=true, DisplayName="Camera Parameters"))
FInstancedPropertyBag Parameters;
};
- FCameraAssetReference 是一个包含对 UCameraAsset 的引用,并管理该资产内所有关联 Camera Rig 的动态参数覆盖的结构体。类似于一个"包装器"。
- FCameraAssetReference 通过内置的动态属性容器 FInstancedPropertyBag,支持对摄像机Rig参数的灵活自定义和实时调整。
FInstancedPropertyBag
强大灵活的运行时动态属性容器,打破了传统静态结构体束缚,允许动态地定义、修改、添加和读取属性。简单用法示例:
// 定义动态属性包作为组件属性
UPROPERTY(EditDefaultsOnly, Category=Common)
FInstancedPropertyBag Bag;
// 动态添加属性
Bag.AddProperties({
{ "Temperature", EPropertyBagPropertyType::Float },
{ "Count", EPropertyBagPropertyType::Int32 }
});
// 设置属性值
Bag.SetValueFloat("Temperature", 25.0f);
Bag.SetValueInt32("Count", 10);
// 读取属性值
auto TempValue = Bag.GetValueFloat("Temperature");
if (TempValue.IsValid()) {
float Temperature = TempValue.GetValue();
}
工作机制
当 CameraAsset 里的 Camera Rig 创建新的外部参数时,这些参数会暴露到 GameplayCameraComponent 的属性编辑界面,实现参数与组件属性的同步。
这种同步及参数绑定主要依赖于源码工程中 Build 目录下的构建器系统(FCameraAssetBuilder, FCameraRigAssetBuilder等)生成反射及绑定代码,确保数据驱动的参数及时更新。

- 如果没有配置 Camera Rig的参数,这里是不会出现对应配置的
IGameplayCameraSystemHost
IGameplayCameraSystemHost 是GCS中的核心接口,定义了作为摄像机系统宿主的对象必须遵循的行为和访问规则。
- 提供统一的摄像机系统上下文访问协议,确保宿主能创建、销毁、更新并共享自身摄像机评估器实例(FCameraSystemEvaluator)。
- 支持动态判定运行时真正的摄像机宿主角色,参考如下:
IGameplayCameraSystemHost* IGameplayCameraSystemHost::FindActiveHost(APlayerController* PlayerController)
{
if (PlayerController)
{
if (PlayerController->PlayerCameraManager)
{
if (IGameplayCameraSystemHost* CameraManagerHost = Cast<IGameplayCameraSystemHost>(PlayerController->PlayerCameraManager))
{
return CameraManagerHost;
}
}
if (AActor* ViewTarget = PlayerController->GetViewTarget())
{
if (IGameplayCameraSystemHost* ViewTargetHost = ViewTarget->FindComponentByInterface<IGameplayCameraSystemHost>())
{
return ViewTargetHost;
}
}
}
return nullptr;
}
- 这是一个Static的方法,期待的行为是以实现IGameplayCameraSystemHost的PlayerCameraManager作为主体Host, 若没有,则通过ViewTarget来挑选。
- 允许有多个实现接口的Host的实例,但运行的时候只有主体Host处于激活状态。
Interface Implementation Without Override (Shared Module)
引擎中 UGameplayCameraSystemComponent、UGameplayCameraComponentBase 以及 AGameplayCamerasPlayerCameraManager 都实现了 IGameplayCameraSystemHost 接口,但接口的核心方法均未被重写,而是使用了接口提供的统一通用实现。
这种设计表现出一种独特的架构思想:
- 接口本身构建了一个分布式的系统框架,不同的宿主(“工作站”)持有独立的摄像机系统工作环境,运行时根据状态动态选取一个宿主进行摄像机系统的控制和管理。
- 这种机制类似于一个分布式工作站集群,只有一台工作站被激活处理实际的请求。
- 各实现类根据自身职责定位,扩展特定功能和状态管理,但不会改变接口的核心行为逻辑,从而保证了系统的一致性和易维护性。
IGameplayCameraSystemHost 的初始化
初始化 以实现了接口 IGameplayCameraSystemHost 的宿主对象为入口和载体,即只有当关卡中至少有一个此接口的实现宿主存在,初始化才会真正发生。
- 如果关卡中不存在任何实现了该接口的宿主,则不会初始化。
- 宿主的接口初始化只能执行一次,IGameplayCameraSystemHost提供了对应的实现。
编辑器预览模式下的初始化示例
在空关卡中添加了一个带有 GameplayCameraComponent 的角色时,初始化流程的触发过程:
// 具体流程包括:
UGameplayCameraComponentBase::TickComponent
UGameplayCameraComponentBase::AutoManageEditorPreviewEvaluator //WITH_EDITOR
UGameplayCameraComponentBase::EnsureCameraSystemHost // 确保初始化只完成一次
IGameplayCameraSystemHost::InitializeCameraSystem
// 由于编辑器预览模式无 PlayerController 存在,组件本身成为当前激活的宿主。
ActivateCameraEvaluationContext(nullptr, this, EGameplayCameraComponentActivationMode::Push);
运行时模式(编辑器运行或真实游戏)下的初始化示例
关卡默认创建挂载 GameplayCameraComponent 的角色, 运行此关卡。
AActor::Beginplay
UGameplayCameraComponentBase::ActivateCameraForPlayerController
UGameplayCameraComponentBase::EnsureCameraSystemHost // 确保初始化只完成一次
IGameplayCameraSystemHost::InitializeCameraSystem
// PlayerController 存在, 但其 PlayerCameraManager 未被替换为支持 GCS 的实现,组件本身成为当前激活的宿主。
ActivateCameraEvaluationContext(PlayerController, this, EGameplayCameraComponentActivationMode::Push);
UGameplayCameraComponentBase
当游戏中 PlayerController 的 PlayerCameraManager 被替换为支持 GCS 的 AGameplayCamerasPlayerCameraManager 类型后,所有挂载了 UGameplayCameraComponentBase 的组件将放弃自身独立持有的摄像机系统评估器,转由 PlayerCameraManager 统一管理。
具体过程(代码片段核心):
IGameplayCameraSystemHost* PlayerControllerHost = nullptr;
if (bSetAsViewTarget && PlayerController)
{
PlayerControllerHost = Cast<IGameplayCameraSystemHost>(PlayerController->PlayerCameraManager);
}
if (PlayerControllerHost)
{
if (HasCameraSystem())
{
DestroyCameraSystem(); // 销毁自身的 CameraSystemEvaluator
}
ActivateCameraEvaluationContext(PlayerController, PlayerControllerHost, ActivationMode);
}
else
{
// 旧版 PlayerCameraManager 或无 PlayerController,组件独立运作
}
void UGameplayCameraComponentBase::ActivateCameraEvaluationContext(APlayerController* PlayerController, IGameplayCameraSystemHost* Host, EGameplayCameraComponentActivationMode ActivationMode)
{
TSharedPtr<FCameraSystemEvaluator> HostEvaluator = Host->GetCameraSystemEvaluator();
FCameraEvaluationContextStack& ContextStack = HostEvaluator->GetEvaluationContextStack();
switch (ActivationMode)
{
case EGameplayCameraComponentActivationMode::Push:
ContextStack.PushContext(EvaluationContext.ToSharedRef());
break;
default:
ensure(false);
break;
}
}
- 组件主动销毁自己的 CameraSystemEvaluator,并将摄像机评估上下文激活权移交给 PlayerCameraManager 的评估器上下文堆栈(ContextStack)。
- AGameplayCamerasPlayerCameraManager 作为全局管理者,通过 ContextStack 统一调度管理若干摄像机评估上下文,最终决定玩家视角。
- 如果不存在支持的 PlayerCameraManager,组件自行作为独立宿主运行,管理自身摄像机轨迹。
UGameplayCameraSystemComponent
在虚幻 5.6的GCS实现中,UGameplayCameraSystemComponent 是独立运行的Host, 并不受这里的PlayerCameraManager管理,只和当前ViewTarget有关。
- 只有挂载这个组件的AGameplayCameraSystemActor可以创建蓝图子类。
- UGameplayCameraSystemComponent 需要配置关联的PlayerController, 才能正常完成接口初始化。
- UGameplayCameraSystemComponent 是通过GCS来进行计算的摄像机视角的,没有配置的话会是一个固定视角。
- 可以激活Global和Visual层级的Game Rig, 但是实测下来摄像机视角没有变化,待后续继续确认原因。
- 最大的问题有两个(等待后续解决):
- 重启编辑器关卡后,CameraSystemEvaluator的Owner 弱指针检查报错。 导致 DebugDraw 无法正常运行。
- 这个组件可以在Global和Visual层添加CameraRig, 但是测试下来未生效。原因未知。
Warning
在5.6版本中不建议使用 UGameplayCameraSystemComponent相关的内容,测试下来感觉还在开发中,依然存在不少问题。