Chapter 4 Actors and Components
Actor and Components
在本章節中,我們將介紹如下內容:
(1)使用C++創建一個自定義Actor
(2)使用SpawnActor實例化一個Actor
(3)使用Destory和Timer銷毀Actor
(4)通過組合實現Actor功能
(5)使用FObjectFinder給組件載入資源
(6)通過集成實現Actor功能
(7)附加Component創建層次樹
(8)創建自定義Actor Component
(9)創建自定義Scene Component
(10)創建自定義Primitive Component
Introduction
Actor是在遊戲世界裡面存在的類。Actor通過集成Component實現特定的功能。這章節主要介紹創建自定義Actor和Component,以及它們的作用和如何在一起工作的。
Creating a custom Actor in C++
雖然Unreal裡面已經有大量的不同類型的Actor,但是在我們項目開發過程仍然需要創建一些自定義的Actor。我們有可能需要為一個存在的類添加某些功能,或者在預設的子類中加入新的組件。
How to do it
(1)在Unreal Editor打開我們的工程,在Content Browser中點擊Add New按鈕;
(2)選擇New C++ Class;
(3)在打開的對話框中,從列表中選中Actor
(4)給新建的Actor一個名字叫MyFirstActor,然後點擊Ok啟動Visual Studio
(5)打開Visual Studio,你會看到如下類似的代碼:
MyFirstActor.h
#pragma once
#include "GameFramework/Actor.h"
#include "MyFirstActor.generated.h"
UCLASS()
class UE4COOKBOOK_API AMyFirstActor: public AActor
{
GENERATED_BODY()
public:
AMyFirstActor();
}
MyFirstActor.cpp
#include "UE4Cookbook.h"
#include "MyFirstActor.h"
AMyFirstActor::AMyFirstActor()
{
PrimaryActorTick.bCanEventTick = true;
}
How it works
(1)#pragma once: 預定義神明,避免一個include文件被多次包含
(2)#include "GameFramework/Actor.h" :包含Actor基類的頭文件
(3)#include "MyFirstActor.generated.h":所有Actor類都需要包含它們的generated.h文件。Unreal Header Tool(UTH)通過檢測我們文件中的宏來自動創建這樣一個頭文件。
(4)UCLASS(): UCLASS是讓我們可以將這個類暴露給Unreal反射系統的宏。反射讓我們在運行時能夠檢查和遍歷屬性,已經為garbage collection管理reference的。
(5)class UE4COOKBOOK_API AMyFirstActor: public AActor:這是我們類的聲明。UE4COOKBOOK_API是UHT創建的,用於保證我們工程模塊中的class能夠在DLL中正確被導出的。你可能注意到MyFirstActor和Actor前面都有前綴A,這個是Unreal中的命名習慣,方式從Actor繼承下來的類,都是以A為前綴。
(6)GENERATED_BODY(): GENERATED_BODY是另外一個UHT宏,用於包含UE類型系統需要的自動生成的函數。
(7)PrimaryActorTick.bCanEventTick = true: 在構造函數實現中,這句代碼讓Actor的tick生效。
Instantiating an Actor using SpawnActor
How to do it
(1)創建一個繼承自GameMode的C++類UE4GameCookbookGameMode
(2)在新GameMode中重寫一個函數
virtual void BeginPlay() override;
(3)是在Cpp中實現BeginPlay
void AUE4CookbookGameMode::BeginPlay()
{
Super::BeginPlay();
GEngine->AddOnScreenDebugMessage(-1, -1, FColor::Red, TEXT("Actor Spawning"));
FTransform SpawnLocation;
GetWorld()->SpawnActor<AMyFirstActor>(AMyFirstActor::StaticClass(), &SpwanLocation);
}
(4)通過Visual Studio或者Unreal Editor中的Compile按鈕編譯代碼
(5)點擊工具欄上Setting圖表,選中World Setting,為當前Level打開它的World Setting面板。在GameMode Override部分,將我們創建AUE4CookbookGameMode替換默認的GameMode。
(6)運行Level,驗證GameMode是否在場景中spawn一個AMyFirstActor的實例。
Destorying an Actor using Destory and a Timer
How to do it
(1)在我們的GameMode做如下修改:
UPROPERTY()
AMyFirstActor* SpawnedActor;
UFUNCTION()
void DestoryActorFunction();
(2)在我們GameMode的cpp中添加#include "MyFirstActor.h"
(3)使用SpawnedActor變數保存SpawnActor函數返回的結果
SpawnedActor = GetWorld()->SpawnActor<AMyFirstActor>(AMyFirstActor::StaticClass(), SpawnLocation);
(4)在BeginPlay函數最後添加如下代碼
FTimerHandle Timer;
GetWorldTimerManager().SetTimer(Timer, this, &AUE4CookbookGameMode::DestoryActorFunction, 10);
(5)最後,實現DestoryActorFunction函數
void AUE4CookbookGameMode::DestoryActorFunction()
{
if (SpawnedActor != nullptr)
{
SpawnedActor->Destroy();
}
}
(6)運行level,通過Outlier來verify SpawnedActor是否在10秒後被刪除
Implementing the Actor functionality by composition
沒有components的自定義Actor沒有位置功能,也不能附加到其它的Actor上面去。沒有root component,一個Acotr沒有基礎的transform,所以它就沒有位置信息。所以,大多數類至少包含一個component。
我們可以通過組合來創建一個自定義Actor:往我們Actor中添加一系列的Component,每個Component提供不同的功能。
How to do it
(1)添加新的成員變數
UPROPERTY()
UStaticMeshComponent* Mesh;
(2)在Cpp中構造函數,添加
Mesh = CreateDefaultSubobject<UStaticMeshComponent>("BaseMeshComponent");
How it works
(1)我們在類中添加的UPROPERTY宏聲明是一個指針,,它用來存取一個我們Actor的一個component
UPROPERTY()
UStaticMeshComponent* Mesh;
(2)使用UPROPERTY保證我們的Mesh指針是referenced,不會被垃圾回收掉,從而避免出現野指針的問題。
(3)我們使用Static Mesh component,但是其它任何的component subclass都能被使用的。另外,根據Epic編碼規範,我們把*放在了類型的後面
(4)在構造函數中,我們使用一個模版函數,初始化了指針。模版函數如下:
template<class TReturnType>
TReturnType* CreateDefaultSubObject(FName SubobjectName, bool bTransient=false);
(5)這個函數調用engine代碼來初始化component,並為新創建的object返回pointer,從而給我們的component指針一個預設值。
(6)這個函數是一個模版函數,接受兩個參數:(1)subobject的名稱;(2)object是否是暫時的(不跟父對象一起存儲)
Loading assets into components using FObjectFinder
How to do it
(1)在構造函數中添加:
auto MeshAsset =
ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT("Static Mesh /Engine/BasicShapes/Cube.Cube"))
if (MeshAsset.Object != nullptr)
{
Mesh->SetStaticMesh(MeshAsset.Object);
}
How it works
(1)我們通過給FObjectFinder傳遞一個模版資源的路徑,創建一個資源實例。
(2)模版資源路徑格式如下:"{ObjectType} /Path/To/Asset.Asset"
(3)有種簡單的方法獲取模版資源路徑,在editor中的content browser選擇模版資源,右鍵「Copy Reference」,就能得到對應的資源路徑。
(4)我們使用C++11中的auto關鍵詞,避免書寫詳細的對象類型,編譯器會自動為我們推斷對象的類型。如果不使用auto,則我們需要使用如下的代碼來代替:
ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset = ConstructorHelpers::FObjectFind<UStatic>(TEXT("Static Mesh /Engine/BasicShapes/Cube.Cube"));
(5)FObjectFinder類有個Object屬性,它既可以指向需要資源的指針,也可以是NULL,如果資源沒有找到的話。
(6)這也就是意味著,我們可以通過判斷它是否是nullptr來檢測是否載入成功。
Implementing the Actor functionality by inheritance
How to do it
(1)在Unreal Editor中,在Content Browser中點擊Add New,然後在New C++ Class中選中GameState作為父類,接著為這個類賦名。
(2)在新類的頭文件中,添加如下代碼:
AMyGameState()
UFUNCTION()
void SetScore(int32 NewScore);
UFUNCTION()
int32 GetScore();
private:
UPROPERTY()
int32 CurrentScore;
(3)在cpp中添加如下代碼:
AMyGameState::AMyGameState()
{
CurrentScore = 0;
}
int32 AMyGameState::GetScore()
{
return CurrentScore;
}
void AMyGameState::SetScore(int32 NewScore)
{
CurrentScore = NewScore;
}
(4)確認我們代碼跟下面一致
MyGameState.h
#pragma once
#include "GameFramework/GameState.h"
#include "MyGameState.generated.h"
UCLASS()
class UE4COOKBOOK_API AMyGameState: public AGameState
{
GENERATED_BODY()
public:
AMyGameState();
UPROPERTY()
int32 CurrentScore;
UFUNCTION()
int32 GetScore();
UFUNCION()
void SetScore(uint32 NewScore);
};
MyGameState.cpp
#include "UE4Cookbook.h"
#include "MyGameState.h"
AMyGameState::AMyGameState()
{
CurrentScore = 0;
}
int32 AMyGameState::GetScore()
{
return CurrentScore;
}
void AMyGameState::SetScore(uint32 NewScore)
{
CurrentScore = NewScore;
}
How it works
(1)首先,我們添加一個默認的構造函數:
AMyGameState();
(2)在構造函數中,我們初始化我們的成員變數
AMyGameState()::AMyGameState()
{
CurrentScore = 0;
}
(3)我們使用int32來保證Unreal支持的不同編譯器之間的兼容性。當遊戲運行時,該變數用來保存當前的game score。一般來說,我們都加上UPROPERTY,用來確保它被garbage collected合適地處理。這個變數使用private來標識來確保我們只能通過我們的函數來修改它的值。
UPROPERTY()
int32 CurrentScore;
(4)GetScore函數,直接返回current score的值。
(5)我們的score函數使用UFUNCTION聲明。首先,UFUNCTION,在添加一些代碼,就能被Blueprint調用和重寫。第二,UFUNCTION被標識為exec,也就是它們能夠以console commands的方式運行。
Attaching components to create hierarchy
當使用component創建自定義的actor時,考慮attaching概念非常重要。Attaching components是創建一種關係:當父組件的transform變化時,attached到它上面的子組件也會發生相應的變化。
How to do it
(1)在Editor中創建一個Actor類的派生類,叫做HierarchyActor
(2)在新類中添加如下代碼:
UPROPERTY()
USceneComponent* Root;
UPROPERTY()
USceneComponent* ChildSceneComponent;
UPROPERTY()
UStaticMeshComponent* BoxeOne;
UPROPERTY()
UStaticMeshComponent* BoxTwo;
(3)在構造函數中,添加
Root = CreateDefaultSubobject<USceneComponent>("Root");
ChildSceneComponent = CreateDefaultSubobject<USceneComponent>("ChildSceneComponent");
BoxOne = CreateDefaultSubobject<UStaticMeshComponent>("BoxOne");
BoxTwo = CreateDefaultSubobject<UStaticMeshComponent>("BoxTwo");
auto MeshAsset = ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT("Static Mesh /Engine/BasicShapes/Cube.Cube"));
if (MeshAsset.Object != nullptr)
{
BoxOne->SetStaticMesh(MeshAsset.Object);
BoxTwo->SetStaticMesh(MeshAsset.Object);
}
RootComponent = Root;
BoxOne->AttachTo(Root);
BoxTwo->AttachTo(ChildSceneComponent);
ChildSceneComponent->AttachTo(Root);
ChildSceneComponent->SetRelativeTransform(FTransform(FRotator(0, 0, 0), FVector(250, 0, 0), FVector(0.1f)));
How it work
(1)像之前一樣,我們為我們的Actor創建一些UPROPERTY的Component。我們創建兩個SceneComponent和Static Mesh Component。
(2)在構造函數中,我們為每個component創建預設的subobject。
(3)我們載入static mesh,如果載入成功,賦值給兩個static mesh component。
(5)我們將第一個scene component作為Actor的root,這個component會決定在層次樹中所有的其它的component。
Creating a custom Actor Component
Actor components是一種能夠實現Actor共享的通用功能的一種方法。Actor component不被渲染,但是它能夠處理一些操作,例如訂閱時間,與actor的其它component進行交流。
How to do it
(1)創建一個叫做RandomMovementComponent的ActorComponent,在class前面添加UCLASS宏
UCLASS(ClassGroup=(Custom), meta = (BlueprintSpawnableComponent))
(2)在class中添加如下成員變數
UPROPERTY()
float MovementRadius;
(3)在構造函數中,添加
MovementRadius = 5;
(4)在TickComponent()添加:
AActor* Parent = GetOwner();
if (Parent)
{
Parent->SetActorLocation(
Parent->GetActorLocation() + FVector(FMath::FRandRange(-1, 1)* MovementRadius,
FMath::FRandRange(-1, 1)* MovementRadius,
FMath::FRandRange(-1, 1)* MovementRaidus)
);
}
(5)編譯工程,在Editor中,創建一個空的actor,添加RandomMovement組件。另外,再添加一個Cube component用來可視化actor的位置
Creating a custom Scene Component
Scene Component時Actor Component的子類。它有transform,也就是有相對位置、旋轉、大小等信息。跟Actor Component一樣,它是不被渲染的,但是它可以被用來為各種對象提供transform信息,例如在離Actor一定的offset時,spawn其它的對象。
How to do it
(1)創建自定義SceneComponent,叫做ActorSpawnerComponent,在頭文件中,添加
UFUNCTION()
void Spawn();
UPROPERTY()
TSubclassOf<AActor> ActorToSpawn;
(2)在cpp文件中添加如下實現:
void UActorSpawnerComponent::Spawn()
{
UWorld* TheWorld = GetWorld();
if (TheWorld != nullptr)
{
FTransform ComponentTransform(this->GetComponentTransform());
TheWorld->SpawnActor(ActorToSpawn, &ComponentTransform);
}
}
(3)詳細代碼如下:
ActorSpawnerComponent.h
#pragma once
#include "Components/SceneComponent.h"
#include "ActorSpawnerComponent.generated.h"
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class UE4COOKBOOK_API UActorSpawnerComponent: public USceneComponent
{
GENERATED_BODY()
public:
UActorSpawnerComponent();
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
UFUNCTION(BlueprintCallable, Category = Cookbook)
void Spawn();
UPROPERTY(EditAnywhere)
TSubclassOf<AActor> ActorToSpawn;
};
ActorSpawnerComponent.cpp
#include "UE4Cookbook.h"
#incluude "ActorSpawnerComponent.h"
UActorSpawnerComponent::UActorSpawnerComponent()
{
bWantsBeginPlay = true;
PrimaryComponentTick.bCanEverTick = true;
}
void UActorSpawnerComponent::BeginPlay()
{
Super::BeginPlay();
}
void UActorSpawnerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
void UActorSpawnerComponent::Spawn()
{
UWorld* TheWorld = GetWorld();
if (TheWorld != nullptr)
{
FTransform ComponentTransform(this->GetComponentTransform());
TheWorld->SpawnActor(ActorToSpawn, &ComonentTransform);
}
}
(4)編譯和打開工程。拖拽一個空的Actor到場景中,然後添加ActorSpawnerComponent組件。選中該組件,在Detail面板中,將你要spawn的actor賦給ActorToSpawn變數。後面,當無論什麼時候Spawn函數被調用的時候,就會自動實例化一個你配置的Actor。
How it works
(1)我們創建Spawn FUNCTION和一個ActorToSpawn變數。ActorToSpawn UPROPERTY變數是TSubclassOf<>類型,這種模版允許我們選擇這種類型或者該類型的派生類。當然,在Editor編譯器中,可以使用pre-filter來過濾無效的類型,避免選錯。
Creating a custom Primitive Component
Primitive component是Actor component中最複雜的。因為它不但有transform,而且還能渲染到屏幕上。
How to do it
(1)書上的例子比較舊,UE4.19代碼請參見我的blog:
https://blog.csdn.net/luofeixiongsix/article/details/80821026
推薦閱讀: