为Unity开发者准备的虚幻引擎4指南

将Unity的知识快速应用到虚幻4中。

Windows
MacOS
Linux

image_0.png

本指南将从 Unity 用户的视角来介绍虚幻4,并帮助你将 Unity 的开发经验应用到虚幻 4 的世界中。

编辑器

下面分别是 Unity 编辑器和虚幻编辑器的截图,我们用颜色标出了界面中的不同区域,并用相同颜色标出了拥有相同功能的区域。每个区域上还添加了名称,以便你了解它们在虚幻引擎语境中的称呼。虚幻编辑器支持自定义布局,你可以通过拖动各个窗口来移动它们。

EditorCompare_Windows.png

EditorCompare_Mac.png

编辑资产

在 Unity 中,用户使用 Inspector 选项卡来编辑当前选中的资产。在虚幻 4 中,我们使用 细节 面板来展示当前选中对象的属性,比较复杂的编辑工作则有专门的窗口或选项卡来处理。每编辑一项资产,都会单独打开一个带有选项卡的窗口,类似于网页浏览器。当然这窗口也可以任意拖拽,或者悬浮在其他窗口之上作为独立窗口显示。

TabbedWindows_Windows.png

TabbedWindows_Mac.png

术语简表

下表左侧是 Unity 中的常见术语,右侧则是对应的(或差不多的)虚幻 4 术语。虚幻 4 的关键词直接链接到更进一步的虚幻在线文档中。

分类

Unity

虚幻 4

游戏内容类型

Component

组件

GameObject

Actor , Pawn

Prefab

蓝图类

编辑器界面

Hierarchy Panel

世界大纲

Inspector

细节面板

Project Browser

内容浏览器

Scene View

视口

网格物体

Mesh

静态网格物体

Skinned Mesh

骨骼网格物体

材质

Shader

材质 , 材质编辑器

Material

材质实例

特效

Particle Effect

特效,粒子,级联

Shuriken

级联

游戏界面

UI

UMG (虚幻示意图行 Unreal Motion Graphics)

动画

Animation

骨骼动画系统

Mecanim

Persona , 动画蓝图

2D

Sprite Editor

Paper2D

编程

C#

C++

Script

蓝图

物理

Raycast

Line Trace, Shape Trace

Rigid Body

碰撞,物理

运行平台

iOS Player, Web Player

支持的平台

项目文件和文件

怎么理解项目中的目录和文件?

和 Unity 项目一样,虚幻项目也保存在专门的目录结构中,并且有着自己的项目文件。你可以 双击 .uproject 文件打开虚幻编辑器并加载该项目,或者 点击右键 查看更多选项。项目目录包含不同子目录,保存了游戏的资产内容和源代码,以及各种配置文件和二进制文件。其中最重要的就是 Content 子目录和 Source 子目录。

我的资产应该放在哪里?

在虚幻 4 中,每个项目都有一个 Content 文件夹。它类似于 Unity 项目的 Asset 目录,是你保存游戏资产的地方。假如你要在游戏中导入资产,只需要将资产拷贝到 Content 目录,它们便会自动导入并出现在 内容浏览器 中。当使用外部程序修改这些资产时,编辑器中的资产也会自动更新。

ProjectOnDisk_Windows.png

ProjectOnDisk_Mac.png

支持哪些常见文件格式?

Unity 支持很多文件格式。虚幻 4 也支持最常见的文件格式,如下表:

资产类型

支持的格式

3D

.fbx, .obj

贴图

.png, .jpeg, .bmp ,.tga, .dds, .exr, .psd, .hdr

声音

.wav

字体

.ttf, .otf

视频

.mov, .mp4, .wmv

场景是如何保存的?

在 Unity 中,你把 GameObjects 放置在场景中,然后将场景(Scene)保存为场景资产文件。虚幻使用 地图文件(Map file),它类似于 Unity 中的场景。地图文件保存了 关卡 的数据、关卡中的对象,光照数据,以及某些关卡特定的设置信息。

如何修改项目设置?

所有项目设置都可以在主菜单的 编辑(Edit) / 项目设置(Project Settings) 中找到。和 Unity 的设置类似,它们允许你设置项目信息(比如项目名称和图标),配置游戏输入绑定,定义运行项目时引擎的行为。你可以在这里 了解更多关于项目设置的单独信息。Unity 还有叫做 "玩家设置" 的部分,在虚幻中,我们叫 "平台设置",你可以在项目设置的 "平台" 分类里找到该设置。

源文件在哪里?

在 Unity 中,用户习惯将 C# 的源文件放在资产目录中。

虚幻 4 的工作机制有点不同。假如项目包含 C++ 代码,你会在项目目录中找到一个 Source 子目录,其中包含各种文件,包括 C++ 源文件(.cpp)和头文件(.h),以及一些编译用的脚本(.Build.cs,.Target.cs)。不过,如果项目只包含蓝图,则项目中没有 Source 目录。

