Skip to main content
 首页 » 资源教程 » UE4教程

Introduction to C++ Programming in UE4——UE4官方文档翻译与理解(二)

2016年09月10日 19:37:29694440

在我写第一部分的时候还没有官方的汉语翻译,所以第一部分是由本人自己翻译的。既然现在已经有了官方翻译,那我就整理一下中英对照,不过我仍然会在其中加入自己的理解来帮助大家分析UE4本身的一些特性。



Diving Deeper Still(继续深入了解)


Alright, it is clear you want to know more. Let us 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 us 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.

Here is an example declaration of a UCLASS:


游戏性类使用特殊的标记。因此在开始了解它们之前,我们有必要了解虚幻属性系统的一些基础知识。UE4 使用其自身的反射实现,可启用动态功能,如垃圾回收、序列化、网络复制和蓝图/C++ 通信。这些功能为选择加入,意味着您需要为类型添加正确的标记,否则引擎将无视类型,不生成反射数据。以下是基础标记的快速总览:

  • UCLASS() - 告知虚幻引擎生成类的反射数据。类必须派生自 UObject。

  • USTRUCT() - 告知虚幻引擎生成结构体的反射数据。

  • GENERATED_BODY() - UE4 使用它替代为类型生成的所有必需样板文件代码。

  • UPROPERTY() - 使 UCLASS 或 USTRUCT 的成员变量可用作 UPROPERTY。UPROPERTY 用途广泛。它允许变量被复制、被序列化,并可从蓝图中进行访问。垃圾回收器还使用它们来追踪对 UObject 的引用数。

  • UFUNCTION() - 使 UCLASS 或 USTRUCT 的类方法可用作 UFUNCTION。UFUNCTION 允许类方法从蓝图中被调用,并在其他资源中用作 RPC。

注:其实虚幻4所谓的反射系统是通过宏来逐步实现的,接触过其他引擎或者大的项目的朋友应该比较熟悉宏。我们可以通过宏结合工厂模式可以创建我们自定义的类名。虚幻4里面会为你的每一个.cpp文件生成一个对应的.generate.cpp文件,这个文件完全是用宏来实现的。通过反射系统,你在宏里面的参数会传入到这个.generate.cpp文件对应的函数,然后函数回调代理等调用到你在蓝图或者其他位置的真正的方法。

以下是 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.

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.

There are too many specifiers to list here, but the following links can be used as reference:

List of UCLASS Specifiers

List of UPROPERTY Specifiers

List of UFUNCTION Specifiers

List of USTRUCT Specifiers


首先注意 - “MyClass.generated.h”文件已包含。虚幻引擎将生成所有反射数据并将放入此文件。必须在声明类型的头文件中将此文件作为最后的 include 包含。

您还会注意到,可以在标记上添加额外的说明符。此处已添加部分常用说明符用于展示。通过说明符可对类型拥有的特定行为进行说明。

  • Blueprintable - 此类可由蓝图延展。

  • BlueprintReadOnly - 此属性只可从蓝图读取,不可写入。

  • Category - 定义此属性出现在编辑器 Details 视图下的部分。用于组织。

  • BlueprintCallable - 可从蓝图调用此函数。

说明符太多,无法一一列举于此,以下链接可用作参考:

UCLASS 说明符列表

UPROPERTY 说明符列表

UFUNCTION 说明符列表

USTRUCT 说明符列表

