Introduction to C++ Programming in UE4



所在原文目錄結構位置:

C++ Programming Guide

 |_____Programming Quick Start Guide

 |_____Introduction to C++ Programming in UE4

 |_____C++ Programming Tutorials

 |_____Managing Game Code

 |_____Development Setup

 |_____Gameplay Programming

 |_____Engine Architecture

 |_____Console Manager: Console Variables in C++

 |_____Command-Line Arguments

 |_____Assertions

 |_____Blueprint Function Libraries

 |_____Unreal Build System

 |_____Plugins

 |_____Coding Standard

 |_____Symbol Debugger

原文地址:Introduction to C++ Programming in UE4

轉載地址:GhostDC的博客


我怕原文鏈接失效,我要複製,粘帖,修改了,原諒我吧....

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Introduction to C++ Programming in UE4

在UE4中介紹C++編程

Unreal C++ is Awesome! 虛幻C++很酷

This guide is about learning how to write C++ code in Unreal Engine. Don't worry, C++ programming in Unreal Engine is fun, and actually not hard to get started with! We like to think of Unreal C++ as "assisted C++", because we have so many features to help make C++ easier for everyone.

這個指南是關於學習如何在虛幻引擎中編寫代碼的。別擔心,C++編程在虛幻引擎中是有趣的,而且並不難上手!我們習慣把虛幻C++認爲是"輔助"C++,因爲我們有很多特性能幫助C++對於所有人來說更容易。

Before we go on, it's really important that you're already familiar with C++ or another programming language. This page is written with the assumption that you have some C++ experience, but if you know C#, Java, or JavaScript, you should find many aspects familiar.

在我們開始之前,你已經熟悉C++或者其他編程語言是很重要的,此頁面已經假設你有一定的C++編程經驗,但是如果你瞭解C#,Java,或者JavaScript,你會發現很多已經熟知的地方。

If you're coming in with no programming experience at all, we've got you covered also! Check out our Blueprint Visual Scripting guide and you'll be on your way. You can create entire games using Blueprint scripting!

如果你從未有過編程經驗,我們也涉及到了,查看藍圖可視化腳本指南來找到自己的途徑。你可以用藍圖腳本來創建一個完整的遊戲。

It is possible to write "plain old C++ code" in Unreal Engine, but you'll be most successful after reading through this guide and learning the basics about the Unreal programming model. We’ll talk more about that we go along.

它是可以寫在虛幻引擎“普通的老式的C ++代碼”,但通過閱讀本指南和學習虛幻編程模型的基礎知識後,你會成功的。我們在以後將討論更多。

C++ and Blueprints C++和藍圖

Unreal Engine provides two methods, C++ and Blueprints Visual Scripting, to create new gameplay elements. Using C++, programmers add the base gameplay systems that designers can then build upon or with to create the custom gameplay for a level or the game. In these cases, the C++ programmer works in their favorite IDE (usually Microsoft Visual Studio, or Apple’s Xcode) and the designer works in the Unreal Editor’s Blueprint Editor.

虛幻引擎提供兩種方式,C++和藍圖可視化腳本,來創建一個新的遊戲元素。用C++,程序員添加能爲設計者創建自定義遊戲關卡或遊戲的基礎遊戲系統。在這種情況下C++程序員工作在他們喜歡的IDE環境下(通常使用微軟的Visual Studio或是蘋果的XCode),設計人員工作在虛幻編輯器中的藍圖編輯器。

The gameplay API and framework classes are available to both of these systems, which can be used separately, but show their true power when used in conjunction to compliment each other. What does that really mean, though? It means that the engine works best when programmers are creating gameplay building blocks in C++ and designers take those blocks and make interesting gameplay.

遊戲API和架構類是提供給這兩個系統的,可以單獨使用,但是配合使用時才能展現它們真正的能力。這就究竟意味着什麼呢?它意味着當程序員在C++中創建遊戲構建模塊和設計人員用這些模塊做出有趣的遊戲時引擎的工作才達到最佳狀態。

With that said, let’s take a look at a typical workflow for the C++ programmer that is creating building blocks for the designer. In this case, we’re going to create a class that is later extended via Blueprints by a designer or programmer. In this class, we’re going to create some properties that the designer can set and we’re going to derive new values from those properties. The whole process is very easy to do using the tools and C++ macros we provide for you.

既然這麼說,讓我們來看看C++程序員爲設計人員創建構建模塊的工作流程。在這種情況下,我們需要創建一個有藍圖設計人員或者程序員擴展的一個類。在這堂課,我們要創建一些特性,設計人員可以設置我們從這些設置中衍生出的新值。整個過程通過使用我們爲你提供的工具和C++宏來說應該是很容易的

Class Wizard 類嚮導

First thing we’re going to do is use the class wizard within the Unreal Editor to generate the basic C++ class that will be extended by Blueprints later. The image below shows the wizard’s first step where we are creating a new Actor.

我們要做的第一件事就是用虛幻編輯器中的類嚮導來生成基礎的C++類以便以後在藍圖中擴展。下圖顯示了我們正在創建一個新的Actor的第一步。

The second step in the process tells the wizard the name of the class you want generated. Here’s the second step with the default name used.

在這個過程中的第二步就是告訴嚮導你想生成類的名字。在這裏我們使用默認名稱。

Once you choose to create the class, the wizard will generate the files and open your development environment so that you can start editing it. Here’s the class definition that is generated for you. For more information on the Class Wizard, follow this link.

一旦你選擇創建類,嚮導會生成文件,並打開你的開發環境,這樣你就可以開始編輯。這裏是正在爲您你生成的類定義。有關類嚮導的更多信息,請點擊此鏈接。

#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public: 
    // Sets default values for this actor's properties
    AMyActor();
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

    // Called every frame
    virtual void Tick( float DeltaSeconds ) override;
};
The class wizard generates your class with BeginPlay() and Tick() specified as overloads. BeginPlay() is an event that lets you know the Actor has entered the game in a playable state. This is a good place to initiate gameplay logic for your class. Tick() is called once per frame with the amount of elapsed time since the last call passed in. There you can do any recurring logic. However if you don’t need that functionality, it is best to remove it to save yourself a small amount of performance. If you remove it, make sure to remove the line in the constructor that indicated ticking should occur. The constructor below contains the line in question.

類嚮導爲你生成了定義爲重載類的BeginPlay()和Tick()。BeginPlay()是一個事件,讓你知道Actor已經進入了遊戲可玩狀態。這是一個爲您的類啓動遊戲邏輯的好地方。Tick()從上次調用中傳遞後的每幀都調用。你可以做任何循環邏輯。然而如果你不需要這些功能,最好將其刪除以減少對性能的損失。如果你刪除它,請確保刪除了每幀應該發生的構造行。以下的構造函數中包含有問題的行。

AMyActor::AMyActor()

{

    // 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;

}


Making a property show up in the editor 製作一個顯示在編輯器的屬性

We have our class created, so now let’s create some properties that can be set by designers in the Unreal Editor. Exposing a property to the editor is quite easy using our special macro, UPROPERTY(). All you have to do is use the UPROPERTY(EditAnywhere) macro before your property declaration as seen in the class below.

我們已經創建了我們自己的類,所以現在我們創建一些設計人員能在虛幻編輯器中設置的屬性。在通過使用我們特殊的宏在編輯器中暴露一個屬性是很容易的,UPROPERTY().你需要做的就是在屬性聲明前使用UPROPERTY(EditAnywhere)宏,如下所示。

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere)
    int32 TotalDamage;

    ...
};

That’s all you need to do to be able to edit that value in the editor. There are more ways to control how and where it is edited. This is done by passing more information into the UPROPERTY() macro. For instance, if you want the TotalDamage property to appear in a section with related properties, you can use the categorization feature. The property declaration below shows this.

這就是你想在編輯器中編輯數值所需要做的。有更多的方式來控制如何一家在哪來編輯它。通過更多的信息進入UPROPERTY()宏來完成。舉例來說如果你想TotalDamege屬性出現在相關性能部分,可以使用分類功能。下面的屬性聲明中顯示了這一點。

UPROPERTY(EditAnywhere, Category="Damage")
int32 TotalDamage;

When the user looks to edit this property, it now appears under the Damage heading along with any other properties that you have marked with this category name. This is a great way to place commonly used settings together for editing by designers.

當用戶想編輯此屬性時,它現在出現在標有Damage類別名稱的任何其他屬性標題下。這是一個偉大的方式來共同放置常用的設置爲設計師編輯。

Now let’s expose that same property to Blueprints.