在虚幻 4 中使用 C++ 的最简单做法就是通过编辑器菜单 为项目添加代码(Add Code To Project)(在主菜单的文件菜单中),或者直接用某个模板新建一个 C++ 项目。你可以在 内容浏览器 中直接找到 C++ 类,双击它们能直接在 Visual Studio 或 Xcode 中打开该文件。

从 GameObjects 到 Actors

GameObject 去哪里了?

在 Unity 中, GameObject 是可以放置在地图中的 "东西"。虚幻 4 中对应的概念是 Actor。在虚幻编辑器中,你可以从放置面板直接拖一个空的 Actor 放置到场景中:

你虽然可以通过空 Actor 来制作游戏,但虚幻 4 提供了各类特殊 Actor,并预制了它们的特性,比如 Pawn(用于作为玩家或者 AI 的角色),或者 Character(用于会做动作的生物)。和空 Actor 一样,你可以直接将它们拖拽至场景中,并给它们添加组件,或自定义属性。你之后会学习到更多相关内容,但目前你只需了解虚幻 4 采用 (Gameplay 框架)[(InteractiveExperiences/Framework)] 来协同各种特殊的 Actor 一起工作。

虚幻 4 中的 Actor 和 Unity 中的 GameObjects 稍有不同。在 Unity 中,GameObject 属于 C# 类,并且无法直接扩展。在虚幻 4 中,Actor 属于 C++ 类,而且允许你通过继承来拓展和自定义。关于这点,我们以后会具体介绍!

组件在哪里?

在 Unity 中,你可以通过为 GameObject 添加组件来赋予其特定的功能。

在虚幻 4 中,你也可以为 Actor 添加组件。在关卡中放置一个空 Actor 后,点击"添加组件"按钮(位于 细节 面板中),然后选择一个组件来添加。这里让我们创建一把火炬:首先放置一个空 Actor,然后添加一个网格体组件作为基座,再添加一个光源和粒子系统作为它的火焰。

在 Unity 中,GameObject 以列表形式保存组件,但在虚幻 4 中, Actor 以 层级 形式保存它的组件,并且组件之间相互绑定。你可以在上面的例子中看到,光源和粒子与网格模型相互绑定。之后在 复合 Actor 和 复合 GameObject 中会对此进行讨论。

从 Unity 的 Prefabs 到虚幻 4 的蓝图类

Unity 的工作流程是基于 预制件(prefab) 的。在 Unity 中,你一般是创建一系列带有组件的 GameObject,然后基于它们生成 Prefab。然后你在场景中放置 Prefab 的实例,或者在运行时将它们实例化。

虚幻 4 则基于蓝图类来工作。在虚幻 4 中,你一般是创建一个带有组件的 Actor,然后选中它并点击 蓝图 / 添加脚本(Blueprint / Add Script) 按钮(位于 细节 面板中)。然后选择一个位置保存你的蓝图类,点击 创建蓝图(Create Blueprint) 来保存你新建的蓝图!

新建的蓝图类可以在 内容浏览器 中找到。你可以直接 双击 打开编辑它们,也可以将它们拖拽到任意场景关卡中。

Unity中的 Script 组件和 MonoBehaviour 去哪里了?

在 Unity 中,你通过为 GameObject 添加脚本(Script)组件来添加 C# 脚本内容。你通过创建继承自 MonoBehavior 的类来定义脚本组件的功能。

虚幻 4 也有类似的内容。你可以自由创建全新的组件类,并将它应用于任意 Actor。组件类可以使用蓝图脚本创建,也可以用 C++ 创建。

那么在虚幻 4 中如何创建自己的组件类呢?在 细节 面板中,添加组件(Add Component)的下拉框中,可以看到,你可以新建组件,或者选择已经存在的组件:

image alt text

在 Unity 中,每当你新建一个继承自 MonoBahaviour 的类,你都会得到一个模板文件,其中包含 Start() 函数和 Update() 函数。

在虚幻 4 中,也有一个充当模板的类,其中包含 InitializeComponent() 函数和 TickComponent() 函数,它们和 Start、Update 具有类似的行为。

如果你创建的是蓝图脚本组件,则会看到类似的函数由可视化节点的形式展现:

image alt text

可编辑脚本的 Actor 蓝图类

虚幻 4 有一项很酷的功能:新建的 Actor 蓝图类会拥有自己的可视化蓝图脚本!这样你就能为整个对象添加逻辑,而不仅仅是单个组件。结合继承结构关系(稍后下文会解释),这会为你在设计游戏时提供很多灵活性。

除了支持蓝图可视化脚本,虚幻 4 还支持通过 C++ 代码来实现功能。 这里对比了Unity和UE4中的代码,还给出了对应的蓝图实现:

Unity C#

UE4 C++

`

using UnityEngine;
using System.Collections;

public class MyComponent : MonoBehaviour
{
    int Count;

    // 使用此函数来初始化。
    void Start ()
    {
        Count = 0;
    }

    // Update函数会每帧更新。
    void Update () 
    {

        Count = Count + 1;
        Debug.Log(Count);
        }
    }`

