【转】从Unity过渡到Unreal

本文旨在帮助Unity用户过渡到虚幻4引擎,详细介绍了从Unity到虚幻4的编辑器界面、资源素材管理、代码编写、组件使用、Actor与GameObjects的区别、蓝图与C++类的对比等内容。
Unreal Engine 4.9
  • image_0.png

    本指南从一个 Unity 的用户视角来整体看待虚幻 4,并帮助将 Unity 的开发经验应用到虚幻 4 的世界中。

    编辑器

    下面是 Unity 编辑器和虚幻编辑器的图片,用颜色标识了同类的区块。每个区块也添加了标签,对应于虚幻术语表。虚幻编辑器完全可以通过对各个区块的拖拽做到窗口布局的自定义。

    image_1.png

    编辑资源素材

    在 Unity 中,Inspector 分页时用来编辑当前项目中选中的素材。在虚幻 4 中,细节 面板则用来展示并修改当前选中物体的属性,然后大量的编辑修改则需要专用的窗口或分页。编辑每种类型的资源时都会打开分页窗口,类似于网页浏览器。当然这些分页窗口也可以任意拖拽,并悬浮在其他窗口之上作为独立窗口显示。

    image_2.png

    快速词汇查找表

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

    分类Unity虚幻 4
    游戏内容类型Component组件
     GameObjectActor,Pawn
     Prefab蓝图类
    编辑器界面Hierarchy Panel世界大纲
     Inspector细节面板
     Project Browser内容浏览器
     Scene View视口
    网格物体Mesh静态网格物体
     Skinned Mesh骨骼网格物体
    材质Shader材质,材质编辑器
     Material材质实例
    特效Particle Effect特效,粒子,级联
     Shuriken级联
    游戏界面UIUMG (虚幻示意图行 Unreal Motion Graphics)
    动画Animation骨骼动画系统
     MecanimPersona ,动画蓝图
    2DSprite EditorPaper2D
    编程C#C++
     Script蓝图
    物理RaycastLine Trace, Shape Trace
     Rigid Body碰撞,物理
    运行平台iOS Player, Web Player支持的平台

    项目文件和文件

    那么目录和文件时怎么回事

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

    我的资源素材应该放在哪里?

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

    image alt text

    支持哪些通用资源文件格式?

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

    资源类型支持的格式
    3D.fbx, .obj
    贴图.png, .jpeg, .bmp ,.tga, .dds, .exr, .psd, .hdr
    声音.wav
    字体.ttf, .otf
    视频.mov, .mp4, .wmv

    场景是如何保存的?

    在 Unity 中,GameObjects 被放置在场景中,并保存为一个场景资源文件。虚幻有一个地图文件,对应于 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 放置到场景中:

    Youtube 视频

    虽然可以通过搭建并扩展空的 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 来创建一个火炬,并为它添加一个网格物体组件作为基座,以及一个光源和粒子系统作为它的火焰。

    Youtube 视频

    在 Unity 中,一个 GameObject 保存了组件的简单列表,但在虚幻 4 中,一个 Actor 保存了属于它的组件以及它们的继承结构关系。可以在上面的例子中看到,光源和粒子是连接到网格模型的。之后在复合 Actor 和 复合 GameObject 中会有重要的描述讨论。

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

    Unity 的工作流程是基于 prefabs 的。在 Unity 中,创建一系列 GameObjects 以及它们的组件,并为它们创建 prefab。然后便可以在世界中放置 prefab 的实例,或者在运行时创建它们。

    虚幻 4 则是基于蓝图类来协同工作。在虚幻 4 中,创建一个 Actor 以及它的组件,选择它并点击 蓝图 / 添加脚本 按钮(在细节 面板中)。然后选择保存蓝图类的地方,并点击创建蓝图 来保存新建的蓝图!

    Youtube 视频

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

    脚本组件和 MonoBehaviour 在哪里?

    在 Unity 中,GameObject 有脚本组件,并可以添加 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 蓝图类可以拥有自己的可视化蓝图脚本编辑!这样能够为整个物体添加逻辑,而不仅仅是每个组件。结合继承结构关系(稍后下文解释),这将会提供游戏制作很多灵活性。

    In addition to Blueprint Classes supporting visual scripting, UE4 also supports C++ Classes implemented with code. Here are both, side-by-side. 除了蓝图支持可视化脚本编辑,虚幻 4 还支持通过代码 C++ 类来实现。这里是双方的一个对照:

    Unity C#虚幻 4 C++
    using UnityEngine;
    using System.Collections;
    
    public class MyComponent : MonoBehaviour
    {
        int Count;
    
        // Use this for initialization.
        void Start ()
        {
            Count = 0;
        }
    
        // Update is called once per frame.
        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;
    
        // Sets default values for this actor's properties.
        AMyActor() 
        {
            // Allows Tick() to be called
            PrimaryActorTick.bCanEverTick = true;  
        }
    
        // Called when the game starts or when spawned.
        void BeginPlay()
        {
            Super::BeginPlay();
            Count = 0;
        }
    
        // Called every frame.
        void Tick(float DeltaSeconds)
        {
            Super::Tick(DeltaSeconds);
            Count = Count + 1;
            GLog->Log(FString::FromInt(Count));
        }
    };
    虚幻 4 蓝图
    image_28.png

    虚幻 4 蓝图类的扩展性

    Unity 的 prefabs 和虚幻 4 的蓝图类在游戏中类似的实例化。然后 Unity 在 prefabs 之间的关系上有并发的问题,这限制了创作的扩展性。

    在虚幻 4 中,可以通过扩展已有的蓝图类来创建新的蓝图类,并定义新的属性,组件功能及可视化脚本功能。

    比如,在虚幻 4 中,可以创建一个蓝图类叫做 Monster,实现基本的怪物功能,比如追击人类。然后可以创建一个叫做 Dragon 的蓝图类来扩展它(某种特定的怪物,添加了火焰吐息的功能),再有一个 Grue(一种当它变黑是就有可能吃人的怪物),以及其他 8 种类型。这样一些 Monster 的子类都继承了基础的 Monster 类的功能,并在此基础上添加新的能力。

    在 Unity 中,则需要创建很多不同的 GameObject 的 prefabs:为 Dragon 创建一个,为 Grue 创建一个,等等。假设这时希望为所有的怪物添加某个功能,比如使用一个 Speak 组件来说话,在 Unity 中则需要更新所有的 10 个 prefabs,拷贝粘贴到每个中。

    在虚幻 4 中,只需简单的修改 Monster 的蓝图类,并为它添加新的 Speak 的能力,便做完了!Dragon,Grue 以及其他 8 种 Monster 的子类都会自动的继承这个说话的新功能,并不需要去修改这些子类。

    但还有更多!我们关于蓝图类所说的一切,都同样适用于 C++ 的类,也同样对 Actors 和 组件 适用。我们的体系设计时考虑支持各种不同程度的开发形式,具有功能上的可扩展性,可以为 10 个开发人员的项目服务,也可以为 100 个人的项目人员服务。

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

    可视化蓝图脚本是对游戏实时逻辑和序列型的事件的理想制作手段。这个系统对策划、美术以及面向可视化编程的程序员是非常好用的,它能够可视化的管理游戏内的物体,并且容易访问。完全可以通过蓝图就完成一个游戏的制作。请参考 Tappy Chicken 示例,它是一个完整的范例。

    C++ 编程可以为更大体量的任务工作,比如构建一个游戏体系,制作复杂的 AI 行为,或者新增引擎功能。对于已有 C++ 技能的开发人员而言,可以翻阅一下 在虚幻 4 中的 C++ 编程简述 页面。

    大部分的项目都将是混合使用蓝图和 C++。很多开发者使用蓝图来创作游戏原型,因为这个过程容易且有趣,但会在之后的性能调整和更严格的引擎使用时将部分蓝图或全部蓝图脚本转移至 C++。

    蓝图类也能扩展 C++ 类

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

    image alt text

    转换组件

    在 Unity 中,每个 GameObject 都有一个转换组件(Transform Component),赋予该 GameObject 在世界中的位置、角度以及缩放比例。

    在 虚幻 4 中类似,Actor 有一个 Root Component,能够作为场景组件的任意子类,场景组件 赋予 Actor 在世界中的位置、角度及缩放比例,这些参数会费赋予该 Actor 的组件关系结构中在它之下的所有组件。很多有用的组件都是场景组件的子类,因此让它们具有位置信息是非常有用的!

    即便只放置一个空的 Actor,虚幻 4 也会为它创建一个“默认场景 Root 组件”,这是一个最简单的场景组件。当放置一个新的场景组件时,默认场景 Root 则会被替换掉。

    复合物体

    在 Unity 中,可以构建 GameObject 之间的关系并将他们的转换组件绑定,来创建复合物体。

    image alt text

    在虚幻 4 中,通过整洁的组件继承便能创建复合游戏物体。

    image alt text

    从上图可以看到,一个整洁的继承关系可以通过将场景组件互相附加来得到,由于他们有转换功能,类似于 Unity 中的转换的绑定。Actor 的各个组件(所有组件的基类)只能直接依附到 Actor 自己身上。

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

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

    有一些在引擎中常见的 Actor 类型,并且大部分种类的游戏都会用到它们。这里是我们已经制作的最常见类型 Actor 的列表:

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

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

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

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

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

    并不是所有的。Actor 是虚幻 4 中最常见的用于游戏的类,并是唯一能够在 世界 中被创建生成 Spawn 的类。因此任何被放置在关卡中的东西都将是一个 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()
    
        // Called at start of game.
        void BeginPlay();
    
        // Called when destroyed.
        void EndPlay(const EEndPlayReason::Type EndPlayReason);
    
        // Called every frame to update this actor.
        void Tick(float DeltaSeconds);
    };

    蓝图

    image_29.png

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

    C++

    UCLASS()
    class UMyComponent : public UActorComponent
    {
        GENERATED_BODY()
    
        // Called after the owning Actor was created
        void InitializeComponent();
    
        // Called when the component or the owning Actor is being destroyed
        void UninitializeComponent();
    
        // Component version of 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)
    {
        // Custom tick stuff here
        Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    }

    You may have noticed some things begin with "A" and others with "U" in C++. The prefix "A" indicates an Actor sub-class. Where as the prefix "U" indicates anObject sub-class. There are some other prefixes too, for example "F" is used for most plain data structures or non-UObject classes. 您也许已经注意到 C++ 中有些以 “A” 开头,而其他一些以 “U” 开头。前缀 “A” 代表它是一个 Actor 的子类,而前缀 “U” 代表它是一个 Object 的子类。还有其他一些前缀,比如 “F” 用于代表一个平铺的数据结构体,或者其他非 Uboject 的类。

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

    好了,现在开始稍微深入一些。我们将谈论一下创建游戏所需要的编程话题。因为您了解 Unity,我们来面向 C# 的用户解释 C++ 的功能,当然您也可以使用蓝图来完成几乎所有的事情!我们尽可能的为范例提供 C++ 的同时也提供蓝图。

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

    Instantiating GameObject / Spawning 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 的子类。对于游戏过程中,它们是那些不需要在游戏世界中创建并看见的存在。

    在 Unity 中,如果要创建自己的 ScriptableObject 子类,可能会像下面这样的初始化:

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

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

    UMyObject* NewObj = NewObject<UMyObject>();

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

    您可能已经注意到,并没有传递一个 Actor,我们传递了一个 Actor 的 “class” 来作为生成 Actor 的参数。在我们的范例中,是一个 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()
        {
            // Create the collision component if we don't already have one
            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
    UnityC++蓝图
    Destroy(MyGameObject);
    MyActor->Destroy();

    image_23.png

    销毁 GameObject / Actor (1 秒延迟)
    UnityC++蓝图
    Destroy(MyGameObject, 1);
    MyActor->SetLifeSpan(1);

    点击查看大图。

    禁用 GameObjects / Actors
    UnityC++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
    UnityC++蓝图
    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. 元组件

    在 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);
        }
    
        void BeginPlay()
        {
            // Register to find out when an overlap occurs
            OnActorBeginOverlap.AddDynamic(this, &AMyActor::OnTriggerEnter);
            OnActorEndOverlap.AddDynamic(this, &AMyActor::OnTriggerExit);
    
            Super::BeginPlay();
        }
    
        void EndPlay(const EEndPlayReason::Type EndPlayReason)
        {
            OnActorBeginOverlap.RemoveDynamic(this, &AMyActor::OnTriggerEnter);
            OnActorEndOverlap.RemoveDynamic(this, &AMyActor::OnTriggerExit);
    
            Super:EndPlay(EndPlayReason);
        }
    
        UFUNCTION()
        void OnTriggerEnter(AActor* Other);
    
        UFUNCTION()
        void OnTriggerExit(AActor* Other);
    };

    虚幻 4 蓝图:

    image alt text

    还可以在 这里 读到更多关于设置碰撞的响应。

    刚体运动

    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
    StringFString,FText
    ListTArray
    DictionaryTMap
    HashSetTSet

    这里 可以阅读到更多虚幻 4 的容器。

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

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

    然后呢?

    感谢阅读本篇指引!这篇文章的创建时为了帮助各地的虚幻社区和虚幻开发者,我们很希望能收到您的反馈意见或者纠错意见。在我们不断了解如何能帮助大家过渡到虚幻 4 的过程中,我们将会保持改进这篇文档。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值