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

发表于2018-01-22
评论0 6.2k浏览

UE4这篇官方文档大体上讲解了UE4本身的诸多特性以及如何编写基本的C++代码,对理解UE4的编程基础内容非常有帮助,但是没有对应的中文翻译。所以,我把这篇文档翻译出来,之后还会对其进行必要的总结。由于内容比较多,会分两个部分进行编写,持续更新~


Unreal C++ is Awesome!(虚幻4的C++是了不起的)

This guide is about learning how to write C++ code in Unreal Engine. Do not 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.

Before we go on, it is really important that you are 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.

If you are coming in with no programming experience at all, we have got you covered also! Check out our Blueprint Visual Scripting guide and you will 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 will be most successful after reading through this guide and learning the basics about the Unreal programming model. We will talk more about that as we go along.

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

这篇编程指导是讲解如何在虚幻引擎书写C++代码的。并不用担心,虚幻引擎中的C++编程会比你想象的要有趣的多,而且上手难度也并不大。我们认为虚幻的C++是“自动协助的C++”,因为我们有许多让C++用起来更简单的特性来给大家去使用。

在开始前,你应该至少熟悉C++或者其他任何一门语言,这是非常重要的。这篇文档就假设你有一些C++的编程经验 ,不过如果你了解C#,java或者JavaScript,你也会发现他们都是想相通的。

如果你之前一点编程经验都没有,那我们也替你考虑到了!去查看一下 蓝图可视化指导(Blueprint Visual Scripting guide),你也可以开始你的UE4旅程了。你完全可以使用UE4提供的蓝图功能做一款游戏,不需要写一行代码!

在UE引擎里面你可以写原生的C++代码,但是一旦你浏览过这篇文档并学会了UE引擎里的编程模型,你就会收获的更多。接下来我们会讲解更多的内容。


C++ and Blueprints

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.

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.

With that said, let us take a look at a typical workflow for the C++ programmer that is creating building blocks for the designer. In this case, we are going to create a class that is later extended via Blueprints by a designer or programmer. In this class, we are going to create some properties that the designer can set and we are 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++,程序员可以创建一个游戏系统来让设计者在上面给游戏关卡构建或创建游戏内容。这样,C++程序员就可以在他们最喜欢的IDE(一般是Visual Studio和Xcode)里工作,而设计师只在虚幻编辑器里面操作就好了。

游戏里面的API以及框架相关的类在这两个系统都可以获取到,你可以分开来使用,但是当你将这两个系统结合的时候你才会真正的体会虚幻的强大所在。那么这意味着什么?当程序员创建了游戏的C++代码块并提供给设计师来制作丰富有趣的游戏时,才能发挥虚幻引擎的真正优势。

既然已经整体的介绍了C++模块,不妨让我们看看C++程序员给设计师创建游戏模块的一个工作流程。这样,我们会创建一个类并让设计师或程序员通过蓝图对其进行扩展。接下来,我们会创建一些属性,设计师可以设置这些属性并产生一些新的值。使用引擎提供的工具和C++的宏,整个流程其实非常简单。

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

Class Wizard(类向导)

First thing we are 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(后面会介绍Actor,也可以参考链接:UE4碰撞规则详解 的第一条)的第一步。

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 is the class definition that is generated for you. For more information on the Class Wizard, follow this link.

一旦你选择了创建类,向导就会生成文件并且打开你的开发环境来让你进行编辑。下面是引擎生成类的定义如果想知道更多关于类向导的内容,点击这个链接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 do not 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()是触发一个事件,让你知道角色刚刚进入了游戏模式。这个位置很适合你的类来初始化一些数据与逻辑。Tick()就是在上次调用过后每过一小段时间来触发的帧循环事件在这里你可以做任何的循环逻辑处理。不过如果你不需要这两个功能,你可以移除这这个函数并保存为自己的风格。如果你移除了他,确保你移除了构造函数内设置循环是否开启的语句。下面的构造函数包含了问题中描述的这个开关。

AMyActor::AMyActor()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you do not need it.
    PrimaryActorTick.bCanEverTick = true;
}