`

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

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
    int Count;

    // 为此Actor的属性设置默认值。
    AMyActor() 
    {
        // Allows Tick() to be called
        PrimaryActorTick.bCanEverTick = true;  
    }

    // 当游戏开始或此Actor生成时调用。
    void BeginPlay()
    {
        Super::BeginPlay();
        Count = 0;
    }

    // 每帧调用。
    void Tick(float DeltaSeconds)
    {
        Super::Tick(DeltaSeconds);
        Count = Count + 1;
        GLog->Log(FString::FromInt(Count));
        }
    };`

UE4 蓝图

(image_28.png)

虚幻 4 蓝图类的扩展性

Unity 的 prefab 和虚幻 4 的蓝图类都可以在游戏中实例化。不过,Unity 的 prefab 在相互嵌套时会产生比较复杂的状况,从而限制其可扩展性。

在虚幻 4 中,你可以让新建的蓝图类继承某个现有的蓝图类,然后通过新的属性、组件和可视化脚本功能来增强它。

比如,在虚幻 4 中,你可以创建一个叫做 Monster 的蓝图类,用它实现基本的怪物功能,比如追击人类。然后你可以再创建一个蓝图类来扩展它,比如 Dragon(会喷火的怪物),或者 Grue(天黑后会吃人的怪物),以及其他 8 种类型。这些 Monster 子类都继承了 Monster 父类的功能,并在此基础上获得了新的能力。

在 Unity 中,你需要创建很多不同的 GameObject prefab 才能实现这点:你要为 Dragon 创建一个prefab,为 Grue 创建一个prefab,诸如此类。假设你希望为所有怪物都添加某个功能,比如添加一个 Speak 组件来让它们说话,在 Unity 中你需要更新所有 10 个 prefab,把功能拷贝粘贴到每个 prefab 中。

在虚幻 4 中,你只需简单地修改 Monster 的蓝图类,为它新增 Speak 的能力,然后就OK了!Dragon,Grue 以及其他 8 种 Monster 的子类都会自动继承这个说话的新功能,并不需要你去一一修改。

好处还不止这些!我们关于蓝图类所说的一切,同样适用于 C++ 类,也同样对 Actor 和 组件 适用。这些系统从设计之初就能支持大规模开发可扩展功能,可以为 10 个开发人员的项目服务,也可以为 100 个人的项目服务。

那么应该用蓝图脚本还是 C++ 代码呢?或者同时使用?

蓝图可视化脚本适合实现简单的游戏逻辑以及按顺序进行的事件。这个系统对策划、美术以及面向可视化编程的程序员是非常好用的,它能以可视化的方式轻松访问并管理游戏内对象。你完全可以仅凭蓝图完成一个游戏的制作。请参考 Tappy Chicken 示例,它是一个完整的范例。

C++ 编程适合用于大型任务,比如编写某个游戏逻辑系统,制作复杂的 AI 行为,或者新增引擎功能。对于已经掌握了 C++ 技能的开发人员来说,可以翻阅一下 在虚幻 4 中的 C++ 编程简述 页面。

大部分的项目都适合混合使用蓝图和 C++。很多开发者都用蓝图来创作游戏原型,因为这个过程容易而且有趣,但之后出于性能优化、项目优化等考量,会把它们迁移到 C++ 中。

蓝图类也能扩展 C++ 类

虚幻 4 的游戏开发中很多令人着迷的过程来存在于程序员用 C++ 实现新的功能,而策划和美术在蓝图中使用这些功能,并提出更多要求!下图是针对一个虚幻 4 的射击游戏项目中实现拾取物品过程时,团队的一种形式,混合了 C++ 类的编程实现,以及蓝图类用于处理行为和表现。

image alt text

变换(Transform)组件

在 Unity 中,每个 GameObject 都有一个变换组件(Transform Component),用于指定该 GameObject 在世界中的位置、旋转度以及缩放比例。

虚幻 4 也一样,Actor 有一个 Root Component,能够作为场景组件的任意子类。场景组件(Scene Component) 指定了 Actor 在世界中的位置、角度及缩放比例,而这些属性会影响该 Actor 的所有子对象。很多有用的组件都是场景组件的子类,因此让它们具有位置信息是非常有用的!

即便你只放置一个空 Actor,虚幻 4 也会为它创建一个"默认场景根(Default Scene Root)"对象,这是一个最简单的场景组件。当你放置一个新的场景组件时,默认场景根对象会被替换掉。

复合对象

在 Unity 中,你可以通过构建 GameObject 层级以及绑定它们的变换组件来创建复合对象。

image alt text

在虚幻 4 中,你可以通过让组件按照层级关系相互嵌套,来创建复合游戏对象。

image alt text

从上图可以看到,你可以让场景组件互相附加来得到一个相互嵌套的层级关系,因为它们都有变换——这点类似于 Unity 中的变换绑定。Actor 组件(所有组件的基类)只能直接附加到 Actor 自己身上。

我是否应该用组件来创造其他一切?