現在我們暴露相同的屬性給藍圖。

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
int32 TotalDamage;
As you can see, there is a Blueprint specific parameter to make a property available for reading and writing. There’s a separate option, BlueprintReadOnly, you can use if you want the property to be treated as const in Blueprints. There are quite a few options available for controlling how a property is exposed to the engine. To see more options, follow this link.

就像你所看到的,這裏有一個具體的藍圖參數使屬性可用於讀取和寫入。如果你想讓這個屬性在藍圖中被視爲常量,你可以使用BluePrintReadOnly。這裏有相當多的選項可用於控制一個屬性如何暴露給引擎。想要查看更多選項,請點擊此鏈接。

Before continuing to the section below, let’s add a couple of properties to this sample class. There’s already a property to control the total amount of damage this actor will deal out, but let’s take that further and make that damage happen over time. The code below adds one designer settable property and one that is visible to the designer but not changeable by them.

在繼續下面的部分之前,讓我們添加一對屬性到這個範例類中。目前已經有一個屬性來控制Actor受到傷害的總量,但是讓我們進一步來控制在一段時間內受到的傷害。下面的代碼添加一個設計人員可以設置的屬性和一個設計人員可見但不能改變的屬性。

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
    int32 TotalDamage;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Damage")
    float DamageTimeInSeconds;

    UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Transient, Category="Damage")
    float DamagePerSecond;

    ...
};

DamageTimeInSeconds is a property the designer can modify. The DamagePerSecond property is a calculated value using the designer’s settings (see the next section). The VisibleAnywhere flag marks that property as viewable, but not editable in the Unreal Editor. The Transient flag means that it won’t be saved or loaded from disk; it is meant to be a derived, non-persistent value. The image below shows the properties as part of the class defaults.

DamageTimeInSeconds 是一個設計人員可以修改的屬性。DamagePerSecond屬性是一個用設計師設置的計算值(請參閱下一節)。VisibleAnywhere標誌,標誌着屬性是可見的,但是無法在虛化編輯器中進行編輯。Transient標誌意味着它作爲一個不保存或讀取於硬盤,非永久性的衍生值。下圖顯示了屬性的一部分默認值。

Setting defaults in my constructor 在構造函數中設置默認值

Setting default values for properties in a constructor works the same as your typical C++ class. Below are two examples of setting default values in a constructor and are equivalent in functionality.

在構造函數中設置默認值的工作就像你在編寫C++類。下面我們有兩個在構造函數中設置默認值的例子,它們在功能上是等同的。

AMyActor::AMyActor()
{
    TotalDamage = 200;
    DamageTimeInSeconds = 1.f;
}

AMyActor::AMyActor() :
    TotalDamage(200),
    DamageTimeInSeconds(1.f)
{
}

Here is the same view of the properties after adding default values in the constructor.

在構造函數中添加默認值後這裏是相同的。

In order to support per instance designer set properties, values are also loaded from the instance data for a given object. This data is applied after the constructor. You can create default values based off of designer set values by hooking into the PostInitProperties() call chain. Here’s an example of that process where TotalDamage and DamageTimeInSeconds are designer specified values. Even though these are designer specified, you can still provide sensible default values for them, as we did in the example above. NOTE: if you don’t provide a default value for a property, the engine will automatically set that property to zero or nullptr in the case of pointer types.

爲了支持每個實例的設計器設置屬性,值也從一個給定的對象實例數據加載。這個數據是在構造函數中的應用。你可以創建基於設計人員設置值的默認值通過鏈接傳輸到PostInitProperties()調用鏈。下面是一個TotalDamage和DamageTimeInSeconds設計人員指定值的例子過程。即使它們被設計人員指定,你仍然可以爲它們提供合理的默認值 ,就像我們在例子中做的那樣。提示:在指針類型下如果你不能給屬性提供一個默認值,引擎將自動將該屬性設置爲0或null。

void AMyActor::PostInitProperties()
{
    Super::PostInitProperties();
    DamagePerSecond = TotalDamage / DamageTimeInSeconds;
}

Here again is the same view of the properties after we’ve added the PostInitProperties() code that you see above.

上面可以看到在添加PostInProperties()代碼後還是相同的屬性視圖。

Hot Reloading 熱重載

Here is a cool feature of Unreal that you might be surprised about if you are used to programming C++ in other projects. You can compile your C++ changes without shutting down the editor! There are two ways to do this:

這是虛幻的一個很酷的功能,如果你用 C++編寫過其他項目你可能會感到驚訝。你可以不用關閉編輯器來編譯你的C++代碼更改!有兩種方法可以做到:With the editor still running, go ahead and Build from Visual Studio or Xcode like you normally would. The editor will detect the newly compiled DLLs and reload your changes instantly!                                                                                                                                           在編輯器還在運行時,就像平常那樣在Visual Studio或Xcode中構建。編輯器將檢測到新編譯的DLL並即時刷新你的更改!                            (Note that if you’re attached with the debugger, you’ll need to detach first so that Visual Studio will allow you to Build.)                                                                                                                                                                                                       (需要注意的是,如果你與調試器鏈接,你需要先分離以便Visual Studio允許你構建。)    Or, simply click the Compile button on the editor’s main toolbar!  ( 或者,只需點擊編輯器的主工具欄上的編譯按鈕!)                              You can use this feature in the sections below as we advance through the tutorial. What a time saver!你可以在下面的高級教程中使用此功能以節省時間!          

Extending a C++ Class via Blueprints 通過藍圖延伸的C++類              

So far, we’ve created a simple gameplay class with the C++ Class Wizard and added some properties for the designer to set. Let’s now take a look at how a designer can start creating unique classes from our humble beginnings here.

到目前爲止,我們已經創建了C++類嚮導一個簡單的遊戲類,並增加了一些爲設計人員設置的屬性。現在讓我們來看看設計人員如何開始從小做起創造出獨特的類。

First thing we’re going to do is create a new Blueprint class from our AMyActor class. Notice in the image below that the name of the base class selected shows up as MyActor instead of AMyActor. This is intentional and hides the naming conventions used by our tools from the designer, making the name friendlier to them.

首先我們將從AMyActor類創建一個新的藍圖類。注意下面的圖片,所選擇的基類的名稱顯示爲MyActor代替了AMyActor。這是有意爲之,隱藏了設計人員通過使用我們的工具的命名約束,使得它們名稱更加友好。                            

                        

Once you choose Select, a new, default named Blueprint class is created for you. In this case, I set the name to CustomActor1 as you can see in the snapshot of the Content Browser below.

一旦你點擊選擇,一個新的,默認名稱的藍圖類將爲你創建。你可以在內容瀏覽器界面看到,我將它的名字設置爲CustomActor1。

      

This is the first class that we’re going to customize with our designer hats on. First thing we’re going to do is change the default values for our damage properties. In this case, the designer changed the TotalDamage to 300 and the time it takes to deliver that damage to 2 seconds. This is how the properties now appear.

第一個類我們將定製我們設計人員的帽子。首先我們將更改默認Damage設置的值。在這裏下設計人員更改TotalDamage的值爲300和每秒傷害爲2次。這就是現在顯示的屬性。

Wait a second...our calculated value doesn’t match what we’d expect. It should be 150 but it is still at the default value of 200. The reason for this is that we are only calculating our damage per second value after the properties have been initialized from the loading process. Runtime changes in the Unreal Editor aren’t accounted for. There’s a simple solution to this problem because the engine notifies the target object when it has been changed in the editor. The code below shows the added hooks needed to calculate the derived value as it changes in the editor.

等一下。。。我們的計算值不符合我們所期望的。它應該是150,但它還是默認值200。這是因爲我們只計算每秒傷害值後的屬性被初始化加載過程。虛幻編輯器運行時不能變更。這個問題有一個簡單的解決方案,因爲引擎通知目標對象時,它已經在編輯器中更改。下面的代碼顯示計算得出的值,因爲它改變了編輯器所需要的附加掛鉤。

void AMyActor::PostInitProperties()
{
    Super::PostInitProperties();

    CalculateValues();
}

void AMyActor::CalculateValues()
{
    DamagePerSecond = TotalDamage / DamageTimeInSeconds;
}

#if WITH_EDITOR
void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
    CalculateValues();

    Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
One thing to note is that the PostEditChangeProperty() method is inside an editor specific #ifdef. This is so that building your game only the code that you need for the game, removing any extra code that might increase the size of your executable unnecessarily. Now that we have that code compiled in, the DamagePerSecond value matches what we’d expect it to be as seen in the image below.

