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生命周期

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM