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:

blog.csdn.net/luofeixio


推薦閱讀:
相关文章