UE的Gameplay框架(二) —— Actor和Component

这篇博客聊一下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,内部是:

    1. 从World里的Actor列表里移除
    2. 将Attach该Actor的子Actor取消Attach(也就是不会销毁子Actor)
    3. 如果该Actor有Attach的父Actor,也将其从父Actor取消Attach
    4. 如果Actor有Owner,通知Owner其失去了一个孩子
    5. 通知NetDriver这个Actor要被销毁了
    6. Unregister该Actor所有的组件,取消他们的物理和渲染
    7. 标记Actor和其所有组件为PendingKill
    8. 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 组件继承自它,其实还有好多没有物理概念没有三维变换的组件继承自它,比如UMovementComponentAIComponent
  • 场景组件:(类 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生命周期


不管是LoadMapAddToWorld 这种从磁盘里加载Actors的方式,还是运行时 spawn 生成Actors,都会经历 PreInitialize Components -> InitializeComponents -> PostInitializeComponents -> BeginPlay 之后 Actors 就会开始 Ticking 了,如果游戏结束了或者Actor设定的生命周期时长到了或者被Destroy了或者发生了关卡迁移等,会触发 EndPlay 也代表Actor的生命周期要结束了,后续也会被标记为 PendingKill 然后从ULevel中的Actors array移除,等待被垃圾回收。


参考资料

《InsideUE4》GamePlay架构(一)Actor和Component
组件
Actors

猜你喜欢

转载自blog.csdn.net/weixin_44491423/article/details/141739281