有一點要注意的是,PostEditChangeProperty()方法是在編輯器內特定的#ifdef。這是爲了構建你的遊戲所需要的遊戲代碼,刪除多餘的代碼可以減少可執行文件不必要的大小。現在,我們已經將代碼編譯完成,DamagePerSecond的值像我們希望的那樣如下圖所示。

Calling Functions across the C++ and Blueprint Boundary 整個C ++和藍圖邊界通話功能

So far, we’ve shown how to expose properties to Blueprints, but there’s one last introductory topic that we should cover before you dive deeper into the engine. During the creation of the gameplay systems, designers will need to be able to call functions created by a C++ programmer as well as the gameplay programmer calling functions implemented in Blueprints from C++ code. Let’s start by first making the CalculateValues() function callable from Blueprints. Exposing a function to Blueprints is just as simple as exposing a property. It takes only one macro placed before the function declaration! The code snippet below show what is needed for this.

到目前爲止,我們已經展示瞭如何暴露屬性給藍圖,在你更深入地研究引擎之前,還有最後一個需要介紹的地方。在創建遊戲系統時,設計人員需要能夠調用由C ++程序員創建的函功能,以及遊戲性程序員在藍圖中調用由C++代碼創建的功能。首先讓我們使用CalculateValues()函數調用藍圖。暴露一個功能給藍圖像暴露一個屬性一樣簡單。只需在函數聲明之前放置一個宏!下圖顯示的代碼片段正是這樣。

UFUNCTION(BlueprintCallable, Category="Damage")
void CalculateValues();
The UFUNCTION() macro handles exposing the C++ function to the reflection system. The BlueprintCallable option exposes it to the Blueprints Virtual Machine. Every Blueprint exposed function requires a category associated with it, so that the right click context menu works properly. The image below shows how the category affects the context menu.

UFUNCTION()宏暴露給了C ++函數的反射系統。該BlueprintCallable選項公開它給藍圖虛擬機。每個暴露功能藍圖需要一個與之相關的範疇,以便右鍵上下文菜單中正常工作。下圖顯示的類別是如何影響上下文菜單。

As you can see, the function is selectable from the Damage category. The Blueprint code below shows a change in the TotalDamage value followed by a call to recalculate the dependent data.

正如你所看到的,功能是從Damage的類別中選擇。下面的藍圖代碼顯示在TotalDamage值然後調用重新計算相關數據的變化。

This uses the same function that we added earlier to calculate our dependent property. Much of the engine is exposed to Blueprints via the UFUNCTION() macro, so that people can build games without writing C++ code. However, the best approach is to use C++ for building base gameplay systems and performance critical code with Blueprints used to customize behavior or create composite behaviors from C++ building blocks.

這將使用我們前面添加到我們的計算依賴屬性相同的功能。大部分的引擎通過UFUNCTION()宏暴露給藍圖,使人們能夠建立無需編寫C ++代碼的遊戲。然而,最好的方法是使用C ++構建基礎的遊戲系統和性能的關鍵代碼用於自定義行爲或創建C ++與藍圖相結合的方式。

Now that our designers can call our C++ code, let’s explore one more powerful way to cross the C++/Blueprint boundary. This approach allows C++ code to call functions that are defined in Blueprints. We often use the approach to notify the designer of an event that they can respond to as they see fit. Often that includes the spawning of effects or other visual impact, such as hiding or unhiding an actor. The code snippet below shows a function that is implemented by Blueprints.

現在,我們的設計師可以調用我們的C ++代碼,讓我們找到更多強大的方式來穿越C ++/藍圖的邊界。這種方法允許藍圖來調用在C ++代碼中定義的函數。我們經常使用的方法來通知可以以他們認爲合適應對的設計人員。通常包含生成效果或其他視覺衝擊,例如隱藏或取消隱藏一個Actor的生成。下面的代碼片段的代碼顯示了由藍圖實現的功能。

UFUNCTION(BlueprintImplementableEvent, Category="Damage")
void CalledFromCpp();
This function is called like any other C++ function. Under the covers, the Unreal Engine generates a base C++ function implementation that understands how to call into the Blueprint VM. This is commonly referred to as a Thunk. If the Blueprint in question doesn’t provide a function body for this method, then the function behaves just like a C++ function with no body behaves: it does nothing. What if you want to provide a C++ default implementation while still allowing a Blueprint to override the method? The UFUNCTION() macro has an option for that too. The code snippet below shows the changes needed in the header to achieve this.

此功能像其他函數一樣被調用。在背後,虛幻引擎生成基礎的如何在藍圖狀態機中調用的C++代碼。這通常被稱爲Thunk。如果藍圖不能用這種方法提供功能體,那麼函數的行爲就像C++沒有了肢體行爲,它什麼都不做。如果你想提供一個默認的C ++實現,同時還允許藍圖覆蓋這種方法?UFUNCTION()宏有一個功能相同的選項。下面的代碼顯示實現這一需要的標頭中的變化。

UFUNCTION(BlueprintNativeEvent, Category="Damage")
void CalledFromCpp();

This version still generates the thunking method to call into the Blueprint VM. So how do you provide the default implementation? The tools also generate a new function declaration that looks like _Implementation(). You must provide this version of the function or your project will fail to link. Here’s the implementation code for the declaration above.

這個版本仍然生成Thunk方法使藍圖狀態機調用。那麼,你如何提供默認的實現呢?這個工具同樣生成一個看似像_Implementation()的新函數。你必須提供該版本的功能否則你的項目將無法鏈接。下面是代碼實現的聲明.

void AMyActor::CalledFromCpp_Implementation()
{
    // Do something cool here
}
Now this version of the function is called when the Blueprint in question doesn’t override the method. One thing to note, is that in future versions of the build tools the auto generated _Implementation() declaration will go away and you’ll be expected to explicitly add that to the header. As of version 4.7, the auto generation of that declaration still occurs.

現在這個版本的函數被調用時,當出現藍圖不能覆蓋這個方法的問題時。有一點需要注意,就是在構建工具未來的版本自動生成的_Implementation()聲明會消失,你要將提前確定將它添加到頭文件。

Now that we have walked through the common gameplay programmer workflow and methods to work with designers to build out gameplay features, it is time for you to choose your own adventure. You can either continue with this document to read more about how we use C++ in the engine or you can jump right into one of our samples that we include in the launcher to get a more hands on experience.

現在,我們通過遊戲程序員與設計人員合作的工作流程和方法,打造出來的遊戲功能,現在是時候讓你選擇你自己的冒險了。你可以繼續使用本文件閱讀更多關於我們如何使用C ++引擎,或者你可以直接進入我們啓動器中的樣例在其中獲得更多的幫助與經驗。





Diving Deeper 更加深入
I see you’re still with me on this adventure. Excellent. The next topics of discussion revolve around what our gameplay class hierarchy looks like. In this section, we’ll start with the base building blocks and talk through how they relate to each other. This is where we’ll look at how the Unreal Engine uses both inheritance and composition to build custom gameplay features.

我就知道你還在跟我進行這次冒險。很好。接下來討論的主題是圍繞什麼是我們遊戲類的層級結構。在這一節,我們將建立基礎模塊並討論它們是如何相互關聯的。我們將看看虛幻引擎如何同時使用繼承和組合構建定製的遊戲功能。

Gameplay Classes: Objects, Actors, and Components 遊戲類:Objects,Actors,和Components。
There are 4 main class types that you derive from for the majority of gameplay classes. They are UObject, AActor, UActorComponent, andUStruct. Each of these building blocks are described in the following sections. Of course, you can create types that don’t derive from any of these classes, but they will not participate in the features that are built into the engine. Typical use of classes that are created outside of theUObject hierarchy are: integrating 3rd party libraries; wrapping of OS specific features; etc.

由廣大的遊戲類中派生出四種主要類型。它們是UObject,AActor,UActorComponent,和UStruct。這些構建模塊將在以下各節中描述。當然,你可以創建不從它們繼承的其他類別,但它們不會作爲特性內置到引擎。一些UObject層次之外創建的類的典型用途有:集成第三方庫、包裝特定的OS等等。

Unreal Objects (UObject)

The base building block in the Unreal Engine is called UObject. This class, coupled with UClass, provides a number of the most important base services in the engine:

基礎的構建模塊在虛幻引擎中被稱爲UObject。這個類,再加上UClass,提供了許多對於引擎最重要的基本服務:

  • Reflection of properties and methods

  • Serialization of properties

  • Garbage collection

  • Finding UObjects by name

  • Configurable values for properties

  • Networking support for properties and methods


  • 屬性和方法的體現

  • 屬性序列化

  • 無用單元收集

  • 通過名稱尋找UObject

  • 配置屬性值

  • 對屬性和方法的網絡支持