其实这完全取决于你自己,大部分情况下,你应该是结合使用 Actor 类和一些自定义组件。我们先前已经提到过,虚幻 4 已经提供了一些特殊类型的 Actor ,它们附带一定功能,并且包含某些组件。比如一个 Character 总是会包含一个 Character Movement 组件

引擎中你会很快遇到一些继承自 Actor 的子类,大部分游戏都会用到它们。这里列出了一些与 Actor 相关的最常见的类:

  • Pawn - Actor 的一种类型,用于表现一个可供控制的游戏对象,比如玩家操控的角色。玩家或者 AI 通常通过 Controller 来控制并移动 Pawn。

  • Character - 一种特殊类型的 Pawn,用于双足类型的角色,并具备一些复杂的功能。

  • Controller - 占有并控制一个 Pawn。通过将 Pawn 和 Controller 的分离,我们可以编写 AI Controller,用于控制 Pawn,并且和玩家控制 Pawn 采用相同的接口。

  • Player Controller - 一个更为特殊的 Controller,用于从玩家的手柄、触控板、鼠标/键盘获得输入信息,并将这些信息驱动玩家所控制的 Pawn 或者 Character 的行为。

那么所有的东西都是 Actor 咯?

并非如此。Actor 是虚幻 4 中最常见的用于游戏的类,并是唯一能够在 世界生成 的类。因此任何能放置在关卡中的对象都属于 Actor。

另外一个需要知道的关键类是 Object。Object 实际上是所有虚幻引擎的类的基类,包括 Actor 以及其他一些类都是如此。这是一个比 Actor 更加底层的类,但作为虚幻中的类,它仍拥有一些基本功能,比如 反射序列化。Object 是一个非常基础的类,当我们需要定义一个新的类但又并非 Actor 的时候会使用它。比如 Actor Component 是所有组件的基类,而它则是继承 Object 而非 Actor。

虚幻 4 中 Gameplay 框架是什么东西?

好吧,从这里开始事情就开始变得有趣了(酷炫的方向)。Unity 提供了一个干净的框架用于设计游戏,虚幻也做了同样的事情。Unity 中你可以基于 GameObjects 和组件来创建所有东西,而在虚幻中则是基于 Actor 和组件来创建。

不过,虚幻在顶层提供了叫做 Gameplay 框架 的机制,而 Unity 完全没有这类内容。虽然做游戏并非一定要用这个框架,但如果用的话会非常酷!简单来说,假如你能利用一些基础类,并且遵循特定规范,你就能很容易获得一些很赞的功能,否则你自己实现可能要花费很多时间,而且也很困难,或者很难改造。(比如完整的多人游戏支持!)

已有大量的酷炫游戏在开发时采用了虚幻的 Gameplay 框架,所以它很值得你花点时间来了解。没错,你可以自己实现这类框架,这么做完全没问题!但虚幻 4 当前已有数以百计的炫酷开发者在使用它,因此我们花点时间来了解一下。

要使用 Gameplay 框架,你只需要了解一些预制的 Actor 类,比如 PawnCharacter,和 Player Controller,并逐步了解虚幻的网络复制和网络其他功能。现在我们先回到最基本的部分。

如何在虚幻 4 中编写代码

写给 MonoDevelop 的程序员

假如是编写蓝图,你只需要用到虚幻编辑器——所有工具都已包括在内!假如要编写 C++ 代码,你还要在 Windows 平台下载 Visual Studio 的免费版本,如果是 Mac 的话则需要安装 Xcode。当第一次创建一个新项目(或者为已有项目添加代码)时,虚幻 4 将会自动创建 Visual Studio 项目文件。你只需要在 内容浏览器 中双击一个 C++ 的类便能在 Visual Studio 中打开它,或者在主菜单的文件菜单中点击 Open Visual Studio

image alt text

虚幻 4 有一点很不同:有时必须手动更新 Visual Studio 项目文件(比如,下载了 UE4 的新版本,或者对源代码文件的磁盘存放位置做了人为改变)。你可以通过在主菜单中点击 Refresh Visual Studio Project 或者在项目目录中 右键点击 .uproject 文件 并选择 Generate Visual Studio project files 便可。

image alt text

编写事件函数(Start,Update 等)

如果你用过 MonoBehaviors,那你一定熟悉 Start,Update 和 OnDestroy 这类方法。以下是对 Unity 的行为和对应的虚幻 4 的 Actor 和组件的比较。

在 Unity 中,某个基本组件的代码可能是这样:

public class MyComponent : MonoBehaviour
{
    void Start() {}
    void OnDestroy() {}
    void Update() {}
}

请记住,在虚幻 4 中,你可以直接为 Actor 写代码而无需创建新的组件来添加代码。这其实是很常见并有意义的。

类似于 Unity 的 Start,OnDestroy 和 Update 函数,虚幻 4 在 Actor 中有类似的方法:

C++

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

    // 游戏开始时调用。
    void BeginPlay();

    // 当此Actor销毁时调用。
    void EndPlay(const EEndPlayReason::Type EndPlayReason);

    // 每帧调用,用于更新此Actor。
    void Tick(float DeltaSeconds);
};

