为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 框架)[(Gameplay/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 的学习资料!

标签
Select Skin
Light
Dark

欢迎来到全新虚幻引擎4文档站!

我们正在努力开发新功能,包括反馈系统,以便您能对我们的工作作出评价。但它目前还未正式上线。如果您对此页面有任何意见与在使用中遭遇任何问题,请前往文档反馈论坛告知我们。

新系统上线运行后,我们会及时通知您的。

发表反馈意见