注:虚幻4的说明符非常实用,你在编写代码时无法离开他。比如对于一个属性实用DefaultEdit,表示你想让他可以在蓝图的默认属性可以读到。有时候你希望某个属性很重要需要保护,那么可以让蓝图可以读取但是不能修改,就可以使用BluePrintReadOnly。还有的属性你希望可以本地保存起来,就可以用SaveGame。当然,这些说明符确实不少,你也并不需要都背下来,在你需要的时候去查查文档就可以了。

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 instancesfor (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){
    // ...}

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(Play In Editor)中使用对象迭代器可能出现意外后果。因为编辑器已被加载,除编辑器正在使用的对象外,对象迭代器还将返回为游戏世界实例创建的全部 UObject。

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

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 are not sure, you can check the ImplementsGetWorld method on a UObject to see if it implements the GetWorld method.


Actor 迭代器与对象迭代器的工作方式非常相近,但只能用于派生自 AActor 的对象。Actor 迭代器不存在下列问题,只返回当前游戏世界实例使用的对象。

创建 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 classfor (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,也可使用 TObjectIterator 找到 AActor 的实例。在 PIE 中操作时请小心谨慎!

Memory Management and Garbage Collection(内存管理和垃圾回收)

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

Wiki: Garbage Collection & Dynamic Memory Allocation


此部分中,我们将了解到 UE4 中的基础内存管理和垃圾回收系统。

百科:垃圾回收和动态内存分配


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 need to derive from UObject in order to be enabled for garbage collection. Here is the simple example class we will be using:

UE4 使用反射系统实现垃圾回收系统。通过垃圾回收便无需手动删除 UObjects,只需维持对它们的有效引用即可。类须派生自 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 us start with a simple example.


在垃圾回收器中存在称为根集的概念。此根集是一个对象列表。回收器不会对这些对象进行垃圾回收。只要根集中的对象到讨论中的对象之间存在引用路径,对象便不会被垃圾回收。如对象到根集的此路径不存在,它便会被识别为无法达到,垃圾回收器下次运行时便会将其收集(删除)。引擎以特定间隔运行垃圾回收器。

什么被视作“引用”?存储在 UPROPERTY 中的 UObject 指针。我们来看一个简单的例子:

void CreateDoomedObject(){
    MyGCType* DoomedObject = NewObject<MyGCType>();}

When we call the above function, we create a new UObject, but we do not 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.

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


Actors 通常不会被垃圾回收。Actors 生成后,必须在其上手动调用 Destroy()。它们不会被立即删除,而会在下个垃圾回收阶段被清理。

常见情况下 actors 带有 UObject 属性。

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 does not know it is being referenced, and will eventually destroy it.

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.


调用上述函数时,将在世界场景中生成一个 actor。Actor 的构建函数创建两个对象。一个指定到 UPROPERTY,另一个指定到裸指针。Actors 自动成为根集的一部分,SafeObject 将不会被垃圾回收,因为它从根集对象中可以获取到关联。然而 DoomedObject 的进展不是十分顺利。我们未将其标为 UPROPERTY,因此回收器并不知道其正在被引用,而会将它逐渐销毁。

UObject 被垃圾回收时,对其的所有 UPROPERTY 引用将被设为 nullptr。这可使您安全地检查一个对象是否已被垃圾回收。

注:这里所说的垃圾回收都是指的虚幻4自带的UObject属性,其他的什么整形指针不包括的


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.

这十分重要,正如之前所述,已在自身上调用 Destroy() 的 actor 在垃圾回收器再次运行之前不会被移除。您可检查 IsPendingKill() 方法,确定 UObject 正等待被删除。如方法返回 true,则应将对象视为废弃物,不进行使用。

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

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 itsAddReferencedObjects class.

普通的非 UObject 也可添加到对象的引用,以防止被垃圾回收。对象必须派生自 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 do not want garbage collected. When the object is deleted and its destructor is run, the object will automatically clear all references that it added.

我们可使用 FReferenceCollector 手动为需要的、不能被垃圾回收的 UObject 添加硬引用。对象被删除,其析构函数运行时,它将自动清除添加的所有引用。

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 do not 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。

  • 派生自 对象 的类前缀为 U,如 UComponent。

  • 枚举 的前缀为 E,如 EFortificationType。

  • 接口 类的前缀通常为 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:

  • 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

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

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


因为不同平台基础类型的尺寸不同,如 shortint 和 long,UE4 提供了以下类型,可用作替代品:

  • int8/uint8 :8 位带符号/不带符号 整数

  • int16/uint16 :16 位带符号/不带符号 整数

  • int32/uint32 :32 位带符号/不带符号 整数

  • int64/uint64 :64 位带符号/不带符号整数

标准 浮点 (32-bit) 和 双倍(64-bit)类型也支持浮点数。

虚幻引擎拥有一个模板 TNumericLimits,用于找到数值类型支持的最小和最大范围。如需了解详情,请查阅此 链接 。


Strings(字符串)

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

Full Topic: String Handling


UE4 提供多个不同类使用字符串,可满足多种需求。

完整要点:字符串处理


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 MyStr = TEXT("Hello, Unreal 4!").

Full Topic: FString API


FString 是一个可变字符串,类似于 std::string。FString 拥有许多方法,便于简单地使用字符串。使用 TEXT() 宏可新建一个 FString:

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

完整要点:FString API


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与FString等字符串之间的转换,可能会经常用到

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.

Full Topic: FName API


FName 将经常反复出现的字符串保存为辨识符,以便在对比时节约内存和 CPU 时间。FName 不会在引用完整字符串的每个对象间对其进行多次保存,而是使用一个映射到给定字符串的较小存储空间 索引。这会单次保存字符串内容,在字符串用于多个对象之间时节约内存。检查NameA.Index 是否等于 NameB.Index 可对两个字符串进行快速对比,避免对字符串中每个字符进行相等性检查。

完整要点: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.

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.


TCHARs 用于存储不受正在使用的字符集约束的字符。平台不同,它们也可能存在不同。UE4 字符串在后台使用 TCHAR 阵列将数据保存在 UTF-16 编码中。使用返回 TCHAR 的重载解引用运算符可以访问原始数据。

完整要点:角色编码

部分函数会需要它。如 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 类型提供一个静态效用函数集,以便使用单个 TCHAR。

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

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

Full Topic: TChar API

完整要点: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.

Full Topic: Containers API


容器也是类,它们的主要功能是存储数据集。常见的类有 TArrayTMap 和 TSet。它们的大小均为动态,因此可变为所需的任意大小。

完整要点:组件 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 indexTArray* FirstActor = ActorArray[Index];// Adds a new element to the end of the arrayAActor* NewActor = GetNewActor();ActorArray.Add(NewActor);// Adds an element to the end of the array only if it is not already in the arrayActorArray.AddUnique(NewActor); // Won't change the array because NewActor was already added// Removes all instances of 'NewActor' from the arrayActorArray.Remove(NewActor);// Removes the element at the specified index// Elements above the index will be shifted down by one to fill the empty spaceActorArray.RemoveAt(Index);// More efficient version of 'RemoveAt', but does not maintain order of the elementsActorArray.RemoveAtSwap(Index);// Removes all elements in the arrayActorArray.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.

TArray 还有一个额外好处 - 可使其元素被垃圾回收。这将假定 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

Full Topic: TArray API


之后章节中我们将深度讨论垃圾回收。

完整要点:TArrays

完整要点:TArray API


TMap

TMap is a collection of key-value pairs, similar to std::map. TMap 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.

Let us 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 us roll with it for example's sake!


TMap 是键值对的合集,与 std::map 相似。TMap 可基于元素的键快速寻找、添加、并移除元素。只要键拥有为其定义的 GetTypeHash 函数(稍后对此进行了解),即可使用任意类型的键。

假设您创建了一个基于网格的桌面游戏,需要保存并询问每个方格上的块。通过 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

Full Topic: TMap API


完整要点:TMaps

完整要点: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 相似。TArray 通过 AddUnique 和 Contains 方法可用作集。然而 TSet 可更快实现这些操作,但无法像 TArray 那样将它们用作 UPROPERTY。TSet 不会像 TArray 那样将元素编入索引。

TSet<AActor*> ActorSet = GetActorSetFromSomewhere();int32 Size = ActorSet.Num();// Adds an element to the set, if the set does not already contain itAActor* NewActor = GetNewActor();ActorSet.Add(NewActor);// Check if an element is already contained by the setif (ActorSet.Contains(NewActor)){
    // ...}// Remove an element from the setActorSet.Remove(NewActor);// Removes all elements from the setActorSet.Empty();// Creates a TArray that contains the elements of your TSetTArray<AActor*> ActorArrayFromSet = ActorSet.Array();

Full Topic: TSet API

Remember that 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.


完整要点:TSet API

需注意:TArray 是当前唯一能被标记为 UPROPERTY 的容器类。这意味着无法复制、保存其他容器类,或对其元素进行垃圾回收。


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 integerEnemyIterator += Offset;EnemyIterator -= Offset;// Gets the index of the current elementint32 Index = EnemyIterator.GetIndex();// Resets the iterator to the first elementEnemyIterator.Reset();

For-each Loop

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.

迭代器很实用,但如果只希望在每个元素之间循环一次,则可能会有些累赘。每个容器类还支持 for each 风格的语法在元素上进行循环。TArray 和 TSet 返回每个元素,而 TMap 返回一个键值对。

// TArrayTArray<AActor*> ActorArray = GetArrayFromSomewhere();for (AActor* OneActor : ActorArray){
    // ...}// TSet - Same as TArrayTSet<AActor*> ActorSet = GetSetFromSomewhere();for (AActor* UniqueActor : ActorSet){
    // ...}// TMap - Iterator returns a key-value pairTMap<FName, AActor*> NameToActorMap = GetMapFromSomewhere();for (auto& KVP : NameToActorMap){
    FName Name = KVP.Key;
    AActor* Actor = KVP.Value;

    // ...}

Remember that the auto keyword does not automatically specify a pointer/reference for you, you need to add that yourself.

注意:auto 关键词不会自动指定指针/引用,需要自行添加。

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.

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.


TSet 和 TMap 需要在内部使用 散列函数。如要创建在 TSet 中使用或作为 TMap 键使用的自定义类,首先需要创建自定义散列函数。通常会放入这些类型的多数 UE4 类型已定义其自身的散列函数。

散列函数接受到您的类型的常量指针/引用,并返回一个 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 和 TMap 在散列键时将使用适当的散列函数。如您使用指针作为键(即TSet<FMyClass*>),也将实现 uint32 GetTypeHash(const FMyClass* MyClass)

评论列表暂无评论
发表评论