Making a property show up in the editor(生成一个显示在编辑器的属性

We have our class created, so now let us 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 is 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()里面添加更多的参数就可以了。举例来说,如果你想 TotalDamage 属性 出现在一个里面含有很多相关联属性的模块,你可以使用分类特性。属性的声明如下所示。(所有Category=“Damage”的属性都会被归为一个名为Damage的分支里面)

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.

Now let us expose that same property to Blueprints.

当用户去查看并编辑该属性时,他就会出现在Damage标题的分类下面,同时你也会看到其他你标记过的分类标题。对于设计师来说,这是一个非常好的将常用属性分类的方式。

下面我们换个写法同样将这个属性添加进去。

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.

Before continuing to the section below, let us add a couple of properties to this sample class. There is already a property to control the total amount of damage this actor will deal out, but let us 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.

正如你所看到的,宏里面有一个特定的蓝图参数来让属性可以阅读和编辑。这个位置也可以换成另外一个参数,BlueprintReadOnly,如果想要你的属性在蓝图里面是const常量你就可以使用这个参数。当然,虚幻还提供了许多的参数来决定你的属性以什么样的方式显示在引擎中。想看到更多的参数选项,点击这个链接link.

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.

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++类操作一样。下面是两种在构造函数里面设置默认值例子并且在功能上是相同的。(不过对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 is 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.

为了使设计师可以随时修改每一个实例的值,这些值也会在一个类的实例构建完成后被及时加载。这些数据在构造函数执行后被提交。你可以通过关联到函数PostInitProperties(),创建一个新的基于之前已经有的属性而计算出来的值。下面是一个例子,该例子里面的 TotalDamage DamageTimeInSeconds是由关卡设计师来设计编辑的。即使这些值是由设计师来决定的,你仍可以像我们在上述例子中那样给他设置默认值。

注:If you do not provide a default value for a property, the engine will automatically set that property to zero or nullptr in the case of pointer types.

如果你不给一个属性提供默认值,引擎就会自动的给他设置为0或nullptr(如果是指针类型的话)。

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

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

在添加了上述的代码PostInitProperties后,就会像下面一样显示属性视图。

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! your changes instantly! your changes instantly! your changes instantly! your changes instantly!                                                                                                                                                  在编辑器运行的时候,就像你在VS或Xcode平时那样编译的操作一样。编辑器会察觉到新的需要编译的DLL并且立刻重新加载修改的代码!随时编译修改的代码!       

    (Note that if you are attached with the debugger, you'll need to detach first so that Visual Studio will allow you to Build.提示一下,如果你是正在调试,你得先将调断开这样VS才能允许你重新编译)

  2. Or, simply click the Compile button on the editor's main toolbar.

       或者简单的在编辑器里面点击Compile按钮。

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 have created a simple gameplay class with the C++ Class Wizard and added some properties for the designer to set. Let us now take a look at how a designer can start creating unique classes from our humble beginnings here.

First thing we are 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.

目前为止,我们已经通过C++类向导创建了一个简单的游戏类并且添加了一些可以设置的属性。下面我们看看一个设计师怎样通过最简单的C++类来在蓝图中拓展出一个独特的类。

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.

一旦你点击了select按钮,一个新的默认的名为Blueprint的类就会被创建。这里,我设置类的名字为 CustomActor1,你可以在内容浏览窗口里面看到该文件的快照。

This is the first class that we are going to customize with our designer hats on. First thing we are 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并且递交的时间改为两秒。属性现在显示的就如下面这样。

Our calculated value does not match what we would 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 are not accounted for. There is 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。造成这样的原因是我们只在属性初始化之后计算了我们每秒的伤害。运行时的改变在虚幻引擎中并没有被及时捕获。下面有一个简单的解决方式,原理是引擎会在属性被修改时进行目标的标记。下面的代码添加了一个关联hook,当编辑器里面属性值改变时添加的就会关联的计算派生的值。

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 would expect it to be as seen in the image below.

有一点需要注意,PostEditChangeProperty()这个方法在编辑器需要用#ifdef来标记。这么写的原因就是在你的游戏需要这样的代码时才编译,移除任何多余的代码会提升你执行不必要的功能的效率(了解C++的朋友都知道这是预编译,网上讲解的博客有很多)。由于我们已经将代码编译完成,下面图片里的DamagePerSecond 属性已经和我们所预期的一样了。

Calling Functions across the C++ and Blueprint Boundary(通过C++和蓝图内部调用函数)

So far, we have shown how to expose properties to Blueprints, but there is 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 us 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.

UFCTION()宏将C++的函数句柄传递给了UE4的反射系统。蓝图唤醒选项又将他传递给蓝图的虚拟机系统。每一个蓝图暴露其方法都需要一个相关联的种类,所以右键点击显示的目录菜单才能正确的工作。下面的图片展示类型如何影响目录菜单的。

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 theUFUNCTION() 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.

这和我们先前添加的计算关联属性的方法是相同的。引擎中大部分的函数都是通过UFCTION()宏展示给蓝图的。所以人们就不用写C++代码就可以构建游戏。然而,最好的实现操作是使用C++构建整个游戏系统然后使用蓝图来表现游戏的具体行为或者通过C++构建的模块再创建复杂一点的操作。

Now that our designers can call our C++ code, let us 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 does not 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++函数的实现并且他知道该如何调用到蓝图的虚拟机。这通常涉及到形式转换。如果蓝图给没有给这个方法提供一个函数体,那么这个函数就会如C++函数所写的那样:什么也不做。如果你想提供一个C++的默认的实现而且还允许蓝图重载这个方法改怎么做?UFCTION()宏也有一个对应的选项。下面的代码段展示了要实现这个效果的代码改动。

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 is the implementation code for the declaration above.

这个版本仍然会生成形式转换的方法来调用蓝图的虚拟机。所以你怎么给函数提供默认的声明呢?虚幻引擎的工具本身也会生成一个像_Implementation的函数声明。所以你必须再提供这个版本的函数实现否则你的工程就会出现链接失败的错误。下面时这个声明的实现代码。

void AMyActor::CalledFromCpp_Implementation()
{
    // Do something cool here
}

Now this version of the function is called when the Blueprint in question does not 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.

如果蓝图没有覆盖这个函数,这个默认的版本就会被调用。有一点需要注意,之后的版本的构建工具将不会自动生成generated _Implementation()并且需要你准确的自己手动添加。在4.7版本中,这个自动的生成功能仍然会保留。

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

There are 4 main class types that you derive from for the majority of gameplay classes. They are UObjectAActorUActorComponent, and UStruct. Each of these building blocks are described in the following sections. Of course, you can create types that do not 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 the UObject hierarchy are: integrating 3rd party libraries; wrapping of OS specific features; etc.

在所有的和游戏进行相关的类里面(gameplay),有四个主要的基类来让你去继承,分别是UObject,AActor,UActorComponent,UStruct,他们几乎涵盖了所有的游戏内容。接下来会逐个对每个部分进行描述。当然,你完全可以创建一个不继承于上述任何的类的类。但是这样的话,你的类就不能实现引擎独有的一些特性。一些典型的没有使用UObject的类如下所示:第三方的库,一些系统特性封装的包等。
---------------------------------------------------------------------------------------------------------------------------------------------------------------

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:

  • Reflection of properties and methods

  • Serialization of properties

  • Garbage collection

  • Finding UObjects by name

  • Configurable values for properties

  • Networking support for properties and methods

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 does not involve directly deriving from UObjects, but instead from AActor and UActorComponent. You do not 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
 ●可以配置的属性值
 ●属性与方法的网络支持

每一个继承自UObject的类都有一个匹配的UClass宏,他包含了所有该类实例相关的数据与代码。UObject和UClass二者在游戏对象生命周期中是一切的基础。区别二者最好的方式就是UClass描述了一个UObject实例的样子,有什么特性可以用于序列化及网络传输。大部分的游戏开发不会直接涉及到UObject,反而AActor和UActorComponent会被大量使用。你并不需要很清楚UObject和UClass在代码里面是如何工作的但是有必要知道他们的存在。

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

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.

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 USceneComponentwhich contains the translation, rotation, and scale for that 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.

  • 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

See Actor for a more detailed discussion on AActor.

一个Actor是游戏进行中的一个实际存在的对象。Actor类既可以通过设计师在场景中创建也可以通过游戏系统在运行时创建。所有的对象都可以通过拓展后被放到一个关卡中。比如 AStaticMeshActor(静态网格Actor),ACameraActor(相机), and APointLight(点光源)等。Actor继承自UObject,所以Actor享有前面列举的所有Uobject的特性。Actor类型可以通过C++与蓝图精确的销毁,也可以在关卡卸载时被垃圾回收机制自动销毁。Actor本身负责游戏对象的高层次的行为,同时也是网络传输中被复制的主要类型。在网络中,只要Actor自身的ActorComponent组件需要网络支持,Actor就可以给其提供足够的信息。

Actor拥有他们自己的行为(尤其是通过继承),但同时他们也扮演者UActorComponent的容器的角色(尤其是通过组合)。这个是通过Actor的根节点成员来实现的,根节点本身可以包含其他的UActorComponent组件。既然Actor需要放在一个关卡中,那么他就必须包含一个USceneComponent组件来记录他的运动位置,旋转,大小比例。

Actor在他的生命周期中有一系列可以触发的事件。下面简单的列举了几个例子:

  • BeginPlay - 当Actor第一次以实体进入场景中时触发

  • Tick -在场景中的每一帧都触发

  • EndPlay -当Actor实体离开场景(被销毁等)触发

查看Actor 来浏览更多相关的信息,


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

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.

我们上面刚刚讨论了一个Actor的生命周期的一部分。对于存在于关卡当中的actor,他的生命周期还是比较容易理解的:actor一开始被加载到场景里面并且在场景卸载的时候被销毁。那么在运行时的创建和销毁流程是什么样的呢?虚幻引擎在运行时执行spawning(生成)时调用actor的创建方法。生成一个actor比在游戏同创建一个object对象复杂一点。原因就是actor需要被注册到一系列的运行时系统来满足自身的需求。actor的位置和朝向需要一开始就被初始化。物理模块也需要了解他的相关信息。管理器需要负责告诉一个actor在tick时需要知道哪些东西。这些都需要actor与运行时系统紧密相连。既然我们不得不这么做,引擎肯定就会提供一个生成actor的方法,那就是UWorld::SpawnActor()。一旦生成执行成功,他的方法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.

To learn more about spawning actors see the 生成 Actors page.

一旦一个actor离开了他的生命周期,你可以通过调用Destroy()方法来销毁他。在这个过程中,EndPlay() 将会被调用,你可以在里面写任何和销毁相关的客户逻辑。另一种来控制actor生存时间的方法就是使用Lifespan(生存时间)成员。你可以在object的构造器或者在其他的代码里设置一个时间间隔。一旦这个时间耗尽,这个actor就会自动的调用Destroy()方法。

想知道更多关于生存actor的内容请参考生成 Actors 页面。

UActorComponent(Actor组件)

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.

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

  • 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()

UActorComponents 有他们自己的行为并且通常负责分享多种actor的特有功能。例如,提供可见的网格实体,粒子效果,摄像机视角以及物理交互等。如果说actor在游戏中通常被赋予其相关角色高层次的任务和目标,那么每个ActorComponent就是表现自己独立的效果和任务从而支持那些高层次的目标。组件可以和组件相关联,也可以成为一个actor的根节点。一个组件只能挂在一个组件或者一个actor下面,但是他自身可以挂任意数量的组件。这个感觉就像是一个有组件而构成的树。子组件有位置,朝向,大小等信息,这些信息被关联到他的父组件和父actor(注意:这里所说的父组件只是单纯的从数据结构的方式来描述其位置关系,并不是C++里面的继承关系

有很多使用actor和Component的方式,其中一种理解Actors-Component关系的方式就是Actor会解释“这是什么?”而Component会解释“这由什么组成”
  • 根组件 - 这是actor的根节点成员,下面关联着actor整棵子树 

  • Ticking - 组件作为其附属actor的成员也会随着actor的ticking来进行自己的tick


Dissecting the First Person Character(剖析第一人称角色)

Over the last few sections we have done a lot of talking and not a lot of showing. In order to illustrate the relationship of an AActor and its UActorComponents, let us 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 和其 UActorComponent 之间的关系,我们来研究基于第一人称模板创建新项目时创建的蓝图。下图是FirstPersonCharacter Actor 的组件树。根节点RootComponent 为 CapsuleComponent。附着到 CapsuleComponent 的是 ArrowComponentMesh 组件和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 Meshcomponent.

从可是的效果来看,组件树展示的就如下面的图片一样,可看到除 Mesh 组件外的所有组件均在 3D 空间中。

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

Now that we have 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.

要使用虚幻4提供的一个结构体UStruct,你不需要从任何具体的类里面扩展,只要在结构体前面只需要使用 USTRUCT() 标记,编译工具就会执行基础工作。与UOBject不同,UStruct没有垃圾回收机制。如果你创建一个动态的实例,那么你必须要自己控制好他的生命周期。UStruct 为纯旧式数据类型。它们拥有 UObject 反射支持,以便在虚幻编辑器、蓝图操作、序列化和网络通信中进行编辑。

到此为止,我们已经讨论了游戏性类构建中使用的基础层级。下面你可以再次选择你的学习路线。你可以在 此处 阅读关于游戏性类的内容、使用 launcher 中带有更多信息的例子、或进一步深入研究构建游戏的 C++ 特性。

参考:Introduction to C++Programming in UE4-官方文档翻译与理解(二)

来自:http://blog.csdn.net/u012999985/article/details/51295225

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引