Each class that derives from UObject has a singleton UClass created for it that contains all of the meta data about the class instance. UObject and UClass together are at the root of everything that a gameplay object does during its lifetime. The best way to think of the difference between a UClass and a UObject is that the UClass describes what an instance of a UObject will look like, what properties are available for serialization, networking, etc. Most gameplay development doesn’t involve directly deriving from UObjects, but instead from AActor and UActorComponent. You don’t need to know the details of how UClass/UObject works in order to write gameplay code, but it is good to know that these systems exist.

每一個由UObject派生的類都有一個單獨的UClass被創建來包含所有關於類實例的元數據。UObject和UClass一起做爲遊戲對象在生存期間的基礎。將UClass與UObject區分的最好方法是UClass描述了UObject的實例中哪些屬性可以用於序列化,網絡等。很多遊戲開發並不直接從UObject衍生,而是從AActor和UActorComponent衍生。你沒必要知道UClass/UObeject如何爲代碼編寫的遊戲細節工作,但是它很容易理解這些系統的存在。

AActor


An AActor is an object that is meant to be part of the gameplay experience. AActors are either placed in a level by a designer or created at runtime via gameplay systems. All objects that can be placed into a level extend from this class. Examples include AStaticMeshActor,ACameraActor, and APointLight actors. AActor derives from UObject, so enjoys all of the standard features listed in the previous section.AActors can be explicitly destroyed via gameplay code (C++ or Blueprints) or via the standard garbage collection mechanism when the owning level is unloaded from memory. AActors are responsible for the high-level behaviors of your game’s objects. AActors are also the base type that can be replicated during networking. During network replication, AActors can also distribute information for any UActorComponents owned by that AActor that require network support.

一個AActor意味着是一部分遊戲體驗的一個對象。AActors是由設計設計人員放置在關卡中或在運行時經由遊戲系統創建。例子中包括AStaticMeshActor,ACameraActor和APointLight actor。 AActor從UObject派生,因此享有所有前一節中列出的標準功能。當關卡已經從內存中卸載時AActors可以通過遊戲代碼(C++或藍圖)或通過標準的垃圾回收機制顯式銷燬。AActors負責遊戲對象的高級行爲。AActors也能夠在聯網期間被複制的基本類型。在網絡複製時,AActors也可以傳播信息以供任何需要網絡支持的AActor所擁有的UActorComponents。

AActors have their own behaviors (specialization through inheritance), but they also act as containers for a hierarchy of UActorComponents(specialization through composition). This is done through the AActor’s RootComponent member, which contains a single UActorComponent that, in turn, can contain many others. Before an AActor can be placed in a level, that AActor must contain at least a USceneComponent which contains the translation, rotation, and scale for that AActor.

AActors有自己的行爲(通過繼承專業化),但是他們也可以作爲容器UActorComponents的層次結構(通過組合物專門化)。這是通過AActor的RootComponent構件,其中包含一個單一UActorComponent,反過來,可以包含許多其他實現。在AActor能被放置在關卡之前,該AActor必須包含至少一個USceneComponent以平移,旋轉和縮放該AActor。

AActors have a series of events that are called during the lifecycle of the AActor. The list below is a simplified set of the events that illustrate the lifecycle.

AActors具有一系列被該AActor的生命週期期間調用的事件。下面是一組簡化的列表,說明了生命週期中的事件。

  • BeginPlay - called when the object first comes into gameplay existence

  • Tick - called once per frame to do work over time

  • EndPlay - called when the object is leaving the gameplay space


  • BeginPlay - 所謂當對象第一次進入遊戲就存在

  • Tick - 所謂每幀運行一次

  • EndPlay - 所謂當對象離開遊戲空間時

See Actors for a more detailed discussion on AActor.

查看Actors瞭解有關於AActor更詳細的討論。

Runtime Lifecycle  運行生命週期

Just above we discussed a subset of an AActor’s lifecycle. For actors that are placed in a level, understanding the lifecycle is pretty easy to imagine: actors are loaded and come into existence and eventually the level is unloaded and the actors are destroyed. What is the process for runtime creation and destruction? Unreal Engine calls the creation of an AActor at runtime spawning. Spawning an actor is a bit more complicated than creating a normal object in the game. The reason is that an AActor needs to be registered with a variety of runtime systems in order to serve all of its needs. The initial location and rotation for the actor need to be set. Physics may need to know about it. The manager responsible for telling an actor to tick needs to know. And so on. Because of this, we have a method devoted to the spawning of an actor,UWorld::SpawnActor(). Once that actor is spawned successfully, its BeginPlay() method is called, followed by Tick() the next frame.

上面我們只是討論了一個AActor生命週期的一個子集。對於一個被放置在關卡中的Actor,理解生命週期是很容易想象的:Actor被加載並開始存在然後最終卸載關卡並且銷燬Actor。創建和銷燬的運行流程是什麼?虛幻引擎運行時產生調用創建一個AActor。產生一個Actor比在遊戲中創建一個正常的對象複雜一些。其原因是,一個AActor需要與各種運行時的系統登記,以便它所有的服務需要。Actor的初始位置和旋轉需要設置。物理可能需要了解它。管理器需要知道Actor的勾選。等等。正因爲如此,我們必須有一個方法專門用於產生Actor,UWorld:: SpawnActor()。一旦成功生成了Actor,其BeginPlay()被調用,隨後Tick()進行下一幀。

Once an actor has lived out its lifetime, you can get rid of it by calling Destroy(). During that process EndPlay() will be called where you can do any custom logic for destruction. Another option for controlling how long an actor exists is to use the Lifespan member. You can set a timespan in the constructor of the object or with other code at runtime. Once that amount of time has expired, the actor will automatically have Destroy() called on it.

一旦一個Actor已經超過了它的生命週期,你可以通過調用Destroy()擺脫它。你可以做任何自定義邏輯銷燬,在此過程中EndPlay()將被調用。另一個選項是使用壽命構件用於控制Actor存在多長時間。你可以在對象的構造函數中或其他運行中的代碼設置一個時間跨度。一旦超過該時間,Actor會自動調用Destroy()。

To learn more about spawning actors see the Spawning Actors page.

學習更多有關於生成Actor的信息請看Spawning Actors頁面

UActorComponent

UActorComponents have their own behaviors and are usually responsible for functionality that is shared across many types of AActors, e.g. providing visual meshes, particle effects, camera perspectives, and physics interactions. While AActors are often given high-level goals related to their overall roles your game, UActorComponents usually perform the individual tasks that support those higher-level objectives. Components can also be attached to other Components, or can be the root Component of an Actor. A Component can only attach to one parent Component or Actor, but it may have many child Components attached to itself. Picture a tree of Components. Child Components have location, rotation, and scaling relative to their parent Component or Actor.

UActorComponents有自己的行爲和通常負責的功能是在許多類型的AActors的共享,例如,共享的功能提供視覺網格物體,粒子效果,攝像機的角度,和物理的相互作用。雖然AActors往往被賦予與他們的整體角色遊戲的高層次目標,UActorComponents通常執行支持這些更高層次的目標的各個任務。組件也可以附加到其他組件上,或者作爲Actor的根組件。一個組件只可以附加到一個父組件或Actor上,但是它可能有很多子組件附加到它自己上。畫面組件樹。相對於它們的父組件或Actor子組件有位置,旋轉和縮放。

While there are many ways to use Actors and Components, one way to think of the Actors-Component relationship is that Actors might answer the question "what is this thing?" while Components might answer “what is this thing made of?”

雖然有很多方法來使用Actor和組件,有一種認爲Actor-組件的關係是方法是Actor可能會回答“這是什麼東西?”的問題而組件可能會回答“這是什麼東西做的?”的問題。

  • RootComponent - this is the member of AActor that holds the top level Component in the AActor’s tree of Components

  • Ticking - Components are ticked as part of the owning AActor’s Tick()


  • 根組件-這是AActor樹成員中持有最高級別的組件

  • Ticking-被選中組件作爲AActor Tick()的一部分

Dissecting the First Person Character 解剖第一人稱角色

Over the last few sections we’ve done a lot of talking and not a lot of showing. In order to illustrate the relationship of an AActor and its UActorComponents, let’s dig into the Blueprint that is created when you generate a new project based off of the First Person Template. The image below is the Component tree for the FirstPersonCharacter Actor. The RootComponent is the CapsuleComponent. Attached to the CapsuleComponent is the ArrowComponent, the Mesh component, and the FirstPersonCameraComponent. The leaf most component is the Mesh1P component which is parented to the FirstPersonCameraComponent, meaning that the first person mesh is relative to the first person camera.

