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

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程)

2016年10月16日 10:24:392720

Unreal Engine 4.7

原文链接:https://docs.unrealengine.com/latest/INT/Programming/Introduction/index.html

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第1张

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的第一步。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第2张

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.

在这个过程中的第二步就是告诉向导你想生成类的名字。在这里我们使用默认名称。


【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第3张

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标志意味着它作为一个不保存或读取于硬盘,非永久性的衍生值。下图显示了属性的一部分默认值。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第4张

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.

在构造函数中添加默认值后这里是相同的。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第5张

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()代码后还是相同的属性视图。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第6张

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++代码更改!有两种方法可以做到:

  1. 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并即时刷新你的更改!

    【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第7张

    (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允许你构建。)

  2. Or, simply click the Compile button on the editor’s main toolbar!                                                                                                                        或者,只需点击编辑器的主工具栏上的编译按钮!

    【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第8张

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。这是有意为之,隐藏了设计人员通过使用我们的工具的命名约束,使得它们名称更加友好。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第9张

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。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第10张

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次。这就是现在显示的属性。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第11张

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_EDITORvoid 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的值像我们希望的那样如下图所示。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第12张

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选项公开它给蓝图虚拟机。每个暴露功能蓝图需要一个与之相关的范畴,以便右键上下文菜单中正常工作。下图显示的类别是如何影响上下文菜单。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第13张

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值然后调用重新计算相关数据的变化。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第14张

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 UObjectAActorUActorComponent, 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的父组件,这意味着第一人称网格物体是第一人称摄像机的组件。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第15张

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.

从外观上来看,这棵组件树看起来像下边这幅图片,这里你可以看到加上网格物体组件在内的所有组件。

【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程) UE4教程 第16张

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

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 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中派生,你也可以使用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前缀带有Enums,例如RFortification类型。

  • 接口类前缀通常带有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 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.

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

完整的主题: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 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 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则返回键值对。

// 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 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图书馆你应该知道的

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