為了便於理解,首先將類之間的繼承關系列出來。
繼承關系
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生命周期