蓝图

image_29.png

在虚幻 4 中,组件使用不同的函数。以下是示例:

C++

UCLASS()
class UMyComponent : public UActorComponent
{
    GENERATED_BODY()

    // 当所属Actor创建时调用
    void InitializeComponent();

    // 当组件或所属Actor销毁时调用
    void UninitializeComponent();

    // 组件版的Tick函数
    void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction);
};

蓝图

image_30.png

注意,在虚幻 4 中调用基类方法很重要。

比如,在 Unity C# 中可能是调用 base.Update(),但在虚幻 4 的 C++ 中我们使用 Super::TickComponent():

void UMyComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    // 此处添加自定义更新内容
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}

你也许已经注意到 C++ 中有些类以 "A" 开头,而其他一些类以 "U" 开头。前缀 "A" 代表它是 Actor 的子类,而前缀 "U" 代表它是 Object 的子类。还有其他一些前缀,比如 "F" 通常表示一个简单的数据结构类,或者其他非 Uboject 类。

在虚幻 4 中编写游戏逻辑代码

好了,现在开始稍微深入一些。我们将探讨游戏创建相关的关键编程话题。因为你了解 Unity,我们将从 C# 用户的角度来解释 C++ 的功能,当然你也可以使用蓝图来完成几乎所有的事情!我们会尽可能提供用 C++ 和蓝图编写的范例。

先说一下一些通用的游戏逻辑编程模式,以及如何在虚幻中实现它们。许多 Unity 中的函数在虚幻中都有类似的函数。我们先从最常见的开始。

实例化 GameObject / 生成 Actor

在 Unity 中,我们使用 Instantiate 函数来新建对象的实例。

该函数可以获取任意一种 UnityEngine.Object 类型(GameObject,MonoBehaviour 等),并创建它的拷贝。

public GameObject EnemyPrefab;
public Vector3 SpawnPosition;
public Quaternion SpawnRotation;

void Start()
{
    GameObject NewGO = (GameObject)Instantiate(EnemyPrefab, SpawnPosition, SpawnRotation);
    NewGO.name = "MyNewGameObject";
}

在虚幻 4 中,根据不同的需要,你需要用一些不同的函数来实例化对象。NewObject 用于新建 UObject 类型的实例,而 SpawnActor 用于新建 AActor 类型的实例。

首先我们简单说下 UObject 和 NewObject。在虚幻中, 子类化 UObject 很像在 Unity 中子类化 ScriptableObject。它们适用于实现一些不需要在游戏中生成的类,或者不需要像Actor那样添加组件的类。

在 Unity 中,如果要创建自己的 ScriptableObject 子类,你可以这样实例化:

MyScriptableObject NewSO = ScriptableObject.CreateInstance<MyScriptableObject>();

在虚幻中,如果要创建 UObject 的继承类,是像下面这样的初始化:

UMyObject* NewObj = NewObject<UMyObject>();

那么 Actor 呢?Actor 在World(C++ 中的 UWorld)对象中生成是通过 SpawnActor 方法。如何获取 World 对象?有些 UObject 会提供一个 GetWorld 的方法,所有的 Actor 都具有这个方法。

你会发现,相比传入另一个 Actor,我们会传入要生成的 Actor 的 "class"。在我们的范例中,"class" 可以是 AMyEnemy 类的任意子类。

但如果你想创建某个对象的"拷贝",就像 Unity 的 Instantiate 函数那样,你该怎么做呢?

NewObject 和 SpawnActor 函数也能通过给一个 "模板" 对象来工作。虚幻引擎会创建该对象的拷贝,而不是"从零创建一个新的对象"。这会拷贝该对象的所有属性(UPROPERTY)和组件。

AMyActor* CreateCloneOfMyActor(AMyActor* ExistingActor, FVector SpawnLocation, FRotator SpawnRotation)
{
    UWorld* World = ExistingActor->GetWorld();
    FActorSpawnParameters SpawnParams;
    SpawnParams.Template = ExistingActor;
    World->SpawnActor<AMyActor>(ExistingActor->GetClass(), SpawnLocation, SpawnRotation, SpawnParams);
}

你也许想知道"从零开始创建"在这里具体是什么意思。每个对象类在创建时都有一个默认模板,包含了它的默认属性和组件。在创建时如果你并不想修改这些默认属性,也没有提供你自己的模板,虚幻将使用这些默认值来创建该对象。为了更好的说明这个,我们先来看一下 MonoBehaviour 的例子:

public class MyComponent : MonoBehaviour
{
    public int MyIntProp = 42;
    public SphereCollider MyCollisionComp = null;

    void Start()
    {
        // 创建碰撞组件(如果还未创建的话)
        if (MyCollisionComp == null)
        {
            MyCollisionComp = gameObject.AddComponent<SphereCollider>();
            MyCollisionComp.center = Vector3.zero;
            MyCollisionComp.radius = 20.0f;
        }
    }
}