在之前的幾個部分中我們已經進行了很多討論,但並沒有進行過多的演示。爲了說明一個AActor及UActorComponents的關係,讓我們深入到當你生成基於封閉的第一人稱模板的新項目的藍圖。下面的圖片是FirstPersonCharacter Actor的組件樹。RootComponent是CapsuleComponent。附加在CapsuleCoponent的是ArrowCompomemt,網格組件和FirstPersonCameraComponent。葉的部分是FirstPersonCameraComponent作爲Mesh1P的父組件,這意味着第一人稱網格物體是第一人稱攝像機的組件。


Visually, this tree of Components looks like the image below, where you see all of the components in 3D space except for the Mesh component.

從外觀上來看,這棵組件樹看起來像下邊這幅圖片,這裏你可以看到加上網格物體組件在內的所有組件。

This tree of components is attached to the one actor class. As you can see from this example, you can build complex gameplay objects using both inheritance and composition. Use inheritance when you want to customize an existing AActor or UActorComponent. Use composition when you want many different AActor types to share the functionality.

這個組件樹被附加到一個Actor類。就像你在這個例子中所看到的,你可以同時使用繼承和組合的複合遊戲對象。當你想想自定義現有的AActor或UActorComponent時使用繼承。當你想讓不同類型的AActor共享功能的時候使用組合

UStruct

To use a UStruct, you don’t have to extend from any particular class, you just have mark the struct with USTRUCT() and our build tools will do the base work for you. Unlike a UObject, UStructs are not garbage collected. If you create dynamic instances of them, you must manage their lifecycle yourself. UStructs are meant to be plain old data types that have the UObject reflection support for editing within the Unreal Editor, Blueprint manipulation, serialization, networking, etc.

使用UStruct,你不需要從任何特定的類擴展的話,你只需要使用USTRUCT()來標記然後我們的構建工具就會爲你做基礎工作。不同於UObject,UStructs不是垃圾收集。如果你創建它們的動態實例,你必須自己管理自己的生命週期。UStructs的目的是作爲具有在編輯器中支持編輯UObject的反饋,操縱藍圖,序列化,網絡等的普通的舊的數據。

Now that we’ve talked about the basic hierarchy used in our gameplay class construction, it is time to choose your path again. You can read about our gameplay classes here, head out to our samples in the launcher armed with more information, or continue digging deeper into our C++ features for building games.

現在我們已經談論了遊戲架構的基本層次的使用,現在是時候重新選擇路徑。你可以在這裏瞭解我們的遊戲類,通過啓動器進入我們的樣例獲取更多信息來武裝自己,或繼續更深的挖掘進入我們的構建C++功能的遊戲。

Diving Deeper Still 持續深入

Alright, it is clear you want to know more. Let’s keep on going deeper into how the engine works.

好吧,顯然你想知道的更多。讓我們繼續深入看看引擎是如何工作的

Unreal Reflection System 虛幻反饋系統

Blog Post: Unreal Property System (Reflection)

博客文章:虛幻屬性系統(反饋)

Gameplay classes make use of special markup, so before we go over them, let’s cover some of the basics of the Unreal property system. UE4 uses its own implementation of reflection that enables dynamic features such as garbage collection, serialization, network replication, and Blueprint/C++ communication. These features are opt-in, meaning you have to add the correct markup to your types, otherwise Unreal will ignore them and not generate the reflection data for them. Here is a quick overview of the basic markup:

遊戲類使用特定的標記,

  • UCLASS() - Used to tell Unreal to generate reflection data for a class. The class must derive from UObject.

  • USTRUCT() - Used to tell Unreal to generate reflection data for a struct.

  • GENERATED_BODY() - UE4 replaces this with all the necessary boilerplate code that gets generated for the type.

  • UPROPERTY() - Enables a member variable of a UCLASS or a USTRUCT to be used as a UPROPERTY. A UPROPERTY has many uses. It can allow the variable to be replicated, serialized, and accessed from Blueprints. They are also used by the garbage collector to keep track of how many references there are to a UObject.

  • UFUNCTION() - Enables a class method of a UCLASS or a USTRUCT to be used as a UFUNCTION. A UFUNCTION can allow the class method to be called from Blueprints and used as RPCs, among other things.


  • UCLASS() - 用於告訴引擎爲類生成反饋數據。這個類必須衍生自UObject。

  • USTRUCT() - 用於告訴引擎爲struct生成反饋數據。

  • GENERATED_BODY() - UE4替換所有被該類型生成必要的樣板代碼。

  • UPROPERTY() - 允許使用UCLASS或USTRUCT的成員變量作爲UPROPERTY出現。UPROPERTY有很多用處。它可以允許變量被替換,序列化並可以從藍圖訪問。他們還被用於垃圾收集器來跟蹤來源於一個UObject有多少引用。

  • UFUNCTION() - 允許使用UCLASS或USTRUCT的類方法作爲UFUNCTION出現。UFUNCTION能夠允許從藍圖調用並作爲RPC的類方法等等。

Here is an example declaration of a UCLASS:

這裏有一個UCLASS生命的例子:

#include "MyObject.generated.h"

UCLASS(Blueprintable)
class UMyObject : public UObject
{
    GENERATED_BODY()

public:
    MyUObject();

    UPROPERTY(BlueprintReadOnly, EditAnywhere)
    float ExampleProperty;

    UFUNCTION(BlueprintCallable)
    void ExampleFunction();
};
You’ll first notice the inclusion of "MyClass.generated.h". Unreal will generate all the reflection data and put it into this file. You must include this file as the last include in the header file that declares your type.

你首先需要注意的就是包含“MyClass.generated.h”。虛幻將生成所有在這個文件中的反饋數據。你必須包含這個帶有你最終聲明的頭文件類型。

You’ll also have noticed that we can also add additional specifiers to the markup. I’ve added some of the more common ones for demonstration. These allow us to specify certain behavior that our types have.

您還會注意到,我們還可以添加額外的說明符標記。我已經添加了一些比較常見的用於演示。這允許我們指定我們類型有的某種行爲。

  • Blueprintable - This class can be extended by a Blueprint.

  • BlueprintReadOnly - This property can only be read from a Blueprint, and not written to.

  • Category - Defines what section this property appears under in the Details view of the Editor. For organizational purposes.

  • BlueprintCallable - This function can be called from Blueprints.


  • Blueprintable - 這個類能夠通過藍圖擴展。

  • BlueprintReadOnly - 這個屬性僅允許從藍圖讀取,而不能寫入。

  • Category - 定義該屬性出現在編輯器的詳細信息那個部分。

  • BlueprintCallable - 這個函數可以被藍圖調用。

There are too many specifiers to list here, so I’ll link to the docs for them:

有太多限定符在這裏列出,所以我會爲他們鏈接到該文檔:
List of UCLASS Specifiers

UCLASS的限定符

List of UPROPERTY Specifiers

UPROPERTY的限定符

List of UFUNCTION Specifiers

UFUNCTION的限定符

List of USTRUCT Specifiers

USTRUCT的限定符

Object/Actor Iterators 對象/Actor迭代器

Object iterators are a very useful tool to iterate over all instances of a particular UObject type and its subclasses.

對象迭代器是有個用來迭代特定UObject類型及其子類所有實例極其有用的工具。

// Will find ALL current UObjects instances
for (TObjectIterator<UObject> It; It; ++It)
{
    UObject* CurrentObject = *It;
    UE_LOG(LogTemp, Log, TEXT("Found UObject named: %s"), *CurrentObject.GetName());
}


You can limit the scope of the search by providing a more specific type to the iterator. Suppose you had a class called UMyClass that derived from UObject. You could find all instances of that class (and those that derive from it) like this:

可以通過提供一個更具體的類型給迭代器限制搜索的範圍。假設你有從UObject派生的類名爲UMyClass。你可以找到類的所有實例(以及那些從它衍生的)是這樣的:

for (TObjectIterator<UMyClass> It; It; ++It)
{
    // ...
}
Warning: Using object iterators in PIE (Play In Editor) can lead to unexpected results. Since the editor is loaded, the object iterator will return all UObjects created for your game world instance, in addition to those that are just being used by the editor.

