UE4中AActor、Component
为了便于理解,首先将类之间的继承关系列出来。
继承关系
AActor
首先看官方解释:
Actor
所有可以放入关卡的对象都是Actor,比如摄像机、静态网格体,玩家起始位置。Actor支持三维变换,例如平移、旋转和缩放。
在C++中,AActor是所有Actor的基类。
注意:Actor不直接保存变换(位置、旋转和缩放)数据;如果Actor的根组件存在,则使用它的变换数据。
组件
在某种意义上,Actor可被视为是包含特殊类型对象(称作组件)的容器。不同类型的组件可用于控制Actor移动的方式及其被渲染的方式,等等。Actor的其他主要功能是在游戏进程中在网络上进行属性复制和函数调用。
组件被创建时与包含该组件的Actor相关联
组件的主要类型有:
UActorComponent:这是基础组件。其可作为Actor的一部分被包含。如果需要,其可进行Tick。ActorComponents与特定的Actor相关联,但不存在于场景中的任意特定位置。它们通常用于概念上的功能,如AI或解译玩家输入。
USceneComponent:SceneComponents是拥有变换的ActorComponents。变换是场景中的位置,由位置、旋转和缩放定义。SceneComponents能以层级的方式相互附加。Actor的位置、旋转和缩放取自位于层级根部的SceneComponent。
UPrimitiveComponent:PrimitiveComponent是拥有一类图像表达(如网格体或粒子系统)的SceneComponent。诸多有趣的物理和碰撞设置均在此处。
理解:
1.在虚幻引擎中,Actor可以简单理解为能够拖放到场景中并且显现出来的对象。以A开头的都表明其是Actor对象,都是可以放置到场景中的。在继承Actor父类时,类的继承关系中会显示会自动显示为AActor。
2.在UE4中,一些不在世界中展示得“不可见对象”也可以是Actor,如AInfo(派生类AWorldSetting,AGameMode,AGameSession,APlayerState,AGameState等),AHUD,APlayerCameraManager等,代表了这个世界的某种信息、状态、规则。你可以把这些看作都是一个个默默工作的灵体Actor。所以,Actor的概念在UE里其实不是某种具象化的3D世界里的对象,而是世界里的种种元素,用更泛化抽象的概念来看,小到一个个地上的石头,大到整个世界的运行规则,都是Actor.
2.Component则是组件,不能够单独出现到场景中,必须依附于某个Actor。以U开头的都是组件,都只能依附于其他组件,不能单独放置到场景中去。
3.Actor可以被视为是组件的容器,一个Actor的最基本的能力是可以挂载多个组件。同时,Actor之间也可以互相“嵌套”,拥有相对的“父子”关系。
4.Actor就好比是人,组件好比是衣服,人可以出去逛街,衣服不行。
总结:组件不能单独出现在场景中,必须依附于Actor才能出现在场景中;Actor可以单独出现在场景中,但是功能很单一,需要不同的组件来丰富Actor的功能。
再看下面官方的一段话
Actor支持拥有一个SceneComponent的层级。每个Actor也拥有一个 RootComponent属性,将指定作为Actor根的组件。Actor自身不含变换,因此不带位置、旋转,或缩放。 它们依赖于其组件的变换,具体来说是其根组件的变换。如果此组件是一个 SceneComponent,其将提供Actor的变换信息。 否则Actor将不带变换。其他附加的组件拥有相对于其附加到的组件的变换。
理解:
1.SceneComponents是拥有变换数据的ActorComponents,就是说SceneComponents是个组件,它封装了变换数据;Actor支持拥有一个SceneComponent的层级,就是说每个Actor支持拥有一个层级结构,这个层级放置一个SceneComponent组件,这个放置的SceneComponent组件包含了该Actor的变换数据,这套变换数据是针对这这个Actor总体而言的,将这个Actor视为一个整体结构进行变换。同时,官方使用的词汇是支持拥有,Actor可以添加上这个功能,但是也可以没有,就好比以前的手机,支持内存卡扩展内存,但是没有内存卡也可使用,只不过内存小而已。
2.每个Actor也拥有一个 RootComponent属性,将指定作为Actor根的组件。这句话是说,每个Actor都有一个 RootComponent属性,这个属性可以赋给这个Actor中的某个组件,从而指定该组件为这个Actor的根组件。
3.Actor自身不含变换,因此不带位置、旋转,或缩放。 它们依赖于其组件的变换,具体来说是其根组件的变换。这句话是说,一个Actor自身是不包含它的变换数据的,它的变换是依赖于该Actor中根组件的变换,当然,这个根组件的变化是针对这整个Actor而言的。
4.如果此根组件是一个 SceneComponent,其将提供Actor的变换信息。 否则Actor将不带变换。在1中知道,SceneComponent组件中封装变换数据,在3中知道,Actor的变换依赖于根组件的变换。那么只有根组件是SceneComponent时,才包含Actor的变换数据,可以进行变换;若根组件不是SceneComponent时,根组件就不包含变换数据,Actor就不进行变换。
5.在权衡到使用的便利性时,大部分Actor都是有变换数据的,我们会经常获取设置它的坐标,如果总是得先获取作为根组件的SceneComponent组件,然后再调用相应接口获取数据则过于繁琐。所以UE也为了我们直接提供了一些便利性的Actor方法,如(Get/Set)ActorLocation等,其实内部都是转发到RootComponent。如下代码:
/*~
* Returns location of the RootComponent
* this is a template for no other reason than to delay compilation until USceneComponent is defined
*/
template<class T>
static FORCEINLINE FVector GetActorLocation(const T* RootComponent)
{
return (RootComponent != nullptr) ? RootComponent->GetComponentLocation() : FVector(0.f,0.f,0.f);
}
bool AActor::SetActorLocation(const FVector& NewLocation, bool bSweep, FHitResult* OutSweepHitResult, ETeleportType Teleport)
{
if (RootComponent)
{
const FVector Delta = NewLocation - GetActorLocation();
return RootComponent->MoveComponent(Delta, GetActorQuat(), bSweep, OutSweepHitResult, MOVECOMP_NoFlags, Teleport);
}
else if (OutSweepHitResult)
{
*OutSweepHitResult = FHitResult();
}
return false;
}
上诉代码也从侧面说明了,Actor的变换数据都是在根组件中的,只有当SceneComponent是根组件时Actor才能进行变换。
6.其他附加的组件拥有相对于其附加到的组件的变换。前面说到,将一个Actor视为一个部件总成,这个Actor的变换数据是针对这整个Actor而言的。但是一个Actor可以有多个层级结构,一个Actor部件总成下面可能还会挂载其他Actor部件总成,这个被挂载的Actor部件总成中也有某个部件负责该Actor部件总成的变换数据,当然,这个Actor的变换数据是针对这整个被挂载的Actor而言的。
ActorComponent
首先看Actor下的继承关系。在这个继承关系里只是列出了最常见的类图。
ActorComponent下面除了SceneComponent之外,还有其他组件,如UMovementComponent、AIComponent等,或者是我们自己写的Component,都是会直接继承ActorComponent的。
但是ActorComponent下面最重要的一个Component就非SceneComponent莫属。在SceneComponent类图中成员变量:
- USceneComponent* AttachParent;表示该SceneComponent组件的父级组件
- TArray<USceneComponent * > AttachChiledren;表示依附于该SceneComponent组件的子级组件。首先,是TArray类型的,表明可以有多个子级组件;Tarray中存储的数据类型为USceneComponent*,表明子级组件都是USceneComponent类型及其派生类类型的;
- FTransform ComponentToWorld; 数据类型为FTransform,保存变换数据。
从SceneComponent的成员变量可知,SceneComponent提供了两大能力:一是Transform,二是SceneComponent的互相嵌套。
- Transform就是本文前面所述SceneComponent中封装的变换数据,当这个SceneComponent作为根组件时提供Actor的变换。
- SceneComponent的互相嵌套则是通过AttachParent和AttachChildren来实现。
一个Actor的组织结构图如下:
-
ActorComponent是不能嵌套的。在UE的观念里,好像只有带Transform的SceneComponent才有资格被嵌套,好像Component的互相嵌套必须和3D里的transform父子对应起来。
-
在上图中,显示了一个Actor有多个ActorComponent,却只有一个SceneComponent,这可能会产生误导,实际上一个Actor可以有多个ActorComponent,也可以带有多个SceneComponent;一个SceneComponent也可以携带多个SceneComponent。
在上述类图中,SceneComponent的派生类ChildActorComponent值得注意。取其中部分成员变量
private:
/** The class of Actor to spawn */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=ChildActorComponent, meta=(OnlyPlaceable, AllowPrivateAccess="true", ForceRebuildProperty="ChildActorTemplate"))
TSubclassOf<AActor> ChildActorClass;
/** The actor that we spawned and own */
UPROPERTY(Replicated, BlueprintReadOnly, Category=ChildActorComponent, TextExportTransient, NonPIEDuplicateTransient, meta=(AllowPrivateAccess="true"))
AActor* ChildActor;
其成员变量为TSubClassOf
Actor和Component的父子级关系确定
从常规逻辑出发,确定父子级关系的方式有两种:
- 父亲找儿子。就是父对象主动添加子对象为自己的子级结构,比如AddChild()方法。
- 儿子找父亲。就是子对象主动认父对象作为自己的父级结构,比如AttachToComponent()方法。
在Actor中部分成员变量如代码所示:
public:
/** Called on clients when Instigator is replicated. */
UFUNCTION()
virtual void OnRep_Instigator();
/** Array of all Actors whose Owner is this actor, these are not necessarily spawned by UChildActorComponent */
UPROPERTY(Transient)
TArray<AActor*> Children;
protected:
/** The component that defines the transform (location, rotation, scale) of this Actor in the world, all other components must be attached to this one somehow */
UPROPERTY(BlueprintGetter=K2_GetRootComponent, Category="Utilities|Transformation")
USceneComponent* RootComponent;
其中,TArray<AActor*> Children则是包含了这个Actor的子Actor的数组。
在UE4中,Actor和组件的父子级关系是通过儿子找父亲的方法来实现的,看如下代码:
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其实更像是一个容器,只提供了基本的创建销毁,网络复制,事件触发等一些逻辑性的功能,而把父子的关系维护都交给了具体的Component,所以更准确的说,其实是不同Actor的SceneComponent之间有父子关系,而Actor本身其实并不太关心。
看官方的下面一个示例:
ActorLevel.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ActorLevel.generated.h"
//声明类类型
class USceneComponent;
class UStaticMeshComponent;
class UParticleSystemComponent;
class UAudioComponent;
class UBoxComponent;
UCLASS()
class MYPROJECT1_API AActorLevel : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AActorLevel();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
//场景组件,用于存储基本信息,比如旋转、位移信息等
USceneComponent* MyScene;
UPROPERTY(EditAnywhere, Category = "MyMesh")
UStaticMeshComponent* MyMesh;
UPROPERTY(EditAnywhere, Category = "MyMesh")
UParticleSystemComponent* MyParticle;
UPROPERTY(EditAnywhere, Category = "MyMesh")
UAudioComponent* MyAudio;
UPROPERTY(EditAnywhere, Category = "MyMesh")
UBoxComponent* MyBoxComponent;
};
ActorLevel.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ActorLevel.h"
#include "Components/StaticMeshComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/AudioComponent.h"
#include "Components/BoxComponent.h"
// Sets default values
AActorLevel::AActorLevel()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
//把场景组件创建出来,并将其定义为根组件
MyScene = CreateDefaultSubobject<USceneComponent>(TEXT("MyScene"));
RootComponent = MyScene;
//添加静态网格体组件,用来显示物体
MyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyMesh"));
//附加到场景组件中
MyMesh->SetupAttachment(MyScene);
//添加粒子组件
MyParticle = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("MyParticle"));
MyParticle->SetupAttachment(MyMesh);
//添加声音组件
MyAudio = CreateDefaultSubobject<UAudioComponent>(TEXT("MyAudio"));
MyAudio->SetupAttachment(MyMesh);
//添加盒体组件
MyBoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("MyBoxComponent"));
MyBoxComponent->SetupAttachment(MyMesh);
}
// Called when the game starts or when spawned
void AActorLevel::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AActorLevel::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
对上述代码的描述:
1.ActorLevel继承自AActor,是一个Actor;
2.ActorLevel可以被视为是组件的容器,内部放置了许多组件,这些组件用于丰富ActorLevel的功能;可以将这个Actor视为是一个部件总成,部件总成下包含了许多其他组件。
3.ActorLevel并不直接保存变换数据,而是将SceneComponents组件设置为根组件,并保存变换数据;
4.静态网格体组件MyMesh衣依附于根组件,其他组件依附于MyMesh组件,这都是SceneComponent类的派生类。也从这里可以确定,SceneComponent是可以多支嵌套的。
在上述代码中创建了组件,将代码编译后,在UE4中将该ActorLevel类拖放到场景中,形成一个ActorLevel对象,可看到ActorLevel对象的层级结构。
而ActorLevel对象在世界中则显示为
SceneComponents(场景组件)
1.SceneComponents是拥有变换的ActorComponents。变换是场景中的位置,由位置、旋转和缩放定义。SceneComponents能以层级的方式相互附加。
2.Actor的位置、旋转和缩放取自位于层级根部的SceneComponent。只有当SceneComponent位于层级根部时才会负责该Actor的变换
3.SceneComponent记录变换的信息,不会有任何显示的外在表现。
RootComponent
RootComponent是Actor的属性,每一个Actor都会有一个被指定为RootComponent属性的组件。这个组件用来存储物体的基础信息,是AActor组件树中的顶级组件。这个被指定为RootComponent属性的根组件是AActor的一个成员,可以被改换为其他组件。
静态网格体组件
在场景中并没有看到在类中创建的静态网格体组件,却有一个立方体线框。这是因为静态网格体组件就是用来在场景当中显示形状的。平时所使用的cube、sphere本质上都是静态网格体的一种显示,所看到的模型也都是静态网格体的显示。若想使用静态网格体显示形状,则需为静态网格体组件赋值相应的物体。
在这里,添加一个shape_sphere。
粒子组件
同样,创建了粒子组件,但是该粒子组件在没被赋值之前也是没有什么内容的。若要使用特定的粒子组件,需要为该组件复制特定的粒子组件效果。
在这里添加P_Fire。
为了方便显示,将粒子组件拖拽到旁边,如下图
声音组件也使用同样的方法赋值
BoxComponent组件,是碰撞盒体组件,用作拾取事件的发生器,用于检测碰撞。也就是当其它对象触碰到该组件时,触发事件发生。
使用组件的步骤总结:
首先,创建组件。组件也是一种变量,在创建后没被赋值时保持为默认值,在世界窗口中什么都不显示。
//只是创建了变量MyMesh,并指定其依附于MyScene,但是并没为其赋值。
//所以该变量保持为默认值
MyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyMesh"));
MyMesh->SetupAttachment(MyScene);
其次,为创建的组件赋值。组件就是一种变量,创建后要为其赋值,然后才能使用。这个赋值过程在c++和蓝图中均可完成。
UObject
四个基本功能
1.反射
在UE中,反射就是指将在c++底层定义的变量和方法能够出现在蓝图中。
在其他方面,反射还有其他含义。
2.垃圾回收(GC)
不需要去管理变量的生命周期,只需要创建变量即可。
3.序列化和反序列化。 序列化:把内存中的东西存储到磁盘的过程; 反序列化:把磁盘的东西读取到内存中
4.类默认对象(CDO)
注意:c++本身是没有反射和垃圾回收的,UE底层写了框架,实现了反射和垃圾回收。
AActor生命周期
posted on 2022-03-19 17:17 hxh_space 阅读(2856) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理