在上面这个例子中,有一个 int 属性,默认是 42,还有一个 SphereCollider 组件,默认半径是 20。

在虚幻 4 中,我们可以利用对象的构造函数达到同样的效果。

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

    UPROPERTY()
    int32 MyIntProp;

    UPROPERTY()
    USphereComponent* MyCollisionComp;

    AMyActor()
    {
        MyIntProp = 42;

        MyCollisionComp = CreateDefaultSubobject<USphereComponent>(FName(TEXT("CollisionComponent"));
        MyCollisionComp->RelativeLocation = FVector::ZeroVector;
        MyCollisionComp->SphereRadius = 20.0f;
    }
};

在 AMyActor 的构造函数中,我们为这个类设置了属性的默认值。请注意 CreateDefaultSubobject 函数。我们可以用它来创建组件并赋予组件默认值。我们使用这个函数创建的所有子对象都作为默认模板,所以我们可以在子类或蓝图中修改它们。

类型转换

在这个例子中,我们获取了一个已知的组件,将它转换为一个特定类型,然后判断能否执行一些操作。

Unity C#:

Collider collider = gameObject.GetComponent<Collider>;
SphereCollider sphereCollider = collider as SphereCollider;
if (sphereCollider != null)
{
        // ...
}

虚幻 4 C++:

UPrimitiveComponent* Primitive = MyActor->GetComponentByClass(UPrimitiveComponent::StaticClass());
USphereComponent* SphereCollider = Cast<USphereComponent>(Primitive);
if (SphereCollider != nullptr)
{
        // ...
}

销毁 GameObject / Actor

Unity

C++

蓝图

Destroy(MyGameObject);

MyActor->Destroy();

image_23.png

销毁 GameObject / Actor (1 秒延迟)

Unity

C++

蓝图

Destroy(MyGameObject, 1);

MyActor->SetLifeSpan(1);

点击查看大图。

禁用 GameObjects / Actors

Unity

C++

Blueprint

MyGameObject.SetActive(false);

// Hides visible components

MyActor->SetActorHiddenInGame(true);

// Disables collision components
MyActor->SetActorEnableCollision(false);

// Stops the Actor from ticking
MyActor->SetActorTickEnabled(false);

点击查看大图。

通过组件访问 GameObject / Actor

Unity

C++

蓝图

GameObject ParentGO =

MyComponent.gameObject;

AActor* ParentActor =

 MyComponent->GetOwner();

点击查看大图。

通过 GameObject / Actor 访问组件

Unity

MyComponent MyComp = gameObject.GetComponent<MyComponent>();

C++

UMyComponent* MyComp = MyActor->FindComponentByClass<UMyComponent>();

蓝图

image_33.png

查找 GameObjects / Actors

// Find GameObject by name
GameObject MyGO = GameObject.Find("MyNamedGameObject");

// Find Objects by type
MyComponent[] Components = Object.FindObjectsOfType(typeof(MyComponent)) as MyComponent[];
foreach (MyComponent Component in Components)
{
        // ...
}

// Find GameObjects by tag
GameObject[] GameObjects = GameObject.FindGameObjectsWithTag("MyTag");
foreach (GameObject GO in GameObjects)
{
        // ...
}

// Find Actor by name (also works on UObjects)
AActor* MyActor = FindObject<AActor>(nullptr, TEXT("MyNamedActor"));

// Find Actors by type (needs a UWorld object)
for (TActorIterator<AMyActor> It(GetWorld()); It; ++It)
{
        AMyActor* MyActor = *It;
        // ...
}

image alt text

// Find UObjects by type
for (TObjectIterator<UMyObject> It; It; ++it)
{
    UMyObject* MyObject = *It;
    // ...
}

// Find Actors by tag (also works on ActorComponents, use TObjectIterator instead)
for (TActorIterator<AActor> It(GetWorld()); It; ++It)
{
    AActor* Actor = *It;
    if (Actor->ActorHasTag(FName(TEXT("Mytag"))))
    {
        // ...
    }
}

image alt text

为 GameObjects / Actors 添加标签

MyGameObject.tag = "MyTag";

// Actors can have multiple tags
MyActor.Tags.AddUnique(TEXT("MyTag"));

image alt text

为 MonoBehaviours / ActorComponents 添加标签

// This changes the tag on the GameObject it is attached to
MyComponent.tag = "MyTag";

// Components have their own array of tags
MyComponent.ComponentTags.AddUnique(TEXT("MyTag"));

比较 GameObjects / Actors 和 MonoBehaviours / ActorComponents 的标签

if (MyGameObject.CompareTag("MyTag"))
{
    // ...
}

// Checks the tag on the GameObject it is attached to
if (MyComponent.CompareTag("MyTag"))
{
    // ...
}

// Checks if an Actor has this tag
if (MyActor->ActorHasTag(FName(TEXT("MyTag"))))
{
    // ...
}

image alt text

// Checks if an ActorComponent has this tag
if (MyComponent->ComponentHasTag(FName(TEXT("MyTag"))))
{
    // ...
}

image alt text