警告:在PIE使用對象的迭代器(在編輯器中)可能會導致意想不到的結果。由於編輯器加載,對象迭代器將返回你的遊戲世界中所有UObjects創建的實例,除了那些僅編輯器正在使用的。

Actor iterators work in much the same way as object iterators, but only work for objects that derive from AActor. Actor iterators don’t have the problem noted below, and will only return objects being used by the current game world instance.

Actor迭代器的工作與對象迭代器的工作大致相同,但僅適用於從AActor派生的對象。Actor迭代器沒有以下的問題,並且只會返回當前遊戲世界正在使用的實例對象。

When creating an actor iterator, you need to give it a pointer to a UWorld instance. Many UObject classes, such as APlayerController, provide a GetWorld method to help you. If you’re not sure, you can check the ImplementsGetWorld method on a UObject to see if it implements the GetWorld method.

當你創建一個Actor迭代器時,你需要給它指向一個UWorld實例。很多UObject類,像APlayerController,這樣提供GetWorld方式來幫助你。如果你不確定,你可以檢查一個UObject的ImplementsGetWorld方法看它是否實現了GetWorld方法。

APlayerController* MyPC = GetMyPlayerControllerFromSomewhere();
UWorld* World = MyPC->GetWorld();

// Like object iterators, you can provide a specific class to get only objects that are
// or derive from that class
for (TActorIterator<AEnemy> It(World); It; ++It)
{
    // ...
}

Since AActor derives from UObject, you can use TObjectIterator to find instances of AActors as well. Just be careful in PIE!

自AActor從UObject中派生,你也可以使用Tobjectlterator來找到AActor的實例。只是要在PIE下要小心。


Memory Management and Garbage Collection 內存管理及垃圾回收

In this section we will go over basic memory management and the garbage collection system in UE4.

在這一節我們將複習UE4中的基礎內存管理及垃圾回收系統

Wiki: Garbage Collection & Dynamic Memory Allocation

Wiki:垃圾回收與動態內存分配

UObjects and Garbage Collection UObject和垃圾回收

UE4 uses the reflection system to implement a garbage collection system. With garbage collection, you will not have to manually manage deleting your UObjects, you just need to maintain valid references to them. Your classes needs to derive from UObject in order to be enabled for garbage collection. Here is the simple example class we will be using:

UE4使用反饋系統來實現垃圾回收系統。有了垃圾回收,你就不必手動刪除你的UObject,你只需要對它們保持有效的應用。你的類需要有UObject衍生以便啓用垃圾收集。這裏是我們使用的簡單的樣例類:

UCLASS()
class MyGCType : public UObject
{
    GENERATED_BODY()
};
In the garbage collector, there is this concept called the root set. This root set is basically a list of objects that the collector knows about will never be garbage collected. An object will not be garbage collected as long as there is a path of references from an object in the root set to the object in question. If no such path to the root set exists for an object, it is called unreachable and will be collected (deleted) the next time the garbage collector is ran. The engine runs the garbage collector at certain intervals.

在垃圾回收器中,這個概念被稱爲根集。這個根集基本上是收集器知道的永遠不會被垃圾收集的列表。對象只要有被根集涉及的對象引用路徑,對象將不會被垃圾收集器收集。如果對象沒有在根集中存在一個這樣的路徑,它被稱爲無法訪問,將在下一次垃圾收集器運行時收集(刪除)。引擎間隔一定時間運行垃圾收集器。

What counts as a "reference"? Any UObject pointer stored in a UPROPERTY. Let’s start with a simple example.

什麼纔算是“引用”?任何的UOject指針都儲存在UPROPERTY中。讓我們先從一個簡單的例子開始。

void CreateDoomedObject()
{
    MyGCType* DoomedObject = NewObject<MyGCType>();
}
When we call the above function, we create a new UObject, but we don’t store a pointer to it in any UPROPERTY, and it isn’t a part of the root set. Eventually, the garbage collector will detect this object is unreachable, and destroy it.

當我們調用以上的函數,我們創建一個新的UObject,但是我們並沒有存儲指針到任何的UPROPERTY,而且它並不是根集的一部分。最終,垃圾收集器將檢測到這個對象無法訪問,並摧毀它。

Actors and Garbage collection Actor和垃圾收集器

Actors are not usually garbage collected. Once spawned, you must manually call Destroy() on them. They will not be deleted immediately, and instead will be cleaned up during the next garbage collection phase.

Actor通常並不作爲垃圾回收。一旦生成,你必須手動爲它們調用Destory()。它們將不會立即刪除,而是將在未來的垃圾收集階段清理。

This is a more common case, where you have actors with UObject properties.

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public:
    UPROPERTY()
    MyGCType* SafeObject;

    MyGCType* DoomedObject;

    AMyActor(const FObjectInitializer& ObjectInitializer)
        : Super(ObjectInitializer)
    {
        SafeObject = NewObject<MyGCType>();
        DoomedObject = NewObject<MyGCType>();
    }
};

void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
    World->SpawnActor<AMyActor>(Location, Rotation);
}

When we call the above function, we spawn an actor into the world. The actor’s constructor creates two objects. One gets assigned to a UPROPERTY, the other to a bare pointer. Since actors are automatically a part of the root set, SafeObject will not be garbage collected because it can be reached from a root set object. DoomedObject, however, will not fare so well. We didn’t mark it with UPROPERTY, so the collector doesn’t know its being referenced, and will eventually destroy it.

當我們調用以上函數時,我們在世界中生成一個Actor。這個Actor構造函數創建了兩個對象。一個被分配到UPROPERTY,另一個用於裸指針。由於Actor都自動變爲根集的一部分,安全對象將不會被垃圾收集器收集,因爲它可以從根集被訪問。然而有些就沒有那麼幸運了因爲他們沒有被標記在UPROPERTY,所以收集器不知道它被引用,最終會銷燬它。

When a UObject is garbage collected, all UPROPERTY references to it will be set to nullptr for you. This makes it safe for you to check if an object has been garbage collected or not.

當一個UObfect被垃圾收集器收集時,會爲你將所有的UPROPERTY引用設置爲空。這使得它安全地爲您檢查對象是否被垃圾回收與否。

if (MyActor->SafeObject != nullptr)
{
    // Use SafeObject
}
This is important since, as mentioned before, actors that have had Destroy() called on them are not removed until the garbage collector runs again. You can check the IsPendingKill() method to see if a UObject is awaiting its deletion. If that method returns true, you should consider the object dead and not use it.

這是非常重要的,因爲,如同前面提到的,Actor將在他們沒有被刪除時調用Destory()直到垃圾收集器再次運行。您可以用IsPendingKill()的方法檢查,看是否有UObject正在等待被刪除。

UStructs
UStructs, as mentioned earlier, are meant to be a lightweight version of a UObject. As such, UStructs cannot be garbage collected. If you must use dynamic instances of UStructs, you may want to use smart pointers instead, which we will go over later.

UStructs,如前所述,意味着它成爲UObject的輕量級版本。因此,UStructs不能被垃圾收集。如果你必須使用動態的UStructs,你可能想要使用智能指針代替,我們會在後邊複習。

Non-UObject References 非UObject反饋

Normal, non-UObjects can also have the ability to add a reference to an object and prevent garbage collection. To do that, your object must derive from FGCObject and override its AddReferencedObjects class.

一般來說,非UObjects也可以有一個添加到對象的引用,並防止垃圾收集。爲了做到這一點,你的對象必須衍生自FGCObject並覆蓋其AddReferencedObjects類。

class FMyNormalClass : public FGCObject
{
public:
    UObject* SafeObject;

    FMyNormalClass(UObject* Object)
        : SafeObject(Object)
    {
    }

    void AddReferencedObjects(FReferenceCollector& Collector) override
    {
        Collector.AddReferencedObject(SafeObject);
    }
};

We use the FReferenceCollector to manually add a hard reference to the UObject we need and don’t want garbage collected. When the object is deleted and its destructor is run, the object will automatically clear all references that it added.

我們需要使用FReferenceCollector爲我們的UObjec手動添加一個硬盤引用t,不想讓其被收集。當對象被刪除,其析構函數運行時,對象將自動清除所有被添加的引用

Class Naming Prefixes 類命名前綴

Unreal Engine provides tools that generate code for you during the build process. These tools have some class naming expectations and will trigger warnings or errors if the names don’t match the expectations. The list of class prefixes below delineates what the tools are expecting.

