UE4-蓝图可视化脚本入门手册-全-
UE4 蓝图可视化脚本入门手册(全)
一、虚幻引擎 4 简介
你好,欢迎来到虚幻引擎 4 的初学者指南。在这本书里,你学习了虚幻引擎 4 的不同方面,并且你学会了用你获得的知识创建一个示例游戏。在本章中,你将学习如何通过 Epic Games Launcher 和 GitHub 下载虚幻引擎。之后,您将学习项目的结构,并熟悉虚幻编辑器界面。
获得虚幻引擎
本章着眼于你如何获得虚幻引擎。你可以通过 Epic Games Launcher 或者 GitHub 下载。无论哪种方式,都需要在 www.unrealengine.com
创建一个账户,是免费的。
首先,我们来看看 Epic Games Launcher 和 GitHub 的区别。
-
虚幻引擎 4 的 Epic Games Launcher 版本(也称为二进制版本或普通版本)带有预构建的引擎,您可以选择您需要的平台。如果需要,您还可以选择引擎源、starter 模板、特性包等等。二进制版本不支持为你的游戏创建专用服务器,所以如果你打算开发一个带有专用服务器的多人游戏,你必须使用源代码版本。
-
GitHub 版本(也称为源代码版本)提供了引擎的完整源代码,没有任何二进制文件,因此您需要手动编译它。引擎的源代码版本通常由想要修复引擎的错误或添加新功能的开发人员使用。如果你的游戏依赖于专用服务器,这个版本也是必需的。该引擎的源代码版本的先决条件是 Windows 上的 Visual Studio 2019(或更高版本)或 macOS 上的 Xcode。
从 Epic Games 启动器下载
如果您没有 Epic Games 帐户,您需要在 www.unrealengine.com/id/register
创建一个。
如果你有一个 Epic Games 账户,那么去 www.unrealengine.com/en-US/get-now
选择你的许可证,为你的平台下载并安装 Epic Games Launcher。安装后,打开启动器,使用您的凭证登录。您应该会看到如图 1-1 所示的截图。
图 1-1
安装了引擎版本 4.24 的 Epic Games 启动器
在 ENGINE VERSIONS 选项卡附近,您可以看到一个 + 按钮,它允许您下载并安装您想要的任何引擎版本。
从 GitHub 下载
如果您更喜欢使用引擎的源代码版本,可以通过下载引擎源代码并自行编译来实现,但如果您在 macOS 上使用 Windows 或 Xcode,则必须安装 Visual Studio 2019(启用 C++ 支持)。
首先,你必须创建一个 GitHub 帐户(它是免费的),并登录你的 Epic Games 帐户。登录后,在 Epic Games 中打开你的帐户面板,链接你的 GitHub 帐户。在此之后,您就可以下载虚幻引擎 4 的完整源代码了。
下载源代码
进入虚幻引擎库后,可以点击克隆或下载按钮,选择下载 ZIP 按钮(如图 1-2 )。
图 1-2
在虚幻引擎 Git 存储库中下载 ZIP 按钮
克隆虚幻引擎库
要克隆一个存储库,您需要安装一个 Git 客户机。克隆是将存储库下载或复制到工作机器的一个空文件夹中的过程,包括完整的 Git 历史,因此您可以使用 Git 命令。您只下载没有任何 Git 文件的源代码,所以您不会跟踪更改或有任何关于以前提交的信息。
我用的是亚特兰蒂斯的 SourceTree。
Note
如果您喜欢其他工具,请访问 Windows 的 https://git-scm.com/download/gui/windows
或 macOS 的 https://git-scm.com/download/gui/mac
。
安装 SourceTree 后,打开应用程序。在新建选项卡中,选择添加账户。在新窗口中,将托管服务切换到 GitHub,并选择刷新 OAuth 令牌按钮。一旦 SourceTree 访问了您的回购,您可以从您的存储库列表中选择虚幻引擎回购,然后选择克隆。这允许您选择保存文件的路径。在高级选项下,选择释放分支,点击克隆按钮。
克隆完成后或下载 ZIP 文件后,转到目录并双击 Setup.bat 文件。(如果您下载了 ZIP 文件,请先解压缩)。您可以通过在 Setup.bat 文件中传递必要的标志来包含或排除特定的平台。例如,要排除 Windows 机器上的 Mac 和 iOS 平台,可以像这样运行 Setup.bat:
Setup.bat --exclude=Mac --exclude=iOS
这可以确保跳过 Mac 和 iOS 平台所需的任何依赖项和文件。Setup.bat 完成后,运行 GenerateProjectFiles.bat ,它会生成可以在 Visual Studio 中打开的 UE4 解决方案文件。打开解决方案文件后,您可以在解决方案资源管理器的 Engine 文件夹下看到 UE4。右键单击 UE4 并选择 Build。这将启动构建过程,编译可能需要一个小时或更长时间,具体取决于您的硬件。
了解虚幻编辑器
现在,您已经安装(或编译)了您的引擎,让我们启动它。在本书中,我们只使用引擎的二进制版本,即 4.24。您将创建一个空白项目,并了解引擎的各个方面。要启动引擎,请单击 4.24.3 的启动按钮。这将打开虚幻项目浏览器窗口,在这里您可以选择一个现有的项目或者从头开始创建一个新的项目或者一个模板(参见图 1-3 )。
图 1-3。
让我们选择空白项目并点击下一步。在下一页上,系统会提示您启动一个空白项目或基于模板创建一个项目。出于我们的目的,让我们选择一个空白模板,然后单击 Next。这将为您提供一个没有代码或内容、使用默认设置的项目。最后,最后一页允许您进行基本的配置并命名您的项目。
让我们浏览一下图 1-4 所示的项目设置页面。
图 1-4。
-
Blueprint (截图中标为 1)让您选择您的项目是基于 Blueprints 还是基于 C++。如果您从蓝图开始,您可以稍后将 C++ 代码添加到您的项目中。
-
根据您的项目,您可以将最高质量(在截图中标记为 2)更改为可缩放 3D/2D 。第一个选项适合 PC/控制台,第二个选项适合移动。
-
如果你的目标是高端 PC 游戏,并且拥有英伟达 RTX 显卡,你可以为你的游戏启用光线追踪功能(截图中标为 3)。
-
桌面/控制台(截图中标为 4)让您选择最接近的等效目标平台。
-
With Starter Content (截图中标为 5)让您选择是否要将 Starter 内容复制到项目中。它包含带有基本材质的简单网格。
-
文件夹(截图中标为 6)是你输入项目文件夹位置的地方。
-
Name (截图中标为 7)是您输入新创建项目的名称的地方。
要创建项目,点击创建项目按钮。这将启动引擎,并为您创建一个空项目。
项目结构
接下来,让我们来看看您刚刚创建的项目文件夹,以了解项目的结构。如果您导航到项目文件夹,您应该会看到类似于图 1-5 中所示的屏幕截图的结构。
图 1-5
一个示例项目(注意项目名称可能不同)
-
Config :这个文件夹是当您更改编辑器首选项或项目设置时保存设置的地方。您还可以创建配置文件来保存数据。
-
内容:这个文件夹是保存你所有游戏资产的地方。
-
中间:编辑器和游戏临时文件在这里生成。删除该文件夹是安全的,当编辑器下次启动时,它会自动重新生成。
-
保存的:该文件夹包含所有自动生成的配置文件、日志文件和自动保存。
以下是您可能会看到的其他文件夹。
-
二进制文件:这个文件夹包含项目的 DLL 文件。只有当您的项目包含 C++ 源代码时,它才会出现。
-
DerivedDataCache :该文件夹包含您的资产在其目标平台上的版本。您可以安全地删除该文件夹,编辑器下次会重新生成它。
-
来源:该文件夹包含标题(。h)和源(。cpp)文件。只有当您的项目包含 C++ 源代码时,它才会出现。
-
插件:这个文件夹包含你的项目的所有插件。要创建新插件,您的项目必须包含 C++。
不真实的编辑之旅
一旦引擎启动,你的屏幕应该看起来类似于图 1-6 。
图 1-7。
图 1-6
默认虚幻编辑器用户界面
让我们浏览一下虚幻编辑器的布局,如图 1-6 所示。
图 1-8。
-
标有 1 的区域是工具栏。在这里,您可以保存当前场景,打开内容浏览器,访问快速设置,构建照明,在编辑器中播放,等等。
-
标记为 2 的区域是模式面板。在这里,您可以在不同的模式之间切换,例如放置演员模式(默认),网格绘制模式,风景模式,树叶模式和笔刷模式。
-
标记为 3 的区域是内容浏览器,您可以在其中导入或创建所有资产。
-
标记为 4 的区域是细节面板,在这里您可以修改放置在一个级别内的所选角色的属性。
-
标记为 5 的区域是世界大纲视图。它显示了当前关卡中的所有角色。使用眼睛图标,您可以快速隐藏/取消隐藏演员。
-
标有菜单栏的区域。它允许您添加新的 C++ 类,访问编辑器和项目设置,重新打开关闭的选项卡,等等。
工具栏
工具栏显示在视口的正上方。它提供了对各种编辑器命令的简单访问。
-
保存:保存当前场景。如果当前场景没有保存,它会提示用户选择一个位置来保存地图。
-
源代码控制:提供对不同源代码控制的访问,比如 Perforce、Git、Subversion 等等。你也可以安装其他的源码控制插件。源代码控制是跟踪和管理代码或资产变更的实践。你可以在
https://en.wikipedia.org/wiki/Version_control
了解更多关于源码控制的内容。 -
内容:打开内容浏览器。
-
Marketplace :在默认浏览器中打开 UE4 marketplace。
-
设置:快速访问一些编辑器设置,以及项目设置和世界设置。
-
蓝图:创建一个新的蓝图职业或者打开一个等级蓝图。你将在下一章学到更多关于蓝图的知识。
-
电影艺术:添加关卡或主序列。
-
构建:构建灯光、导航、几何等等。当“在会话中播放”处于活动状态时,或者当前级别正在小于着色器模型 5 中预览时,此按钮被禁用。
-
播放:在活动视口中播放当前关卡。“播放”按钮旁边的箭头显示了一个下拉菜单,其中包含允许开发者在新窗口中播放游戏、移动预览、作为独立游戏等等的选项。也可以设置游戏启动专用服务器测试多人游戏。
-
启动:启动给定设备中的当前级别。
模式
“模式”面板在编辑器的各种工具模式之间切换。通过按 Shift + 1 到 5 可以切换各个模式面板。
-
放置模式(Shift+1) :将演员放置在场景中。
-
绘制模式(Shift+2) :允许你直接在视口顶点绘制一个静态网格演员。这意味着您可以将颜色数据绘制到放置在层级上的静态网格角色的顶点上,并在指定给该静态网格的材质中使用该信息。
-
风景模式(Shift+3) :创建新风景或编辑现有风景。
-
树叶模式(Shift+4) :绘制树叶。
-
笔刷模式(Shift+5) :修改 BSP(二进制空间划分)笔刷。这是一个几何工具,可以快速原型化或封闭级别。
内容浏览器
内容浏览器是项目的核心。构成你的大片游戏的所有资产都在这里。您可以导入支持的文件类型,并创建新的资源,如蓝图、材料和序列。内容浏览器允许您收藏您的资源,并将它们排列在您自己的收藏中以便快速访问,这极大地改进了您的工作流程。所以让我们来看看他们。
收藏夹
您可以将任何文件夹指定为收藏夹,以便快速访问。默认情况下,不会启用收藏夹。可以通过单击内容浏览器右下角的“视图选项”并选择“显示收藏夹”来启用它。收藏夹部分位于主内容文件夹上方。
Note
只能将文件夹指定为收藏夹,不能将资产指定为收藏夹。
收集
集合允许您将资产组织到单独的集合中。比如你在做一个开放世界的游戏,你可以为玩家建筑、城市建筑、目标建筑、任务道具等等做不同的收集。每个集合可以有子集合,并且可以随时添加或删除资产。从集合中移除一个项并不会移除实际的资产,因为它只是在集合中保存一个引用。您可以在多个集合中拥有相同的资源,并根据需要创建任意多个集合。
您可以通过单击内容浏览器中的切换到收藏视图按钮来切换到收藏视图。
有三种类型的集合—共享、私有和本地。
-
共享集合可以与其他团队成员共享。要使此选项生效,您必须启用源代码管理。
-
私人收藏可以与被邀请查看该收藏的任何人共享。要使此选项生效,您必须启用源代码管理。
-
本地收藏仅供您使用。它不通过网络共享。无论您的源代码管理设置如何,此选项始终可用。
创建收藏后,您可以将资产拖放到其中。您可以查看收藏中可用的项目数量。
如图 1-10 所示,要从集合中删除一个资产,您必须首先选择该集合,右键单击该资产,然后选择从 中删除 YourCollectionName 。这将从集合中删除资产,但是它不会删除资产。
图 1-12。
图 1-11。
图 1-10。
图 1-9。
细节
详细信息面板包含所选参与者的信息和功能。它显示特定演员的所有变换控件和所有可编辑属性。可以双击细节面板中的所有缩略图,在各自的编辑器中打开它们。例如,双击静态网格缩略图会打开该网格。同样,如果双击材质缩略图,它会在材质编辑器中打开该材质。
详细信息面板还提供了一个搜索面板,可以根据文本过滤属性。修改属性时,旁边会显示一个黄色小箭头。这将属性重置为其默认值。
世界离群值
世界大纲视图显示当前层级中的所有演员。当编辑器中的游戏激活时,它会用黄色显示当前游戏的所有演员。可以在大纲视图中选择任何演员,并且“细节”面板会显示与该演员相关的所有属性。还支持拖放,因此您可以将一个执行元拖到另一个执行元上以附加它。高级选项也支持搜索,如精确匹配和排除。
要从搜索中排除某个项目,请将-
追加到搜索词中;例如,-表显示除了任何包含术语的演员之外的所有内容,表。
要搜索一个精确的项目,将 + 追加到搜索词中;例如,+表格显示了带有精确术语表格的所有内容。
若要使用完整的术语搜索精确的项目,请将术语放在双引号("")内;例如,“午餐桌”显示带有确切术语午餐桌的所有内容。
视口
视口是你花大部分时间开发游戏的地方。它是你看到实际游戏的地方,所以理解视口对你的开发是至关重要的。按 G 键在游戏模式和编辑器模式之间切换视口。游戏模式通过隐藏所有与编辑器相关的元素来显示游戏中出现的场景。
除了视口的最大化状态,Unreal Editor 还提供了一个名为沉浸式模式 、的附加状态,通过按 F11 快捷键或访问视口选项来激活该状态。激活后,视口将最大化到包含视口面板的窗口的最大范围。
下面解释了如何导航视口。
-
右键单击并按住鼠标。使用 W 、 A 、 S 、 D 左右移动。
-
左键单击并按住鼠标。向前、向后和横向移动鼠标。
-
按住鼠标中键并移动到平移位置。
-
右键单击并按住鼠标。移动它四处看看。
了解这些快捷方式可以改进您的开发工作流程。
在视口顶部,您可以看到视口工具栏。它切换到不同的视口布局和视图模式,移动/旋转/缩放资源,相机速度,等等。您可以使用以下快捷方式切换翻译模式。
-
W 移动演员
-
E 旋转演员
-
R 缩放演员
使用空格键在所有三个选项之间切换。
Note
您可以通过按下 Ctrl+Shift+T 来切换该工具栏的可见性。
在工具栏的最末端,您会看到一个数字图标。这是编辑器内的相机移动速度。您可以点按它并将滑块调整到右侧以加快相机移动,或者将滑块滑动到左侧以减慢相机移动。要快速更改它,请在视口中按住鼠标右键,并使用鼠标滚轮来调整相机速度。向上滚动可获得较高速度,向下滚动可获得较低速度。
二、蓝图介绍
UE4 Blueprints 是一种基于节点图的可视化游戏脚本语言,在节点图中,从左到右连接节点。它可以创建成熟的游戏或简单/复杂的游戏机制,例如那些打开关卡范围谜题之门的游戏。蓝图的最大优势是你不需要程序员来创建逻辑。艺术家可以轻松地在蓝图中制作任何他们想要的东西,并与程序员分享。
这个系统非常强大,因为它为艺术家提供了通常只有程序员才能使用的全套工具。最重要的是,C++ 程序员可以创建基线系统,Blueprint 用户可以访问或修改这些系统。
节点
在 Blueprints(或任何其他可视化编程语言)中,节点是一个独立的功能,做一些独特的事情。在 Unreal Engine 中,蓝图节点可以是任何对象—事件、流控制操作、函数、变量等等。节点的源代码可大可小。在虚幻引擎 4 中,开发人员可以使用 C++ 制作一个蓝图节点,该节点可在图形编辑器面板中访问。
图 2-1 显示了在节点编辑器中打开需要钥匙的门的逻辑。
图 2-1
节点的一个例子
蓝图类型
蓝图有多种类型,您需要了解这些类型才能有效地使用它们。所有蓝图类型(级别除外)都是在内容浏览器中创建的。
蓝图类
Blueprint 类是游戏中最常用的类型,因为它自包含游戏机制,并且很容易在多个级别中重用。Blueprint 类继承自本机 C++ 类,可以拥有它们的功能。
您可以创建自己的 C++ 类并将它们标记为 Blueprintable 来创建蓝图类。他们可以彼此互动,创造有趣的游戏机制;例如,灯蓝图和开关蓝图进行通信以打开或关闭灯。您还可以让开关与多个灯相互作用,以随机或按顺序打开/关闭它们。
要创建蓝图类,右键单击内容浏览器,在“创建基本资产”下,选择蓝图类,如图 2-2 所示。
图 2-2
选择蓝图类
图 2-3 显示了一个带有事件的示例图,当该事件被触发时,会列出世界上存在的所有参与者(获取类节点的所有参与者),逐个遍历每个参与者(For Each Loop 节点),并为每个参与者调用切换灯光功能。
图 2-3
带有自定义事件的 Blueprint 类示例
水平蓝图
级别蓝图不能手动创建,但包含在级别本身中。该选项在载入标高时可用。你可以引用世界上的任何资产并与之交互。如果您在级别蓝图中引用一个蓝图类,您可以访问该类中的所有公共变量和函数。
Note
从类外部访问公共变量或函数。
使用关卡蓝图的好处是更容易访问关卡上的角色,因为你可以直接引用他们而不用强制转换。这在创建应该隔离到该级别的事件或函数时非常有用。一个例子是当满足特定条件时触发电影。
图 2-4 展示了如何打开关卡蓝图。
图 2-4
从编辑器工具栏访问级别蓝图
在关卡蓝图中,您可以通过选择并右键单击来引用任何资产(参见图 2-5 )。
图 2-5
参考资产
蓝图界面
蓝图接口是一种特殊类型的蓝图,在其中您只能创建带有输入和输出参数和变量的函数。让我们快速检查一下这些术语。
-
功能是具有单个输入引脚和单个输出引脚的图形。在内部,您可以连接构成逻辑的任意数量的节点,以便在调用函数时,它从入口引脚开始,激活所有连接的节点,并通过输出引脚退出。接口不能包含任何函数实现,这意味着你只能创建一个没有任何逻辑的函数(图形是只读的),不能创建变量。
-
变量是节点,保存一个值或对世界上一个对象或角色的引用。
接口可以被添加到多个其他蓝图中,并且它们保证包含在接口中创建的功能,然后这些功能可以被实现。这允许多个蓝图共享一个公共接口;例如,想象你有两个完全不同的蓝图,比如树和冰。你可以有一个包含伤害函数的接口,并在树和冰中实现这个接口。在树和冰蓝图里面,你可以实现伤害功能,让树燃烧,冰融化。如果没有接口,那么你必须把击中的演员转换成每种类型的演员,并调用破坏函数。
图 2-6 是一个示例图,显示了有接口和没有接口之间的区别。
图 2-6
接口与无接口的比较
要创建蓝图接口,右键单击内容浏览器,并在创建高级资产下,从蓝图子部分中选择蓝图接口(参见图 2-7 )。
图 2-7
创建蓝图界面
图 2-8 显示了创建后的界面。
图 2-8
蓝图界面示例
蓝图宏库
蓝图宏库是一个蓝图容器,由作为节点放置在其他蓝图中的图形集合组成。您不能在宏库中编译图形,因为它是一个容器。只有当包含宏的蓝图被重新编译时,对宏图的任何更改才会被反映出来(见图 2-9 和 2-10 )。
图 2-10
如何在蓝图中使用宏的示例
图 2-9
从数组中获取随机项的示例宏,数组是元素的列表或集合
第章 3 讲解如何结合 C++ 和 Blueprints。您可以创建自己的类和基于蓝图的类。此外,您可以在 C++ 中声明可以在 Blueprint 中访问的变量和函数。
三、C++ 和虚幻引擎 4
虚幻引擎 4 中使用 C++ 来创建游戏性元素或修改引擎。在本章中,您将学习如何安装 Visual Studio for 虚幻引擎。您还将学习如何创建 C++ 类,并了解它们的结构。
安装 Visual Studio
在编写和编译 C++ 代码之前,您需要在您的计算机上安装 Microsoft Visual Studio(Community Edition 或更高版本)(在 macOS 上,您需要 Xcode)。如果您是第一次安装 Visual Studio,请确保在您的安装中包含 C++(参见图 3-1 )。
图 3-1
安装 Visual Studio 并启用 C++ 进行游戏开发
安装完成后,重启电脑。可以开始用虚幻引擎 4 写 C++ 了。
添加 C++ 代码
从虚幻编辑器中添加新的 C++ 类。C++ 类是一种用户定义的数据类型,它拥有自己的变量和函数,可以通过创建该类的实例来访问这些变量和函数。这些变量和函数定义了类的行为。类定义不占用任何内存,但是在创建类的实例时会分配内存。在 UE4 中,Blueprints 从 C++ 中创建的一个类扩展而来,并继承了所有的类属性。
在本章中,您将创建一个基于 Actor 的类,它可以放在您的级别上。在 Unreal Engine 中,您需要了解两个主要的类:Actor 类和 Object 类。尽管用途取决于您的目标,但在创建类时,有些事情您必须牢记在心。
任何基于 Actor 的类都可以被放置或派生到该级别中。这些类有一个可视化的表示,并支持网络。基于对象的类通常用于存储数据,它们的内存大小通常小于基于角色的类。
在我们基于 Actor 的类中,您将某些属性和功能暴露给蓝图,用于修改 Actor 的行为。要创建一个新的 C++ 类,点击文件菜单并选择新建 C++ 类…选项(见图 3-2 )。或者,您可以右键单击内容浏览器并选择“新建 C++ 类”。创建 Visual Studio (。sln)文件中,右键单击。向上投影文件,并选择“生成 Visual Studio 项目文件”。
图 3-2。
从文件菜单创建新的 C++
向导会提示您,您可以在其中选择基类。从类列表中,选择 Actor 类(参见图 3-3 ,然后点击 Next。
图 3-3。
选择执行元类别
下一页提示您命名您的类,输入保存位置,并选择是否要将您的类组织成文件夹结构(见图 3-4 )。
图 3-4。
选择名称、位置和范围
通常,将头文件放在公共文件夹中,将源文件放在私有文件夹中是一种很好的推荐做法,因此选择 public 选项,将头文件放在公共文件夹中,将源文件放在私有文件夹中。现在,让我们坚持使用默认的 MyActor 名称,并单击 Create Class。
虚幻引擎现在将代码添加到您的项目中,并开始编译 C++ 代码。这可能需要几秒钟才能完成,因此您可以转到项目文件夹并查看创建了哪些新文件夹和文件。
在您的根项目文件夹中,您会看到以下新文件夹。
-
二进制文件:该文件夹包含可执行文件或编译过程中创建的其他文件。如果编辑器没有运行,可以将其删除,并在下次编译项目时创建。
-
中间:这个文件夹包含临时游戏对象文件和 Visual Studio 生成的项目文件。可以安全删除。
-
Source :这个文件夹包含了你游戏特有的代码文件。很明显,不要删。
如果您进入源文件夹,您会看到创建了一些额外的文件,例如your project name. target . cs 和your project name. build . cs 文件(您的项目的实际名称替换了您的项目名称)。目标文件决定了特定目标(如游戏、编辑器等)的构建设置(如何构建项目)。),而构建文件决定应该构建哪些模块。模块是容器,包括 C++ 类的集合,附带一个 C#编译文件(扩展名为*.build.cs)。当您生成一个模块时,在 Binaries 文件夹中会生成一个相应的 DLL 文件。当您发布项目时,所有模块都在一个 EXE 文件中链接在一起。
在公共文件夹中,MyActor.h 是头文件。在私有文件夹中,MyActor.cpp 是源文件。头文件是你声明变量和函数的地方。您在源文件中实现变量和函数。
检查标题
我们来分析一下 MyActor.h 文件。
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class BOOKTEMPLATE_API AMyActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
#杂注一次
pragma once 被称为预处理程序指令,这意味着您只能包含这个头文件一次。如果在任何其他文件中多次包含 MyActor.h,所有后续包含都将被忽略。
UCLASS()宏
UCLASS()宏是一个特殊的宏,Unreal Engine 需要它来使编辑器知道该类,并包含它以实现序列化、优化和其他引擎相关的功能。这个宏与 GENERATED_BODY()宏成对出现,后者在类体中包含了额外的函数和类型定义。
Note
UCLASS()宏可以接受参数(也称为说明符)。你可以在 https://docs.unrealengine.com/en-US/Programming/UnrealArchitecture/Reference/Classes/Specifiers/index.html
了解更多。
class book template _ API Amy actor:public a actor
是你上课的开始。_API
宏与 DLL 链接有关,这意味着它对 DLL 文件公共的函数、类或数据进行标记;导入这个 API 模块的任何其他模块都可以直接访问这些类或函数。这是从虚幻构建工具传递给编译器的。public AActor
意味着你从 Actor 类型继承了这个类。继承是一个特性,你可以从一个现有的类(也称为一个基类)创建一个新的类(也称为一个派生类)。继承的类继承了其基类(也称为父类)的所有特性,并且可以拥有自己的功能。
在这一行中,您可能已经注意到了类名的前缀。不是MyActor
,是AMyActor
。这是因为虚幻反射系统要求类以某个字母为前缀。反射系统是虚幻引擎的基础技术。它可以在运行时自我检查。以下是前缀列表以及每个前缀的含义。
-
a 表示参与者类型(例如,参与者、控制者、AGameMode)
-
u 表示非真实对象(例如,UObject、UActorComponent、USceneComponent)
-
模板的 t(例如,TWeakPtr、TArray、TMap)
-
s 代表 Slate(例如 SWidget、SCompundWidget、SCurveEditor)
-
I 表示接口(例如,IAssetRegistry、ILevelViewport、IAsyncTask)
-
e 代表 Enum(例如,EAnchorWidget、EAcceptConnection)
-
g 代表全球(如 GEditor、GWorld)
-
f 代表浮点型(例如,FVector、FGameplayTag)
公共:,受保护:,和私有:
public:
、protected:
和private:
被称为访问说明符(也称为访问修饰符)。它们定义了如何在这个类之外访问变量、函数等等。
-
public :任何外部类都可以访问成员
-
protected :任何继承类都可以访问成员
-
private :没有其他类可以访问成员
AMyActor()
AMyActor()
是构造器,是在创建对象时自动调用的特殊函数。它与类同名,并且从不返回任何类型。在这里,您可以初始化头文件中定义的任何类型的所有默认值。例如,如果创建一个 int32 类型的变量(比如 int32 MyVariable)在构造器内部,可以赋任意默认值,比如 MyVariable = 1。
虚拟 void BeginPlay()覆盖
关键字override
意味着这个函数已经在 Actor 类中声明和定义了,并且您正在重写它以拥有您自己的定制功能。因此,您声明了来自 Actor 类的 BeginPlay 函数。同样的想法也适用于 Tick 方法。
检查源文件
头文件只声明函数,不包含实现(代码不执行任何操作)。因为所有的实现都是在源文件(*。cpp),我们来看源文件。
#include "MyActor.h"
AMyActor::AMyActor()
{
PrimaryActorTick.bCanEverTick = true;
}
void AMyActor::BeginPlay()
{
Super::BeginPlay();
}
void AMyActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
这个源文件只包含一个非常基本的实现。这里需要记住的重要事情是Super
调用(Super::begin play();和 Super::Tick();),这意味着即使您覆盖了这些函数,它们仍然调用父类中定义的基实现。如果您正在覆盖本机引擎实现,包含Super
调用是非常重要的。
向蓝图展示变量和函数
从 C++ 类中,您可以将您需要的函数或变量暴露给蓝图,以便设计人员可以相应地修改它们。您修改新添加的 actor,使变量和函数暴露给蓝图。
修改标题
首先,让我们添加几个变量。在 GENERATED_BODY()宏下添加以下代码。
-
UCLASS 宏是针对类的。
-
UPROPERTY 宏用于变量。
-
UFUNCTION 宏是针对函数的。
private:
/* Our root component for this actor. We can assign a Mesh to this component using Mesh variable. */
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* MeshComponent;
/* Determines if this item can be collected. */
UPROPERTY(EditAnywhere)
bool bCanBeCollected;
/* Just an example to show toggleable option using metadata specifier. */
UPROPERTY(EditAnywhere, meta = (EditCondition = "bCanBeCollected"))
int32 ToggleableOption;
UPROPERTY 是一个特殊的引擎宏。在内部,指定如何公开变量。
以下是一些常见的 UPROPERTY 说明符。
-
EditAnywhere :该属性可以在默认蓝图和世界中放置的实例中编辑。
-
EditDefaultsOnly :该属性只能在默认蓝图中编辑。当您在世界中放置一个演员的实例时,您不能按实例单独编辑该属性。
-
EditInstanceOnly :该属性只能为放置在级别中的实例更改。此属性在默认蓝图中不可用。
-
VisibleAnywhere :该属性与 EditAnywhere 具有相同的可见性,但是该属性不可编辑。它是只读的。
-
VisibleDefaultsOnly :该属性与 EditDefaultsOnly 具有相同的可见性,但是该属性不可编辑。它是只读的。
-
VisibleInstanceOnly :该属性与 EditInstanceOnly 具有相同的可见性,但该属性不可编辑。它是只读的。
如有必要,可以基于布尔值启用或禁用属性编辑。这是使用名为 EditCondition 的元数据说明符实现的。前面的代码中提供了一个示例。
在AMyActor();,
下面添加一个暴露在蓝图中的功能。
/* Just a sample Blueprint exposed function. This comment appears as a tooltip in Blueprint Graph. */
UFUNCTION(BlueprintCallable, Category = "My Actor")
void CollectMe(bool bDestroy = true);
修改源文件
现在让我们修改源文件。首先,您需要分配一个网格组件变量,这是一个静态网格组件类型,用于创建静态网格的实例。您这样做是因为您需要一个有效的根组件来移动这个 actor。为此,您需要在构造器中构造静态网格组件对象,并在那里对其赋值。让我们看看下面的示例代码。
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
RootComponent = MeshComponent;
在代码中,您会看到一个名为 CreateDefaultSubobject 的特殊引擎函数。这个函数允许你创建一个给定类型的对象,它在编辑器中是可见的。该函数只能在构造器内部调用;在运行时调用它会使编辑器崩溃。
然后在源文件中为 CollectMe 函数创建一个定义。
Note
函数名可以是任何东西,但一般来说,建议使用描述函数用法的动词或基于返回值的动词。
现在,您基于输入参数和 bCanBeCollected 变量记录信息。如下定义该函数。
void AMyActor::CollectMe(bool bDestroy /*= true*/)
{
// Check if this actor can be collected...
if (bCanBeCollected)
{
// ...Now check if the actor must be destroyed...
if (bDestroy)
{
// ...Actor has to be destroyed so log that information and destroy.
UE_LOG(LogTemp, Log, TEXT("Actor collected and destroyed."));
Destroy();
}
else
{
// ...Dont destroy the actor. Just log the information.
UE_LOG(LogTemp, Warning, TEXT("Actor collected but not destroyed."));
}
}
else
{
// ...Actor cannot be collected thus cannot be destroyed.
UE_LOG(LogTemp, Error, TEXT("Actor not collected."));
}
}
最终代码
整个标题应该类似于下面的代码。请注意,BeginPlay 和 Tick 函数已被删除。
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class BOOKTEMPLATE_API AMyActor : public AActor
{
GENERATED_BODY()
private:
/* Our root component for this actor. We can assign a Mesh to this component using Mesh variable. */
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* MeshComponent;
/* Determines if this item can be collected. */
UPROPERTY(EditAnywhere)
bool bCanBeCollected;
/* Just an example to show toggleable
option using metadata specifier. Uncheck Can Be Collected boolean and this option disable (grey out). */
UPROPERTY(EditAnywhere, meta = (EditCondition = "bCanBeCollected"))
int32 ToggleableOption;
public:
AMyActor();
/* Collect this actor. Blueprint exposed example function. This also acts as a tooltip in Blueprint Graph. */
UFUNCTION(BlueprintCallable, Category = "My Actor")
void CollectMe(bool bDestroy = true);
};
源代码应该是这样的。
#include "MyActor.h"
#include "Components/StaticMeshComponent.h"
AMyActor::AMyActor()
{
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
RootComponent = MeshComponent;
bCanBeCollected = true;
ToggleableOption = 0;
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
void AMyActor::CollectMe(bool bDestroy /*= true*/)
{
// Check if this actor can be collected
...
if (bCanBeCollected)
{
// ...Now check if the actor must be destroyed...
if (bDestroy)
{
// ...Actor has to be destroyed so log that information and destroy.
UE_LOG(LogTemp, Log, TEXT("Actor collected and destroyed."));
Destroy();
}
else
{
// ...Dont destroy the actor. Just log a warning.
UE_LOG(LogTemp, Warning, TEXT("Actor collected but not destroyed."));
}
}
else
{
// ...Actor cannot be collected thus cannot be destroyed. Log as an error.
UE_LOG(LogTemp, Error, TEXT("Actor not collected."));
}
}
使用类
在 Visual Studio 中,按 F5 编译并启动项目。编辑器启动并运行后,右键单击内容浏览器并选择 Blueprint Class。在 Pick Parent Class 窗口中,展开所有的类并搜索 My Actor。然后,您应该选择我们的自定义 Actor 类作为 Blueprint 的基础(参见图 3-5 )。
图 3-5。
选择我们以前创建的 C++ 类作为父类
接下来,打开蓝图演员。您可以分配您的自定义网格并调整您选择暴露给 Blueprint 的属性(参见图 3-6 )。将鼠标悬停在属性上,会以工具提示的形式显示您在 C++ 中添加的注释。
图 3-6。
调整从 C++ 公开的属性
在蓝图中调用 C++ 函数
我的演员蓝图已经在编辑器里准备好了。您还在名为 Collect Me 的 actor 内部创建了一个蓝图可调用函数。在本节中,我们使用相对简单的逻辑在关卡蓝图中快速调用这个函数。
首先,将新创建的我的演员蓝图拖放到关卡上。之后,确保在关卡编辑器中选中我的演员蓝图,打开关卡蓝图。在 Level Blueprint 中,右键单击图表并创建对所选 My Actor Blueprint 的引用。在图 3-7 中,我把我的演员蓝图放在关卡上,选中它,在关卡蓝图中创建了一个引用。
图 3-7。
参考我的演员蓝图
从参考中,拖动一个大头针并选择收集我功能(参见图 3-8 )。
图 3-8。
呼叫对方付费功能
现在,您可以随时调用 Collect Me 功能。举个例子,让我们在延迟两秒钟后开始播放。最终的图形如图 3-9 所示。
图 3-9。
最终图形
尽管这里只使用了 boolean 作为输入,但是也可以使用其他类型,比如 floats、integers、units,甚至 UObjects 或 AActors。例如,如果您想要使用 int32,请在头文件和源文件中将布尔值更改为 int32。void collect me(int 32 Myint variable);。之后,可以在源文件中定义的 CollectMe 函数中使用 MyIntVariable。
单击播放按钮。2 秒钟后,My Actor Blueprint 将消息(见图 3-10 )打印到 C++ 中定义的日志中,并自我销毁。
图 3-10。
显示记录的信息
四、游戏类
本章着眼于你在几乎所有项目中使用的一些重要和常见的游戏职业。
行动者
Actor 类是游戏世界中所有可放置和可生成对象的基础。任何可以拖放到世界中或在运行时产生的东西都是从 Actor 类继承的。Actor 类是可以支持平移(位置)、旋转和缩放(大小)的基本类。Actors 可以包含任意数量的 Actor 组件类,这些类定义了它应该如何移动和呈现。默认情况下,Actors 是泛型类,没有任何类型的可视化表示。由组件给它一个可视化的表示。Actor 类还支持跨网络的复制和函数调用。
游戏模式库
游戏模式基础定义了游戏的基本规则,例如允许哪些演员、玩家分数、获胜规则等等。它保存了游戏世界中所有玩家的信息。例如,您可以定义赢得比赛所需的分数。游戏模式设置棋子、玩家控制器、游戏状态、玩家状态、观众等类别。在多人游戏中,游戏模式类只存在于服务器端,对于客户端来说,它总是空的,就好像从来没有存在过一样。这仅适用于多人游戏,因为在单人游戏中,没有客户端,只有服务器。
游戏模式是游戏模式基础的一个子类,是为多人游戏设计的。它包含为每个玩家选择种子点的默认行为。
游戏状态基础
游戏状态基础定义了游戏的当前状态。游戏状态类是由游戏模式产生的。它们在多人游戏中特别有用,因为游戏状态类被复制到每个客户端,所以对复制值的任何更改都会反映到所有客户端。例如,假设你有一个游戏模式,需要一定数量的杀戮才能赢得游戏。每当玩家消灭另一个玩家时,它运行一个服务器函数来更新游戏模式中当前的总击杀数,然后在游戏状态中改变该击杀数,并且在所有客户端上更新该击杀数。游戏状态是游戏状态库的子类。
游戏实例
游戏实例类是在运行游戏时创建的。它存在于游戏运行的时候。它有助于在不同级别之间传递信息。例如,想象在你的游戏中,玩家完成了某个挑战。你可以在游戏实例中存储这些特定信息。当玩家返回主菜单时,您可以在屏幕上显示一条消息,说明挑战已经完成。游戏实例是一个基于对象的类;它不会复制给任何人。服务器或客户端不知道除了他们自己之外的任何其他游戏实例。
游戏实例通过项目设置➤地图和模式➤游戏实例类设置(见图 4-1 )。
图 4-1
设置游戏实例类
下面描述了三个主要的游戏类别。
-
游戏模式持有游戏流程的权限,只存在于服务器上,防止作弊。
-
游戏状态保存游戏的复制状态。它存在于服务器和客户端。客户端可以向游戏状态询问游戏的当前状态,服务器可以修改游戏的当前状态,并将其复制到所有客户端。
-
游戏实例是一个真正的持久类,保证从游戏开始到退出都是可用和可访问的。它不被复制,同时存在于服务器和客户机上。
卒
卒是一个基于角色的类,充当玩家或人工智能(AI)的物理表示。棋子由控制器控制,控制器可以是玩家控制器,也可以是 AI 控制器。尽管默认情况下棋子没有视觉表示,但它仍然表示游戏世界中的位置、旋转等。棋子可以包含自己的移动逻辑,但最好在控制器中完成。棋子可以在网络上复制。
性格;角色;字母
Character 从 Pawn 类扩展而来,通过使用动画的骨架网格组件来支持可视化表示。它还包括模拟运动碰撞的胶囊组件和角色运动组件,角色运动组件是一个功能丰富的组件,包括行走、奔跑、跳跃、游泳和飞行等运动。它还包括基本的网络支持,这使它成为两足动物角色的首选。角色移动组件是特定于角色类的,因此它不能用于任何其他类。
播放器控制器
玩家控制器是控制器的子类,控制器本质上是它所控制的棋子的大脑。基于上下文,如果控制器由人类玩家控制(并且可以处理所有玩家输入),或者 AI 控制器由人工智能控制,则该控制器是玩家控制器。
默认情况下,通过调用 possession()函数,一个控制器在任何给定时间只能控制一个棋子。它通过调用 UnPossess()函数停止控制。控制器还允许来自它所控制的卒类的通知。在网络游戏中,服务器知道所有的控制器,而客户端只知道自己的本地控制器。
玩家状态
玩家状态是每个玩家都可以使用的复制角色。在单人游戏中只有一个玩家状态,但是在多人游戏中,每个玩家都有自己的玩家状态。因为它是复制的,所以服务器和客户机知道所有的玩家状态,并且它们都可以从游戏状态类中访问。这是一个存储玩家网络相关信息的好地方,比如分数、玩家名字等等。
让我们分享一个可以使用这些类的场景。写这本书的时候,battle royale games ( 堡垒之夜, PUBG 等。)在我们游戏玩家中很受欢迎,所以如果你要创建一个皇室战争游戏,你可以使用下面的类。
-
游戏模式:这个类定义了规则。在这个类中,你可以跟踪进入比赛的玩家数量,活着的玩家数量,死去的玩家数量,以及退出的玩家数量。该类还跟踪并决定其他游戏元素,如战斗机(或堡垒之夜的战斗巴士)、圆圈等等,以及比赛是已经开始还是正在等待开始(热身)。
-
游戏状态:这个类跟踪游戏的复制状态。例如,当游戏模式从预匹配(大厅)转换到匹配状态(战斗飞机或巴士在地图顶部开始)时,它会通知游戏状态状态已改变,并且游戏状态可以通知所有匹配开始的客户端。
-
游戏实例:这个类保存游戏中完成的任何目标或挑战的信息,以便玩家可以在返回主菜单时获得奖励。
-
角色:角色的视觉表现。
-
棋子:游戏中的车辆或动物。(可选)
-
玩家控制器:这个类处理所有的输入,创建和管理用户界面。它还可以向服务器发送 RPC(远程过程调用)。服务器知道所有的玩家控制器(例如,如果游戏有 100 个玩家,服务器知道所有的 100 个玩家控制器),但是客户端只知道它自己的。
-
玩家状态:这个类保存与玩家特定状态相关的信息(例如,你杀死的玩家数量,你发射的弹药数量,等等)。
创建角色类和玩家控制器类
在本节中,您将学习如何创建一个新的角色类,允许玩家使用玩家控制器类在游戏世界中移动。在本节的后面,一个 UMG(虚幻运动图形)小部件向玩家显示健康信息。
要创建新的角色类,请在内容浏览器上单击鼠标右键,然后选择“蓝图类”。系统会提示您为蓝图选择一个父类。从公共类别部分选择角色(参见图 4-2 )。
图 4-2
选择字符类
接下来,打开角色蓝图。您会看到默认的胶囊组件、网格组件和角色移动组件(参见图 4-3 )。
图 4-3。
默认组件
-
CapsuleComponent 是 actor 的根组件。它负责碰撞。CharacterMovement 知道这个胶囊组件,并根据玩家的输入移动它。
-
ArrowComponent 是一个编辑器专用组件,帮助指示对象面向哪个方向。
-
网格是角色的视觉表示,属于骨骼网格组件类型。
-
角色移动处理相关角色的移动逻辑。该组件更新 CapsuleComponent 的位置和旋转,使角色移动。它是一个高度特色的组件,包括网络支持。
我们将使用引擎自带的默认人体模型作为我们的角色。要使用它,您必须将第三人称项目添加到您的项目中。如果尚未添加,您可以通过点击内容浏览器中的添加新项按钮并选择添加特性或内容包来添加(参见图 4-4 )。
图 4-4。
从内容浏览器添加功能包或初学者内容
接下来,从蓝图功能选项卡中选择第三个人,然后单击添加到项目。现在你有了人体模型。将人体模型指定给之前创建的角色蓝图类。
您可能会注意到网格没有正确对齐。角色网格位于胶囊组件的中心,角色的旋转方向错误。要解决此问题,请将网格组件的位置和旋转调整为适当的值。
-
位置:X: 0,Y: 0,Z:–97
-
旋转:滚动:0,俯仰:0,偏航:270
你还需要给播放器增加一个摄像头来获得一个合适的视角。首先,添加一个弹簧臂组件,使其子组件与其父组件保持固定距离。为此,点击“组件”选项卡中的“添加组件”按钮,并选择“摄像机”部分下的“弹簧臂”(见图 4-5 )。
图 4-5。
添加弹簧臂组件
使用相同的方法,将相机添加到弹簧臂组件中。结果应该如图 4-6 所示。
图 4-6。
将相机添加到弹簧臂后
如果你将角色拖放到关卡编辑器中,你可以预览玩家如何从这个视角看到角色。角色类中几乎没有需要更改的设置。单击蓝图编辑器工具栏中的类默认值按钮。在“细节”面板中,禁用“棋子”区域下的“使用控制器旋转偏航”。之后,选择添加的弹簧臂组件,并启用“摄影机设置”下的“使用棋子控制旋转”。最后,选择角色移动组件,并启用“将旋转定向到移动”。
现在我们的角色差不多准备好了,让我们实现一个基本的功能来移动它。
首先,您需要添加两个函数,使角色向前/向后和向左/向右移动。要创建新函数,请在角色类的“我的蓝图”选项卡中单击函数部分下的函数按钮。点击按钮后,您可以重命名该功能;姑且称之为前进或者后退。
还添加了一个名为 ScaleValue 的浮点参数,用于缩放输入。要向函数添加参数,选择紫色节点,在细节面板中,添加新的输入参数,并从下拉列表中选择 Float(参见图 4-7 )。
图 4-7。
向函数添加输入
在这个函数中,你得到角色的控制旋转。您可以使用它的偏航旋转来查找向前向量,并将其用作移动角色的方向。向前向量是一个规范化的向量,它指向演员面对的方向。最终的功能应该如图 4-8 所示。
图 4-8。
最终功能
同样的功能可以复制;称之为向左或向右移动。使用向右向量,而不是向前向量。最终的功能应该如图 4-9 所示。
图 4-9。
获得右向量的功能相同
请注意,除了获取前向向量和获取右向向量之外,这两个函数看起来是相同的。
在继续输入之前,您需要在引擎部分下的输入设置中定义这些函数,该部分可从项目设置中访问。在图 4-10 中,你可以看到我定义的基本输入设置。
图 4-10。
通过项目设置添加输入
可以从棋子类或控制器类访问轴值。我们将使用后一种选择。
要创建新的播放器控制器,右键单击内容浏览器,然后选择 Blueprint Class。在下一个窗口中,选择“玩家控制器”并打开资源以添加节点。在播放器控制器图形中,右键单击图形并搜索 MoveForward,该名称与您在输入设置中定义的名称相同。选择节点后,再次右键单击图表并搜索“获取受控棋子”。“获得控制的棋子”节点的返回值指向玩家正在控制的当前角色或棋子。因此,从返回值中,拖动一个大头针并将其转换为您创建的字符类。从铸造的角色类中,拖动另一个大头针并调用您创建的向前或向后移动函数。将这组节点连接到 InputAxis MoveForward 事件。相同的一组节点连接到 InputAxis MoveRight 事件,但是不使用向前或向后移动函数,而是调用向右或向左移动函数。最终的图表应该如图 4-11 所示。
图 4-11。
包含所有功能的最终图形
最后一步使用我们游戏中的角色和控制器类。为此,创建一个新的游戏模式蓝图,并将我们的角色指定为默认棋子,将控制器指定为游戏模式中的玩家控制器类。在图 4-12 中,你可以看到我将我们的蓝图创建的角色和控制器分配给了游戏模式类。
图 4-12。
在游戏模式中分配控制器和棋子
要使用我们创建的 BP_GameMode,您需要将其分配到您的关卡的世界设置中。在关卡编辑器的主工具栏中,点击设置并选择世界设置(见图 4-13 )。
图 4-13。
从设置中选择世界设置
在世界设置选项卡中,指定游戏模式(参见图 4-14 )。
图 4-14。
分配游戏模式
如果您按下播放,您可以使用 W、A、S、D 键或您在输入设置中指定的任何键来移动角色。
在 HUD 上创建和显示数据
在本节中,您将为玩家角色添加一个基本的 HUD,创建一个在玩家受到伤害时会改变的健康变量,并创建一个在 3 秒后自动消失的欢迎文本。注意,这里显示的实现只适用于单人游戏;它在多人游戏中不起作用。
首先,让我们通过右击内容浏览器并选择用户界面➤小部件蓝图来创建一个 UMG 小部件蓝图(见图 4-15 )。
图 4-15。
创建小部件蓝图
打开小部件蓝图。您会看到主设计器部分,在这里您可以从面板中拖放不同的小部件。在右上角,您可以在设计器视图和图形视图之间切换(参见图 4-16 )。图形视图用于创建所有的蓝图逻辑。
图 4-16。
绕图排文
让我们将一个进度条小部件从面板拖放到 Designer 选项卡中。选择进度条小部件。在详细信息面板中,输入进度条的名称(如健康条)(见图 4-17 )。
图 4-17。
设置变量名
为了实现这个逻辑,让我们首先切换到图形视图并创建一个新事件。在图形中右键单击,并从添加事件类别中选择添加新事件。将此事件重命名为 UpdateHealth,并添加两个名为 CurrentHealth 和 MaxHealth 的浮点参数。从 CurrentHealth 浮动引脚拖动,搜索除法运算符,并选择它。然后将 MaxHealth 连接到 Divide 的 B 输入端。这是您在 ProgressBar 中使用的结果。这样做是因为 ProgressBar 在 0–1 范围内工作,并且因为您将 CurrentHealth 除以 MaxHealth,所以您会得到这个范围内的结果。
要设置 ProgressBar 的值,请将 ProgressBar 从变量类别拖放到图形视图,从大头针拖动,然后搜索 Set Percent。将“除法”节点的结果连接到“设置百分比的百分比”节点。您的节点设置应该类似于图 4-18 。
图 4-18。
更新健康设置
要使用 widget,您需要将其添加到屏幕上。我们打开角色蓝图,切换到事件图。在图形中右键单击并搜索创建小部件节点。在类输入中,设置新创建的 UMG HUD。然后右击返回值并选择提升为变量(见图 4-19 )。命名为 PlayerHUD 。这会将结果缓存到一个变量中,以便您可以随时访问它。
图 4-19。
提升为变量
从 PlayerHUD 变量中,拖动一个大头针,搜索添加到视口,并选择它(参见图 4-20 )。
图 4-20。
将小部件添加到视口
如果你现在玩,你可以在你的屏幕上看到新添加的 HUD,但它还没有做任何事情,因为我们还没有调用更新健康事件。要快速修复此问题,请从 PlayerHUD 变量中拖动一个大头针,然后搜索 UpdateHealth。现在创建一个名为 Health 的浮点变量,并将其默认值设置为 100。从我的蓝图选项卡中拖动此变量,将其连接到更新健康功能的当前健康,并将最大健康设置为 100(参见图 4-21 )。
图 4-21。
最初调用更新健康事件
设置好一切后,添加欢迎文本。将文本小部件从调色板选项卡拖放到设计器中。要将此文本在屏幕中央对齐,请通过扩展它并将最小值和最大值都设置为 0.5 来将锚点设置为居中。您还需要将对齐设置为 0.5。之后,将位置 X 和位置 Y 设置为 0。文本现在在屏幕上居中。接下来,启用“根据内容调整大小”来根据文本自动调整小部件的大小。
在内容类别下,根据您的喜好更改文本。对于这个例子,让我们写一些类似欢迎来到 UMG 的东西。展开“外观”下的字体,并将“大小”设置为 48。你需要文本在 3 秒钟后消失。为此,确保细节选项卡中的 Is 变量设置为真,并输入小部件的名称(如 WelcomeText)(参见图 4-22 )。
图 4-22。
为文本设置变量名
切换到 Graph 视图,现在可以将 WelcomeText 小部件从 MyBlueprint 选项卡(在 Variables 部分下)拖放到图形中。右键单击图表,搜索延迟,并选择它。将延迟节点连接到事件构造。然后从小部件文本的输出管脚拖动一条线,搜索 Set Visibility,并选择它。可见性从可见变为折叠,并将延迟节点的输出完成连接到设置可见性节点。最后,将延迟节点的持续时间更改为 3 秒(参见图 4-23 )。
图 4-23。
带延迟节点的图形
单击播放按钮。您会看到欢迎文本和进度条正在显示(参见图 4-24 )。
图 4-24。
添加了 UMG 插件来玩游戏
每当你的玩家受到伤害时,你可以降低你的生命值,并调用生命值与当前生命值关联的更新生命值,这样可以正确更新 UMG 的进度条。
五、物理学和光线投射
虚幻引擎中的物理负责所有物理角色的模拟和碰撞,这意味着任何运动,如下落或施加力或它们之间的相互作用都是使用物理来完成的。在写这本书的时候,Unreal 使用的是 PhysX 版,用于所有的模拟。
UE4 包括一个内置的高性能定制物理引擎,名为 Chaos Physics Engine,该引擎(在撰写本书时)只能通过 GitHub 源代码获得。混沌物理预计将在 UE4 版本 4.26 中投入生产。
光线投射(Raycasting,也称为 trace )是在游戏世界中从一个位置向另一个位置发送不可见光线,并确定光线是否击中任何东西的过程。点击结果包含可以用来改变游戏状态的信息。
在本章中,您将学习模拟、碰撞和追踪。
模拟
当一个物体通过施加一些力而下落或移动时,那么这个物体就是在模拟物理。要进行物理模拟,网格必须首先具有碰撞形状。因为实际的 3D 网格可能很复杂,所以 UE4 使用简单的代理形状,这些形状称为物理实体(也称为实体实例),如长方体、球体、胶囊或自定义凸包来模拟物理。通过选择要应用物理的网格来调整该物理实体的属性。例如,在第三章中,我们分配了一个静态网格组件,并为我们的 C++ actor 创建了一个蓝图类。
图 5-1。
继承网格组件
- 打开蓝图,从组件选项卡中选择 MeshComponent(继承的)(见图 5-1 )。
图 5-2。
详细信息面板中的物理类别
- 在细节面板中,向下滚动到物理类别(参见图 5-2 )。
让我们来看看这些属性。
-
模拟物理:启用或禁用物理模拟。如果该选项是灰色的,则您没有在选定的网格上设置碰撞。对于骨骼网格,您需要一个物理资源设置,对于静态网格,您需要一个简单的碰撞设置。
-
质量:物体的质量,单位为千克。
-
线性阻尼:施加在直线运动上的阻力,决定直线运动。
-
角阻尼:施加在角运动上的阻力,角运动相当于线性运动的旋转。
-
启用重力:决定物理模拟是否应该应用重力。
冲突
在 Unreal 中,每个具有碰撞的对象都被指定一个对象类型,该类型定义了它如何与其他对象类型交互。有些对象类型可以阻塞,有些可以重叠,有些可以忽略。对象还可以使用跟踪通道定义它应该如何对跟踪做出反应。默认情况下,有两种类型的跟踪通道:可见性和摄影机。
有两种类型的冲突:阻塞和重叠。
-
阻挡碰撞发生在两个物体碰撞而无法通过的时候(比如一个物体撞到一面墙)。
-
重叠碰撞发生在两个物体相互重叠的时候(例如,一个物体落入水中)。
碰撞由碰撞类别下组件上的预设决定。冲突生成带有信息的事件,包括与谁发生了冲突。图 5-3 显示了碰撞特性。
图 5-3。
静态网格的碰撞特性
-
模拟生成碰撞事件:如果为真,当碰撞成功时,该物理实体调用一个本地 on 组件碰撞事件。
-
物理材质覆盖:指定一种物理材质(不要与下一章的材质混淆)来定义摩擦、恢复和密度等属性。
-
生成重叠事件:如果为真,该物理实体在重叠开始时调用本机 On Begin Overlap 事件,在重叠结束时调用 On End Overlap 事件。
-
角色能否登上:决定玩家角色能否登上该物体。如果没有,玩家试图踩上去就会被拒绝。
碰撞有四种不同的状态。
-
无碰撞:没有任何类型碰撞的表示。这意味着没有重叠或阻塞。
-
仅查询:物体只能触发重叠事件,没有刚体碰撞;该对象不能阻止任何其他对象。
-
Physics only :该对象可以与其他对象交互,但不支持重叠。
-
碰撞使能:物体可以重叠并遮挡其他物体。
现在让我们来看看如何定义一个新的轨迹类型,并在游戏中使用它来拾取一个物品。
使用跟踪(光线投射)拾取项目
当你想拿起游戏中的一个物品时,你先看着这个物品,然后按下一个特定的键。当您按下引擎盖下的那个键时,引擎会从摄像机位置发出一条指定长度的不可见光线。在开始位置和结束位置之间,如果跟踪命中某个对象,它将返回包含命中信息的命中结果。图 5-4 说明了跟踪是如何工作的。
图 5-4。
展示跟踪如何工作的示例
那么 trace 怎么知道要打什么呢?这是使用跟踪通道完成的。虚幻引擎带有两个跟踪通道:可见性和相机。在本节中,您将创建我们自己的跟踪通道,将其设置为一个项目,并使用 trace 来检测它。
首先,让我们创建一个跟踪通道。点击菜单栏上的编辑打开项目设置,然后选择项目设置(参见图 5-5 )。
图 5-5。
访问项目设置
从项目设置中,单击引擎部分下的碰撞以修改碰撞设置(参见图 5-6 )。
图 5-6。
添加新的跟踪通道
单击“跟踪通道”类别下的“新建跟踪通道”按钮。出现一个新对话框,提示您命名跟踪和默认响应。输入名称 MyItemTrace 并将默认响应设置为忽略(参见图 5-7 )。
图 5-7。
用名称和响应定义新跟踪
按下 Accept 按钮后,您会在 Trace Channels 类别下看到新创建的跟踪。现在,您可以访问每个网格组件下的跟踪通道来启用或禁用它。
在我们做任何其他事情之前,让我们首先确保您使用的网格(1M_Cube)有碰撞可用。转到网格所在的内容浏览器,将鼠标悬停在网格上。在工具提示中,如果碰撞优先级大于 0,则网格包含碰撞(参见图 5-8 )。
图 5-8。
显示碰撞基本体数量的工具提示
如果网格是缺失碰撞,可以在静态网格编辑器中生成碰撞形状。从菜单栏中,单击“碰撞”并选择一个选项来生成所需的碰撞形状。对于简单的形状,可以选择长方体、圆柱体、球体等等,但是对于复杂的形状,选择自动凸碰撞(见图 5-9 )。
图 5-9。
各种碰撞
要使用跟踪通道,首先要打开之前创建的 Blueprint Actor,并从 Components 选项卡中选择 MeshComponent(继承的)。指定包含静态网格的碰撞。在我们的示例中,您使用 1M_Cube 静态网格。
接下来,转到细节面板,向下滚动到碰撞类别。展开“碰撞预设”区域,然后单击 BlockAllDynamic 下拉按钮。从选项列表中,选择自定义。通过选择“自定义”,可以选择对象如何响应与其他对象的碰撞。通过启用 block(参见图 5-10 )来选择阻止 MyItemTrace 跟踪响应。
图 5-10。
将我们的跟踪设置为阻止
您现在已经完成了项目设置,但是跟踪呢?您可以在字符类中这样做,但是您需要有一个输入来开始跟踪。为此,请单击菜单栏上的编辑,然后选择项目设置。接下来,选择引擎部分下的输入。在右侧,点击动作映射旁边的 + 按钮。将输入命名为您想要的任何名称,并为其选择一个键。我将其命名为 Trace,并选择 E 作为键(见图 5-11 )。
图 5-11。
创建要跟踪的新输入
如果尚未添加第三人称项目,可以通过在内容浏览器中单击绿色的“添加新项目”按钮来添加。选择添加功能或内容包。在对话框中,选择第三个人,点击+添加到项目。
接下来,按下 Shift+Alt+O 打开打开资产对话框。搜索 ThirdPersonCharacter 并打开它。或者,您可以导航到内容/第三个人/蓝图并打开第三个人角色蓝图(参见图 5-12 )。
图 5-12。
开放式第三人称角色
在事件图中,右键单击并搜索输入跟踪(在输入→动作事件下)。您可以添加跟踪事件,当您按下 E 键时会调用该事件。再次右键单击图形,并按通道搜索线追踪。(还有其他类型的轨迹,如盒子、胶囊和球体。请访问 https:// docs 了解更多信息。不现实。com/en-US/Engine/Physics/Tracing/Overview/index。html 。
按通道的线追踪是从起始位置追踪到结束位置的主要节点。图 5-4 显示了从摄像机开始的轨迹,并向摄像机面对的方向拍摄。要创建该设置,请右键单击图表并搜索 GetFollowCamera(无空格),然后选择“获取跟随摄影机”节点。这是组件中的角色包含的摄像机的 getter 节点。(或者,可以将跟随相机从“组件”选项卡拖动到图形上以创建节点)。
从该节点的输出管脚,拖动一条线,搜索 GetWorldLocation,并选择它。该节点显示摄影机组件在世界空间中的位置。将 GetWorldLocation 黄色输出连接到线跟踪节点的开始输入(见图 5-13 )。
图 5-13。
设置线追踪起始位置
现在,您需要跟踪在相机正在寻找的特定长度处结束。为此,从“跟随摄影机”中拖动一个关联,搜索并选择 GetWorldRotation,它表示摄影机组件的世界旋转。要找到方向,请从 GetWorldRotation 蓝色输出中拖动一条线,然后搜索并选择 GetForwardVector。
GetForwardVector 节点表示方向,但它返回一个单位向量,这意味着它在 0-1 范围内,在我们的上下文中没有用( https://en.wikipedia.org/wiki/Rotation_matrix
)。所以你要把它乘以一个更大的数字,来决定这个轨迹应该走多远。
从 GetForwardVector 节点的黄色输出中拖动一条线,搜索乘并选择 vector * float 节点。选择该节点后,在新创建的乘法节点的绿色框中输入 5000 。5000 是我们追踪的长度。将乘法节点输出连接到线路跟踪节点的末端输入。
接下来,单击线追踪节点上的可见性下拉按钮,并将其更改为 MyItemTrace(参见图 5-14 )。
图 5-14。
设置线追踪结束位置
您还没有完全完成,但是您已经完成了跟踪设置。如果你现在玩游戏并按下 E(或者你用来追踪的键),看起来好像什么都没发生,但是你在追踪。要对此进行检查,请单击绘制调试类型处的无下拉按钮,并将其更改为持续时间。再次按下播放按钮,开始跟踪。你现在会看到一条从你的相机出发的红线,它射向你正在看的地方。
即使您完成了跟踪,您仍然没有使用来自命中结果的任何信息。让我们解决这个问题。从线条跟踪的红色返回值中拖动一个大头针,搜索并选择分支节点。返回值是布尔值(真或假)。如果命中成功则为真(如果命中任何带有 MyItemTrace 的对象,跟踪响应为阻塞;在我们这里是我们创造的蓝图演员)。如果跟踪没有命中任何东西,则为假。从“出点击”节点拖动另一条线,并选择“断开点击结果”。产生的节点称为结构节点,它是包含其他数据类型的容器。
在“中断命中结果”节点中,单击向下箭头展开节点并显示所有高级类型。右键单击事件图,搜索并选择 Print String,这是一个将消息打印到屏幕上的简单节点。从 Hit Actor 拖动一条线,并将其连接到打印字符串的 In 字符串。这将在打印字符串和结构节点之间自动创建一个新节点。最后,用打印字符串连接分支节点,你就有了一个完整的设置(见图 5-15 )。
图 5-15。
使用打印节点添加调试设置
因为您需要跟踪来做出反应,所以您必须放置您创建的蓝图。将蓝图从内容浏览器拖放到游戏世界。现在按下播放键,查看放置在世界中的物品。按下跟踪输入键。您会看到一条红色的跟踪线(如果您将绘制调试类型设置为持续时间或持续时间),并在屏幕上显示一条消息(参见图 5-16 )。
图 5-16。
打印节点输出正确的信息
使用物理的破坏
在本节中,您将使用 Apex 破坏插件创建一个可破坏的网格,并在游戏中与之交互。在写这本书的时候,虚幻引擎正在从 Physx 过渡到混沌物理系统,使得这一部分在未来变得过时。我不介绍 Chaos,因为它还没有准备好投入生产,需要 GitHub 源代码版本,而不是启动程序版本。
要创建一个可破坏的网格,你首先需要从插件部分启用 Apex 插件。单击菜单栏中的编辑按钮,然后选择插件。转到物理类别,并启用 Apex 破坏插件。重启编辑器(参见图 5-17 )。
图 5-17。
启用 Apex 销毁插件
重新启动后,您可以在内容浏览器中右键单击任何静态网格角色(在我们的示例中为 1M_Cube ),然后选择 Create Destructible Mesh。一个新的可析构编辑器打开,静态网格旁边有一个新的可析构编辑器 actor。让我们来看一下图 5-18 中的可破坏网格编辑器的基础。
图 5-18。
可析构编辑器布局
-
断裂网格(图 5-18 中的 1 区)将网格断裂成多个块。组块的数量基于 Voronoi 部分(右下角)下的小区站点计数。
-
预览深度 0 (图中 2 区 5-18 )为组块深度。
-
爆炸量(图 5-18 中的区域 3)是一个滑块,将碎片移开。
-
可破坏设置(图 5-18 中的区域 4)是调整各种可破坏设置的面板。
-
断裂设置(图 5-18 中的区域 5)是您决定生成组块数量的区域。
首先,将小区站点计数设置为 100,然后按“断开网格”按钮。这将网格分成 100 个不同的大块(见图 5-19 )。
图 5-19。
100 片网眼
返回内容浏览器,将新创建的可析构网格角色(1M_Cube_DM)拖到游戏世界中。如果你现在按下播放按钮,什么也不会发生,因为可破坏的网格没有模拟物理。要修复它,请在视口中选择可析构角色,并在“细节”面板中启用模拟物理(参见图 5-20 )。
图 5-20。
模拟物理已启用
现在,如果你按下 Play,你会看到可破坏的网格掉落并撞击地面,但没有破坏。为什么呢?这是可破坏的网格,但是没有破坏,对吧?嗯,这是因为你还没有启用破坏网的伤害。那你是怎么做到的呢?打开可破坏网格角色,打开启用冲击破坏(见图 5-21 )。
图 5-21。
撞击伤害启动
回到你的游戏世界,按 Play。观察网状物是如何落下并碎成碎片的(参见图 5-22 )。
图 5-22。
跌落后网眼断裂
当网格破裂时,让我们通过应用粒子效果来增加一点趣味。对于粒子系统,如果尚未添加,请添加 Starter 内容包。要添加初学者内容,请在内容浏览器中单击“添加新内容”按钮,然后选择第一个添加功能或内容包。在下一个对话窗口中,切换到内容包选项卡,选择起始内容,并单击+添加到项目(参见图 5-23 )。
图 5-23。
向我们的项目添加入门内容
添加 Starter 内容后,打开 1M_Cube_DM,并在“效果”类别下的“破坏设置详细信息”面板中,展开“断开设置”。这里你看到两个数字:0 和 1。展开 1 并分配 P_Smoke(这是起始内容中的粒子)(见图 5-24 )。
图 5-24。
指定烟雾粒子系统
如果你现在玩,当物体掉落破碎时,你看到的是在破碎位置播放的粒子效果(见图 5-25 )。
图 5-25。
网格断开后正在播放烟雾效果
六、导入网格和纹理
让我们从 Autodesk 3ds Max 中导出网格,并将其导入到虚幻引擎中。首先,打开 3ds Max 场景,并准备好导出网格。从“文件”菜单中选择“导出”,然后选择保存 FBX 文件的位置和名称。FBX 导出选项弹出来设置以下设置,因为您正在导出静态网格。
-
展开几何体类别并启用平滑组。
-
展开“动画”、“摄影机”和“灯光”类别并禁用它们。
-
(可选)禁用嵌入媒体。通过启用嵌入媒体选项,可以在 FBX 文件中包含纹理。
-
展开“单位”类别并启用自动转换。
-
最后,确保 FBX 文件格式是 2018,然后按确定。在写这本书的时候,UE4 FBX 进口管道使用 FBX 2018。使用较早或较新的版本可能会导致不兼容。
导出资源后,打开虚幻引擎和内容浏览器。右键单击空白区域并选择导入选项。从“导入文件浏览器”对话框中,导航到导出资源的位置,选择它,然后导入。这将打开 FBX 导入选项对话框。让我们来看看一些重要的设置(见图 6-1a 和 6-1b )。
图 6-1b。
图 6-1a。
-
自动生成碰撞(图 6-1a 中的区域 1)如果 FBX 文件中没有包含自定义碰撞,自动为你的网格生成碰撞。
-
合并网格(图 6-1a 中的区域 2)是一个高级选项。如果在一个 FBX 文件中有多个网格,启用该选项会将它们合并到一个网格中。
-
导入平移/旋转和缩放(图 6-1a 中的区域 3)调整导入网格相对于 FBX 文件的位置、旋转和缩放。这意味着,如果您以统一比例输入 2,那么网格将比原始文件大两倍。
-
转换场景(图中 4 区 6-1b )将 FBX 坐标系转换到 UE4 坐标系。建议打开此选项。
-
导入纹理(图中 5 区 6-1b )。由于我们选择在导出 FBX 文件时不嵌入媒体,因此该选项没有任何作用,因为没有嵌入媒体。如果您的网格是带纹理的,并且您选择在导出时嵌入媒体,则启用该选项也会导入关联的纹理。
因为这个网格已经被纹理化了,我导入了所有相关的纹理并创建了一个材质来应用到这个网格上。以下是创建纹理时需要记住的一些提示。
-
仅支持以下格式。
-
位图文件的扩展名(Bitmap)
-
巴布亚新几内亚
-
热重量分析法
-
使用 jpeg 文件交换格式存储的编码图像文件扩展名
-
PSD: Photoshop 文件
-
PCX:图片交换
-
EXR 或 HDR: OpenEXR 或 HDR 格式,通常用于环境贴图
-
DDS:立方体贴图
-
-
所有纹理(除了用户界面/HUD)必须是 2 的幂(例如,32×32、64×64、128×128、256×256、128×32、512×256、2048×512 等)。)虚幻引擎支持的最大纹理分辨率为 8192,但游戏中的显示被限制在 4096。
-
强烈建议尽可能对灰度纹理使用打包纹理,因为 UE4 材质编辑器可以从纹理中读取单个通道。例如,将“粗糙度”纹理设置为红色通道,将“金属色”设置为绿色通道,将“环境光阻挡”设置为蓝色通道,将“发射”设置为 Alpha 通道。这种工作流程效率更高,并将纹理数量从 4 个减少到 1 个。
你需要了解每一个纹理以及它的用处,包括单独的纹理通道。首先,让我们看看一些常见的纹理术语,因为它们很重要。
-
漫射(非 PBR):这张贴图定义了你的物体的外观。想象在砖墙前拍摄一张照片,用作漫反射贴图。它包括所有的照明信息和阴影细节。
-
反照率 (PBR):与漫反射相同,但没有任何照明或阴影信息。
-
镜面反射(非 PBR):通常是一个灰度贴图,定义反射应该出现的位置。
-
粗糙度 (PBR):定义反射清晰度的灰度贴图。更接近白色意味着它更粗糙,所以光线向更多方向散射,使反射模糊。越接近黑色意味着越平滑,反射越清晰。
Note
一些软件可能会称之为地图光泽。术语光泽度和粗糙度是可以互换的,它们是相互颠倒的。
-
金属质感 (PBR):一张黑白贴图,定义你的表面有多像金属。使用黑色或白色的原因是表面要么是金属的,要么是非金属的。介于 0 和 1 之间的任何值都是罕见的用例。
-
正常 (PBR 和非 PBR):代表每个通道不同方向轴的紫色地图。它为表面提供了额外的高微观细节。
-
环境遮挡 (AO) (PBR 和非 PBR):包含微阴影或细节阴影的黑白贴图。将这张地图乘以反照率。
一些纹理(也称为贴图)旁边写着 PBR 和非 PBR。这意味着该映射仅在特定的工作流中有用。PBR 代表基于物理的渲染,UE4 和所有现代游戏引擎都使用它。
现在你有了纹理的概念,让我们把它们导入到虚幻引擎。导航到有纹理的文件夹,或者直接拖放到内容浏览器,或者右键单击内容浏览器并导入它,就像您对我们的网格所做的那样。
创建材料
要使用纹理,您需要创建定义对象外观和感觉的材质。您可以设置材质的亮度、纹理的外观、透明度等等。在材质内部,可以创建表达式节点来定义材质的感觉。要创建材质,请在内容浏览器中单击鼠标右键,然后选择“材质”。之后,双击新创建的材质打开材质编辑器。你看到一个图形编辑器,如图 6-2 所示。
图 6-2。
材质编辑器分为四个部分。
-
视窗(图 6-2 中的区域 1)是你看到最终素材的地方。
-
在细节面板(图 6-2 中的区域 2)可以编辑所选节点的属性(也称为材质表达式节点)。如果没有选择节点,则显示材质的属性。
-
图形编辑器(图 6-2 中的区域 3)是你创建材质表达节点网络的地方。默认情况下,它从具有一系列输入的单个基础节点开始,您可以将这些输入连接到材质表达式。
-
调色板面板(图 6-2 中的区域 4)包含所有可以拖放到图形编辑器中的节点列表。
-
统计面板(图 6-2 中的区域 5)显示了材料的重要信息,如指令数和纹理样本数。
让我们应用基本颜色的材料。按住键盘上的 3 键。在图形编辑器中单击鼠标左键,创建一个 vector3 节点。或者在图形编辑器中右键单击,搜索 Constant3Vector 并选择该节点。创建后,右键单击选定的节点并选择“转换为参数”。系统会提示您输入新节点的名称。为了简单起见,我们称之为 Color 并将第一个白色大头针连接到你的材质的基色节点。结果图应该如图 6-3 所示。
图 6-3。
您创建的参数值可以通过 C++ 或 Blueprints 在运行时修改。
材料类型
在继续学习材质之前,我们先来看看 Unreal Engine 中可用的不同类型的材质。材料有三种类型。
-
素材:您刚刚创建的主素材。
-
材质实例常量:实例化的材质(基本上是主材质的子材质)在运行时不能改变其属性。
-
材质实例动态:一个实例化的材质,可以在运行时改变其属性。它只能在运行时通过蓝图或 C++ 创建,不能通过内容浏览器访问。
材质实例(常量和动态)用于更改材质的外观,而无需重新编译主材质。您在主材质编辑器中所做的任何更改都需要重新编译着色器,如果材质非常复杂,这将需要很长时间。为了节省时间,您可以选择参数化要更改的主材质中的某些节点,而无需重新编译材质。这是通过创建一个 Constant3Vector 节点并将其转换为一个参数来完成的。
返回到内容浏览器,右键单击刚刚添加的材料,并从上下文菜单中选择创建材料实例(参见图 6-4 )。
图 6-4。
现在,在材质旁边有了一个新资源,这就是我们的实例化材质。打开它,你应该会看到一个类似于图 6-5 所示截图的窗口。
图 6-5。
-
视口(图 6-5 中的区域 1)是你看到材质实例变化的地方。
-
细节面板(图 6-5 中的区域 2)列出了来自母材料的所有参数。在这里,您可以覆盖或更改它们,并在视口中立即看到更改。默认情况下,参数不会被覆盖;要覆盖它们,左键单击参数名称旁边的灰色复选框。
让我们将这个材质实例的颜色改为蓝色。为此,请覆盖颜色参数并单击默认颜色以打开拾色器。随意选择你喜欢的任何颜色(见图 6-6 )。
图 6-6。
更改颜色时,视口会无延迟地实时更新更改。
返回到内容浏览器,打开之前创建的原始材质,更改颜色,然后应用。您会看到,在显示更改后的颜色之前,视口会暂时将材质更改为基于栅格的材质。这是因为着色器重新编译。
除了提到的所有材质类型,还有两种类型对材质工作流有帮助。
-
材料功能
-
材料参数收集
材质函数本质上是不同材质表达式节点的集合,您可以在材质中重用这些节点。把它们想象成一个容器,或者包,或者一组有输入和输出的节点。如果您经常创建一系列节点,并且您发现自己在重用它们,那么您最好创建一个材料函数,并将这些节点系列保存在一个函数图中。
Unreal Engine 4 已经包含了几个常用着色器操作的材质函数,如混合、模糊着色、简单草风等。它们可以接受任何输入,并根据输入和其他参数进行处理,然后发送输出。
材料参数集合是一种数据资产,用于存储任何材料中使用的一组标量和矢量参数。这个集合可以从任何地方访问,使其成为向多种材料发送全局数据的强大工具。
材料功能示例
为了演示一个材质函数,让我们创建一个简单的在 X 和 Y 方向平移纹理的函数。要创建材质函数,单击内容浏览器,在“材质和纹理”类别下,选择材质函数(参见图 6-7 )。
图 6-7。
创建后,给它命名(例如,MyMaterialFunction)并双击打开它。一旦你打开,你会看到一个默认输出(这应该是默认选择)节点,和材料应该是说缺少功能输出结果。这是因为一个材料函数可以有多个输入和输出。您可以通过右键单击输出节点并选择开始预览来预览输出。默认情况下,Unreal Engine 预览默认输出节点,由于没有连接任何节点,因此它只报告错误。现在要修复它,右键单击蓝色输出节点并选择停止预览节点(见图 6-8 )。这将消除错误。
图 6-8。
你创建了一个平移纹理的材质函数,让我们开始吧。
-
按住键盘上的 T 键,在图形内单击鼠标左键或右键,搜索并选择纹理样本。
-
在图形中右键单击,搜索函数输入并选择它。这些节点是将数据传递到图中的一种方式(类似于编程语言中的函数输入)。因为您想要使用任何纹理的能力,您使用它作为输入。
-
在“细节”面板中(确保选择了“输入”节点),将“函数输入”设置为“函数输入纹理 2D”,并将“输入名称”设置为“纹理”(这可以是您喜欢的任何名称,因为它除了在图形外部可见之外没有任何用途)。将该节点连接到先前创建的纹理采样节点的纹理输入。
-
右键单击图形内部,搜索纹理对象并选择它。您可以使用该节点为之前创建的输入节点提供默认值。将纹理节点连接到函数输入节点的预览输入。
-
右键单击图表,搜索“平移”,然后选择它。这是进行平移或移动的主节点。将声相器输出连接到纹理采样节点的 UVs 引脚。
-
创建另一个函数输入节点并选择它。将功能输入设置为功能输入向量 2,并将其连接到之前创建的声相器节点的速度输入。确保启用“使用预览值作为默认值”。
-
右键单击图形,搜索 Constant2Vector 节点,并选择它。将 Constant2Vector 的 R 和 G 的默认值都设置为 0.1,并将其连接到之前创建的函数节点的预览输入。
-
剩下的唯一事情是将纹理样本节点的输出连接到主输出节点。右键单击最终输出节点,然后选择“开始预览节点”。
你现在应该有一个类似于图 6-9 的图表。
图 6-9。
要将其显示给材质,请确保没有选择任何节点。在“细节”面板中,启用“向库公开”。展开“库”类别文本部分。将“杂项”类别替换为您喜欢的任何名称,并保存材质功能。比如我把它改成了一本 press Book(见图 6-10 )。
图 6-10。
回到本章开始时创建的主要材料,右键单击图形内部。您应该看到新材料功能已准备就绪(见图 6-11 )。
图 6-11。
使用 Blueprint 和 C++ 在运行时修改材料
在“材质类型”部分,我提到了可以使用材质实例动态来更改材质属性。本节介绍如何使用您创建的材质创建动态材质实例,并在运行时更改其颜色。
使用蓝图
对于这个材质运行时章节,你使用一个虚幻引擎附带的静态网格,这样你就可以很容易地跟随。
打开或转到内容浏览器,在空白区域单击鼠标右键,然后选择“蓝图类”。从“选择父类”对话框中选择 Actor 作为我们的基础,并为其命名(例如,BP_MyActor)。双击新创建的 Blueprint 类将其打开。打开后,您会在中间看到“视口”选项卡,在右侧看到“组件”选项卡。
单击绿色的+添加组件按钮,并从列表中选择静态网格。您可以将其重命名为您喜欢的任何名称,对于这个示例,我将其重命名为 MyStaticMeshComponent。选择新创建的静态网格组件,并从“静态网格”类别下的“详细信息”面板中,单击标记为“无”的下拉按钮。这将打开一个下拉列表,您可以在其中选择要指定的网格。由于您使用的是引擎自带的网格,单击查看选项按钮并选择显示引擎内容(参见图 6-12 )。
图 6-12。
接下来搜索 SM_MatPreview,选择 SM_MatPreviewMesh_01。您应该在“视口”选项卡中看到新的网格。现在您已经指定了我们需要的网格,您不再需要显示引擎内容。转到相同的视图选项,取消选中显示引擎内容。
现在,您已经完成了网格的指定,但是您仍然需要在运行时更改材质。下面解释一下怎么做。
-
切换到“构造脚本”选项卡,在图形中单击鼠标右键,然后搜索“创建动态材质实例”。选择第一个节点。
-
在新创建的节点中,单击父按钮(显示“选择资源”)并选择先前创建的材质。无论选择主父材质还是它的一个实例,都没有关系。
-
因为您在运行时更改了它,所以您需要一个对此动态材质实例的引用。单击、拖动并释放返回值锁定,然后选择提升到变量。让我们称这个新变量为 MID(材质实例动态的简称)。
-
从“组件”选项卡中单击 MyStaticMeshComponent 组件,并将其拖到图形中。这将自动为 MyStaticMeshComponent 创建一个 getter 节点。从 MyStaticMeshComponent 的输出管脚,左键单击并拖动一条新的线,然后释放它。从出现的上下文菜单中,搜索“Set Material”并选择它。确保选择“设置材质”而不是“按名称设置材质”。
-
将设置中间节点连接到设置材质节点。
-
再次右键单击图表,搜索 Get MID 并选择它。这与您在步骤 3 中创建的变量相同。将中间节点连接到在步骤 4 中创建的“设置材质”节点的材质输入。
图 6-13 显示了完整的图形。
图 6-13。
切换到 Viewport 选项卡,你会看到网格现在是黑色的,这是你在 Material 中指定的默认颜色(见图 6-14 )。
图 6-14。
现在是时候创建一个新的函数来动态改变颜色了。切换回构造脚本选项卡,并再次创建获取中间节点。从中间节点拖动一条线,并选择“设置向量参数值”节点。在该节点中,将参数名称设置为颜色,该名称与您在材质编辑器中设置的名称相同(参见图 6-15 )。
图 6-15。
您可以创建一个功能节点来轻松切换颜色。要创建函数,请执行以下步骤。
图 6-16。
-
选择中间节点和“设置向量参数值”节点。(按下 Ctrl 并选择它们。)
-
右键单击任何一个选定的节点。
-
选择折叠到功能(见图 6-16 )。
在“我的蓝图”选项卡中,系统会提示您重命名这个新功能,因此我们将其命名为 ChangeColorTo。双击此新节点以打开图表,并将“设置向量参数值”的值大头针拖动到紫色的“将颜色更改为”节点上。您应该会看到将引脚添加到节点工具提示(参见图 6-17 )。
图 6-17。
当您看到此工具提示时,松开鼠标左键,您现在有一个连接到“设置向量参数值”节点的此函数的值输入。
返回到构造脚本,并在设置材质节点后将新创建的“更改颜色”连接到节点。对于值,设置不同的颜色(例如红色)并编译。切换回“视口”选项卡,您会看到应用了红色。你现在有了一个可以在游戏过程中随时调用来改变颜色的函数。
使用 C++
既然你已经知道了如何使用蓝图来修改运行时材料,让我们深入 C++ 看看事情是如何完成的。在第四章中,你已经创建了一个可以修改的 Actor 类。你需要做的第一件事是将一个类型为 material 的变量暴露给 Blueprints,这样你就有了一个 Material 来创建 Material 动态实例。在 MyActor.h 头文件中编写以下代码。
/* Material to be used as the parent for Material Instance Dynamic.*/
UPROPERTY(EditAnywhere)
class UMaterialInterface* ParentMaterial;
/* Cached reference to the instance created. */
UPROPERTY()
class UMaterialInstanceDynamic* MID;
/* Use this color in MID. */
UPROPERTY(EditAnywhere)
FLinearColor NewColor;
在 Blueprints 中,您可能已经注意到在 Construction 脚本中创建了动态材质实例,并且创建了一个自定义函数来更改颜色。在 C++ 中,您可以使用 OnConstruction 本机方法和一个自定义函数来完成同样的工作。在 MyActor.h 头文件中,添加以下代码。
/* This is the same as Construction Script in Blueprints */
virtual void OnConstruction(const FTransform& Transform) override;
/* You create the same function from Blueprint, which can change the color in MID. */
UFUNCTION(BlueprintCallable, Category = "My Actor")
void ChangeColorTo();
现在要实现,将以下代码添加到 MyActor.cpp 文件中。
void AMyActor::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
// Make sure you have a valid material to create from.
if (ParentMaterial != nullptr)
{
// Create the dynamic material instance using the static Create method.
MID = UMaterialInstanceDynamic::Create(ParentMaterial, this);
// Assign the material to Mesh Component.
MeshComponent->SetMaterial(0, MID);
}
}
void AMyActor::ChangeColorTo()
{
// It is important to check if MID is valid.
if (MID != nullptr)
{
// Set the color to NewColor value.
MID->SetVectorParameterValue(FName("Color"), NewColor);
}
}
在 C++ 中,可以从内容浏览器中直接引用资源。您可以快速浏览一下在 C++ 中直接分配父材质的情况。
首先,在“内容浏览器”中的材质上单击鼠标右键,然后选择“复制参照”,这将复制剪贴板的参照路径。在 C++ 中,稍微修改一下就可以使用这个路径(参见图 6-18 )。
图 6-18。
打开 MyActor.cpp 文件,在 AMyActor 构造器下添加以下代码。
static ConstructorHelpers::FObjectFinder<UMaterialInterface> ContentBrowserMaterial(TEXT(""));
现在,在(TEXT(" ")的引号内,粘贴从剪贴板中从内容浏览器复制的参考路径。现在看起来应该是这样的(注意:路径可能会根据材料的位置而有所不同)
static ConstructorHelpers::FObjectFinder<UMaterialInterface> ContentBrowserMaterial(TEXT("Material'/Game/Blueprints/M_Example.M_Example'"));.
接下来,在最后一行下面添加这个条件流。
if (ContentBrowserMaterial.Succeeded())
{
ParentMaterial = ContentBrowserMaterial.Object;
}
您的构造器代码现在应该如下所示。
AMyActor::AMyActor()
{
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
RootComponent = MeshComponent;
bCanBeCollected = true;
ToggleableOption = 0;
static ConstructorHelpers::FObjectFinder<UMaterialInterface> ContentBrowserMaterial(TEXT("Material'/Game/Blueprints/M_Example.M_Example'"));
if (ContentBrowserMaterial.Succeeded())
{
ParentMaterial = ContentBrowserMaterial.Object;
}
PrimaryActorTick.bCanEverTick = true;
}
在 Visual Studio 中按 F5 运行游戏。一旦编辑器启动,打开基于 MyActor 的蓝图,这是您在第四章中创建的。现在,您应该在蓝图编辑器中看到新的父材质和新的颜色。将蓝图拖放到游戏视口。你现在可以看到网格是黑色的,这是默认的颜色(见图 6-19 )。
图 6-19。
您可以将新颜色调整为任何其他颜色,并立即看到变化。因为你也向 Blueprint 暴露了 C++ 中的改变颜色功能,你可以在游戏过程中的任何时候调用它。
七、演示游戏
在这最后一章中,您将使用虚幻引擎(发射器版本)附带的默认第一人称射击游戏模板(蓝图版本)创建一个演示游戏,并扩展它以包括各种功能,如弹药计数、弹药拾取等。完成这些功能后,您将学习如何为 Windows 打包游戏。
创建项目
让我们从使用第一人称模板创建一个项目开始。打开 Epic Games Launcher,点击右上角的黄色启动按钮。这将启动虚幻引擎,很快,你就会看到虚幻项目浏览器窗口(见图 7-1 )。
图 7-1
。虚幻项目浏览器
选择游戏,然后单击下一步。从模板选择屏幕中,选择第一个人模板并点击下一步(参见图 7-2 )。
图 7-2。
选择第一个人模板
在项目设置页面中,您可以设置项目的初始设置和类型(Blueprint 或 C++)。从一个 C++ 项目开始,创建蓝图。选择 C++,设置一个位置,命名您的项目,然后单击“创建项目”。重要的是要记住,这些文件是在这个上下文中命名的:
图 7-3。
选择 C++ 项目类型
创建项目后,Visual Studio 会自动打开该项目的解决方案文件。虚幻编辑器也会启动。按下工具栏上的播放按钮(或 Alt+P )来测试 FPS 模板。玩的时候,用 W 键前进,S 键后退,A 键向左扫射,D 键向右扫射,鼠标环顾四周,鼠标左键发射弹丸。该模板还包括基本的物理模拟,因此当你拍摄任何一个白盒时,它都会翻滚(见图 7-4 )。
图 7-4。
启用物理的盒子
弹药设置
你可能已经注意到了,你可以随心所欲地发射炮弹。我们需要改变这种情况。我们需要设置武器的弹夹数量和每个弹夹的弹药量。例如,如果这个武器有 3 个弹夹,每个弹夹有 20 发弹药,那么这个武器可以拥有的最大弹药量是 3 * 20 = 60。每次发射 20 发子弹,每个弹夹都会被耗尽。在拍摄下一个片段之前有两秒钟的暂停时间。在实际项目中,这个超时被一个重新加载动画所取代。我们将使用变量来定义值,因此您可以调整一切,而不必担心内部工作。
要有弹药功能,打开 Chapter07Character.h 头文件。以下变量是在 GENERATED_BODY()宏下创建的。
-
最大弹夹数(整数):武器可以拥有的最大弹夹数。默认 5。
-
开始片段(整数):游戏开始时武器拥有的片段数。这不应大于最大片段数。默认 3。
-
每个弹夹的弹药量(整数):每个弹夹的弹药量。默认 20。
-
当前弹夹(整数):武器当前拥有的弹夹数量。每次武器重新装填时,将该变量减一,以表示弹夹已被使用。默认为 0。
-
当前弹药(整数):武器当前的弹药量。每次射击时,将这个变量减一,表示你发射了一颗子弹。默认为 0。
-
bCanShoot (boolean):决定你能不能开枪的真或假变量。默认为假。
-
ReloadTime :当所有子弹发射完毕后,武器重新装弹的时间(秒)。默认 2。
因为您希望它们向编辑器公开,所以将它们标记为 UPROPERTY。以下是变量的代码。请注意 BlueprintReadOnly 和 BlueprintReadWrite 的用法。
/* Maximum amount of clips this weapon is allowed to have. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 MaxClips;
/* Amount of clips for the weapon to have when the game starts. This should never be greater than Max Clips. */
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
int32 StartingClips;
/* Amount of ammo per clip. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 AmmoPerClip;
/* True or false variable that determines if you can shoot or not. */
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
bool bCanShoot;
/* Time in seconds for the weapon to reload when all bullets are fired. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float ReloadTime;
/* Amount of clips this weapon currently has. Every time the weapon reloads, you subtract this variable by one to denote that the clip has been used. */
UPROPERTY(BlueprintReadWrite)
int32 CurrentClips;
/* Amount of ammo this weapon currently has. Every time you shoot, you subtract this variable by one to denote that you fired one bullet. */
UPROPERTY(BlueprintReadWrite)
int32 CurrentAmmo;
设置完变量后,让我们在构造脚本图中创建初始逻辑。在此之前,您必须为这些变量分配默认值;否则,它们为 0 和 false。打开 Chapter07Character.cpp。在文件的顶部(第 20 行周围),您会看到 AChapter07Character 构造器(例如,a chapter 07 character::a chapter 07 character())。在这个块中,按如下方式分配默认变量。
MaxClips = 5;
StartingClips = 3;
AmmoPerClip = 20;
CurrentClips = 0;
CurrentAmmo = 0;
bCanShoot = false;
ReloadTime = 2.f;
按 F5 从 Visual Studio 编译并启动项目。编辑器启动后,打开第一个人物蓝图和建筑脚本图。
从构造脚本的输出管脚拖动一条线,搜索序列节点,并添加它。Sequence 节点是一个特殊的 Blueprint 节点,允许您从上到下运行多个输出(您可以添加任意数量的输出)。添加一个分支节点,并将序列节点的第一个输出(标签为 Then 0)连接到该分支节点。
您需要使用分支节点条件来确保起始片段不大于最大片段。将起始剪辑和最大剪辑从我的蓝图选项卡拖放到构造脚本图表中。从起始剪辑拖动一个大头针,搜索>符号,然后选择“整数>整数节点”(起始剪辑现在自动连接到>节点的第一个输入)。如果第一个输入大于第二个输入,则该节点返回 true,因此将 Max Clips 连接到>(大于)节点的第二个输入,并将红色输出连接到分支节点的条件输入。
要设置起始剪辑节点的值,请按住键盘上的 Alt 键,然后再次将起始剪辑节点从蓝图选项卡拖放到构造脚本图中。这将为开始剪辑创建设置节点。将分支节点的真实输出连接到集合节点。连接最大片段作为设定开始片段节点的新输入。该图应如图 7-5 所示。
图 7-5。
弹药准备。设置起始剪辑
第二步是为这个武器设置当前的弹药。只有当你至少有一个弹夹时,你才需要设置当前弹药。因此,首先将当前剪辑设置为起始剪辑,然后检查当前剪辑是否大于 0。如果是的话,那么你就把当前弹药设置为和每个弹夹的弹药相同的值。如果当前剪辑是 0,那么当前弹药也是 0。图表的第二步应该如图 7-6 所示。
图 7-6。
弹药准备。设置当前弹药
构造图的最后一步是确定能不能拍。创建以下节点。
-
为 Can Shoot 布尔变量设置节点。
-
获取当前弹药的节点。
-
大于整数节点(整数>整数)。
将当前弹药节点连接到>节点的第一个输入,将>节点的红色输出连接到 Set Can Shoot 节点。最终的图形应该如图 7-7 所示。
图 7-7。
设置我们是否可以拍摄
现在你已经为弹药和弹夹定义了一个基本的逻辑,所以是时候使用它们了。
既然这是在 Blueprint 中完成的,那么我们就来做一个每当玩家射出子弹时都会在 Blueprint Graph 上调用的函数。要创建函数,首先打开 Chapter07Character.h 并添加以下函数。
/* Event called whenever the player shoots. */
UFUNCTION(BlueprintImplementableEvent, Category = "Chapter 07 Character")
void OnFireEvent();
然后打开 Chapter07Character.cpp,找到 OnFire()函数(应该在 140 行左右)。在这个函数中,添加一个新的 if 条件,该条件只有在 bCanShoot 为 true 时才会继续。然后在 if 条件的最后,调用我们之前添加的 OnFireEvent Blueprint 事件。以下是修改后的 OnFire 函数的完整代码。
void AChapter07Character::OnFire()
{
// See if you can shoot first.
if (bCanShoot)
{
// try and fire a projectile
if (ProjectileClass != NULL)
{
UWorld* const World = GetWorld();
if (World != NULL)
{
if (bUsingMotionControllers)
{
const FRotator SpawnRotation = VR_MuzzleLocation->GetComponentRotation();
const FVector SpawnLocation = VR_MuzzleLocation->GetComponentLocation();
World->SpawnActor<AChapter07Projectile>(ProjectileClass, SpawnLocation, SpawnRotation);
}
Else
{
const FRotator SpawnRotation = GetControlRotation();
// MuzzleOffset is in camera space, so transform it to world space before offsetting from the character location to find the final muzzle position
const FVector SpawnLocation = ((FP_MuzzleLocation != nullptr) ? FP_MuzzleLocation->GetComponentLocation() : GetActorLocation()) + SpawnRotation.RotateVector(GunOffset);
//Set Spawn Collision Handling Override
FActorSpawnParameters ActorSpawnParams;
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;
// spawn the projectile at the muzzle
World->SpawnActor<AChapter07Projectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams);
}
}
}
// try and play the sound if specified
if (FireSound != NULL)
{
UGameplayStatics::PlaySoundAtLocation(this, FireSound, GetActorLocation());
}
// try and play a firing animation if specified
if (FireAnimation != NULL)
{
// Get the animation object for the arms mesh
UAnimInstance* AnimInstance = Mesh1P->GetAnimInstance();
if (AnimInstance != NULL)
{
AnimInstance->Montage_Play(FireAnimation, 1.f);
}
}
// Call the Blueprint event.
OnFireEvent();
}
}
按 F5 从 Visual Studio 编译并启动项目。一旦编辑器启动,打开我们的第一个人物角色蓝图。在事件图中,右键单击并搜索 On Fire Event。您可以看到您在 C++ 中声明的事件现在可以在蓝图图中访问(参见图 7-8 )。
图 7-8。
在蓝图中添加 C++ 事件
选择它并在图中再次右键单击,搜索获取当前弹药并选择它。从当前弹药中拖出一条线,搜索并选择它。这个宏节点取一个整数,减去 1,设置值,然后返回它,所以输出管脚包含当前 Ammo–1 的结果。
如果 Decrement Int 的输出节点不为 0,不要做任何事情,因为玩家可以再次开火。如果是 0,意味着你没子弹了。要比较结果,右键单击图表,搜索 Compare Int 并添加它。然后将 Decrement Int 节点的输出连接到 Compare Int 的第一个输入(带标签输入)。
这也是一个有三个输出节点和两个输入节点的宏。第一个输入节点是要比较的值,第二个输入是要比较的值。如果第一个输入值大于第二个输入值,则触发第一个输出。如果第一输入与第二输入相同,则触发第二输出。如果第一输入小于第二输入,则触发第三输出。
在我们的例子中,您对第二个输出(label ==)感兴趣,所以将名为 Compare With 的输入设置为 0。右键单击图表并搜索“Set Can Shoot”。将其添加到图形中,并将其设置为 false。连接 Compare Int 的第二个输出(带 label ==)设置 Can Shoot 节点。如果玩家现在试图射击,什么也不会发生,因为你刚刚告诉武器一旦当前弹药为 0 它就不能射击了(见图 7-9 )。
图 7-9。
更新当前弹药,可以射击变量
这是添加重新加载逻辑的地方。右键单击图表,搜索按事件设置计时器,并选择它。该节点可以在设定的时间(即重新加载时间)后触发给定的事件。该事件是重新加载事件。右键单击图表,搜索获取重新加载时间并选择它。将其连接到设置定时器节点的时间输入。然后从设置定时器节点的事件输入的红色输入中拖动一条线,并从添加事件部分选择自定义事件(参见图 7-10 )。
图 7-10。
将事件添加到计时器
选中事件后,将其重命名为 Reload(或者任何你喜欢的名字),并将 Can Shoot 变量连接到 Set Timer By Event 节点(见图 7-11 )。
图 7-11。
重命名事件以重新加载
重载事件很简单。简而言之,这就是将要发生的事情。
图 7-12。
更新可以拍摄变量
-
检查一下你是否还有剩余的夹子。如果你有一个弹夹,那么把它减一,把当前弹药设置为每个弹夹的弹药。
-
根据当前弹药启用或禁用射击变量(见图 7-12 )。
随着重装事件的完成,你已经完成了我们的弹药设置。如果你回到视口,按下 play,开始射击,你最终会耗尽弹药,它会在 2 秒后自动重新加载。一旦你完全用完了弹夹,你就再也不能开枪了。
从这里开始,您继续实现 HUD 和拾取项。首先,你使用虚幻的运动图形(UMG)创建一个基本的 HUD,显示你的弹药数量。然后你继续创造一个补充弹药的弹药拾取物品。
转到内容浏览器,右键单击并从用户界面类别中选择 Widget Blueprint。系统会提示您重命名蓝图。姑且称之为 WBP_PlayerHUD (WBP 是 Widget BluePrint 的简称)。打开它,在设计器的右下角添加一个来自通用类别的文本块(见图 7-13 )。
图 7-13。
在 UMG 设计器中添加文本块
保持文本块处于选中状态。在细节面板中,将名称设置为 AmmoText(或您想要的任何名称),并确保选择了 Is Variable(参见图 7-14 )。
图 7-14。
将弹药文本设置为变量
将变量设置为 true 很重要;否则,您无法在事件图中访问它。您也可以展开“锚点”部分,并将“最小值”和“最大值”设置为 1。现在,您可以切换到图形选项卡(右上角)并创建一个新的自定义事件,方法是在图形内单击鼠标右键并选择 Add Event 类别下的 Add Custom Event… 。您将这个新事件称为 UpdateAmmo,并添加两个整数参数。第一个输入称为 CurrentAmmo,第二个输入称为 TotalAmmo。由于您在 Designer 选项卡中为 AmmoText 启用了 Is Variable,因此现在您可以从 My Blueprint 选项卡中拖放对我们的文本块的引用。从“弹药文本”节点拖动一条线,搜索“设置文本”并选择它。该节点接受文本输入,因此您可以使用一个特殊的节点创建格式化的文本。在图形中右键单击,搜索格式文本,然后选择该节点。此节点有助于使用花括号{}构建格式化文本。
在格式文本节点的格式输入内,键入 {Current}/{Total} 并按回车键。节点现在用两个新的灰色输入(也称为通配符)进行自我更新,这两个输入分别叫做 Current 和 Total,可以直接接受任何节点(见图 7-15 )。
图 7-15。
使用格式文本节点设置文本
将更新弹药事件的当前弹药连接到当前灰色输入,将更新弹药事件的总弹药连接到总灰色输入(见图 7-16 )。
图 7-16。
连接到格式文本后
现在剩下的就是从我们的第一人称角色蓝图类中调用这个事件。为此,您首先需要在该类中创建这个 HUD。打开 FirstPersonCharacter Blueprint 类并找到事件 BeginPlay 节点。这个节点在游戏开始时为这个演员自动调用。
如果你没有使用 VR(虚拟现实),请随意删除所有连接到 Event BeginPlay 的节点(对于本教程,我将删除它们,因为 VR 不是我们的重点)。右键单击图形,搜索 Create Widget,然后选择该节点。该节点基于类输入创建一个小部件,因此单击选择类按钮(紫色输入)并选择我们之前创建的 WBP _ 玩家类。右键单击创建小部件节点的返回值输出,并选择提升为变量。这是因为你想在弹药更换后访问它。要将此小部件添加到屏幕,请从 PlayerHUD 节点的输出中拖动一条线,搜索添加到视口,然后选择它(参见图 7-17 )。
图 7-17。
将玩家 HUD 添加到视口
如果你现在按 Play (Alt+P),你可以在屏幕上看到默认的文本块(在 UMG 设计器中创建的那个)(见图 7-18 )。
图 7-18。
增加了玩家 HUD 的游戏画面
因为你必须在游戏开始时更新弹药,所以你需要在射击时和重装弹药后在多个地方调用更新弹药事件。最好创建一个函数,所以在我的蓝图选项卡中,创建一个新函数,并将其命名为 UpdateAmmo。打开这个函数图,将 PlayerHUD(在上下文菜单中选择 Get PlayerHUD)变量拖放到这个图中。从 PlayerHUD 变量中拖动一根线,搜索更新弹药并选择它。您为此做了两个输入,所以对于第一个输入,连接当前的弹药变量。对于第二次输入,你需要全部弹药。
记住,在这一章的开始,我向你展示了如何用弹夹的数量和每个弹夹的弹药数来计算弹药总数。要获得弹药总量,首先将当前剪辑拖放到图表中。从该当前“片段”节点中,拖动一条线,搜索“倍增”节点,然后选择“整数*整数”。现在将每个剪辑的弹药拖放到图形中,并将其作为第二个输入连接到乘法节点,并将乘法节点连接到更新弹药节点的第二个输入(见图 7-19 )。
图 7-19。
更新玩家 HUD 中的弹药
现在唯一剩下的事情就是调用函数。您可以将该功能从“我的蓝图”选项卡拖放到事件图中。调用该函数的第一个位置是在开始播放执行链的末端(参见图 7-20 )。
图 7-20。
开始游戏后调用更新弹药功能
第二个位置是在减少了 InputAction Fire 节点中的当前弹药数量之后(见图 7-21 )。
图 7-21。
开火后呼叫更新弹药
第三个位置在重载事件内部。设置好 Can Shoot 变量后,调用更新弹药功能(见图 7-22 )。
图 7-22。
重装弹药后呼叫更新弹药
如果你现在按 Play (Alt+P)并射击,你可以看到弹药数正确更新(见图 7-23 )。
图 7-23。
玩家 HUD 上的弹药计数更新
弹药收集
在第五章中,您学习了如何创建输入键并使用跟踪来检测我们面前的物品。作为一个练习,我想让你重新创建那个设置,但是有两个不同之处。
图 7-24。
更新线追踪的结束位置
-
请使用 ItemPickup,而不是使用 MyItemTrace 作为名称。
-
将追踪的开始位置设置为球体组件的世界位置。将该位置添加到乘法节点,然后将结果连接到线跟踪节点的末端输入(见图 7-24 )。
右键单击图形,搜索输入操作跟踪,并选择跟踪节点。将 Trace 节点按下的执行引脚连接到 Line Trace By Channel 节点。你已经完成了追踪设置,现在,让我们创建我们的补充弹药功能,这给了我们一个单一的剪辑。
创建一个新函数,并将其命名为 ReplenishAmmo。这个函数没有输入,但是你创建了一个布尔输出,它决定了弹药是否被成功补充。要创建输出,请选择函数(在 My Blueprint 选项卡中或打开函数图并选择紫色节点),然后在 Details 面板中,单击 Outputs 类别下的加号按钮。这将创建一个带有布尔输出的新返回节点。
将 NewParam 输出重命名为 bReturnValue。该函数负责在最大剪辑下添加新剪辑。首先,将当前 Clips 变量拖放到图形中(在上下文菜单中选择 Get CurrentClips)。从此变量中拖动一条线,搜索 less,然后选择 integer < integer node。CurrentClips 现在自动连接到< node 的第一个输入。让我们将 MaxClips 变量拖放到图形中(在上下文菜单中选择 Get MaxClips)并将其连接到< node 的第二个输入。
创建一个分支节点,连接< node to the Condition input of Branch input. If the current clips are less than max clips, the True output of the Branch node trigger drags a wire from Current Clips, search for Increment Int and select it. This is the same as Decrement Int except instead of subtracting by 1 this node add 1. Now drag and drop the Update Ammo function and connect it to the ++ node. After that, connect this to the Return node and make sure the return value is true. Right-click the graph, search for Add Return node and select it. Connect this to the Branch node’s False output and make sure it is set to False (see Figure 7-25 的输出。
图 7-25。
补充弹药
转到内容浏览器,创建一个新的 Blueprint 类(基于 Actor),并将其命名为 BP_AmmoPickup。这是我们的职业,给玩家一个弹夹,然后自我毁灭。打开我们新创建的 actor (BP_AmmoPickup)并创建一个新的自定义事件。让我们称之为 GiveAmmo,并向该事件添加一个新的输入,将类型设置为 FirstPersonCharacter,并将其命名为 Character。
这个输入是我们的第一人称角色,所以从这个输入中拖动一根线,搜索补充弹药并选择它。创建一个分支节点,并将补充弹药的输出连接到这个分支节点。然后右键单击图表,搜索并选择 DestroyActor。将分支节点的真实输出连接到分解器,这个类就完成了(见图 7-26 )。
图 7-26。
给弹药事件
尽管您已经完成了我们的函数和事件,但是您还没有使用它们中的任何一个,所以让我们来解决这个问题。让我们回到跟踪设置。创建一个新的分支节点,并将 Line Trace 节点的返回值连接到分支节点的条件输入。你只需要在追踪遇到什么的时候继续。
从“出点击”节点拖动一条线,并选择“断开点击结果”。通过单击箭头按钮展开“点击结果”节点,并从“点击执行元”插针拖动一条线。搜索 Cast to BP_AmmoPickup 节点并选择它。这是一个方便的节点,它试图将给定的对象输入转换为相应的类型,如果成功,则触发第一个输出,如果转换失败,则触发 Cast Failed。
从标记为 BP Ammo Pickup 的输出节点拖动一条线,并搜索 Give Ammo,这是您创建的事件。选择它,您可以看到它需要一个应该是第一人称字符类的输入。因为您在第一人称角色中使用了跟踪,所以右键单击图表并搜索 self,然后选择 Get a reference to self。Self 节点输出对该蓝图实例的引用,因此将其连接到 Give Ammo 输入(见图 7-27 )。
图 7-27。
从线追踪结果给出弹药
你完了!将 BP_AmmoPickup 类拖放到游戏世界中,然后按 Play (Alt+P)。继续射击,直到弹药耗尽。然后走到世界上一个弹药拾取物品附近,按 E 键追踪并拾取该物品。
包名
包装游戏是一个简单的过程。打包是以优化的方式为目标平台编译和烹饪内容的过程。调用打包命令时,首先编译所有源代码(如果有)。如果编译成功,那么所有的内容(蓝图、网格、纹理、材质等等)都会被转换(也称为烹饪)成目标平台可以使用的特殊格式。虚幻引擎支持各种各样的平台,从一台 Windows 机器上,你可以为除苹果 Mac 之外的任何平台编译和打包。
每个平台都有自己的设置,您可以覆盖这些设置。为此,点击工具栏中的编辑并选择项目设置(参见图 7-28 )。
图 7-28。
访问项目设置
在“项目设置”的右侧面板中,向下滚动直至到达“平台”类别。在此类别中,您可以选择您想要的平台并覆盖任何设置(参见图 7-29 )。
图 7-29。
平台类别
要打包您的项目,请从文件菜单打开打包设置➤打包项目➤打包设置(参见图 7-30 )。
图 7-30。
访问打包设置
这将打开打包设置。在“设置”窗口中,在“项目”类别下,将构建配置切换到“发货”,并确保“完全重建”和“用于分发”复选框处于选中状态(参见图 7-31 )。
图 7-31。
选择装运配置
验证这些设置后,再次单击“文件”菜单,并从“包项目”菜单中选择“Windows (64 位)”。如果这是第一次,Unreal Engine 现在会提示您选择保存打包版本的位置。导航到所需位置,然后单击选择文件夹。虚幻引擎现在开始打包你的构建,你会在屏幕的右上角看到一个提示通知(见图 7-32 )。
图 7-32。
显示 Windows 打包的 Toast 通知
您可以单击 Toast 通知上的 Show Output Log 来查看构建过程。