物理:刚体 vs. 图元(Primitive)组件

在 Unity 中,假如要给一个 GameObject 赋予物理特性,需要给它添加一个刚体组件。在虚幻中,任何图元组件(C++ 中为 UPrimitiveComponent)都可以是物理对象。一些通用的图元组件,比如 ShapeComponent(胶囊形,球形,盒形),StaticMeshComponent,以及 SkeletalMeshComponent。

和 Unity 不同,Unity 将碰撞和可视性划分到不同的组件中。而虚幻则将潜在的物理碰撞和潜在的可视效果组合到了 PrimitiveComponent 中。任何在世界中具有形状的物体,要么就是能够被渲染显示,要么就是能和继承自 PrimitiveComponent 的物理子类交互。

层 vs 通道

在 Unity 中,它们被称为"层(Layer)"。虚幻则称之为碰撞通道(Collision Channel),它们是类似的概念。你可以在 此处 了解更多内容。

RayCast vs RayTrace

Unity C#:

GameObject FindGOCameraIsLookingAt()
{
    Vector3 Start = Camera.main.transform.position;
    Vector3 Direction = Camera.main.transform.forward;
    float Distance = 100.0f;
    int LayerBitMask = 1 << LayerMask.NameToLayer("Pawn");

    RaycastHit Hit;
    bool bHit = Physics.Raycast(Start, Direction, out Hit, Distance, LayerBitMask);

    if (bHit)
    {
        return Hit.collider.gameObject;
    }

    return null;
}

虚幻 4 C++:

APawn* AMyPlayerController::FindPawnCameraIsLookingAt()
{
    // You can use this to customize various properties about the trace
    FCollisionQueryParams Params;
    // Ignore the player's pawn
    Params.AddIgnoredActor(GetPawn());

    // The hit result gets populated by the line trace
    FHitResult Hit;

    // Raycast out from the camera, only collide with pawns (they are on the ECC_Pawn collision channel)
    FVector Start = PlayerCameraManager->GetCameraLocation();
    FVector End = Start + (PlayerCameraManager->GetCameraRotation().Vector() * 1000.0f);
    bool bHit = GetWorld()->LineTraceSingle(Hit, Start, End, ECC_Pawn, Params);

    if (bHit)
    {
        // Hit.Actor contains a weak pointer to the Actor that the trace hit
        return Cast<APawn>(Hit.Actor.Get());
    }

    return nullptr;
}

虚幻 4 蓝图:

点击查看大图。

触发器

Unity C#:

public class MyComponent : MonoBehaviour
{
    void Start()
    {
        collider.isTrigger = true;
    }
    void OnTriggerEnter(Collider Other)
    {
        // ...
    }
    void OnTriggerExit(Collider Other)
    {
        // ...
    }
}

虚幻 4 C++:

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

    // My trigger component
    UPROPERTY()
    UPrimitiveComponent* Trigger;

    AMyActor()
    {
        Trigger = CreateDefaultSubobject<USphereComponent>(TEXT("TriggerCollider"));

        // Both colliders need to have this set to true for events to fire
        Trigger.bGenerateOverlapEvents = true;

        // Set the collision mode for the collider
        // This mode will only enable the collider for raycasts, sweeps, and overlaps
        Trigger.SetCollisionEnabled(ECollisionEnabled::QueryOnly);
    }

    virtual void NotifyActorBeginOverlap(AActor* Other) override;

    virtual void NotifyActorEndOverlap(AActor* Other) override;
};

虚幻 4 蓝图:

image alt text

你还可以在 这里 进一步了解如何设置碰撞响应。

刚体运动(Kinematic Rigidbodies)

Unity C#:

public class MyComponent : MonoBehaviour
{
    void Start()
    {
        rigidbody.isKinimatic = true;
        rigidbody.velocity = transform.forward * 10.0f;
    }
}

在虚幻 4 中,碰撞组件和刚体组件是同一个组件,它的基类是 UPrimitiveComponent,这个基类有很多不同的子类(USphereComponent,UCapsuleComponent 等)来配合不同的需求。

虚幻 4 C++:

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

    UPROPERTY()
    UPrimitiveComponent* PhysicalComp;

    AMyActor()
    {
        PhysicalComp = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionAndPhysics"));
        PhysicalComp->SetSimulatePhysics(false);
        PhysicalComp->SetPhysicsLinearVelocity(GetActorRotation().Vector() * 100.0f);
    }
};

输入事件

Unity C#:

public class MyPlayerController : MonoBehaviour
{
    void Update()
    {
        if (Input.GetButtonDown("Fire"))
        {
            // ...
        }
        float Horiz = Input.GetAxis("Horizontal");
        float Vert = Input.GetAxis("Vertical");
        // ...
    }
}

虚幻 4 C++:

UCLASS()
class AMyPlayerController : public APlayerController
{
    GENERATED_BODY()