虛幻引擎提供在你構建過程中生成代碼的工具。這些工具有一些預設的類命名,如果名稱不符合預設將會觸發警告。下面的類前綴列表界定了工具的預設。

  • Classes derived from Actor prefixed with A, e.g. AController.

  • Classes derived from Object are prefixed with U, e.g. UComponent.

  • Enums are prefixed with E, e.g. EFortificationType.

  • Interface classes are usually prefixed with I, e.g. IAbilitySystemInterface.

  • Template classes are prefixed by T, e.g. TArray.

  • Classes that derive from SWidget (Slate UI) are prefixed by S, e.g. SButton.

  • Everything else is prefixed by the letter F, e.g. FVector.


  • 由Actor衍生的類前綴帶A,例如AController。

  • 由Object衍生的類前綴帶有U,例如UComponent。

  • Enums前綴帶有E,例如EFortification類型。

  • 接口類前綴通常帶有I,例如IAbilitySystemInterface。

  • 模板類前綴爲T,例如TArray。

  • 由SWidget(Slate UI)衍生的類前綴帶有S,例如SButton。

  • 所有其他的前綴爲字母F,例如FVector。

Numeric Types 數值類型

Since different platforms have different sizes for basic types such as shortint, and long, UE4 provides the following types which you should use as an alternative:

由於不同的平臺有不同基本類型的尺寸,例如short,int和long,UE4提供了以下幾種類型,你應該作爲替代使用:

  • int8/uint8 : 8-bit signed/unsigned integer

  • int16/uint16 : 16-bit signed/unsigned integer

  • int32/uint32 : 32-bit signed/unsigned integer

  • int64/uint64 : 64-bit signed/unsigned integer


  • int8/ uint8:8位有符號/無符號整數

  • int16/uint16:16位有符號/無符號整數

  • int32/uint32:32位有符號/無符號整數

  • int64/uint64:64位有符號/無符號整數

Floating point numbers are also supported with the standard float (32-bit)and double (64-bit) types.

同時還支持與標準float(32位)和double(64位)類型的浮點數。

Unreal Engine has a template, TNumericLimits, for finding the minimum and maximum ranges value types can hold. For more information follow this link.

虛幻引擎有一個模板,TNumericLimits,用於求出類型可以容納的最小值和最大值的範圍值。欲瞭解更多信息,請點擊此鏈接。

Strings 字符串

UE4 provides several different classes for working with strings, depending on your needs.

根據您的需要,UE4提供了幾種不同的處理字符串的類。

Full Topic: String Handling

完整的主題:字符串的處理

FString

FString is a mutable string, analogous to std::string. FString has a large suite of methods for making it easy to work with strings. To create a new FString, use the TEXT() macro:

Fstring是一個可變的字符串,類似於std:: string。FString有一大套便於處理字符串的方法。使用TEXT()宏創建一個FString

FString MyStr = TEXT("Hello, Unreal 4!").

Full Topic: FString API

完整的主題:FStringAPI

FText

FText is similar to FString, but it is meant for localized text. To create a new FText, use the NSLOCTEXT macro. This macro takes a namespace, key, and a value for the default language:

FText類似於一個FString,但它是本地化文本。使用NSLOCTEXT宏創建一個新的FText。這個宏需要一個命名空間,重點是爲默認語言設置值:

FText MyText = NSLOCTEXT("Game UI", Health Warning Message”, Low Health!”)

You could also use the LOCTEXT macro, so you only have to define a namespace once per file. Make sure to undefine it at the bottom of your file.

你也可以使用LOCTEXT宏,所以你只需要每個文件中定義一次命名空間。請確保在你的文件底部取消定義它。

// In GameUI.cpp
#define LOCTEXT_NAMESPACE "Game UI"

//…
FText MyText = LOCTEXT("Health Warning Message", Low Health!”)
//…

#undef LOCTEXT_NAMESPACE
// End of file

Full Topic: FText API

完整的主題:FText API

FName

FName stores a commonly recurring string as an identifier in order to save memory and CPU time when comparing them. Rather than storing the complete string many times across every object that references it, a FName uses a smaller storage footprint Index that maps to a given string. This stores the contents of the string once, saving memory when that string is used across many objects. Two strings can be compared quickly by checking to see if NameA.Index equals NameB.Index, avoiding checking each character in the string for equality.

FName當比較它們時爲節省內存和CPU時間存儲常用的重複的字符串。而不是通過每個對象多次存儲完整的字符串,FName參數使用更小的存儲空間目錄映射到一個給定的字符串。當通過多個對象使用時,存儲字符串一次以節省內存。兩個字符串可迅速通過檢查,以查看是否NameA.Index等於NameB.Index,避免檢查字符串的相等在每個字符進行比較。

Full Topic: FName API

完整的主題:FName API

TCHAR

TCHARs are used as a way of storing characters independent of the character set being used, which may differ between platforms. Under the hood, UE4 strings use TCHAR arrays to store data in the UTF-16 encoding. You can access the raw data by using the overloaded dereference operator which returns TCHAR.

TCHARs被用作存儲獨立的字符的一種方式,它在平臺之間可能有所不同。在底層,UE4字符串使用TCHAR陣列將數據存儲在UTF-16編碼中。你可以通過使用重載的引用操作返回TCHAR訪問原始數據。

Full Topic: Character Encoding

完整的主題:字符編碼

This is needed for some functions, such as FString::Printf, where the ‘%s’ string format specifier expects a TCHAR instead of an FString.

這是一些功能的需要,比FString:: Printf,這裏的“%s”的字符串格式說明符預設是一個TCHAR而不是一個FString。

FString Str1 = TEXT("World");
int32 Val1 = 123;
FString Str2 = FString::Printf(TEXT("Hello, %s! You have %i points."), *Str1, Val1);

The FChar type provides a set of static utility functions for working with individual TCHARs.

該FChar類型提供了一組靜態的效用函數與與單個TCHARs的工作。。

TCHAR Upper(‘A’);
TCHAR Lower = FChar::ToLower(Upper); // ‘a’

The FChar type is defined as TChar (as it is listed in the API).

該FChar類型定義爲TCHAR(因爲它被列在API中)。

Full Topic: TChar API

Containers 容器

Containers are classes whose primary function is to store collections of data. The most common of these classes are TArray, TMap,and TSet. Each of these are dynamically sized, and so will grow to whatever size you need.

容器,其主要功能是存儲數據的集合的類。最常見的這些類是TArray,TMap,和TSet。這些都是動態的大小,所以它可以增長到你所需要的任何大小,。

Full Topic: Containers API

完整的主題:容器API

TArray

Of these three containers the primary container you’ll use in Unreal Engine 4 is TArray, it functions much like std::vector does, but offers a lot more functionality. Here are some common operations:

你會在虛幻引擎4中使用這三個容器的主容器TArray的,它的作用很像std:: vector,但提供了更多的功能。下面是一些常用的操作:

TArray<AActor*> ActorArray = GetActorArrayFromSomewhere();

// Tells how many elements (AActors) are currently stored in ActorArray.
int32 ArraySize = ActorArray.Num();

// TArrays are 0-based (the first element will be at index 0)
int32 Index = 0;
// Attempts to retrieve an element at the given index
TArray* FirstActor = ActorArray[Index];

// Adds a new element to the end of the array
AActor* NewActor = GetNewActor();
ActorArray.Add(NewActor);

// Adds an element to the end of the array only if it is not already in the array
ActorArray.AddUnique(NewActor); // Won’t change the array because NewActor was already added

// Removes all instances of ‘NewActor’ from the array
ActorArray.Remove(NewActor);

// Removes the element at the specified index
// Elements above the index will be shifted down by one to fill the empty space
ActorArray.RemoveAt(Index);

// More efficient version of ‘RemoveAt’, but does not maintain order of the elements
ActorArray.RemoveAtSwap(Index);

// Removes all elements in the array
ActorArray.Empty();

TArrays have the added benefit of having their elements garbage collected. This assumes that the TArray is marked as a UPROPERTY, and that it stores UObject derived pointers.

TArrays具有元素垃圾收集的好處。這裏假定TArray被標記爲UPROPERTY,並且它存儲UObject衍生指針。

UCLASS()
class UMyClass : UObject
{
    GENERATED_BODY();

    // …

    UPROPERTY()
    TArray<AActor*> GarbageCollectedArray;
};

We’ll cover the garbage collection in depth in a later section.

我們將在後面的章節深入垃圾收集。

Full Topic: TArrays

完整的主題:TArrays

Full Topic: TArray API

完整的主題:TArray API

TMap

TMap is a collection of key-value pairs, similar to std::mapTMap has quick methods for finding, adding, and removing elements based on their key. You can use any type for the key, as long as it has a GetTypeHash function defined for it, which we will go over later.

