这篇博客聊一下UE的Gameplay框架很重要的一部分 Actor 和 Component
Actor
如UE文档所述,所有可以放入关卡的对象都是 Actor,比如摄像机、静态网格体、玩家起始位置。Actor 支持三维变换,例如平移、旋转和缩放。在 C++ 中,AActor是所有Actor的基类。
但这里的变换(位置、旋转和缩放)数据并不是直接作为 Actor 的属性保存起来,而是通过组件(准确的说是场景组件)给Actor提供了三维变换的功能。Actor 本身只提供了一些基础的属性和功能(比如网络同步、Tick、创建销毁、生命周期)的小baby,而组件则是让Actor成为有各种技能的成年人(移动、感知、渲染、物理)。
关于组件的内容放到下一节细说,下面简单介绍下 Actor 本身。
- 创建 Actor:可以使用泛型
SpawnActor()
函数或它的一个特殊模板化版本进行操作,可以看到用模板的方法内部也是调用了上面传入UClass
版本的SpawnActor()
。
/**
* Spawn Actors with given transform and SpawnParameters
*
* @param Class Class to Spawn
* @param Location Location To Spawn
* @param Rotation Rotation To Spawn
* @param SpawnParameters Spawn Parameters
*
* @return Actor that just spawned
*/
AActor* SpawnActor( UClass* InClass, FVector const* Location=NULL, FRotator const* Rotation=NULL, const FActorSpawnParameters& SpawnParameters = FActorSpawnParameters() );
/** Templated version of SpawnActor that allows you to specify a class type via the template type */
template< class T >
T* SpawnActor( const FActorSpawnParameters& SpawnParameters = FActorSpawnParameters() )
{
return CastChecked<T>(SpawnActor(T::StaticClass(), NULL, NULL, SpawnParameters),ECastCheckedType::NullAllowed);
}
-
Ticking:Ticking赋予了Actor生命,以规则间隔(每帧/设定的时间间隔)在一个actor或组件上运行一段代码或蓝图脚本。我们想要对于子类Actor每帧都要执行一定的操作,就可以重写
AActor
的 Tick。
Ticking的高级用法就涉及到Tick组和Tick依赖关系了,这里不展开(博主也没完全掌握),可以看下官方文档 -
网络同步:属性值和函数调用均可被复制,以便对客户端上游戏的状态进行完整控制,这里涉及到网络同步了,可以看下博主之前写过的有关网络同步的内容。
-
销毁Actor:Actor通常不会被垃圾回收,因为场景对象保存一个Actor引用的列表。调用
Destroy()
即可显式销毁 Actor。这会将其从关卡中移除,并将其标记为"代销毁",这说明其在下次垃圾回收中被清理之前都将存在(挖个坑,后续好好研究下UE的内存管理和GC)。
主要就是调用 World 里的DestroyActor
,内部是:- 从World里的Actor列表里移除
- 将Attach该Actor的子Actor取消Attach(也就是不会销毁子Actor)
- 如果该Actor有Attach的父Actor,也将其从父Actor取消Attach
- 如果Actor有Owner,通知Owner其失去了一个孩子
- 通知NetDriver这个Actor要被销毁了
- Unregister该Actor所有的组件,取消他们的物理和渲染
- 标记Actor和其所有组件为PendingKill
- Unregister Actor和其组件的 Tick 函数
bool AActor::Destroy( bool bNetForce, bool bShouldModifyLevel )
{
// It's already pending kill or in DestroyActor(), no need to beat the corpse
if (!IsPendingKillPending())
{
UWorld* World = GetWorld();
if (World)
{
World->DestroyActor( this, bNetForce, bShouldModifyLevel );
}
else
{
UE_LOG(LogSpawn, Warning, TEXT("Destroying %s, which doesn't have a valid world pointer"), *GetPathName());
}
}
return IsPendingKillPending();
}
Component
Component 给Actor提供各种能力,Actor可以看作是Components的容器。按UE官方文档所述,组件可以按照使用场景分为如下几个主要的类(这里也是存在明显的继承关系,比如Primitive组件自然可以有自己的三维变换,那么继承自Scene组件是很直观的):
- Actor组件:(类
UActorComponent
) 最适用于抽象行为,例如移动、物品栏或属性管理,以及其它非物理概念。Actor组件没有变换,即它们在场景中不存在任何物理位置或旋转,除了 Scene 组件继承自它,其实还有好多没有物理概念没有三维变换的组件继承自它,比如UMovementComponent
,AIComponent
。 - 场景组件:(类
USceneComponent
) 支持基于位置的行为,这类行为不需要几何表示。这包括弹簧臂、摄像机、物理力和约束(但不包括物理对象),甚至音频(有位置概念才能根据远近有不同的音量大小)。 - Primitive组件:(类
UPrimitiveComponent
) 是拥有几何表示的场景组件,通常用于渲染视觉元素或与物理对象发生碰撞或重叠。这包括静态或骨架网格体、Sprite或公告板、粒子系统以及盒体、胶囊体和球体碰撞体积。
SceneComponent
除了提供三维变换,SceneComponent还提供了在这一层的嵌套,所以其实对于Actor来说,Actor之间的嵌套也是在SceneComponent这一层实现的,我感觉这里的嵌套更多是为了实现相对于父节点的三维变换。
下面可以看到 AttachToActor 也是转发到 SceneComponent 这一层处理的
void AActor::AttachToActor(AActor* ParentActor, const FAttachmentTransformRules& AttachmentRules, FName SocketName)
{
if (RootComponent && ParentActor)
{
USceneComponent* ParentDefaultAttachComponent = ParentActor->GetDefaultAttachComponent();
if (ParentDefaultAttachComponent)
{
RootComponent->AttachToComponent(ParentDefaultAttachComponent, AttachmentRules, SocketName);
}
}
}
void AActor::AttachToComponent(USceneComponent* Parent, const FAttachmentTransformRules& AttachmentRules, FName SocketName)
{
if (RootComponent && Parent)
{
RootComponent->AttachToComponent(Parent, AttachmentRules, SocketName);
}
}
注册组件
组件还有一个注册的概念,为了让Actor组件能够逐帧更新并影响场景,引擎必须注册这类组件。
如果在Actor产生过程中,作为Actor子对象自动创建了组件,则这类组件会自动注册。游戏期间创建的组件,需要我们手动使用 RegisterComponent
进行注册。
在注册组件的过程中,引擎会将组件与场景关联起来,让其可用于逐帧更新,并会调用如下 UActorComponent
函数:
OnRegister
在注册组件时,可以覆写此函数来添加代码CreateRenderState
初始化组件的渲染状态OnCreatePhysicsState
初始化组件的物理状态
与注册对应,我们想从更新、模拟和渲染过程中移除Actor组件,可以使用 UnregisterComponent
函数将其取消注册。
在组件取消注册时,将调用下面的 UActorComponent
函数:
OnUnregister
在取消注册组件时,可以覆写此函数来添加代码DestroyRenderState
取消初始化组件的渲染状态OnDestroyPhysicsState
取消初始化组件的物理状态
Actor生命周期
不管是LoadMap
,AddToWorld
这种从磁盘里加载Actors的方式,还是运行时 spawn 生成Actors,都会经历 PreInitialize Components
-> InitializeComponents
-> PostInitializeComponents
-> BeginPlay
之后 Actors 就会开始 Ticking 了,如果游戏结束了或者Actor设定的生命周期时长到了或者被Destroy了或者发生了关卡迁移等,会触发 EndPlay
也代表Actor的生命周期要结束了,后续也会被标记为 PendingKill
然后从ULevel中的Actors array移除,等待被垃圾回收。