UE5 | 面向对象编程和游戏(GamePlay)框架
蓝图基于面向对象编程 (OOP) 的原则。 OOP 的目标之一是使编程概念更接近现实世界。
虚幻引擎游戏框架包含视频游戏所需的所有核心系统,例如游戏规则、玩家输入和控制、相机和用户界面。
在本章中,我们将学习以下主题:
- 熟悉 OOP 概念
- 管理演员(Actors)
- 探索游戏框架类
1. 熟悉 OOP
我们将了解一些OOP的基本概念,例如类,实例和继承。这些概念将有助于我们理解蓝图可视化脚本的各种元素。
1.1 类
在OOP中,类是一个模板,用于创建对象并为状态(变量或属性)和行为(事件或函数)的实现提供初始值。
许多现实世界的对象都可以用同样的方式来思考,即使它们是独一无二的。作为一个非常简单的例子,我们可以想到一个人的类。在这个类中,我们可以有姓名和身高的属性,以及移动和吃东西的动作。使用 person 类,我们可以创建这个类的几个对象。每个对象代表一个人,他们的名字和身高属性有不同的值。
当我们创建一个蓝图时,我们正在创建一个新的类,可用于在游戏的关卡中创建对象。如下图所示,创建新的蓝图资产时出现的选项是蓝图类。
封装是另一个重要的概念。当从另一个类的角度来看它时,它允许我们隐藏一个类的复杂性。蓝图类的变量和函数可以是私有的,这意味着它们只能在创建它们的蓝图类中被访问和修改。公共变量和函数是可以被其他蓝图类访问的。
1.2 实例
从类创建的对象也称为该类的实例。每次从内容浏览器中拖动一个蓝图类并将其放入关卡中时,都会创建该蓝图类的一个新实例。
所有实例均使用与蓝图类中定义的相同变量默认值创建。但是,如果一个变量被标记为 可编辑实例(Instance Editable),则该变量的值可以在每个实例的 关卡(Level )中更改,而不会影响其他实例持有的值。
例如,假设创建了一个蓝图来表示游戏中的角色类型。下图显示此蓝图类的三个实例已添加到关卡中:
1.3 继承
在OOP中,类可以从其他类中继承变量和函数。当我们创建一个蓝图时,我们要做的第一件事就是选择这个蓝图的父类。一个蓝图类只能有一个父类,但可以有几个子类。父类也被称为超类,而子类则被称为子类。
作为一个使用继承的例子,想象一下,我们正在创建几个蓝图,代表游戏中不同类型的武器。我们可以创建一个名为 "Weapon "的基础蓝图类,其中包含游戏中所有武器共有的东西。然后,我们可以使用Weapon类作为父类来创建代表每个武器的蓝图。下图显示了这些类之间的层次结构。
继承的一个好处是,我们可以在父类中创建一个函数,并在子类中用不同的实现方式覆盖它。例如,在Weapon 父类中可以有一个名为Fire的函数。子类继承Fire函数,所以Shock Rifle类用一个发射能量束的版本重写Fire函数,而Rocket Launcher类重写Fire函数来发射火箭。在运行时,如果我们有一个对武器类的引用并调用Fire函数,实例类就会被识别出来以运行其Fire函数的版本。
继承也被用来定义一个类的类型,因为它积累了其父类的所有类型。例如,我们可以说 Shock Rifle 类的一个实例是属于 Shock Rifle类型和Weapon 类型。正因为如此,如果我们有一个带有Weapon输入参数的函数,它可以接收Weapon类的实例或其子类的任何实例。
OOP 的这些基本概念将帮助我们理解 Gameplay 框架。虚幻引擎有一些用于游戏开发的基本类。这些类是游戏框架的一部分。 Gameplay 框架的主要类是 Actor。
2. 管理 Actors
Actor 类包含了一个对象在关卡中存在所需的所有功能。因此,任何可以在关卡中放置或生成的对象都是 Actor 类的子类。我们将创建的大多数蓝图都是基于Actor类本身或其子类。因此,我们将在本节中看到的功能将对这些蓝图有用。
2.1 引用 Actor
整数、浮点数和布尔值等变量类型被称为基本类型,因为它们只存储指定类型的简单值。在使用对象或 Actor 时,我们使用一种称为对象引用(object reference)的变量。蓝图中的引用允许不同的对象相互通信。我们将在下文中更详细地学习这种通信。
例如,下图表示内存中两个蓝图类的实例。BP_Barrel蓝图类的实例有一个名为Hit Counter的整数变量,当前值为2。 另一个变量名为BP_Fire,是一个对象引用,它引用的是蓝图效果Fire的实例。我们可以使用对象引用变量访问另一个蓝图的公共变量和函数。
在蓝图中,我们可以创建引用其他对象/Actor 的变量。接下来,我们创建一个循序渐进的功能示例来看看这个概念的实际效果:
- 基于第一人称模板和初学者内容包创建一个项目。
- 单击 内容侧滑菜单 按钮以打开 内容浏览器,然后单击 添加 按钮并选择 蓝图类。
- 接下来,选择 Actor 作为父类。
- 将蓝图命名为 BP_Barrel 并双击它以打开蓝图编辑器。
- 单击“组件”面板中的“添加”按钮,然后选择“静态网格体组件”。在 细节 面板中,选择 Shape_Cylinder 静态网格体:
- 在 我的蓝图 面板中,创建一个名为 BP_Fire 的变量。在 细节 面板中,单击变量类型参数的下拉菜单。对象类型类别列出了虚幻引擎中可用的类以及我们在项目中创建的蓝图类。搜索 fire 并将鼠标悬停在 Blueprint Effect Fire 上以显示子菜单,然后选择 对象引用:
- 对象引用变量的默认值为 None(也称为 null),这意味着该变量没有引用任何实例。我们可以在关卡编辑器中为这个变量分配一个实例。为此,请勾选 可编辑实例 属性,以便它可以在关卡编辑器中访问。
- 将 BP_Fire 变量从 我的蓝图 面板拖放到 事件图表 中。选择 获取 BP_Fire 选项以创建节点。从 BP_Fire 节点的蓝色引脚拖放到图中以打开上下文菜单。搜索 hidden 并选择 设置游戏中隐藏 (P_Fire) 函数:
- 右键单击事件图表并添加事件命中 节点。将 事件命中 节点连接到 设置游戏中隐藏(P_Fire) 节点。New Hidden 参数必须 不被勾选。当击中 BP_Barrel 蓝图的实例时,这些操作将 不隐藏 BP_Fire 引用的实例的粒子系统组件:
- 编译蓝图并返回关卡编辑器。将 BP_Barrel 蓝图从 内容浏览器 拖放到关卡中。
- 在内容浏览器中,转到 内容 | StarterContent | Blueprints 文件夹,拖动 Blueprint_Effect_Fire,并将其放在已添加到关卡的 BP_Barrel 蓝图的顶部:
- 在 Blueprint_Effect_Fire 实例的 细节 面板中,选择 P_Fire 组件,搜索 hidden,并勾选 游戏中隐藏 属性:
- 在 BP_Barrel 实例的 细节 面板上,点击BP_Fire变量的下拉菜单,列出关卡中属于Blueprint_Effect_Fire实例的Actor。选择我们丢在BP_Barrel上面的实例,将其 实例 分配给BP_Fire变量。
- 点击关卡编辑器的 "播放 "按钮来测试这个关卡。朝我们放在关卡上的BP_Barrel实例的方向看。Blueprint_Effect_Fire实例是隐藏的。用鼠标左键射击BP_Barrel实例,当BP_Barrel实例被击中时,Blueprint_Effect_Fire实例将出现。
2.2 生成和销毁 Actor
有一个名为 从类生成Actor 的函数,可以创建一个Actor实例。要把这个函数添加到事件图表中,右击事件图表面板打开上下文菜单,在搜索框中写上Spawn以过滤结果,然后点击函数名称。
这个函数接收Actor的Class
和Spawn Transform
作为输入参数。转换(transform) 定义了将被新角色使用的位置、旋转和缩放。另一个输入参数,称为碰撞处理覆盖(Collision handing Override),定义了在创建时如何处理碰撞的问题。Return Value
输出参数中提供了对新实例的引用,并且可以存储在变量中。
要从关卡中移除一个Actor实例,请使用 销毁Actor函数。目标输入参数表明哪个实例将被删除。下图显示了一个使用 从类生成Actor( Spawn Actor from Class )和 销毁Actor(DestroyActor)函数的例子。
在事件图表中 右键,在搜索框中搜索 1,添加输入事件。 1 键输入事件可以在输入 > 键盘个事件中找到。
- 按下 1 键会创建一个蓝图效果火(Blueprint Effect Fire)的实例,使用包含此脚本的蓝图实例的相同转换。例如,如果上述代码添加到
FirstPersonCharacter
的事件图表中(可在第一人称模板中的 Content | FirstPersonBP | Blueprints 找到),那么一旦游戏启动,按下键盘上的1就会在玩家角色的当前位置创建一个火的效果。 - 对新的蓝图效果火(Blueprint Effect Fire)实例的引用被存储在BPFire变量中。如果你没有存储该实例的变量,你可以很容易地将 SpawnActor 函数的返回值提升为变量,然后自动赋予它正确的变量类型。要做到这一点,可以从返回值引脚拖动到事件图表上,打开上下文菜单,选择提升到变量。
- 按下2号键时,会使用 Is Valid 宏进行测试,以检查BPFire变量是否引用了一个有效的实例。这种检查是必要的,以免使用一个空的引用来调用一个函数。如果BPFire的值是None,那么它就是无效的。如果它是有效的,那么它就会调用DestroyActor函数,该函数接收BPFire变量作为目标输入参数,并销毁之前创建的蓝图效果火(Blueprint Effect Fire)实例。
- 注意,按 2 键只能删除最后创建的蓝图效果火(Blueprint Effect Fire)实例。如果你在删除之前创建了一个以上的火灾实例,其他的将保留在关卡(Level)中,因为在创建蓝图效果火(Blueprint Effect Fire)实例时,BPFire变量被覆盖了。
2.3 构造脚本(Construction Script)
构造脚本如图所示。构造脚本是所有 Actor 蓝图在以下三种情况下执行的函数:
- 当蓝图第一次被添加到关卡中;
- 当在关卡编辑器中对其属性进行更改;
- 该蓝图的实例在运行时被生成时。
例如,让我们创建一个带有实例可编辑静态网格体(Instance Editable Static Mesh)的蓝图,这样我们就可以为关卡上的每个蓝图实例选择不同的静态网格体:
- 创建或使用具有 初学者内容包 的现有项目。
- 单击 内容浏览器 中的 添加 按钮并选择 蓝图类 选项。
- 下一步,选择 Actor 作为父类。
- 将蓝图命名为 BPConstruction 并双击它以打开蓝图编辑器。
- 单击“组件”面板中的“添加”按钮,然后选择“静态网格体组件”。重命名为 StaticMeshComp,如下图所示:
- 在 我的蓝图 面板中,创建一个名为 SM_Mesh 的新变量。在 细节 面板中,单击 变量类型 下拉菜单并搜索 Static Mesh。将鼠标悬停在静态网格体上以显示子菜单,然后选择 对象引用。检查 可编辑实例 属性,如下图所示:
对象引用 变量也可以指向 在运行时创建的实例。
点击工具栏上的编译按钮。让我们在 细节 面板底部为 SM_Mesh 变量定义一个初始静态网格体。单击下拉菜单并选择 SM_TableRound 静态网格体。
单击“构造脚本”面板。将 StaticMeshComp 组件从组件面板中拖放到Construction Script 图表中以创建一个节点。
单击 StaticMeshComp 节点的蓝色引脚,然后在图形中拖放以打开上下文菜单。搜索 设置静态网格体 并选择这个函数,如图所示:
- 将 SM_Mesh 变量从 我的蓝图 面板拖放到 Construction Script 图表中,然后在出现的菜单中选择 获取SM_Mesh 选项。将 SM_Mesh 节点引脚连接到 设置静态网格体 函数的 New Mesh 引脚。如图所示。当 Construction Script 执行 Set Static Mesh 函数时,它会从 SM_Mesh 变量中获取 静态网格体 并将其设置在 StaticMeshComp 组件上:
- 编译蓝图。在关卡编辑器中,将 BPConstruction 从内容浏览器中拖放到关卡中以创建一个实例。再次拖放 BPConstruction 以再创建一个实例。选择关卡上的一个实例,然后在关卡编辑器的 细节 面板中,检查 SM_Mesh 变量是否可见且可编辑,如图所示:
- 单击 SM_Mesh 变量的下拉菜单,然后选择另一个静态网格体,例如 SM_Couch。构造脚本将立即执行并更改所选实例的静态网格体。下图显示了 BPConstruction 类的两个实例。一个实例使用默认的静态网格体,但另一个实例的静态网格体已修改为 SM_Couch:
Actor 类是 Gameplay Framework 的主要类,但我们需要了解用于不同目的的其他类。
3. 探索其他游戏框架类
创建新蓝图的第一步是选择将被用作模板的父类。下图显示了可选择父类的面板。按钮上显示的类被称为通用类,是游戏框架的一部分。要使用其他类作为父类,请展开所有类的类别并搜索你想要的类。
下图显示了通用类的层次结构。在虚幻引擎中,有一个父类叫做 Object。类继承了它父类上面的特性。根据 OOP 的继承概念,我们可以说 Character 类的一个实例是 Character 类型,Pawn 类型,Actor 类型:
通过分析这个层次结构,我们可以看到 Actor Component 和 Scene Component 类都不是 Actor。这些类用于创建可以添加到 Actors 的组件。组件的两个示例是静态网格体组件和旋转运动组件,我们在前面的示例中使用了它们。我们将在后续的文章中介绍组件的创建。
下面让我们仔细看看一些通用类。
3.1 Pawn
Pawn 是 Actor 的子类。 Pawn 是一个可以在游戏中被控制者(Controller)占有的角色(Actor)。 Controller 类代表玩家或人工智能 (AI)。从概念上讲,Pawn
类的实例是游戏角色的身体,而拥有它的 Controller 类的实例是角色的一种大脑,允许它在关卡中移动并执行其他操作。
基于 Pawn 类创建蓝图,然后单击 (类默认值)Class Defaults 按钮将其显示在 细节 面板上。从 Pawn 类继承的参数如下图所示:
一些参数表明 Pawn 类可以使用拥有它的 控制器(Controller )类的旋转值。其他参数则表明 Pawn 类被控制器(Controller )类占有的方式。Pawn 的两个主要子类是 Character 和 WheeledVehicle。
3.2 角色类 Character
角色类是 Pawn 类的一个子类;因此,Character 类的实例也可以被 Controller 类的实例所拥有。创建这个类是为了表示可以走路、跑步、跳跃、游泳和飞行的角色。
一个基于Character类的蓝图将继承以下特定于角色的组件。
- 胶囊体组件(CollisionCylinder):这是用来进行碰撞测试的。
- Arrow:它表示角色的当前方向。
- Mesh:这个组件是一个骨架网,在视觉上代表角色。网格组件的动画由一个动画蓝图控制。
- 角色运动(CharacterMovement):该组件用于定义各种类型的角色运动,如行走、跑步、跳跃、游泳和飞行。
这些组件如下图所示;
CharacterMovement 组件处理运动以及多人游戏中的复制和预测。它包含很多参数,为角色定义各种类型的运动。
3.3 PlayerController
Controller 类有两个主要的子类。PlayerController和AIController。PlayerController类由人类玩家使用,而AIController类使用AI来控制Pawn。
Pawn和Character类的实例只有在PlayerController的实例拥有它们时才能接收输入事件。
输入事件可以放在PlayerController或Pawn的事件图表中。将输入事件放在PlayerController中的好处是,这些事件可以独立于Pawn,从而更容易改变被Controller 类拥有的Pawn类。无论选择哪种方式,都需要在项目中保持一致。
下图显示了如何在游戏中改变被PlayerController占有的Pawn,并展示如何使用Possess函数。在这个例子中,关卡中有两个角色可以由玩家通过按 1 或 2 键来控制。只有当前被占有的Character 实例才会收到PlayerController的命令:
3.4 游戏模式基础 Game Mode Base
Game Mode Base 是创建 Game Mode 的父类。 Game Mode 类用于定义游戏规则,并指定用于创建 Pawn、PlayerController、GameStateBase、HUD 和其他类的默认类。要在 Game Mode 中更改这些类,请单击 类默认值 按钮,在 细节 面板上显示它们,如下图所示:
要在关卡编辑器中指定项目的默认游戏模式类,请单击编辑 | 项目设置.... 然后,在 项目 类别中,选择 地图和模式 选项。在 默认游戏模式 属性的下拉列表中选择 Game Mode,如下图所示:
每个关卡可以有不同的游戏模式。关卡的游戏模式会覆盖项目的默认游戏模式。要指定关卡的游戏模式,请单击关卡编辑器中的设置按钮并选择世界场景设置选项。在 游戏模式重载 属性的下拉列表中选择 游戏模式,如下图所示:
3.5 游戏实例
Game Instance 不是 Common Classes(通用类) 之一,但重要的是我们需要知道这个类的存在。 Game Instance 类及其数据在关卡之间保持不变,因为 Game Instance 类的实例是在游戏开始时创建的,并且仅在游戏关闭时才被删除。
每次加载关卡时,关卡中的所有 Actor 和其他对象都会被销毁并重新生成。因此,如果您需要在关卡转换中保留一些变量值,则可以使用 Game Instance 类。
要分配 Game Instance 类以在您的游戏中使用,请转到编辑 | 项目设置....在 项目 类别中,选择 地图和模式 选项,如下图所示:
4. 总结
在本章中,我们了解了 OOP 的一些原则,这些原则有助于我们理解蓝图是如何工作的。
我们学习了Actor类是如何作为父类,用于放置或生成关卡中的对象。
我们还看到 Gameplay 框架包含用于表示某些游戏元素的类,并学习了如何基于一些通用类创建蓝图。
在下文中,我们将了解蓝图如何相互通信。