    void SetupInputComponent()
    {
        Super::SetupInputComponent();

        InputComponent->BindAction("Fire", IE_Pressed, this, &AMyPlayerController::HandleFireInputEvent);
        InputComponent->BindAxis("Horizontal", this, &AMyPlayerController::HandleHorizontalAxisInputEvent);
        InputComponent->BindAxis("Vertical", this, &AMyPlayerController::HandleVerticalAxisInputEvent);
    }

    void HandleFireInputEvent();
    void HandleHorizontalAxisInputEvent(float Value);
    void HandleVerticalAxisInputEvent(float Value);
};

虚幻 4 蓝图:

image alt text

这里是项目设置和输入属性有关的设置界面:

image alt text

关于如何设置输入,你可以从在 这里 了解更多。

常见问题

如何自动加载最后一个项目?

如果你习惯了 Unity 的自动加载最后项目的功能,在虚幻 4 中也一样可以做到。要开启这个功能的话,请在当打开项目时勾选 "启动时总是加载最后一个项目"。你可以随时通过编辑器主菜单中的 编辑/编辑器首选项/加载和保存/启动 来修改设置。

哪里可以设置游戏的输入绑定?

在 Unity 中,如果你习惯于为项目用 Input Manager 来设置默认的输入的话,虚幻 4 也一样。你可以打开项目设置,并选择 Input 的分类。然后就可以添加不同的案件(Action)或者摇杆控制(Axe)。给每一种控制一个名称和默认绑定。然后,在游戏中的 Pawn 中当该输入事件发生时就能获取回调函数。查看 输入文档页面 来了解详情。

如何修改项目的默认初始场景?

你可以在项目设置选项卡中设置项目的默认初始场景。从主菜单中选择 编辑/项目设置->地图和模式 便能进行修改。

如何运行游戏?

运行游戏最简单的方式是点击编辑器的主工具栏上的"运行"按钮,这会在编辑器窗口中运行游戏。如果想要以单独程序运行游戏,点击"运行"按钮边上的下拉箭头,并选择"独立窗口运行(Standalone Game)"。如果想要在移动设备或者网页浏览器中运行游戏,那需要使用工具栏中的"启动"按钮(对应平台需要预先安装所需软件)。

引擎使用什么测量单位?

在 Unity 中,主要的测量单位是米,在虚幻 4 中,主要的测量单位是厘米。

因此在 Unity 中移动一个单位(一米)相当于在虚幻 4 中移动 100 个单位(厘米)。

如果想要在 Unity 中移动 2 英尺,那么相当于 0.61 个单位,而在虚幻 4 中则是 61 个单位。

坐标系是怎么回事?那个方向朝上?

Unity 和虚幻 4 都使用左手坐标系,但坐标轴则需要轮换一下。在虚幻 4 中,X 的正方向是"前",Y 的正方向是"右",Z 的正方向是"上"。

如何查看游戏的输出日志?

在虚幻 4 编辑器中,你可以从"窗口->开发人员工具"的菜单中打开"输出日志"。也可以在运行游戏时增加 "-log" 命令行参数,在游戏窗口以外启动一个专用的日志窗口,这非常有用!

说道日志输出,Debug.log 在哪里?

虚幻 4 中的日志是高度可定制化的。阅读 这里 了解如何记录信息。

如何抛出异常?

在 Unity 中,你可能习惯了发生问题时抛出异常。虚幻 4 并不处理异常。取而代之的做法是,使用 'check()' 函数来触发严重的断言错误。你可以传入一个错误信息提示。如果只是想报告一个错误,但不希望打断整个程序,你可以使用 'ensure()'。这会记录一个带有完整调用堆栈的错误信息,但程序会继续执行。如果当前附加了调试器,那么这两个函数都会暂定并进入调试器。

.NET Framework 去哪里了?

和 Unity 不同,虚幻 4 并不使用 .NET Framework。虚幻 4 有自己的一套容器类和库。常见的容器来对照:

.Net Framework

虚幻 4

String

FString , FText

List

TArray

Dictionary

TMap

HashSet

TSet

你可以在这里 进一步了解虚幻 4 的其他容器。

代码改变时虚幻会自动重新加载吗?

是的!你可以在编写代码时保持编辑器开启的状态。要在编写完成后直接从 Visual Studio 中编译代码,编辑器则会"热加载"你刚刚做的改动。你也可以点击编辑器主工具栏上的 编译 按钮。这招在你附加了 Visual Studio 调试器时也很有用。

然后呢?

感谢你阅读本指南!这篇文章旨在帮助各地的虚幻社区和虚幻开发者,我们期待收到你的反馈意见或者纠错意见。随着我们不断了解如何更好地帮助大家过渡到虚幻 4,我们会一直更新这篇文档。

我们还有很多虚幻 4 的学习资料!

Tags
Select Skin
Light
Dark

Welcome to the new Unreal Engine 4 Documentation site!

We're working on lots of new features including a feedback system so you can tell us how we are doing. It's not quite ready for use in the wild yet, so head over to the Documentation Feedback forum to tell us about this page or call out any issues you are encountering in the meantime.

We'll be sure to let you know when the new system is up and running.

Post Feedback