TMap是鍵值對的集,類似於std::map。 TMap可以基於它們來快速查找,添加,移除要素。你可以使用任何定義了GetTypeHash功能類型的鍵,我們將在後邊討論。

Let’s say you were creating a grid-based board game and needed to store and query what piece is on each square. A TMap would provide you with an easy way to do that. If your board size is small and is always the same size, there are obviously more efficient ways at going about this, but let’s roll with it for example’s sake!

比方說,你正在創建一個基於網格的棋盤遊戲,以及需要存儲和查詢是每平方上的塊。建立一個TMap會爲你提供一個簡單的方法來做到這一點。如果你的板尺寸小並始終是相同的大小,這裏顯然有更加有效的方式,這是我們以它爲例的緣故!

enum class EPieceType
{
    King,
    Queen,
    Rook,
    Bishop,
    Knight,
    Pawn
};

struct FPiece
{
    int32 PlayerId;
    EPieceType Type;
    FIntPoint Position;

    FPiece(int32 InPlayerId, EPieceType InType, FIntVector InPosition) :
        PlayerId(InPlayerId),
        Type(InType),
        Position(InPosition)
    {
    }
};

class FBoard
{
private:

    // Using a TMap, we can refer to each piece by its position
    TMap<FIntPoint, FPiece> Data;

public:
    bool HasPieceAtPosition(FIntPoint Position)
    {
        return Data.Contains(Position);
    }
    FPiece GetPieceAtPosition(FIntPoint Position)
    {
        return Data[Position];
    }

    void AddNewPiece(int32 PlayerId, EPieceType Type, FIntPoint Position)
    {
        FPiece NewPiece(PlayerId, Type, Position);
        Data.Add(Position, NewPiece);
    }

    void MovePiece(FIntPoint OldPosition, FIntPoint NewPosition)
    {
        FPiece Piece = Data[OldPosition];
        Piece.Position = NewPosition;
        Data.Remove(OldPosition);
        Data.Add(NewPosition, Piece);
    }

    void RemovePieceAtPosition(FIntPoint Position)
    {
        Data.Remove(Position);
    }

    void ClearBoard()
    {
        Data.Empty();
    }
};

Full Topic: TMaps

完整的主題:TMaps

Full Topic: TMap API

完整的主題:TMap API

TSet

TSet stores a collection of unique values, similar to std::set. With the AddUnique and Contains methods, TArrays can already be used as sets. However, TSet has faster implementations of these operations, at the cost of not being able to use them as UPROPERTYs like TArrays.TSets are also do not index their elements like TArrays do.

TSet存儲唯一值的集合,類似於std::set。通過添加獨特和包含的方法,TArrays已經可以作爲集。然而,TSet能夠更快的視線這些操作,代價是無法像UPROPERTY和TArrays一樣使用。TSets像TArrays一樣也沒有自己的索引元素。

TSet<AActor*> ActorSet = GetActorSetFromSomewhere();

int32 Size = ActorSet.Num();

// Adds an element to the set, if the set does not already contain it
AActor* NewActor = GetNewActor();
ActorSet.Add(NewActor);

// Check if an element is already contained by the set
if (ActorSet.Contains(NewActor))
{
    // ...
}

// Remove an element from the set
ActorSet.Remove(NewActor);

// Removes all elements from the set
ActorSet.Empty();

// Creates a TArray that contains the elements of your TSet
TArray<AActor*> ActorArrayFromSet = ActorSet.Array();

Full Topic: TSet API

完整的主題:TSet API

Remember! Currently, the only container class that can be marked as a UPROPERTY is TArray. This means other container classes cannot be replicated, saved, or have their elements garbage collected for you.

切記!目前,能被標記爲UPROPERTY唯一容器類是TArray。這意味着其它容器類不能被複制,保存,或它們的元素爲你收集垃圾。

Container Iterators 容器迭代器

Using iterators, you can loop through each element of a container. Here is an example of what the iterator syntax looks like, using a TSet.

通過使用迭代器,你可以循環通過容器中的每個元素。這是使用TSet來觀察迭代器語法的例子。

void RemoveDeadEnemies(TSet<AEnemy*>& EnemySet)
{
    // Start at the beginning of the set, and iterate to the end of the set
    for (auto EnemyIterator = EnemySet.CreateIterator(); EnemyIterator; ++EnemyIterator)
    {
        // The * operator gets the current element
        AEnemy* Enemy = *EnemyIterator;
        if (Enemy.Health == 0)
        {
            // ‘RemoveCurrent’ is supported by TSets and TMaps
            EnemyIterator.RemoveCurrent();
        }
    }
}

Other supported operations you can use with iterators:

你可以使用迭代器實現其他支持操作:

// Moves the iterator back one element
--EnemyIterator;

// Moves the iterator forward/backward by some offset, where Offset is an integer
EnemyIterator += Offset;
EnemyIterator -= Offset;

// Gets the index of the current element
int32 Index = EnemyIterator.GetIndex();

// Resets the iterator to the first element
EnemyIterator.Reset();

For-each Loop For-each循環

Iterators are nice, but can be a bit cumbersome if you just want to loop through each element once. Each container class also supports the "for each" style syntax to loop over the elements. TArray and TSet return each element, whereas TMap returns a key-value pair.

迭代器是不錯,但是可能有點麻煩如果你只是想訪問每個元素一次。每個容器類還支持“爲每個”風格的語法來訪問元素。 TArray和Tset返回的每個元素,而TMap則返回鍵值對。

// TArray
TArray<AActor*> ActorArray = GetArrayFromSomewhere();
for (AActor* OneActor : ActorArray)
{
    // ...
}

// TSet - Same as TArray
TSet<AActor*> ActorSet = GetSetFromSomewhere();
for (AActor* UniqueActor : ActorSet)
{
    // ...
}

// TMap - Iterator returns a key-value pair
TMap<FName, AActor*> NameToActorMap = GetMapFromSomewhere();
for (auto& KVP : NameToActorMap)
{
    FName Name = KVP.Key;
    AActor* Actor = KVP.Value;

    // ...
}

Remember that the auto keyword doesn’t automatically specify a pointer/reference for you, you need to add that yourself!

請記住,自動關鍵字不會爲你自動指定一個指針/引用,你需要自己補充!

Using your own types with TSet/TMap (Hash Functions) 使用自己的類型與TSet/ TMap的(散列函數)

TSet and TMap require the use of hash functions internally. If you create your own class that you want to use it in a TSet or as the key to a TMap, you need to create your own hash function first. Most UE4 types that you would commonly put in these types already define their own hash function.

TSet和TMap需要使用內部散列函數。如果你創建你想創建一個在TSet中使用的或是作爲Tmap的關鍵的類,你需要先創建自己的哈希函數。大多數UE4類型,你會經常把已經定義好的散列函數放入這些類型。

A hash function takes a const pointer/reference to your type and returns a uint64. This return value is known as the hash code for an object, and should be a number that is pseudo-unique to that object. Two objects that are equal should always return the same hash code.

散列函數接受一個const指針/引用到你的類型並返回一個uint64值。該返回值被認爲是從同一個對象返回的的哈希碼,而且應該是一個數字,是到該對象僞唯一值。兩個相等的對象應該總是返回相同的散列碼。

class FMyClass
{
    uint32 ExampleProperty1;
    uint32 ExampleProperty2;

    // Hash Function
    friend uint32 GetTypeHash(const FMyClass& MyClass)
    {
        // HashCombine is a utility function for combining two hash values
        uint32 HashCode = HashCombine(MyClass.ExampleProperty1, MyClass.ExampleProperty2);
        return HashCode;
    }

    // For demonstration purposes, two objects that are equal
    // should always return the same hash code.
    bool operator==(const FMyClass& LHS, const FMyClass& RHS)
    {
        return LHS.ExampleProperty1 == RHS.ExampleProperty1
            && LHS.ExampleProperty2 == RHS.ExampleProperty2;
    }
};

Now, TSet<FMyClass> and TMap<FMyClass, ...> will use the proper hash function when hashing keys. If you using pointers as keys (i.e.TSet<FMyClass*>) implement uint32 GetTypeHash(const FMyClass* MyClass) as well.

現在,Tset<FMyClass>和TMap<FMyClass,...>當散列密鑰時將使用正確散列函數。如果你也在使用指針作爲鍵(ieTSet<FMyClass*>)實現uiny32 GetTypeHash(const FMyClass* MyClass)。

Blog Post: UE4 Libraries You Should Know About

博客文章:UE4圖書館你應該知道的


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章