UE4中AActor、Component

为了便于理解,首先将类之间的继承关系列出来。

继承关系

image

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下的继承关系。在这个继承关系里只是列出了最常见的类图。

image

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的组织结构图如下:

image

  • 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 ChildActorClass,提供了Component下再叠加Actor的能力,这样也就实现了Actor中能够继续嵌套Actor的能力。

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对象的层级结构。

image

而ActorLevel对象在世界中则显示为
image

SceneComponents(场景组件)
1.SceneComponents是拥有变换的ActorComponents。变换是场景中的位置,由位置、旋转和缩放定义。SceneComponents能以层级的方式相互附加。

2.Actor的位置、旋转和缩放取自位于层级根部的SceneComponent。只有当SceneComponent位于层级根部时才会负责该Actor的变换

3.SceneComponent记录变换的信息,不会有任何显示的外在表现。

RootComponent
RootComponent是Actor的属性,每一个Actor都会有一个被指定为RootComponent属性的组件。这个组件用来存储物体的基础信息,是AActor组件树中的顶级组件。这个被指定为RootComponent属性的根组件是AActor的一个成员,可以被改换为其他组件。

静态网格体组件
在场景中并没有看到在类中创建的静态网格体组件,却有一个立方体线框。这是因为静态网格体组件就是用来在场景当中显示形状的。平时所使用的cube、sphere本质上都是静态网格体的一种显示,所看到的模型也都是静态网格体的显示。若想使用静态网格体显示形状,则需为静态网格体组件赋值相应的物体。
在这里,添加一个shape_sphere。
image

粒子组件
同样,创建了粒子组件,但是该粒子组件在没被赋值之前也是没有什么内容的。若要使用特定的粒子组件,需要为该组件复制特定的粒子组件效果。
在这里添加P_Fire。
为了方便显示,将粒子组件拖拽到旁边,如下图
image

声音组件也使用同样的方法赋值

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   hxh_space  阅读(2856)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示