虚幻引擎-VR-快速启动指南-全-
虚幻引擎 VR 快速启动指南(全)
原文:
zh.annas-archive.org/md5/3F4ADC3F92B633551D2F5B3D47CE968D译者:飞龙
前言
对于我们许多开发者来说,虚拟现实(VR)代表着一个相对未被充分挖掘的独特游戏市场,这些游戏能够利用令人惊叹的新技术。VR 有能力将我们的玩家直接带入我们的数字世界,并为他们提供在其他地方无法获得的经验。然而,采用这项新技术并创建这些世界的技能尚未得到广泛传播且不易获得。我们的目标是改变这一现状,并帮助传播关于 VR 力量的信息。
Epic Games 一直是 VR 的长期支持者。在过去的几个版本中,Unreal Engine 4 扩展了对 VR 的支持,并继续优化其软件,以便更多开发者能够完成令人惊叹的工作。在硬件方面,市场上的制造商数量和 VR 头盔的功能都在不断增加。许多开发者正在为 Oculus Rift 和 HTC Vive 开发应用程序,尽管还有其他选择可供选择,包括 PlayStation VR、Samsung Gear VR 和 Windows 混合现实头盔。
无论你选择哪个,本书都能帮助你踏上与 VR 一起工作的旅程。在本书的整个过程中,我们将探讨如何为 VR 设计。我们将为这个独特环境编写灵活的交互系统,创建用户界面元素,并讨论该媒体的具体游戏艺术需求。最后,我们将完成一个游戏原型并为其准备分发。
本书面向的对象
本书是为对 Unreal Engine 4 有兴趣的中级到高级用户编写的,他们希望使用 VR 技术工作。这些用户熟悉游戏引擎,但尚未探索如何在 VR 中创建游戏和应用。
为了充分利用本书
-
阅读本书需要具备对 Unreal 游戏引擎的中级知识
-
需要安装 Unreal Engine 4.20.x 版本
-
需要一个虚拟现实头盔以及能够运行它的计算机硬件
下载示例代码文件
您可以从 www.packt.com 的账户下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问 www.packt.com/support 并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
在 www.packt.com 登录或注册
-
选择“支持”选项卡。
-
点击“代码下载与勘误表”。
-
在搜索框中输入本书的名称,并遵循屏幕上的说明。
下载文件后,请确保您使用最新版本的软件解压缩或提取文件夹:
-
Windows 系统的 WinRAR/7-Zip
-
Mac 系统的 Zipeg/iZip/UnRarX
-
Linux 系统的 7-Zip/PeaZip
该书的代码包也托管在 GitHub 上,网址为 github.com/PacktPublishing/Unreal-Engine-Virtual-Reality-Quick-Start-Guide。如果代码有更新,它将在现有的 GitHub 仓库中更新。
我们还有其他来自我们丰富的书籍和视频目录的代码包可供在 github.com/PacktPublishing/. 查看它们!
下载彩色图像
我们还提供了一份包含本书中使用的截图/图表的彩色图像的 PDF 文件。您可以从这里下载:www.packtpub.com/sites/default/files/downloads/9781789617405_ColorImages.pdf。
使用的约定
本书使用了多种文本约定。
粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。以下是一个示例:“点击创建项目按钮,让我们继续!现在看看界面。”
警告或重要注意事项看起来像这样。
小贴士和技巧看起来像这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并通过 customercare@packtpub.com 发送邮件给我们。
勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告这一点。请访问 www.packt.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。
盗版:如果您在互联网上以任何形式遇到我们作品的非法副本,我们将不胜感激,如果您能向我们提供位置地址或网站名称。请通过 copyright@packt.com 联系我们,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com。
评论
请留下评论。一旦您阅读并使用了这本书,为何不在您购买它的网站上留下评论?潜在读者可以查看并使用您的客观意见来做出购买决定,我们 Packt 可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!
如需更多关于 Packt 的信息,请访问 packt.com。
第一章:在虚幻引擎 4 中引入 VR 技术
虚拟现实(VR)。这些词让人联想到 20 世纪 80 年代和 90 年代的电影画面,例如《割草人》等角色,人们被绑在覆盖全身的设备上,以及占据整个房间的计算机,以及延伸到永远和基本几何形状,旨在模拟现实世界。VR 这个术语在 20 世纪 80 年代出现,指的是用于与这些计算机生成世界交互的手套和头盔系统。从那时起,电子和数字显示技术的进步使得可以制造出更小、更强大的设备。2010 年,帕尔默·卢克发现手机显示屏技术已经达到了足够的分辨率,可以用于 VR,这导致了第一个 Oculus Rift 头显的诞生。这一事件将引发一场技术军备竞赛,从而产生了 Oculus Rift、三星 Gear VR、HTC Vive 以及新的 Windows 混合现实头显。随着每年价格的下降,VR 已经进入了全球 1.71 亿用户的手中。
对于你们中的许多人来说,这本书代表了你们走向创建自己 VR 作品的第一个步骤。你们可能最近对开始使用这项技术产生了兴趣,或者你们可能已经有一个完整的设计,并且你们已经准备好开始开发。在两种情况下,这本书都将引导你们选择目标硬件和受众,独特的 VR 设计、移动和游戏玩法概念,为你的虚拟世界创建艺术作品,以及最后测试和准备你的游戏以便分发。通过创建我们的示例游戏Server 17,我们将讨论 VR 开发者面临的一些问题,例如优化以及如何帮助玩家应对 VR 不适。
本书假设读者对虚幻引擎 4 有实际了解,并且能够访问 VR 设备。如果您刚开始接触虚幻引擎 4,我建议您先查看我的其他作品,例如《使用虚幻引擎 4 和 Blender 进行 3D 游戏设计》,或者 Packt 出版公司出版的其他优秀的游戏开发书籍,然后再回到这本书。
在本章中,我们将涵盖以下主题:
-
为什么使用虚幻引擎 4 进行 VR 开发?
-
开发者可以获取哪些类型的 VR 技术?
-
流行 VR 头显的优缺点
-
VR 的限制
-
介绍我们的示例项目—Server 17
为什么使用虚幻引擎 4 进行 VR 开发?
VR 代表了游戏开发者面临的新伟大前沿。就像移动技术允许开发者进入新市场并将电子游戏带入主流一样,VR 游戏也将能够挖掘新市场并吸引那些寻求更积极游戏风格的新的粉丝。为了适应这一点,游戏引擎开发者一直在迅速工作,添加新功能并改进现有功能,以吸引开发者选择他们的平台来发布他们的下一个大型游戏:

Robo Recall 是第一个使用虚幻引擎 4 开发的引人注目的游戏之一。图片由 Epic Games 提供
从 2014 年开始,Epic Games 开始在 Unreal 中为全 VR 支持打下基础。这导致了他们第一个 VR 游戏 Robo Recall 的开发。在这款游戏中,玩家能够射击并摧毁被杀人机器占领的城市。游戏使用了传送系统进行移动,并为基于枪械的游戏提供了相当扎实的控制。游戏于 2016 年开始开发,并于 2017 年发布在 Oculus Rift 上。它充分利用了当时 Unreal 的最新构建,展示了最新的渲染技术和 VR 优化的优化。
从那时起,Epic Games 一直更新和发展虚幻引擎 4,为游戏开发者提供他们项目可用最佳工具。虚幻引擎版本 4.17、4.18 和 4.19 包含了许多针对 VR 头戴设备和 VR 开发者的优化和性能更新,目标是提高性能和帧率,这两者是 VR 游戏开发中最关键的监控元素之一。
除了为开发者提供他们 VR 游戏的最佳工具的承诺之外,Epic Games 还为开发者提供了许多选择虚幻引擎作为他们游戏引擎的其他原因:
-
虚幻引擎免费使用,每个季度每款游戏的前 3,000 美元只需支付 5% 的版税。这些条款允许小型开发团队直接进入项目,无需担心如何支付他们的工具费用。
-
虚幻引擎支持大多数 VR 设备,包括三星 Gear VR、Oculus Rift + Touch、HTC Vive 等。
-
蓝图可视化脚本语言允许非编码人员在不需要程序员的情况下开发游戏功能和原型。
-
虚幻引擎能够实现高端材质和着色器,从而为您的玩家提供极其沉浸式的体验。
我们将在我们的示例项目中使用最新版本的虚幻引擎。在撰写本书时,这个版本是 4.20.2。在您游戏开发的过程中,可能会有新版本的引擎变得可用。是否更新您的项目到最新版本取决于您。通过查看每个新版本提供的发布说明,看看新功能是否会对您的游戏有益来做出这个决定。
开发者可以获取哪些类型的 VR 技术?
了解 Unreal Engine 4 与绝大多数 VR 技术兼容后,我们的下一步是选择我们希望使用的 VR 头显类型。有许多因素可以影响这一决定。首先,我们需要知道我们的体验的目标受众是谁。从研究与我们设计类似的前期游戏开始。通过这样做,我们可以找出哪些游戏玩家群体会购买我们这种风格的游戏,并看看我们是否能采访一些符合这一人口统计的玩家。有了这些信息,我们可以在开发过程中做出明智的设计决策,这将导致一款真正吸引其玩家群体的游戏。
了解你的玩家群体的人口统计信息以及他们的喜好和厌恶是设计一款商业上成功的视频游戏的关键部分。这同时也是以人为中心的设计过程的第一步,这是一种以问题解决为中心的创造性方法,始终将用户的需要和愿望作为设计决策的核心。了解更多信息,请查看www.ideo.com/和dschool.stanford.edu。
我们需要关注的下一个因素是我们设计最适合哪种类型的体验。VR 体验可以分为几个不同的类别:
-
房间规模 VR
-
坐式 VR
-
移动 VR
房间规模 VR
第一类是房间规模体验。
这些是需要玩家在预定区域内移动、跳跃和执行动作的活跃体验。以下是一个由 HTC Vive 附带的光塔传感器覆盖的房间规模游戏空间示例:

房间规模 VR
坐式 VR
对于可能不需要那么高水平活动的体验,有坐式 VR 体验。以下是一个坐式或仅站立式 VR 体验所需空间的示例:

坐式 VR 体验
坐式体验是基于玩家保持静止,并且只需要头显的方向数据这一理念。
移动 VR
最后,还有移动体验。这些体验专门为移动设备格式化,例如三星 Gear VR 和 Oculus Go。原始版本可以在以下链接:https://commons.wikimedia.org/wiki/File:Samsung_Gear_VR_Experience_(16241072054).jpg找到。

移动体验
移动体验旨在利用智能手机中的加速度计和陀螺仪来提供沉浸式体验和控制游戏玩法。对于我们的游戏,我们可能会让玩家站在一个狭小的区域内,他们的工具就在手边,尽管可能需要一些移动。考虑到这一点,我们将设计服务器 17作为一个房间规模体验。
流行 VR 头盔的优缺点
现在我们知道了我们的玩家是谁以及我们希望他们有什么样的体验,我们可以做出决定,选择我们想要用于设计体验的 VR 头盔。每种类型的 VR 头盔都会给我们的项目带来不同的优势和劣势,以及运行它的计算机的具体要求。让我们来看看目前市场上一些最受欢迎的 VR 头盔:
-
HTC Vive
-
Oculus Rift + Touch
-
Windows 混合现实头戴式设备
-
Samsung Gear VR
HTC Vive
2016 年 4 月发布的 HTC Vive 系统包括以下内容:
-
VR 头盔
-
2 x Vive 运动控制器
-
2 x 红外传感器,称为灯塔
由于其 16 英尺 x 16 英尺的最大游戏区域,Vive 非常适合房间规模体验,它是目前最受欢迎的两个 VR 系统之一,在 2018 年 2 月占所有 SteamVR 玩家的 45%。它能够达到 2,160 x 1,200(每只眼睛 1,080 x 1,200)的分辨率,这是许多开发者的首选头盔。运动控制器可以追踪到毫米级别,每个控制器都提供九个不同的按钮,可以在 Unreal Engine 4 中进行映射,这为开发者提供了大量的输入选项。音频由玩家插入自己的耳机提供,尽管可以购买单独的 Deluxe Audio Strap 以提供更舒适的重量分布和集成耳机。
最小硬件要求
-
Intel Core i5-4590/AMD FX 8350 或更高
-
Nvidia GeForce GTX 970/AMD Radeon R9 290 或更高
-
4 GB 的 RAM
-
HDMI 1.4、DisplayPort 1.2 或更新版本
-
1x USB 2.0 或更新版本
-
Windows 7 SP1、Windows 8.1 或更高版本、Windows 10
值得注意的是,虽然 HTC Vive 与 Unreal Engine 4 兼容,但在使用时需要运行 SteamVR。凭借其出色的追踪、稳定的分辨率和灵活的运动控制器,HTC Vive 是房间规模和坐姿 VR 体验的绝佳选择。
Oculus Rift + Touch
原版的 Oculus Rift 头戴式设备于 2016 年 3 月由 Oculus VR 发布,在成功的 Kickstarter 活动之后。由于其作为众筹项目的地位,Oculus Rift 有一个有趣的区别,那就是其开发过程得到了广泛的宣传。公司在开发期间向其支持者发布了两个不同的开发套件(DK1 和 DK2),然后在发布第一个商业版本。当前的头盔版本包括以下内容:
-
Oculus Rift 头戴式设备
-
2 x 基于桌面的红外传感器
-
2 x Oculus Touch 控制器
这些控制器有六个不同的按钮输入,由 Unreal 追踪,但它们真正的成名之处在于这些控制器能够进行手指追踪和手势。Rift 的分辨率为 2,160 x 1,200(每只眼睛 1,080 x 1,200),与 HTC Vive 完全相同。
最小硬件要求
-
Intel i3-6100/AMD Ryzen 3 1200、FX4350 或更高
-
Nvidia GTX 960/AMD Radeon RX 470 或更高
-
8 GB+的 RAM
-
兼容 HDMI 1.3
-
1 个 USB 3.0 端口和 2 个 USB 2.0 端口
-
Windows 10
与 HTC Vive 类似,Oculus Rift + Touch 需要在后台运行自己的应用程序才能操作。尽管 Rift 配备了两个传感器,但其推荐的房间规模游戏区域为 5 英尺 x 5 英尺。虽然比 Vive 小得多,但鉴于 Rift 降低的硬件要求,它仍然是坐着和房间规模体验的绝佳选择。
Windows 混合现实头戴式设备
以令人印象深刻的分辨率 2,880 x 1,440(每只眼睛 1,440 x 1,440),目前由 Acer、Lenovo、HP 等公司制造的最新一批 Windows 混合现实头戴式设备,有可能真正颠覆 VR 领域。这些头戴式设备集成了动作追踪,以减少设置时间并可能增加游戏玩家可用的房间规模游戏空间。与 Vive 和 Oculus 相比,它们的低价位也是一个吸引人的特点。然而,即使现在,它们仍然是一项新技术,尚未得到验证。集成的动作追踪可能看起来像是一个节省时间的功能,但这要求玩家看向他们想要与之交互的任何东西以获得流畅的交互。对于试图开发下一个大型 VR 大片的开发者来说,这是一个不能忽视的问题,因为玩家体验是一切。最终,Windows 混合现实硬件为 VR 硬件市场带来了急需的多样性,但它可能还太新,不能作为开发平台的选择。
最小硬件要求
-
英特尔 Core i5-7200U 或更高
-
英特尔 HD 图形 620 或更高/DX12 兼容 GPU
-
HDMI 1.4 或 DisplayPort 1.2
-
1 个 USB 3.0 Type-A 或 Type-C
-
Windows 10 秋季创造者更新
三星 Gear VR
Gear VR 代表了智能手机和 VR 技术的融合,在一个时尚的小包装中。这款设备的最新版本允许用户将三星 Gear 手机插入一个时尚的框架中,并享受专为该设备构建的各种 VR 游戏。控制由头戴式设备侧面的输入以及手持动作控制器处理,这些控制器让人联想到与 Vive 一起发货的控制器。这为玩家提供了各种舒适的输入选项和精确的控制器动作。那么,这对开发者意味着什么呢?移动 CPU 和 4 GB 的 RAM 限制了该设备运行高端内容的能力,但 Gear VR 拥有令人印象深刻的分辨率 2,560 x 1,440(使用三星 Gear S8 和 S8 Plus 手机时为 2,960 x 1,440)。Gear VR 还代表了一个未得到充分服务的市场。尽管其硬件可能很小,但 Unreal Engine 4 自 4.7 版本以来就支持三星 Gear VR 的开发,并且这款设备是开发移动体验中最好的之一。对于想要构建小型应用的精明开发者来说,这可能是一个绝佳的机会。
VR 的局限性
虽然 VR 能够为玩家提供沉浸式、直观的数字体验,但重要的是要注意,它并非没有局限性。除了 VR 所要求的所有 RAM、处理能力、传感器、线缆和控制器之外,这项技术还向我们展示了一些我们自己的局限性——其中最著名的就是 VR 不适或视觉所展示的内容与我们的其他感官所感知的内容之间的脱节。VR 不适,或称为模拟不适,通常在 VR 游戏中的移动时最为常见,它可能会导致以下任何一种情况:
-
恶心
-
晕眩
-
方向感丧失
-
出汗
-
各种其他不适
所有这些我们都不希望成为任何玩家体验的一部分。这些可能是由游戏中的某些特性引起的,我们在非虚拟环境中有时会视其为理所当然,例如从玩家手中接管控制以展示一个剪辑场景,使用摄像机抖动、摄像机震动或覆盖玩家的视野。问题也可能是由帧率过低引起的。因此,作为开发者,我们必须始终关注我们游戏在我们选择的平台上的性能。在下面的屏幕截图中,我们有 Unreal Engine 4 支持的几个 VR 头戴式设备的推荐帧率:

Unreal Engine 4 的 VR 推荐帧率,由 Epic Games 提供
选择一种提供玩家持续稳定加速感的移动和转向方法,这是实现这一目标的一种方式。从 VR 技术发展中涌现出的一些已建立的移动方法包括基于驾驶舱的、自然的、人工的、物理的和瞬移。我们将在第二章“移动、设计和启动我们的项目”中了解更多关于这些移动方法以及它们如何适合我们的游戏。其他控制 VR 不适的方法包括避免改变玩家视力的电影式摄像机效果,如运动模糊和使用较暗的照明。明亮的颜色和模糊效果会导致玩家因眼睛疲劳而感到恶心。最后,值得注意的是,医学科学尚未彻底研究 VR 长期使用的效果,以及儿童定期使用该设备可能引发的问题。作为设计师,玩家的体验始终应该放在我们心中的首位,让玩家保持身体舒适是我们让他们回归游戏的最重要方式之一。
介绍我们的示例项目——服务器 17
为了帮助框架我们的 VR 开发探索,我们将开发一个我喜欢的游戏,称为服务器 17。设计为一个面向喜欢 VR 和基于技能的游戏玩家的房间规模体验,服务器 17将玩家置于一个腐败政府和巨型企业的反乌托邦未来的赛博朋克黑客的鞋子里。玩家将不得不解锁一个企业服务器的秘密,在游戏中将代表为一个谜题盒。他们将有访问各种工具和程序的权利,这将使他们能够在网络安全抓住他们之前成功完成任务并击败他们。
对于这个项目,我选择使用虚幻引擎 4 和 HTC Vive。Vive 为我们的玩家提供了一个宽敞的房间空间体验,对头戴设备和手部控制器的跟踪都非常出色。它还减少了计算机上的 USB 端口使用。这是一个很好的功能,因为我正在使用一个端口有限的顶级笔记本电脑进行开发。尽管它没有 Oculus Rift + Touch 可能找到的手指跟踪功能,但它是一块出色的硬件,将满足这个项目的需求。
摘要
在我们踏上成为 VR 开发者的道路的第一步时,我们审视了在开始开发我们的第一个 VR 游戏之前我们必须做出的决定。我们还审视了选择虚幻引擎 4 的几个令人信服的理由,例如对 VR 的持续支持、免费访问、蓝图视觉脚本和强大的着色器工具。然后我们讨论了目前存在的不同类型的 VR 体验以及哪种硬件最适合每一种类型。最后,我们讨论了 VR 不适感和如何最好地避免这种令人衰弱的状况。
随着我们进入第二章,运动、设计和启动我们的项目,我们将就如何最好地设计我们的样本游戏,服务器 17,做出一些决定,并创建必要的设置文件以开始开发进程。为了确保我们所有人都理解每个蓝图文件是如何相互作用的,我将尽可能从零开始创建内容,仅在需要时使用模板和预制文件。欢迎来到这场冒险——让我们继续前进!
第二章:移动、设计和启动我们的项目
基于我们对当前 VR 技术和游戏开发的了解,我们现在可以开始着手我们的第一个项目。正如我们在第一章,“在虚幻引擎 4 中引入 VR 技术”,Server 17让玩家扮演一位勇敢的黑客,在一个腐败的反乌托邦未来中寻找真相。每个人都有秘密,有些人愿意支付大笔金钱来换取他人的秘密。从设计角度来看,这意味着我们将创造一个解谜游戏,玩家需要解决谜题并使用不同的工具打开代表网络中计算机服务器的不同谜盒。通过构建这个简单的游戏,我们将涵盖 VR 游戏设计的所有步骤,目标是为你提供工具和知识,以便开始你自己的项目。这个过程的第一步是设计,为此,我总是转向以人为中心的设计过程。
在本章中,我们将涵盖以下主题:
-
以人为中心的设计过程
-
选择我们的移动方式
-
在虚幻引擎 4 中设置新的游戏文件
-
项目设置
以人为中心的设计过程
以人为中心的设计(HCD)过程是一套灵活的设计原则,允许设计几乎任何东西,从鞋子到汽车,再到软件。HCD 的核心原则是设计师在每一步都考虑最终用户。这个决定是否会让我用户的产品变得更好?让颜色可定制是否会让我用户感到高兴,并使他们更有可能使用我的产品?通过提出这些问题并考虑你的用户,你能够创造出一个更有可能在其目标受众中取得成功的设计。
以人为中心的设计过程有以下步骤:

以人为中心的设计过程
-
发现:在这个阶段,我们研究我们面临的问题和感兴趣的目标受众。我们搜索看看是否有人尝试过解决这个问题,如果是的话,他们尝试了什么。我们联系潜在用户,找到愿意与我们交谈的人,以便发现他们从解决方案中需要什么。
-
同理心:在这里,我们尽可能多地与潜在用户交谈,以收集他们在解决方案中寻找的东西。我们想要深入挖掘。为什么他们想要或需要这个?是否有任何与记忆或情感的关联?例如,怀旧是对与一个人青年时期相关的经历的强烈渴望。它也是游戏开发者喜欢触及的最强烈的情感之一。
-
创意:一旦我们知道用户想要什么,就是时候进行头脑风暴了!当我们进行创意时,我们希望尽可能多地提出想法。没有哪个想法太离谱或疯狂(只要它有利于我们的用户)。
-
原型:一旦我们确定了一个想法,我们希望快速简单地构建一个版本,让我们的用户尝试。这里没有复杂的编程或惊人的艺术——我们希望快速且低成本地创建一个可玩的游戏版本。
-
测试:将原型放在用户面前并记录结果!通常在这个时候,产品可能会失败。这可能是由于设计问题或用户不理解或不喜欢的某些内容。记录数据并从中学习!
-
精炼:手头有了用户测试数据后,我们回到构思阶段并再次尝试。在你完成游戏之前,你可能需要经历几次这样的循环,但如果你定期与用户沟通并从其他设计师和朋友们那里获得反馈,你肯定能想出下一个杀手级应用。
对于服务器 17,我们理想的用户是那些从未体验过 VR 的人。他们感兴趣的是简单但非常沉浸式的体验,能够利用 VR 的独特能力。他们可能是《银翼杀手》和《银翼杀手:遗产》以及《Shadowrun》游戏系列的粉丝。当他们回想起第一次使用新游戏硬件的经历时,怀旧感也可能发挥作用。你可能有很多符合这一特征的朋友,你可以在设计游戏时作为参考,我们还可以利用在线社区来收集更多的意见。一旦我们通过一些研究和与用户的访谈完成了发现和同理心步骤,我们现在就可以就移动方式或玩家在游戏世界中移动的过程做出一些设计决策。
选择我们的移动方法
作为 VR 设计师,我们的一个目标是通过利用这项新技术的独特优势来创造玩家想要反复体验的新颖体验。这项技术非常适合让玩家感觉他们正在与一个充满生机和活力的幻想世界互动,否则他们无法居住其中。使用手柄控制器,我们的玩家可以触摸我们的世界并与它进行非常直观的互动。然而,移动系统还没有达到这种沉浸感的水平。
在 VR 中,玩家的移动动作还处于初级阶段,因此还没有找到一种能够有效工作的方法。已经尝试了许多方法。有车辆模拟游戏,玩家保持在驾驶舱内。还有动作/冒险游戏,玩家通过使用摇杆或传送等人工方法在景观中奔跑。其他人则试图通过让玩家站在一个狭小区域内,所有必需品都触手可及,来保持沉浸感,虽然这样牺牲了广阔的环境,但允许玩家自然地移动。每种移动方法都是给予玩家沉浸感和自然感觉与避免不愉快感觉(如 VR 不适感)之间的权衡。
在我担任大学教授期间,我有机会帮助我的学生为当地博物馆设计一个皮划艇模拟器。学生们设计了一个运动系统,允许玩家保持坐姿,并使用带有传感器的桨来操纵他们的虚拟船只在河流急流中前进。尽管这个系统在与他们的同伴测试时表现良好(感觉自然且对我而言效果良好),但我们发现它对博物馆的几位用户产生了深远的不利影响。其中一位测试者甚至在测试我们的游戏后不得不离开工作。在询问测试者一些问题后,我们发现,尽管我们的控制器上的桨运动感觉正确,但水中船只的真实物理特性使得他们无法玩游戏。进一步询问测试组显示,这位测试者并不是唯一遇到问题的玩家。最后,他们不得不在水的运动上采取一些艺术上的自由,以创造一个更愉快的体验,并了解到我们的用户体验和乐趣比现实主义更重要。
虽然这种缺乏最佳实践可能会让一些人感到担忧,但我们内心的设计师应该将其视为一个令人兴奋的机会,去创新并创造新的系统,为我们的玩家提供最佳的游戏体验。这些系统可以分解为四个主要类别:
-
自然
-
人工
-
驾驶舱
-
物理(创意)
自然运动
不要与同名应用程序混淆,自然运动是指一种在 VR 中移动的方法,通过将自然玩家动作(如挥动手臂和跳跃)带入游戏世界来最小化 VR 不适。我们可以在游戏 Tribe XR 中看到自然运动的工作方式如下:

Tribe XR 是一个新兴的 DJ 应用程序,可以通过游戏中的课程教你如何混音音乐
这涵盖了各种方法,但所有方法似乎都为玩家提供了将他们的动作直接转换为游戏中的玩家动作的直接翻译。尽管这种方法已被证明可以限制玩家的不适,但它并非没有缺点。这种方法限制了玩家只能在他们系统传感器的覆盖范围内移动,并要求设计师考虑到这一点进行设计。这可能意味着专门设计关卡以适应平均游戏空间大小,并确保玩家所需的一切都在可触及范围内。这种方法已在诸如 Job Simulator、Tribe VR 和 Waltz of the Wizard 等游戏中使用。
人工运动
人工运动基本上是自然运动的相反。这种技术依赖于更传统的游戏控制方式,如摇杆、触摸板和其他输入方法,以在游戏关卡中移动玩家。
尽管在将传统游戏移植到 VR 时(想想Skyrim VR和Fallout 4 VR)这是最好的移动方式,但由于视觉上的困扰,即我们的眼睛所看到的与我们的其他感官所经历之间的脱节,人工移动方式最有可能导致 VR 不适感。我们可以使用几种技术来最小化这种困扰。一种方法是在玩家移动时动态地减小他们的视野。这产生了一种非常微妙的效果,当玩家移动或转向时,它会隐藏玩家的一些周边视野。另一种方法是让玩家在移动过程中以恒定的速率加速。恒定的加速度对前庭系统——我们大脑中感知加速度的部分——的压力更小。同样的原理也可以应用于旋转。一致的缓慢转向或突然转向对玩家来说可能感觉更好。我想提到的最后一项技术是传送。传送是一种允许玩家在没有 VR 不适感的情况下导航大型环境的好方法。然而,这种方法根据游戏的故事和背景可能并不感觉自然。
驾驶舱移动
与自然移动相似,驾驶舱移动允许游戏如太空模拟、赛车和其他让玩家坐在车辆中的游戏进行非常自然的移动。
通过欺骗身体,使其认为移动类似于乘坐汽车,我们可以绕过许多 VR 不适感的成因。这种方法的一个缺点是它不适用于大多数情况。成功使用这种方法的游戏包括《危险精英》(Elite: Dangerous)、《星际迷航舰桥船员》(Star Trek Bridge Crew)和《天使:地狱之火》(Archangel: Hellfire)。
物理移动
物理移动系统指的是为特定游戏体验设计的独特移动控制方案。正因为如此,它们往往是我们在今天的 VR 空间中看到的最具创新性的想法之一。
这种类型的移动涵盖了专门设计的移动方法,例如我的学生建造的皮划艇划桨系统,以及手对手攀爬、在挥动控制器的同时原地跑步、游泳动作和通过移动玩家的手臂进行飞行。由于玩家在进行特定的手臂或身体动作,他们在体验时往往感觉沉浸感非常高,同时 VR 不适感非常少。这些系统并非没有缺点。一些玩家可能会觉得这种移动很愚蠢或很花哨。它们通常也不能在它们特定的游戏之外使用。成功实施这种移动方式的游戏包括《攀爬》(The Climb)、《鹰之飞行》(Eagle Flight)和《巫师之舞》(Waltz of the Wizard)中的施法机制。
考虑到我们对用户(新手 VR 用户,寻求沉浸和直观体验,以及赛博朋克爱好者)的了解,在这里哪种或哪些类型的移动方式会是最合适的呢?对于新手用户来说,自然移动似乎会是最有效的,因为它通常直观且有助于保持沉浸感。因此,我们不会将我们的关卡设计限制在玩家定义的游戏空间内,我们还可以选择将传送功能整合到我们的控制方案中。尽管传送的缺点是不够沉浸,但我认为我们游戏的赛博空间设置实际上会支持玩家在关卡内进行传送。
在做出这些设计决策后,是时候启动虚幻引擎 4(UE4)并设置我们的项目文件了!
在 UE4 中设置新的游戏文件
UE4 是一个多功能的工具集合,帮助你创建你梦想中的游戏。为了让你开始这一旅程,Epic Games 提供了一系列启动项目,通过为你设置一些最重要的功能来快速启动你的游戏。这通常包括玩家角色、示例武器和其他必要的文件。在这本书中,我们将从 Virtual Reality Starter 项目开始创建文件,以便我们可以利用该项目提供的一些基本艺术资产。除此之外,我将向你展示如何从头开始创建我们需要的蓝图。
每个游戏或软件项目都需要保持组织性,我们使用命名约定来做到这一点。命名约定是一种命名方案和文件夹结构,确保每个文件名都是标准化的,以便任何特定的游戏团队成员都能通过文件名确切地了解他们正在查看的内容。你可能注意到了在浏览 UE4 内部的一些项目文件时。Epic Games 在其 Unreal Wiki 上提供了其命名约定wiki.unrealengine.com/Assets_Naming_Convention,并且我们将在这本书中一直使用它。
在 Unreal 中,任何新的游戏都需要创建某些蓝图来定制项目以满足我们的需求。这些文件包括以下内容:
-
GameMode -
GameState -
PlayerPawn
但所有这些文件是如何相互作用的,为什么我们需要它们呢?当 UE4 启动游戏时,引擎会创建两个文件来帮助它在加载和设置关卡或玩家之前理解游戏规则。这些是GameMode和GameState。GameMode包含使我们的游戏独特的规则,例如玩家的总数以及这些玩家如何连接到游戏,以及默认设置,例如默认玩家 pawn、玩家控制器和游戏状态。通过创建我们自己的游戏模式,我们迈出了定制项目的第一步。游戏开始后加载的GameState文件旨在跟踪对我们游戏重要的所有内容,例如得分、完成的任务以及其他与整个游戏相关的元素。这不是针对特定玩家的东西,因为有一个不同的玩家状态。我经常用它来存储需要构建生成式游戏关卡所需的数据。最后,我们还需要一个定制的PlayerPawn。PlayerPawn是玩家在游戏中的物理表示,并在游戏过程中被我们的玩家控制。
项目设置
在我们开始创建自定义项目文件之前,我们需要让 Unreal 创建我们的项目基础并开始布局我们的文件夹结构。这将保持我们在构建Server 17时的文件组织。
首先打开 Unreal Engine 4.20.2 并开始一个新项目:

你第一次打开 UE4 时看到的屏幕
欢迎来到新项目窗口。从这里,我们将根据 UR 模板创建我们的项目:
-
从屏幕顶部选择新的项目标签。
-
选择蓝图部分。
-
在列表底部附近选择虚拟现实模板。
-
将中间的项目设置更改为可伸缩 3D 或 2D。
-
确保项目文件夹在一个容易找到的地方。将其名称更改为
VRQuickStart。
点击那个创建项目按钮,我们继续!现在看看界面:

与游戏引擎的大部分交互都在内容浏览器中进行
在我们的新项目打开后,四处看看界面并找到你的内容浏览器:
-
是时候整理一下了。第一步之一是创建我们自己的项目文件夹,类似于 Epic Games 使用的
VirtualReality和VirtualRealityBP文件夹。在内容浏览器内的Content文件夹上右键点击,然后从列表顶部选择新建文件夹。将新文件夹命名为Server17。 -
点击我们的新文件夹以进入它。在内容浏览器中右键点击并选择新建文件夹。将其命名为
Blueprints。这将是我们新蓝图的家。
随着我们继续开发,我们将向我们的文件结构添加几个更多文件夹,以帮助包含和组织我们的文件。记住,在继续进行时坚持我们的命名约定,因为这将在以后添加团队成员或休假后返回项目时提供巨大帮助。
创建我们的自定义游戏模式
在我们的项目基础和文件结构建立之后,让我们创建我们的第一个自定义文件:我们的GameMode文件。点击我们的新Blueprints文件夹,然后右键单击以显示菜单。从创建基本资产部分,选择蓝图类。这将显示选择父类菜单,如下面的截图所示:

从选择父类菜单中,我们可以创建一个扩展父文件功能的新文件
是时候扩展基本的GameMode类并告诉我们的项目使用我们的新文件了:
-
从选择父类菜单中,我们可以扩展引擎提供的任何基本类。点击游戏模式基类按钮,并将新文件命名为
S17GameMode。 -
简单地创建
GameMode对于引擎在项目中识别它来说是不够的。为了确保它被使用,点击视口的设置按钮并选择项目设置。
或者,我们也可以打开世界设置并导航到菜单中的游戏模式部分,然后点击 GameMode 覆盖旁边的+按钮。就像做数学或大多数创造性追求一样,在 Unreal 内部创建某物的方式不止一种。我分享的是我在职业生涯中学到的方法和过程。如果你发现不同的方式或更好的方式来做我在这本书中提到的任何事情,请随时与我以及 UE4 开发社区分享!
查看项目设置菜单:

项目设置
-
在菜单的项目部分下,选择地图和模式。此菜单部分允许您指定当编辑器打开时打开的默认地图,以及定义默认
GameMode。 -
在菜单的默认模式部分,使用默认游戏模式下拉菜单并选择 S17GameMode。
将 S17GameMode 设置为默认设置后,我们现在可以开始创建其余的自定义项目文件。一旦我们放置好所有其他部分,我们将返回到项目设置屏幕并进一步更改默认设置。
创建一个 GameState
虽然GameMode非常适合设置我们的游戏特定规则和存储模式默认值,但GameState用于存储重要的全局元素。类似于我们创建GameMode类时,我们需要扩展GameState类作为自定义状态的基类。让我们回到选择父类菜单:

扩展 GameStateBase 以创建我们的自定义 GameState 类
然而,与我们的 GameMode 不同,我们将扩展 GameStateBase 类,或者所有 GameState 文件都从中扩展的类:
-
在内容浏览器中右键点击,并从菜单的创建基本资产部分选择蓝图类。
-
在选择父类菜单中,我们将跳过菜单顶部列出许多常见扩展类的部分,并选择所有类折叠菜单。
-
这将显示我们可以在引擎中扩展的所有类。使用搜索框找到 GameStateBase 并选择它。然后,按菜单底部的选择按钮。
-
我们将新游戏状态命名为
S17GameState。
在单人和多人游戏背景下,讨论 GameMode、GameState 和单个 PlayerStates 之间的交互。
创建自定义玩家实体
现在我们有了两个不同的文件来帮助我们管理将在我们的关卡中出现的信息和变量,是时候构建我们的 PlayerPawn,即我们玩家的物理表示。PlayerPawn 从头戴设备和手控制器接收信息,并将其转换为游戏中的移动和动作。根据我们选择如何表示玩家,我们可以选择几个不同的方向:
-
第一人称格式:玩家没有化身。手和头在空中飘浮。
-
带有手臂的第一人称视角:通过逆运动学,我们能够为玩家提供与手控制器位置移动的手臂。然而,头部仍然飘浮。
-
带有全身的第一人称视角:类似于带有手臂的设置,这允许玩家通过全身来表示,同时使用逆运动学来处理手和头部。
-
第三人称视角:一个完整的第三人称角色,玩家从设置在玩家模型上方和后面的摄像机向下看。这种选项已知会限制 VR 不适,但以牺牲沉浸感为代价。当与第一人称选项结合使用时,这种玩家设置在一些游戏中已被成功使用——例如:当玩家射击和执行动作时使用第一人称视角,而当玩家移动时使用第三人称视角。
对于 Server 17,玩家扮演一个试图破解服务器以寻找加密文件数据来窃取的网络黑客。设定在未来,玩家和服务器在玩家黑客硬件创建的虚拟环境中交互。因此,我们可以使用基本的第一个视角格式来表示玩家,而不会牺牲体验。这种方法也有助于保持我们的游戏优化。
虽然我们可以使用模板中提供的运动控制器实体,但让我们从头开始创建一个。首先,创建一个新的 Pawn 类供我们使用:
-
在我们的
Blueprints文件夹内的内容浏览器中右键点击,并从菜单的新基本资产部分选择蓝图类。 -
我们希望为玩家创建一个新的 Pawn,以便玩家可以控制或接收控制器输入数据。从菜单中选择 Pawn,并将其命名为
S17PlayerPawn。双击新 Pawn 以打开界面,如下所示:

打开的 Pawn 类及其组件显示
-
首先,我们将向玩家添加一个场景组件,作为我们摄像机的根对象。在蓝图界面的左上角,在菜单的组件面板中,点击添加组件按钮并搜索场景组件。创建它并将其命名为
HMDRoot。 -
是时候将我们的玩家摄像头添加为我们的新场景对象的孩子了。选择
HMDRoot后,返回到添加组件按钮,找到一个摄像头对象。创建它并将其命名为HMDCam。 -
现在我们需要创建一种方法来跟踪玩家的运动控制器,以便玩家的角色手部能够适当地移动和行动。选择
HMDRoot后,使用添加组件菜单创建一个运动控制器组件,并将其命名为MotionController_L。 -
重复前面的步骤创建另一个运动控制器组件,并将其命名为
MotionController_R。详细面板将如下所示:

运动控制器 _R 设置
- 在选择
MotionController_R组件后,查看详细面板并找到菜单中的运动控制器部分。将运动源选项更改为右侧。
编程我们的自定义 PlayerPawn
让我们继续进行一些编程,以确保我们的 PlayerPawn 被正确使用并设置以适应玩家的硬件。虚幻需要知道在哪里设置跟踪原点,或者在哪里设置玩家的默认高度,这可能会根据您的设备而有所不同。对于 HTC Vive,默认情况下是针对站立 VR 体验,通过将跟踪原点设置为地板来实现。另一方面,Oculus Rift 默认设置为针对坐着 VR 体验,并使用眼睛高度跟踪原点。虽然为 Vive 设置东西可能很简单,因为我们知道这是我们针对的平台,但我希望创建一些可能在这个项目之外可重用的东西。我们将在 PlayerPawn 的事件图中添加以下内容:

玩家 Pawn 事件图
要做到这一点,我们将检测玩家可能使用的硬件,并根据玩家的设备设置跟踪原点:
-
在
S17PlayerPawn的中心点击事件图标签,并将执行线从事件开始节点拖出。 -
将其放下并选择按名称切换节点。按名称切换是一个流程控制节点,这意味着它限制了并通过蓝图来指导执行流程。此节点将指导蓝图根据找到的 HMD 硬件设置跟踪原点。
-
从按名称切换节点上的选择输入拖出。使用搜索框查找获取 HMD 设备名称。此节点代表一个用于获取玩家 HMD 硬件名称的函数。
-
在“开关名称”节点上,点击添加锚点按钮两次。在详细信息面板中,将锚点命名为 OculusHMD 和 SteamVR。同样在详细信息面板中,关闭“具有默认锚点”的选项。
-
现在,让我们为我们的每个硬件选项设置标准。将 OculusHMD 锚点拖离,并搜索设置跟踪原点节点。选择它,并使用节点上的下拉框选择眼睛水平跟踪。
-
将 SteamVR 锚点拖离,并创建另一个设置跟踪原点节点。这次,我们将它设置为地板水平跟踪。
-
现在点击保存按钮,以免丢失你的工作。
在我们的跟踪原点设置好之后,我们现在可以回到我们的项目设置中,告诉 S17GameMode 使用我们的 S17PlayerPawn 作为默认玩家锚点。
摘要
在本章的开头,我们学习了针对特定类型用户设计的一种最佳方法:HCD 流程。在使用 HCD 时,我们总是希望在每个设计决策中都考虑到我们的玩家,确保我们创建的游戏满足我们玩家的每一个期望,并且玩起来令人愉悦。这是我们 Server 17 的目标。在我们的一些设计决策确定后,我们创建了我们的项目,并开始通过创建自定义的 GameMode、GameState 和 PlayerPawn 文件来自定义我们的文件。
在下一章中,我们将进一步完善 Server 17 的设计,并探讨在当今 VR 市场上流行的不同类型的游戏玩法。具体来说,我们将专注于利用 VR 提供的独特输入方法和沉浸式特性,以及我们如何将这些应用到我们自己的游戏中。最后,我们将决定我们的游戏将如何运作,并开始构建这些系统。很快,我们将有一个可以展示给玩家的工作原型!
第三章:探索虚拟现实中的引人入胜的游戏玩法
在我们的自定义游戏文件就绪后,是时候开始做出一些关于游戏玩法的决定了。在 Server 17 中,玩家扮演一个未来黑客的角色,试图破解企业服务器并为其个人利益窃取其秘密。那么,我们如何将这种体验在 VR 中呈现出来呢?在我们思考这一点之前,让我们先做一些研究和探索。了解哪些机制将挑战和娱乐我们的玩家,首先需要看看 VR 带给游戏的独特元素。之后,我们可以看看现在正在玩哪些游戏以及为什么。有了这些信息,我们就可以做出一些关于如何最好地为 Server 17 设计我们自己的游戏玩法的决定了。我们有很多东西要探索,让我们开始吧!
在本章中,我们将涵盖以下主题:
-
虚拟现实(VR)给电子游戏带来了什么?
-
流行的游戏玩法机制
-
设计 Server 17 的游戏玩法
虚拟现实(VR)给电子游戏带来了什么?
虚拟现实是游戏和娱乐的新前沿。它能够让玩家置身于他们最喜欢的英雄的鞋子中,或者坐在他们最喜欢的体验的前排座位上。在大多数体验中,玩家的移动被转换成游戏内的几乎一对一的移动——这在传统电子游戏中通常是做不到的。VR 还能够将我们的玩家沉浸在一个不同的世界中,让他们真正感觉到自己就在那里。正是这些特殊能力标志着 VR 是游戏未来的标志。
重要的是要理解,在 VR 中的输入和控制可以超越我们在上一章中讨论的移动方法。Vive 或 Oculus 的每个控制器至少都有六个不同的按钮,可以映射到游戏中的不同功能。这些按钮中的每一个都可以与位置或特定的玩家移动相结合,创造出几乎无限的变化。让我们看看 Epic Games 的 Robo Recall 中的枪战。
在这款游戏中,玩家使用挂在玩家身上的各种武器来摧毁机器人。角色在臀部两侧别着一把手枪,在肩膀上别着另一对武器。这似乎很简单,对吧?设计师利用 VR 的优势在于使用武器的手部放置。玩家必须弯腰到腰部去抓一把手枪,或者伸手到肩膀后面去抓武器。这种简单地将手部位置添加到装备枪械的输入中,增加了游戏玩法的沉浸感和直观性。
流行的游戏玩法机制
在过去几年中,许多开发者将 VR 技术作为他们首选的媒介。这导致了各种不同类型和想法的 VR 游戏。每款游戏都以自己的方式处理 VR 带来的输入和沉浸感。一些,如 Bethesda 和 ID Software,选择使用这项技术将他们的热门游戏《辐射 4》和《毁灭战士》带入 VR 时代(评价褒贬不一)。其他人则发现为该媒介专门制作游戏更为成功,例如 Beat Games 的《节奏光剑》和 Schell Games 的I Expect You To Die。为了研究什么可能适用于《服务器 17》,我们将研究七种不同的游戏玩法:
-
射击体验
-
动作/冒险体验
-
车辆体验
-
基于物理的体验
-
智力挑战体验
-
节奏体验
-
教育体验
射击体验
在过去一年中,Valve 的 Steam 下载服务上销售的 25 款顶级 VR 游戏中,超过一打属于射击类别。射击游戏被定义为任何将枪战作为体验主要部分的游戏。许多 VR 射击体验的变体都利用了独特的机制。仍然位于排行榜前列的是《机器人回忆》。
Epic Games 能够将波射击的狂热动作与 VR 带来的沉浸式环境和机制相结合。大部分游戏玩法都集中在使用你的双手。《机器人回忆》在武装自己时使用了有趣的机制。玩家从腰间和肩膀上的皮套中抓取他们选择的武器。每个皮套都装满了各种定制的武器,非常适合与游戏中的机器人敌人战斗。抓取机制也应用于《机器人回忆》中的一种特定品牌近战战斗。玩家可以通过位于他们胸中心方便位置的把手抓住许多机器人敌人。一旦抓住,敌人可以通过抓住肢体、扔向其他敌人或举起作为盾牌来保护自己。
Superhot Team 的《超级热 VR》是另一款将枪战提升到全新水平的游戏。该游戏将物理和射击游戏玩法与它们独有的时间机制相结合,创造了一种罕见体验。在它的简约环境中,几乎每个物体都是《超级热 VR》中的武器。这些物品可以被扔向敌人以解放他们的武器,使其飞入空中,然后被玩家抓起。结合时间只有在玩家行动时才会前进的事实,每个关卡都变成了一场战斗谜题。你首先面对哪个敌人?在手臂范围内有什么武器或物品可用?每个决定和动作对于发现关卡解决方案和进步都至关重要。
流行 VR 射击游戏最重要的元素是结合了活跃的枪战和另一种独特的机制。这可以是抓取、物理游戏玩法或特定的移动机制。这个类别中其他优秀的例子包括Sairento VR,因其移动机制和枪战与剑术的结合,以及Damaged Core,这款游戏让玩家利用从敌人到敌人传送的能力,控制他们并使用他们的武器。
动作/冒险体验
动作/冒险体验将战斗体验与探索和基于故事的游戏玩法相结合。这些游戏拥有令人印象深刻的环境和视觉效果,利用了 VR 将玩家直接带入游戏世界的事实。然而,在这样一个广阔的世界中探索,设计师必须使用人工移动方法来允许玩家探索如此大的空间。对于许多玩家来说,亲身体验故事的能力超过了使用摇杆或快速转向进行移动带来的任何不适。在这个类别中,最大的竞争者都来自Bethesda Softworks:Fallout 4 VR和Skyrim VR。
Fallout 4 VR是 2015 年流行游戏的直接移植,重新设计以适应 VR。玩家在荒野中建立定居点,制作装备,并以第一人称视角体验故事。战斗是直面敌人的。大多数早期敌人偏好近战,这意味着玩家在对手站在他们面前之前几乎没有使用枪械的机会。幸运的是,近战战斗就像装备正确的武器,或者根本不装备,然后挥舞武器一样简单。远程战斗感觉也不错,但远不如 VR 射击游戏中的那样令人满意。尽管如此,Fallout 4还是满足了所有正确的条件。这是一次动作/冒险体验,为玩家提供了一个巨大、令人敬畏的环境。在这里,他们可以制作、建造和互动,还可以参与一个深刻的故事,故事中有各种有趣的角色。
车辆体验
车辆体验通过让玩家驾驶船只、机甲或其他旅行和破坏方式来解决移动问题。大脑处理方式与我们在车内驾驶时几乎相同。我们不会质疑我们在移动,因为我们坐着并控制着车辆。这给了设计师机会为玩家提供驾驶体验。这个领域有许多优秀的例子。然而,我想给你一个代表这个类别中最好游戏玩法的例子,那就是 Frontier Developments 的Elite Dangerous。
精英系列最新作品是一部太空冒险、战斗和贸易模拟游戏,玩家将扮演一名精英指挥官。在银河系一对一、开放世界的版本中,玩家可以通过与主要势力的互动赚取金钱、提升等级和扩大影响力。游戏无需 VR 硬件即可游玩,但玩家戴上头戴式设备后,游戏体验将更加出色。在 VR 模式下,玩家能够看到飞船内部,并看到自己定制的玩家角色坐在驾驶舱中。
随着菜单通过一瞥即可打开,操作飞船的系统和管理菜单变得更加容易。战斗和驾驶变得轻松自如,因为玩家可以通过查看飞船的机舱来帮助追踪自己的位置。所有这些 VR 特有的功能都为玩家在游戏世界中战斗、交易和探索以获得财富和名声的体验增添了深度。
内置飞船环境、可定制的玩家角色和基于眼动追踪的菜单,都得益于 VR 提供的第一人称视角。能够在车辆内部看到自己,使玩家与周围环境产生了联系。这让他们感觉自己像是游戏中的活生生的一部分。无论是驾驶改装的高性能赛车比赛,还是在太空深处与外星人战斗,VR 视角都增强了玩家的驾驶体验。
基于物理的体验
在游戏世界中,基于物理的游戏并不像其他游戏那样广为人知,它们利用了玩家使用站立或房间空间体验瞄准和投掷物体的能力。这不仅仅局限于简单地捡起和扔日常物品,如Superhot VR中的游戏玩法。它们利用游戏引擎模拟真实世界物理的能力来创造游戏玩法。在这个类型中,游戏通常以基于物理的格斗游戏和物理解谜游戏的形式出现。让我们来看看基于物理的格斗游戏Gorn。
Devolver Digital 的Gorn让玩家扮演一名角斗士,为几个大头颅的观众提供娱乐。游戏采用卡通风格的艺术和暴力元素,围绕基于物理的打斗展开。每一轮开始时,玩家进入竞技场,有时可以选择武器,有时则赤手空拳。规则很简单:在别人击败你之前先击败他们。乐趣在于玩家选择使用的武器。当游戏将他们的动作转换到游戏世界中时,会加入一些卡通物理元素。动作变得夸张。武器感觉像是泡沫或橡胶制成的,使得战斗变得既奇特又荒谬。这些特点共同创造了一种既直观又荒诞的打斗体验。Gorn目前可在 HTC Vive、Oculus Rift 和 PlayStation VR 上玩。
与Gorn的战斗游戏玩法形成对比的是基于物理的拼图游戏Bounce。由 Steel Wool Studios 开发的Bounce让玩家帮助一个球形机器人穿越星际飞船的走廊。玩家被赋予了各种物理装置,旨在推动他们的机器人朋友从关卡的一侧到达出口传送门。为了到达那里,机器人必须绕过激光墙、重力井和其他科幻障碍。通关游戏解锁风格模式。这允许玩家再次玩游戏的 50 个关卡,并奖励他们最富有创意的解决方案。
在这两款游戏中,我们再次看到了在 VR 中经常看到的独特移动机制和第一人称视角,它们被有效地运用。Gorn使用玩家的手臂动作和卡通物理来创造一种既有趣又紧张的游戏体验。相比之下,Bounce使用第一人称视角,让玩家看到当球体撞击特定的物理装置时会发生什么。这给了玩家亲身体验他们行动效果的能力,并让玩家感觉更能控制体验。
拼图体验
由于它们使用物体操作作为主要机制,并且常常是幻想环境,拼图体验非常适合 VR 体验。自Myst及其续作以来,玩家一次又一次地表明他们喜欢解决古老遗迹、黑暗地牢和天空中的城堡的谜题。然而,直到现在,玩家还没有真正能够与一些这些谜题和环境达到眼平视的高度,这可能会使得需要瞄准激光或射箭的问题解决变得更加困难。以 Croteam 开发的 2014 年流行的拼图游戏The Talos Principle VR中的激光谜题为例。在几个谜题中,玩家需要使用设备弯曲激光束以击中特定的目标。一些玩家报告称,由于他们能够弯腰并从眼平视的高度看到激光的路径,这项任务在 VR 中要容易得多。
另一个将 VR 原则应用于解谜游戏的杰出例子可以在 Schell Games 的突出作品I Expect You to Die中找到。在这款游戏中,玩家扮演一个心灵感应超级间谍的角色,负责完成各种任务以击败邪恶的 Zoraxis 组织。游戏中的五个地点中的每一个都让人感觉有点像逃脱室。玩家被赋予逃离该地区的任务,必须使用他们找到的工具来完成使命。例如,游戏的第一项任务要求玩家在飞机慢慢充满毒气的同时,驾驶邪恶的 Dr. Zor 的武器化汽车从货机的后面开出去,而飞机上只有汽车或从汽车多次试图杀死你而拆卸的工具。游戏通过让玩家选择将其作为坐着或房间规模体验,以及赋予玩家心灵感应能力,使他们永远不必移动,来解决运动问题。游戏利用 VR 的独特性和创造力,创造出一种让玩家赞不绝口的体验。
节奏体验
在许多方面,VR 机制正在为我们所爱的游戏类型注入新的活力。这尤其适用于节奏游戏,其基本的游戏玩法机制“在这个时间按这个按钮”在今天看来似乎已经过时了。然而,VR 特定的游戏玩法机制,如一对一的手臂运动的应用,使这个类型焕发了新生,并催生了几个独特的节奏体验。其中最突出的无疑是 Beat Games 开发和发布的Beat Saber。
Beat Saber 将节奏游戏与 VR 光剑战斗相结合,创造了一个独特的游戏,玩家需要挥舞虚拟的红蓝能量光剑,在音乐的节奏下击中相应的颜色方块。玩家必须用正确的颜色光剑以正确的角度击中方块,同时躲避墙壁障碍和不应该击中的地雷。所有这些动作都随着歌曲的节奏进行。凭借各种曲目和难度设置,Beat Saber 为 VR 时代的节奏体验设定了新的标准。在这款游戏中,我们看到两种游戏玩法相互碰撞:VR 战斗(挥击和躲避)和节奏机制。这种结合创造了一种几乎像跳舞一样的体验,让人容易享受。尽管如果你以坐着的方式玩这款游戏,也要做好锻炼的准备,因为有些歌曲有时会非常充满活力。
教育体验
自其诞生以来,教育工作者一直对 VR 作为教授和培训下一代的一种引人入胜的方式感兴趣,并允许学习者从新的视角看待材料。新一代的学生正在寻找教育机构拥抱技术,同时提供独特和体验式的学习风格。为了满足这一需求,教育软件开发者、大学发展团队,甚至一些游戏开发者已经开始创建 VR 体验,让学生能够近距离体验历史或学习使用高端设备而无需担心受伤。除了沉浸感和独特的机制外,VR 还可以为那些希望学习需要昂贵设备或稀有资源的人提供机会。以下是目前可用的部分标题:
-
The Body VR: Journey Inside a Cell:这款游戏利用 VR 的沉浸感,让玩家体验在人体血液中旅行并进入血细胞,以观察细胞层面的内部运作。
-
Sharecare VR:这款应用程序允许玩家探索人体解剖学,模拟疾病,并展示治疗如何与身体相互作用。玩家还可以调用特定的器官,并启用显示标签以显示特定结构。这允许玩家以前所未有的方式学习和研究人体解剖学。
-
Apollo 11 VR:阿波罗 11 号结合了被动观看体验和偶尔的小游戏,让玩家亲身体验阿波罗 11 号任务。
我还想在这里提及 Tribe XR。尽管开发者选择教授用户如何使用高端设备进行 DJ,但目标是创建一个可以用来教授多种技能并利用他们所说的指数学习法的创意教育平台。作为他们平台计划的一部分,他们允许经过审查的用户,在这种情况下是其他 DJ,通过录制和现场课程教授那些新加入平台的人。这种机制有巨大的潜力改变学校和企业在教学和培训学生方面的方式。
VR 有能力将用户带入我们创造的虚拟游戏世界,并将他们的动作转化为真实的游戏动作。这两个独特功能有潜力彻底改变玩家享受电子游戏的方式,并为设计师创造新的创造性表达途径。然而,我们需要确保这些在游戏世界中创建交互的新技术不会成为支撑糟糕设计的拐杖。就像 20 年代初期的图形进步一样,我们绝不能让 VR 成为一种新奇的借口,为糟糕的游戏开脱。
设计服务器 17 的游戏玩法
现在我们已经发现了一些我们潜在用户正在玩的优秀 VR 游戏,并深入了解了 VR 如何使它们变得独特,让我们来看看 VR 如何真正让“服务器 17”这个体验熠熠生辉。这就是我们从设计过程的发现和同理心阶段过渡到构思阶段的地方。构思阶段是魔法发生的地方,也是想法被头脑风暴并转化为更具体事物的地方。对于我们的用户(第一次使用 VR 的用户、科幻迷和怀旧者),我们需要考虑到他们作为新用户的事实意味着基本移动方案很重要。我们还想确保游戏玩法直观且易于学习。当前的 VR 控制器能够实现多种基于按钮的功能,但我们将保持我们的控制方案简单,以降低学习曲线并使我们的游戏易于接触。最后,我们希望我们的环境简单,同时真正融入赛博朋克和未来美学。
考虑到这些因素,让我们开始设计。为了使移动简单并最大限度地减少 VR 不适,我们可以在保持游戏区域较小的情况下使用传送。这将带来额外的优势,即与我们的赛博朋克背景以及游戏关卡意在以 VR 形式进行的事实相契合。我们将让玩家按下按钮并使用一些虚拟手来操作物体。这使我们能够使游戏玩法尽可能自然,同时使其易于快速掌握(有意为之)。游戏玩法将围绕破解企业服务器展开,重新构想为房间中央一个看起来很科技的谜题盒,玩家将通过抓住部分来推动和滑动来解决问题。我们还希望为玩家提供一些特殊工具来加快这个过程。这些工具将分布在代表玩家电脑内部的虚拟房间中的不同工具站上。最后,为了表示被发现的危险,玩家必须在一定时间内完成谜题盒。以下是关卡的一个粗略想法:

带有设计标记的水平地图
现在,你的直觉可能是在决定艺术风格或开始制作静态网格和着色器。抵制这种冲动!尽管在设计纸上听起来很有趣,但我们不知道所有功能是否都能按预期工作,或者它们是否真的像我们想象的那样有趣。为了测试我们的假设,我们需要进入设计过程的原型阶段,快速构建一个功能原型,并让用户测试以获取反馈。这样,我们不会在可能不使用的游戏元素上浪费时间和资源。
我们将从实现手部功能开始。
添加手部功能
我们希望玩家在游戏中的表现尽可能平滑和无缝。为此,我们实施的解决方案需要能够完成几件事情。首先,玩家需要能够看到手。其次,玩家需要能够知道哪些物体可以交互,哪些可能只是作为场景存在。第三,玩家需要能够抓取、拿起,并可能抛出我们希望他们与之交互的物体。第四,我们需要玩家能够按下关卡内的按钮,以利用在不同工具站找到的工具,并解决拼图立方体。
跟随我们的设计的第一步是将手部模型添加到我们的Server17PlayerPawn中,以便我们的玩家在游戏中有一个自己的代表。为此,我们将添加 Epic Games 为我们提供的标准手部模型到我们的 pawn 中:

在类蓝图屏幕中添加的手部
这是我们添加手部的步骤:
-
为了让手部网格读取玩家运动控制器的位置,我们首先在我们的 pawn 中添加一个附加到 MotionController_L 组件的骨骼网格组件。点击 MotionController_L 组件,然后点击组件面板顶部的添加组件按钮。选择骨骼网格选项,并将新组件命名为
SkeletalMesh_L。 -
在详细信息面板中,找到骨骼网格下拉菜单,并选择 MannequinHand_Right。这将把手添加到玩家的运动控制器末端。
-
由于手部是为了作为右手设计的,我们需要更改一些设置,使其正确地作为左手工作。在详细信息面板中找到变换部分,并将位置属性的 X 字段更改为-10。这将使网格与玩家在控制器上的物理手更对齐,感觉更自然。
-
向下移动到旋转属性,并将 X 值更改为 90 度。这将使手旋转到更自然的位置。
-
向下移动一个属性到缩放属性,并将 Z 值更改为-1。这将翻转手的朝向,真正地代表左手。
-
左手完成之后,重复步骤一至四来创建右手,并将其命名为
MotionController_R。
如果我们现在测试,我们可以看到玩家 pawn 现在有了手!像你不在乎一样在空中挥舞它们。然而,它们实际上并没有做什么,除了看起来像塑料,但打理得很好。让我们开始通过添加一些碰撞形状来赋予它们一些功能,这样我们就可以记录它们与物体重叠时的情况。让我们从左手开始:
-
选择 MotionController_L 组件,并导航到添加组件菜单。在下拉菜单的碰撞部分找到 Sphere Collision 组件,并选择它。将其命名为
Sphere_L。 -
在详细信息面板中,将球体半径更改为 10。这将定义对象被认为与玩家的手重叠的区域。
-
重复步骤一和二以创建右手的一个碰撞球体,并将其命名为
Sphere_R。
我们的手现在已设置好以记录重叠事件,这样我们就可以在游戏世界中操纵对象。此时,我们希望这些对象执行几个动作。我们希望它们能够检测到它们被注视和未被注视的情况。我们还需要它们知道我们对它们执行了某种动作,即激活和未激活状态。为此,我们将使用一种称为蓝图接口的东西。蓝图接口可以被定义为可以分配给需要共享数据和功能的对象的一个或多个函数的集合。它允许我们创建许多函数,每个使用该接口的对象都可以以独特的方式定义这些函数。在我们的游戏中,我们将创建一个包含控制双手如何操纵游戏对象的全部功能的接口。
Tribe XR广泛使用蓝图接口来促进其不同用户界面的创建和通信,因为其中许多共享共同元素。从用于显示音乐曲目信息的界面元素到选项屏幕,它们都有一个共同点,即它们都是投影到三维平面上的二维界面。正如我们将在下面的屏幕截图中所看到的,提前创建一个通用函数的蓝图接口可以促进这些菜单的创建!
右键菜单中的蓝图接口选项
首先创建一个新的蓝图接口:
-
确保我们位于
Sever17\Blueprints文件夹中。在内容浏览器中右键单击,并导航到菜单的创建高级资产部分。找到蓝图选项并选择蓝图接口。将其命名为ObjectInteractionInterface。 -
双击
ObjectInteractionInterface以打开它。 -
我们需要在这里创建几个函数。在屏幕右侧的我的蓝图面板的函数部分,点击加号(+)按钮创建一个新函数。将其命名为
TraceHitObject。每当我们的对象被运动控制器或 HMD 的线迹击中时,它就会触发,这表示我们正在以某种方式查看或与之交互。 -
在选择我们的新函数后,查看详细信息面板。此面板允许我们向我们的函数添加输入和输出以处理我们的数据。让我们添加一个名为
Hit的输入,并使用击中结果类型。将击中数据传递给对象允许我们在以后访问重要信息。 -
要完成函数,我们需要添加一个名为 Return 的布尔输出。虚幻引擎将没有输出的函数读取为事件,这不是我们想要的。为了避免这种情况,我们使用一个虚拟的布尔变量来完成函数。
-
下一个我们需要创建的函数将能够读取当痕迹离开对象时的情况,为我们提供额外的功能选项。返回到
MyBlueprint面板的函数部分,并创建另一个函数。将其命名为TraceLeaveObject。创建一个名为Hit的输入变量,其类型为 Hit Result。通过创建一个布尔型输出并将其命名为Return来完成函数。 -
现在对象能够判断是否被注视,让我们将这一功能提升到下一个层次。让对象知道正在被注视的哪一部分将非常有用。我们可以通过在组件级别检测痕迹来实现这一点。创建一个新函数,并将其命名为
TraceHitComponent。就像我们的其他函数一样,我们需要创建一个名为Hit的输入,其类型为 Hit Result,以及一个名为Return的输出,其类型为布尔型。 -
为了能够读取被击中的组件,我们还需要添加另一个输入。创建第二个输入,并将其命名为
Component,使其类型为原始组件。这样,我们就可以传递被击中的特定组件。 -
让我们完成这个功能。创建另一个函数,并将其命名为
TraceLeaveComponent。这个函数应该具有与TraceHitComponent相同的输入和输出。 -
只需再创建几个函数,我们就能完成!让我们创建一个可以在每一帧调用的函数,以跟踪对象的潜在移动。创建另一个函数,并将其命名为
TraceMove。这个函数应该有一个名为Hit的输入,其类型为 Hit Result,以及一个名为Return的输出,其类型为布尔型。 -
TraceMove需要一个额外的更改才能正确工作。由于这是一个每帧调用的函数,性能肯定是一个考虑因素。为了优化这个函数,我们将检查两个选项。首先,在Hit输入下,点击变量名称旁边的小箭头,然后点击复选框以使函数通过引用传递。通过引用传递允许Hit变量通过名称传递而不传递值。因为我们选择以这种方式传递变量,所以我们需要在详细信息面板的图形部分中选择常量选项。如果你看不到选项,请点击该部分底部的向下箭头以显示复选框。 -
最后,让我们再创建一点功能。我们希望能够激活和关闭对象上的某种效果。这涵盖了各种潜在场景,例如能够打开和关闭你捡起的闪光灯。为了创建这种效果,我们需要两个额外的函数。首先,创建一个新函数,并将其命名为
TraceActivateDown。 -
TraceActivateDown将表示按下按钮以激活对象。它需要两个输入。第一个名为Hit,具有击中结果类型(这样就可以在击中结果中包含大量信息)。第二个名为Instigator,类型为兵种。这样,我们可以传递激活对象的兵种。最后,创建一个名为Return的布尔类型输出。 -
如果
TraceActivateDown代表按钮按下,那么我们需要为我们的接口创建一个最终函数来表示该按钮的释放。创建一个名为TraceActivateUp的最终函数。这个函数应该具有与TraceActivateDown相同的输入和输出。
完成所有操作后,你的界面应该看起来像这样:

对象交互接口的“我的蓝图”部分
ObjectInteractionInterface现在封装了我们希望对象能够执行的大多数函数。然而,许多函数依赖于成为线迹的目标才能操作。线迹是一个可以调用的函数,它将从一点绘制到另一点,并报告被线迹击中的任何东西。使用这些击中数据,我们可以做各种事情!例如,我们可以找到玩家到物体的距离。我们可以对物体进行操作以改变颜色、发出声音或执行其他行为。我们甚至可以导致物体删除自己。在射击游戏中,这是直接伤害武器,如激光、狙击步枪或其他没有实际弹丸的武器的工作方式。由于我们的线迹将检查玩家正在看的地方,并且可能会用来确定他们可以传送的地方,因此我们将使用抛物线或曲线而不是直线进行追踪。为此,我们将创建一个自定义组件,可以添加到我们的 HMD 或动作控制器中,以投射线迹:

创建交互组件
我们需要创建一个自定义场景组件,可以添加到我们的自定义兵种中:
-
在内容浏览器中,在
Server17\Blueprints文件夹中右键单击,然后点击蓝图类选项。在“选择父类”窗口的底部部分,使用搜索框搜索场景组件。点击它,然后点击选择按钮。将这个新组件命名为InteractionComponent。 -
这个新组件将处理我们的线迹追踪以及它收集到的任何数据。然后,它将通过我们的接口将击中数据分发到所有交互函数。首先,通过点击“我的蓝图”面板中的+函数按钮创建一个新的函数。将这个新函数命名为
ParabolicLineTrace。 -
我们通过获取交互组件面向的方向并基于固定时间和速度值计算投影曲线来计算抛物线曲线。公式是 z = (t * v[z]) - (0.5 * g * t²),其中 z 是 z 轴上弧线的投影终点,v 是速度,g 是重力,t 是时间。弧线将覆盖的距离通过 y = t * v[y] 来估算,其中 y 是 y 轴上弧线的终点。为了使我们的函数计算弧线,它需要三个输入。第一个名为
Steps,类型为整数。第二个名为TimeStep,是一个浮点值。最后,我们有Speed,类型为浮点。
如果你不懂数学,不用担心!它创建的弧线非常适合估算我们未来传送的最终位置。一旦完成,Unreal Engine 4 将能够为我们直观地表示线迹,这将变得有意义。
-
双击我们的新函数以打开它。
ParabolicLineTrace需要六个局部变量才能工作。在 My Blueprints 面板的底部找到局部变量部分。点击 +Local Variable 按钮并创建一个名为 Init Loc 的新变量,类型为 Vector。接下来,创建一个名为 Prev Loc 的变量,类型也是 Vector。这些将存储我们曲线计算的初始位置。 -
创建另一个名为
Velocity的局部变量,类型为 Vector。这是我们的速度值。 -
现在,我们需要两个局部变量来处理我们的时间计算。创建两个新的局部变量,名为
InTimeStep,类型为 Float,以及名为 In Steps,类型为 Integer。 -
这是最后一个了!我们需要一个额外的局部变量来存储从线迹获取的命中数据。创建一个名为
TempHit的最终局部变量,并确保其类型设置为命中结果:

在抛物线线迹中设置变量
我们终于设置好了所有变量,现在可以继续创建曲线计算了。
-
我们函数的第一步是存储我们的位置数据。从你的局部变量部分,拖入一个
InitLoc变量的副本并选择设置。同样,对你的PrevLoc变量也这样做。将函数开始处的执行输出连接到 Init Loc 的输入。将 Init Loc 的执行输出连接到 Prev Loc 的输入。 -
现在我们需要填充它们的数据。我们首先需要知道交互组件在游戏世界中的位置。在蓝图窗口中右键单击或使用调色板获取 GetWorldLocation 节点。将我们新节点的输出连接到 Init Loc 向量输入,然后将 Init Loc 的向量输出连接到 Prev Loc 的向量输入。
-
返回 My Blueprint 中的局部变量部分,获取 In Steps 和
InTimeStep变量,并对它们都选择设置。将 Prev Loc 节点的执行输出连接到 In Steps 节点的执行输入。然后,将 In Steps 的执行输出连接到 In Time Step 的输入。 -
为了完成这一部分,我们需要将“步骤”和“时间步长”连接到它们适当的数据输入。从函数中获取“步骤”输入并将其连接到“步骤”的整数输入。最后,从函数中获取时间步长输入并将其连接到“时间步长”的浮点输入。
-
在计算曲线之前,我们还需要初始化一些最后的数据。为了得到用于计算的速率,我们需要确定交互组件的前进向量,并将其乘以函数输入的
Speed变量。回到屏幕的局部变量部分,拖入一个Velocity变量的副本。从菜单中选择“设置”。将我们的“In Time Step”节点的执行输出连接到新的速度节点。 -
在蓝图上右键单击并使用菜单找到目标为场景组件的“获取前进向量”节点。从节点的返回值拖出一条线,将其拖到搜索菜单中,并搜索“
Vector * Float”节点。向量输入将是“获取前进向量”的返回值,浮点值将是函数起始处的速度输入。得到的值将成为“设置速度”节点上向量的输入:

在蓝图中进行抛物线轨迹计算
-
在所有变量都整理好之后,是时候进行有趣的部分了!通过创建一个
ForLoopWithBreak节点开始计算。将我们的设置速度的执行输出连接到ForLoopWithBreak的执行输入。拖入一个本地变量“步骤”的副本,并将其连接到ForLoopWithBreak的最后一个索引输入。最后,将第一个索引整数设置为 1。 -
接下来,创建一个“通过通道进行线迹”节点。这个节点将完成所有繁重的工作并创建实际的线迹。将
ForLoopWithBreak的循环体输出连接到新线迹的执行输入。在线迹节点上,将“绘制调试类型”更改为“为一帧”。这将帮助我们可视化抛物线弧。我们总是可以在之后将其关闭。现在,获取一个PrevLoc变量的副本,并将其连接到线迹的“开始”输入。 -
开始数学运算!让我们先获取一个
Velocity局部变量的副本。在变量的输出上右键单击并选择“拆分结构引脚”选项。这将显示组成我们的速度向量的所有值。 -
现在创建三个
Float * Float节点的副本。我们将把刚刚在速度节点上公开的X、Y和Z值连接到每个乘法节点的顶部输入。 -
创建一个
InTimeStep局部变量和一个Integer * Float节点的副本。将变量的输出连接到乘法节点上的浮点输入。节点的整数部分输入来自ForLoopWithBreak节点的Index输出。这段数学计算的结果将被许多节点使用。 -
将输出结果插入到我们在步骤 17中创建的乘法节点之前的第二个浮点输入。
不要忘记,你可以创建重路由节点来帮助你清理代码!在 Blueprint 中右键单击,就像你正在创建一个新节点一样,并在菜单底部找到 Reroute 节点。
-
是时候在我们的计算中补偿重力了。创建一个
Float * Float节点,并将其放置在步骤 18中创建的乘法节点附近。在新创建的节点上,有一个标有+ Add Pin 的按钮。让我们点击两次以创建两个额外的输入引脚。将步骤 18中乘法节点的输出插入到我们新节点的第一个两个输入值(这代表方程中的时间平方)。在第三个输入中,将值更改为 0.5。在最后一个输入中,将值更改为 980。 -
我们几乎完成了数学计算。创建一个
Float - Float节点,并将我们用于Z值的乘法节点的输出插入到第一个输入,将我们在步骤 20中创建的乘法节点的输出插入到第二个输入。 -
最后,创建一个最终的数学节点,
Vector + Vector,并在第一个Vector输入引脚上右键单击。从菜单中选择 Split Struct Pin。X值将是 X 速度乘法节点的输出。Y将是 Y 速度乘法节点的输出。Z值将是我们在上一步中创建的Float - Float的输出。所有这些都将添加到 Init Loc 的值。创建变量的副本并将其插入到最后一个输入。此节点的输出将成为线迹节点上的 End 向量输入。
关于此函数的最后一段内容。随着我们的 Line Trace by Channel 节点获取所有所需的信息,我们需要设置输出值以存储我们从追踪中接收到的信息。我们将通过更新我们存储在 Prev Loc 节点中的位置并将击中结果信息保存到 TempHit 节点中来实现这一点:

完成线迹追踪
让我们把这个东西完成!
-
为我们的
PrevLoc变量创建一个 SET 节点,并将线迹的执行输出连接到其执行输入。要更新向量输入,从我们在步骤 22中创建的Vector + Vector节点拖动一个连接并将其插入到输入。 -
接下来,为 Temp Hit 局部变量创建一个 Set 节点,并将我们的 set Prev Loc 节点的执行输出连接到执行输入。将线迹的 Out Hit 输出连接到输入。
-
为了确保在没有找到击中时,我们不会进行任何不必要的击中计算,让我们添加一个 Branch 节点。添加一个 Branch 节点,并将 set TempHit 节点的执行输出连接到它。将线迹的 Return Value 输出传递到条件输入。
-
将此节点的执行输出全部返回到 ForLoopWithBreak 节点,并将其插入到 Break 输入。
-
通过将完整的执行输出连接到函数的返回节点输入来完成此操作。创建 TempHit 的副本并将其传递到函数的输出。
现在抛物线射线追踪函数终于完成了(哇!那里有很多东西),是时候对它收集到的击中信息做些处理了。InteractWithHit函数将接收来自射线追踪的击中数据并将其传递到我们在对象交互接口中创建的函数:

InteractWithHit函数的变量
让我们从创建一些变量开始:
-
在我的蓝图面板中,找到变量部分并点击+变量按钮。我们将创建的第一个变量将被命名为
FocusComponent。将变量类型设置为原始组件。这将允许我们处理对象的各个组件以及对象本身。接下来,创建一个名为FocusObject的新变量,类型设置为Actor。这将存储当前被击中的对象。最后,创建一个名为CurrentHit的变量,类型设置为Hit Result,用于存储我们从射线追踪中收集到的击中信息。一旦创建了所有三个变量,点击每个变量并在详细信息面板中将它们标记为私有。 -
是时候创建我们的第二个函数了。在我的蓝图函数部分,点击+函数按钮并将新函数命名为
InteractWithHit。新函数需要能够将击中数据传递给它。点击函数,并在详细信息面板中创建一个名为Hit的新输入,类型为击中结果。 -
现在我们需要在我们的新函数内部设置三个局部变量。双击
InteractWithHit函数并转到局部变量部分。点击+局部变量按钮,并将新变量命名为InHitComponent,类型为原始组件。创建第二个局部变量,并将其命名为 In Hit,类型为击中结果。最后,创建第三个局部变量,并将其命名为 In Hit Actor,类型为演员。 -
初始设置已完成!随着我们的变量创建完成,我们现在可以开始用数据初始化它们。从函数节点开始,复制我们的 In Hit 局部变量,并从结果菜单中选择设置。将函数节点的执行输出连接到设置 In Hit 节点的输入。我们还想传递从函数外部来的击中数据,所以我们将函数节点的 Hit 引脚连接到设置 In Hit 节点的输入:

将击中数据接收并存储在我们的变量中
-
接下来,我们将分解击中信息,并将击中演员和击中组件数据传递到我们的局部变量中,这样我们就可以处理它们了。在蓝图上右键单击并创建一个分解击中结果节点。将设置
In Hit节点的变量输出连接到新节点的输入上。这将把击中信息分解成各个部分。接下来,引入我们的In Hit Actor和InHitComponent变量的副本,并从菜单中选择设置。创建了两个设置器后,按照之前截图中的顺序将它们连接起来。然后,将分解击中结果中的击中演员输出连接到设置In Hit Actor节点的输入上。务必为击中组件和InHitComponent节点做同样的事情。 -
我们在这里所做的大部分工作都是检查数据,看看我们正在查看的内容是否可以交互,然后通过我们之前创建的接口传递这些信息。我们还需要能够清除我们的变量,如果我们不再查看具有交互性的内容。这个过程的第一步是检查我们正在查看的对象(存储在焦点对象中)是否等于我们当前正在查看的对象,为此我们需要一个
Branch节点。创建一个新的Branch节点,并将其执行输入连接到设置InHitComponent的输出。现在我们需要比较的两个变量。获取我们的焦点对象变量和In Hit Actor局部变量的副本。从FocusObject拖动一个连接并放置到打开搜索菜单。寻找相等(对象)节点。这将比较传递给它的两个对象,并返回一个布尔值,显示它们是否匹配。将In Hit Actor连接到第二个输入,并将布尔输出运行到分支的输入引脚上:

通过我们的接口传递数据
-
我们的分支已经准备好了,现在是时候传递信息了。从
True输出拖动一个连接并将其放置到我们之前在对象交互界面中创建的追踪移动(消息)函数上。获取焦点对象变量的副本并将其传递到追踪移动的目标引脚上。现在获取In Hit变量的副本并将其连接到追踪移动的Hit输入上。 -
创建另一个分支节点,并将其执行输入连接到追踪移动的输出。这次,我们将检查焦点对象是否等于
InHitComponent,如果不等于,则传递新的信息。为此,我们需要获取Focus Object和InHitComponent,并使用我们的相等(对象)节点来比较它们。将比较的布尔输出插入到新分支的输入上。 -
下一个我们需要的是接口中的另一个函数。右键单击并搜索 Trace Leave Component(消息)。创建它并将其连接到上一个分支的 False 输出。对于输入,我们需要获取 FocusObject 的副本并将其传递到 Target,将 In Hit 传递到 Hit,并将 FocusComponent 传递到 Component。
-
现在我们已经对当前关注的组件调用了 leave,我们将调用 Trace Hit Component 并让它传递我们正在查看的当前组件。创建 Trace Hit Component(消息)的副本并将其连接到 Trace Leave Component。将 FocusObject 传递到 Target,将 In Hit 传递到 Hit,并将
InHitComponent传递到 Component。 -
在这个流程分支中,还有最后一步。我们需要将 FocusComponent 设置为新的
InHitComponent。为 FocusComponent 创建一个 Set 节点,并获取InHitComponent的副本。将 Trace Hit Component 的执行输出连接到设置 FocusComponent,并将InHitComponent传递到变量输入引脚。 -
我们创建的分支是关于如果查看的 Focus Object 和 In Hit 对象相同,则传递组件数据,但如果它们不同怎么办?如果它们不同,我们需要清除击中数据并通过接口传递新的击中信息。回到我们之前在步骤 6中创建的分支。从
False输出拖动一个连接并放下。搜索 Trace Leave Object 函数并创建节点。Trace Leave Object 有两个输入值。将当前存储在 FocusObject 中的对象传递到 Target 输入。对于Hit输入,获取当前 In Hit 的值并将其连接到输入引脚:

清除变量
-
由于我们正在离开对象并将注意力转向其他地方,我们需要清除 FocusObject 和 FocusComponent 中存储的值。为每个变量创建一个 Set 版本。将新的设置 FocusObject 节点连接到 Trace Leave Object 的执行输出。将新的设置 FocusComponent 节点连接到设置 FocusObject 节点。我们为它们的输入值留空以清除它们的值。
-
在我们将新的命中数据传递之前,我们还需要进行一个检查。我们只想在查看的对象使用了对象交互接口时传递命中信息,因为其他任何东西对我们来说都不重要。这是作为设计师的我们创建可交互对象和仅作为场景的对象的方式。为此,我们将使用另一个分支节点。将新分支的输入连接到设置焦点组件的执行输出。为了检查我们正在查看的对象是否使用了该接口,我们需要使用“是否实现接口”函数。此函数接受一个演员并检查它是否附加了特定的接口,然后返回一个布尔值——非常适合与分支节点一起使用。创建一个“是否实现接口”的副本。将“命中演员输入”的值作为测试对象传递,然后点击菜单下的下拉菜单。使用菜单顶部的搜索框找到我们的对象交互接口。设置完成后,将节点的返回值插入到分支的“条件”输入。我们的检查已经设置好了:

传递新的命中数据
-
现在剩下要传递的就是新的命中数据。在蓝图上右键点击并搜索“追踪命中对象(消息)”。将其连接到分支的“真”输出,并将“命中演员输入”传递到目标输入,将“命中输入”传递到命中输入。
-
接下来,创建一个“追踪命中组件(消息)”函数的副本。将“命中演员输入”传递到目标输入,将“命中输入”传递到命中输入,并将“命中组件输入”传递到组件输入。
-
在此分支的最后一步,我们需要更新存储在“焦点对象”和“焦点组件”中的值,使用“命中演员输入”和“命中组件输入”的值。创建一个设置焦点对象节点并将其连接到追踪命中组件函数的执行输出。将“命中演员输入”的值传递到变量输入。最后,将设置焦点组件的执行输入连接到设置“焦点演员”的执行输出,并传递“命中组件输入”的值。我们现在已经完全完成了这个函数的使用!
到这里已经编写了相当多的代码,不是吗?然而,我们在构建我们的手部交互方面已经取得了巨大的进步。我们的交互组件现在能够进行抛物线线迹以找到对象和安全的传送位置。它现在可以通过我们创建的对象交互接口传递信息,使我们的交互对象能够与手部协同工作,以创建游戏玩法。现在是时候在事件图中将所有这些整合在一起,以实现我们创建的所有功能。为此,我们将使用事件计时节点:

触发抛物线线迹
首先创建几个自定义事件:
-
我们需要两个自定义事件来表示我们希望交互组件具有的开和关状态。自定义事件将由蓝图外的元素调用,以通过玩家的动作控制器上的按钮点击触发交互。在事件计时节点附近右键单击并找到菜单中的添加事件部分。打开它并选择添加自定义事件选项。将这个新事件命名为
Enable。重复此过程创建第二个自定义事件并将其命名为Disable。 -
接下来,我们将使用一个门节点来仅允许玩家在通过按钮启用交互组件时与对象交互。门是一个流控制节点,它打开和关闭以允许程序员允许时才通过数据。在蓝图上右键单击并搜索门节点。我们将事件计时输出连接到门上的进入输入。为了控制数据流,我们将我们的启用自定义事件的执行输出连接到打开输入,并将禁用自定义事件的执行输出连接到关闭输入。
-
现在我们将添加我们的抛物线线迹函数。从我的蓝图面板中获取它的副本并将其拖入蓝图。将门节点的执行输出连接到函数的执行输入。为了确保我们的抛物线函数正确运行,将步骤数设置为
10,时间步长设置为0.1,速度设置为500。 -
我们需要存储我们的线迹输出,以便我们可以将其传递给与击中交互函数。幸运的是,我们已创建了一个变量来保存它。通过从我的蓝图面板中拖动它创建我们的
CurrentHit击中变量的副本,并从菜单中选择设置。将其连接到线迹函数的执行输出,并将线迹的击中输出传递给它。 -
通过引入“与击中交互”函数并连接从设置当前击中节点执行输出到它来结束序列。将设置当前击中的变量输出作为函数上的击中输入连接。
这个交互谜题还有最后一个部分。我们需要将交互组件添加到Server17PlayerPawn并将功能映射到玩家的动作控制器上的按钮。让我们首先向交互组件蓝图添加两个更多的自定义事件:

交互蓝图激活部分
每个按钮都有两种状态:上和下。我们将通过创建两个自定义事件来定义这些状态:
-
在蓝图的新部分中右键单击,找到菜单中的添加事件部分,打开它并选择添加自定义事件。将第一个命名为
ActivateUp。创建第二个自定义事件并将其命名为ActivateDown。这两个事件都需要一个输入。单击每个自定义事件,然后在详细信息面板的输入部分单击+按钮。将输入命名为Instigator并将其类型设置为 Pawn。 -
从 ActivateUp 拖动一条连接并搜索 Trace Activate Up(消息)。我们作为接口的一部分创建了此函数。将其执行连接到 ActivateUp 事件,并将事件的 Instigator 输出连接到函数的 Instigator 输入。
-
Trace Activate Up 还需要两个更多的输入值才能正确工作。获取
FocusObject变量的副本并将其连接到目标输入。最后,获取CurrentHit变量的副本并将其连接到函数的Hit输入。 -
我们将重复此过程为
ActivateDown——只是这次,我们将连接来自我们接口的 Trace Activate Down(消息)函数。
是时候完成玩家 pawn 中的设置了:

在玩家 pawn 中设置组件
首先为每只手添加交互组件的副本:
-
在内容浏览器中找到
Server17PlayerPawn并双击以打开它。 -
在视口选项卡中,转到组件面板并点击添加组件按钮。使用搜索框找到我们创建的交互组件。它将在自定义标题下。点击它并命名新组件为
InteractionComponent_L。 -
将新组件拖放到 MotionController_L 组件上。
-
重复步骤 2和步骤 3以创建一个作为右侧运动控制器子组件的第二交互组件。将其命名为
InteractionComponent_R。 -
现在我们可以将我们的新交互组件绑定到控制器上的按钮。这将赋予玩家最终与世界交互的能力!点击事件图选项卡。在图的空白部分右键单击并搜索 MotionController (L) 触发事件。重复此过程以创建一个用于 MotionController (R) 触发事件的节点:

在玩家 pawn 中将动作映射到我们的运动控制器
-
对于我们每个按钮事件,我们需要为我们在交互组件蓝图中所创建的自定义事件创建两个引用。首先从我的蓝图面板中获取左侧交互组件的引用。将其放置在 MotionController (L) 触发事件附近。从它拖动一条线并放下。使用搜索框找到我们的 Activate Down 函数。将其连接到左侧触发事件的 Pressed 执行输出。接下来,从 Activate Down 函数的 Instigator 输入引脚上拖动一条连接并将其拖到搜索菜单中。搜索对
Self变量的引用。在这种情况下,玩家是激活的施动者。 -
使用右侧交互组件的引用重复此过程以处理 MotionController (R) 触发事件。
我选择使用左右触发器进行交互,有几个不同的原因。首先,这是一个对于初次用户来说自然可以按下的按钮,用于激活某物或使其发生。它是直观的。其次,按钮事件存在于 Oculus Touch 控制器和 Vive 运动控制器中,因此我无需在硬件之间进行任何更改。稍后,我们将创建一个拾取交互,我将出于相同的原因使用握把按钮。
在这个编程的第一个部分,我们设置了手的静态网格并编程了ObjectInteractionInterface,这是一个可以被我们希望玩家能够与之交互的对象利用的函数集合。这个蓝图界面连接到我们的交互组件,这是一个具有利用抛物线线迹功能查找交互对象能力的自定义场景组件。最后,我们将激活功能映射到运动控制器的按钮上,作为玩家蓝图的一部分。在下一节中,我们将在此基础上构建新的功能来创建传送机制。
构建传送
现在已经实现了线迹和交互的代码,我们可以将注意力转向传送系统本身。在Server 17中使用传送的目标是给我们的玩家提供一个直观的环境移动方法。尽管传送通常被视为破坏沉浸感,但我认为我们游戏中的网络空间环境支持玩家角色能够在空间中传送的想法。为了开始,让我们首先考虑 VR 传送系统的各个部分:
-
视觉:玩家需要能够可视化当他们点击按钮时将移动到的地方。这可以通过将一条线或一个弧投影到他们将要着陆的地方来完成(我们通过之前设置的线迹调试选项完成了这个操作)。我们还应该在他们会着陆的表面上有一个视觉元素,以进一步突出他们的着陆点。最后,我们可能还会考虑粒子效果和/或声音效果来增强玩家体验。
-
传送代码:这将是在蓝图内构建的实际功能。代码需要处理显示视觉元素以及传送本身。对于设计师来说,构建一些控制也很不错,以限制玩家只能在特定区域内传送,这样他们就不能传送出我们为他们构建的区域或看到他们不应该看到的东西。
与我们通常开始构建此类功能(功能,然后是艺术)的方式相反,这里我们将从一些视觉组件开始。在这种情况下,我们需要一些可以打开和关闭的视觉元素来确保我们的代码正常工作。构建这个系统也将是对我们的界面和线迹代码工作情况的一个极好的测试。让我们从地面上的一个用于传送的视觉元素开始:

TeleportViz 视口
我们首先创建一个新的演员蓝图:
-
在内容浏览器中,在
Server17\Blueprints文件夹上右键单击,并从菜单的创建基本资产部分选择蓝图类选项。从选择父类菜单中选择 Actor,并将这个新蓝图命名为 TeleportViz。双击蓝图以打开它。 -
在视口选项卡中,转到组件面板,并点击添加组件按钮。从菜单中选择静态网格。这应该创建一个新的静态网格组件,它是默认场景根的子组件。
-
点击它,并在详情面板的静态网格部分中搜索。有一个名为静态网格的选项,我们可以设置我们想要使用的形状。点击下拉菜单,搜索 SM_FatCylinder,尽管你可以使用你喜欢的任何形状。将部件命名为
Visualizer。 -
这个静态网格看起来有点单调。让我们用自定义材质让它发光一点,使其突出。这还将作为游戏整体环境潜在艺术风格的一个很好的测试。在内容浏览器中,导航到
Server17文件夹,在内容浏览器窗口中右键单击,并创建一个新的文件夹。将其命名为Materials。双击新文件夹,并在其中右键单击以创建一个新的材质。将这个材质命名为M_TeleportViz。双击新材质以打开它:

TeleportViz 材质
-
当我们打开材质时,我们会看到一个大的结果节点来开始。点击它,让我们在详情面板中调整一些设置。找到标签为混合模式的选项。我们希望我们的新材质感觉高科技,所以让我们将其模式更改为半透明,以给它一点这种感觉。接下来,找到着色模型。由于我们将给这个元素一个自发光的光芒,让我们将其模型更改为不发光。最后,勾选复选框以使材质双面。
-
现在是选择颜色的时候了。通过从调色板面板中抓取它或在材质蓝图上按3键并点击来创建一个常量 3 向量。如果你没有打开调色板,并希望它打开,请点击屏幕右上角的窗口按钮,并在菜单中选择调色板。在新的向量上右键单击,并在结果菜单的顶部选择转换为参数。将参数命名为颜色。通过将其设置为参数,我们使其在需要时能够使用蓝图来更改它。这将来可能很有用。
-
点击颜色节点,然后在详情面板中点击颜色块,以打开颜色选择器。选择你喜欢的任何颜色,然后点击确定按钮。如果你想使用我使用的浅蓝色,请将你的 RGB 值设置为R=0.84,G=0.74,和B=1.0。确保你的 Alpha 设置为A=1.0。
-
为了给材料添加一些发光效果,让我们创建一个常量值并将其乘以我们的颜色。在调色板中搜索 Constant 或右键单击并使用搜索框。右键单击新节点并选择转换为参数,就像我们在步骤 6中为颜色所做的那样。在详细信息面板中,将此值命名为 Strength 并将默认值设置为
10。最后,创建一个 Multiply 节点并将颜色参数的输出作为值A连接,将 Strength 的输出作为值B连接。最后,将乘法节点的输出连接到结果节点的 Emissive 槽。 -
为了给我们的新颜色添加一些渐变效果,我们将稍微调整纹理坐标。通过使用调色板或按住U键并在蓝图上点击来创建一个纹理坐标节点。
-
接下来,我们需要一个组件遮罩。使用调色板或右键单击蓝图并使用搜索框来复制节点。在详细信息面板中,关闭使用 R 通道的选项。将纹理坐标的输出连接到组件遮罩的输入。
-
现在创建一个余弦节点。余弦函数将确保渐变从材料的两端开始。这将连接到组件遮罩的输出。
-
余弦函数输出介于-1 和 1 之间的值。我们需要结果值仅为正值,因此我们将使用一个 One Minus 节点。这将给我们介于 0 和 2 之间的值。创建 One Minus 节点并将其连接到余弦节点的输出。
-
为了将值恢复到 0 到 1 之间,我们可以将答案除以 2。使用 Divide 节点,将 One Minus 节点的输出作为
A值连接。在详细信息面板中找到B值的值。将此值更改为2.0。 -
现在我们将添加一些控制渐变从不透明到透明距离的功能。这被称为衰减。创建一个新的 Power 节点,并将我们的 Divide 节点的输出连接到新节点的 Base 输入。接下来,创建一个 Constant。这可以通过调色板或搜索菜单完成,或者通过按下一个键并在蓝图上点击。右键单击它,将其转换为参数。将参数命名为
GradientFalloff并将默认值设置为3。将此参数的输出连接到 Power 节点的指数或 Exp 输入。 -
为了确保结果值永远不会超过 1,我们需要夹紧这个值。创建一个新的 Clamp 节点并将 Power 节点的输出引入。将 Clamp 节点的输出连接到结果节点的不透明度槽。
-
检查一下!结果应该是发光且部分透明的。你可以通过调整渐变衰减、强度和颜色参数来调整材料,直到感觉合适为止。为了完成视觉效果,回到我们的 TeleportViz 类蓝图,点击静态网格组件。使用详细信息面板,应用我们新的材料,享受高科技的发光效果。
视觉组件处理完毕后,我们现在可以构建功能。为了满足我们需要的能够控制玩家可以传送到的位置的系统的需求,我们将创建一个只允许玩家在我们放置的位置传送的体积。当它检测到线追踪时,它将显示我们的视觉元素,并包含我们的传送代码。首先,在内容浏览器中的 Server17\Blueprints 文件夹上右键单击,创建一个新的蓝图类。从选择父类菜单中选择演员,并将其命名为 TeleportVol:

TeleportVol 组件
我们将从视口选项卡开始:
-
TeleportVol 有两个组件使其工作。第一个是用于检测碰撞和线追踪的盒体体积。第二个是我们之前创建的可以打开和关闭的视觉元素。首先,前往组件面板并创建一个新的盒体碰撞。将新组件命名为
TeleportVol。 -
在组件面板中点击 TeleportVol。我们希望碰撞盒覆盖相当大的空间,但它实际上不需要那么高。在详细信息面板中找到盒体范围条目,并将值更改为 X=200,Y=200,和 Z=1.0。
-
现在,我们需要对碰撞进行一些定制。在详细信息面板的碰撞部分,找到碰撞条目。选择碰撞预设下拉菜单,将其更改为自定义。在追踪响应下,我们希望将两个选项都设置为阻止。在对象响应部分,将所有选项更改为忽略。这将使体积可用于线追踪,但其他则不行。
-
体积定制完成后,我们可以继续到第二个组件。在组件面板中,点击添加组件按钮,从菜单中选择子演员。这是一个类蓝图副本,我们可以将其放置在这个蓝图内部。我们实际上可以用类蓝图构建一个类蓝图!点击组件,在详细信息面板中找到子演员类选项。使用下拉菜单,将此选项设置为 TeleportViz。我们也不希望它在未被射线追踪击中时可见,所以请在详细信息面板的渲染部分关闭可见选项。
-
现在我们将进入代码部分!我们需要这个对象能够对线追踪做出反应,对吧?嗯,我们为此编写了一个完整的接口。这将是我们对象交互接口的第一个应用。要将接口中的函数添加到这个对象中,请点击位于事件图和视口选项卡区域上方的类默认值。这将在详细信息面板中打开此类的默认选项。找到接口部分,并点击添加按钮。在菜单中搜索对象交互接口,并选择它以将它的函数添加到我们的传送体积中。
-
我们现在可以访问我们在界面中编写的每个函数。这些可以在 My Blueprint 面板的 Interfaces 部分找到。只需单击 Interfaces 旁边的箭头即可查看它们。我们甚至可以双击它们的名称来打开它们。打开以下函数:
TraceMove、TraceHit、TraceLeaveObject和TraceActivateUp:

Trace Move 函数
-
对于 Trace Move,我们需要编写当线迹穿过我们的对象时会发生什么。对于传送体积,我们希望将我们之前创建的视觉元素(发光的环,TeleportViz)移动到线迹击中的任何地方。为此,我们将使用一个 SetWorldLocation 节点。在蓝图上右键单击并搜索 Set World location。从列表中选择 SetWorldLocation (TeleportViz)。将函数节点的执行输出连接到其输入,并将执行输出连接到 Return 节点。
-
SetWorldLocation 需要一个 New Location 输入来知道将 TeleportViz 移动到何处,我们可以通过将作为函数输入传入的 Hit 结果分解来找到它。创建一个 Break Hit Result 节点并将函数节点上的 Hit 作为输入。我们可以从分解中获取 Location 输出并将其用作 Set World Location 上的 New Location 输入。
-
接下来,选择 TraceHit 函数。在这个函数中,我们希望打开我们的 TeleportViz 网格的可见性,以便我们的玩家可以看到他们将会出现的位置。在蓝图上右键单击并创建一个 Set Visibility (TeleportViz) 节点。将其连接到函数节点和 Return 节点。在节点上,勾选 New Visibility 复选框和 propagate to children 复选框。
-
现在,让我们继续到 TraceLeaveObject 函数。这个函数将与 TraceHit 函数执行非常类似的功能,除了我们将把可见性重新设置为关闭。类似于前面的步骤,创建一个 Set Visibility (TeleportViz) 节点并将其连接到函数节点和 Return 节点。在节点本身上,不要勾选 New Visibility 旁边的框,但请点击 Propagate to Children 旁边的复选框:

Teleport 函数
-
在我们继续到
TraceActivateUp之前,我们需要创建一个可以处理传送机制的功能。在 My Blueprint 面板的 Functions 部分中,点击 + 按钮,并将新功能命名为 Teleport Player。Teleport Player 需要一些输入值才能工作。第一个名为Player,类型为 Actor。第二个将命名为Loc,类型为 Vector。 -
传送本身需要一点数学计算,并且需要从我们的 Player Controller 获取一些信息。让我们通过从函数节点拖动一个执行线并将其放下以打开搜索菜单来开始序列。搜索“Cast to PlayerController”节点并创建它。转换允许我们假装成另一个蓝图,并且只是许多允许我们在蓝图之间通信数据的方法之一。
-
转换需要一个对象输入,即我们要假装成以访问其数据的特定对象的引用。为了获取我们需要的精确 PlayerController,点击并从函数节点的 Player 输入拖动一条线并将其放下。搜索“Get Controller”节点并将其输出连接到“Cast to PlayerController”节点的输入。
-
接下来,创建一个“SetActorLocation”节点。这将通过将玩家移动到指定的向量坐标来完成实际传送。将转换的执行输出连接到“SetActorLocation”的执行输入。目标输入应设置为 Player,通过从函数节点的 Player 输入拖动一个连接并将其连接到“SetActorLocation”的目标输入。
-
现在是计算新位置的时候了!首先,我们需要获取两样东西的位置:玩家和玩家相机。为了获取玩家的位置,从函数节点的 Player 输入拖动一条线并将其放下以打开搜索框。搜索“GetActorLocation”节点并创建一个。我们很快就需要这些数据。
-
从我们的“Cast to PlayerController”节点的“as Player Controller”输出拖动一条线,并使用它来搜索“Get Player Camera Manager”节点。从该节点的输出拖动并搜索“Get Camera Location”节点。
-
下面是数学部分:我们需要从玩家的相机位置减去玩家本身的位置,以找到我们传送的真确位置。为此,我们需要一个“Vector - Vector”节点。顶部输入将是“GetActorLocation”节点的向量输出。底部输入将是“GetCameraLocation”节点的向量。
-
我们几乎完成了!在
Vector - Vector节点的输出引脚上右键单击并拆分结构引脚。我们只需要计算下一部分所需的 X 和 Y 值。创建一个新的Vector + Vector节点,并在顶部输入上拆分结构引脚。将Vector - Vector的 X 和 Y 输出值连接到Vector + Vector节点的 X 和 Y 输入值。底部向量输入来自函数节点的 Loc 输入。从函数节点的 Loc 输入拖动一个连接并将其连接到Vector + Vector的底部向量输入。
不要忘记,您可以使用重定向节点来清理代码,使其有组织且易于阅读。如果您拖动一个连接并将其放下,可以在搜索菜单中找到重定向节点。您还可以通过在想要创建重定向节点的连接上双击来创建它们。
- 这样,计算就完成了!将
Vector + Vector节点的输出连接到 SetActorLocation 的 New Location 输入以完成序列:

Trace Activate Up
-
最后要编程的功能是 Trace Activate Up,或者当玩家释放动作控制器的按钮时会发生什么。点击我们之前打开的选项卡或双击 My Blueprint 面板的函数部分中的函数名称。使用我们新的 Teleport Player 函数,我们将玩家移动到新位置。从函数节点拖取我们的 Teleport Player 函数副本并将其拖入蓝图。将函数节点的激发者输入连接到 Teleport Player 的玩家输入。
-
计算精确的传送位置只需要一点数学知识。从函数节点的 Hit 输入拖出一条线并将其拖到打开搜索菜单。创建一个 Break Hit Result 节点,并右键单击位置输出以拆分结构。
-
创建一个获取演员位置节点并将其放置在 Break Hit 节点附近。右键单击矢量输出并将该结构引脚也拆分。
-
接下来,右键单击 Teleport Player 节点上的 Loc 输入,并(不出所料!)拆分结构引脚。
-
是时候传递XYZ值了!将 Break Hit 的 Location X 和 Location Y 输出连接到 Teleport Player 的 Loc X 和 Loc Y 输入。同时,将 Get Actor Location 节点的 Z 输出连接到 Teleport Player 的 Loc Z 输入。
-
通过将 Teleport Player 的执行输出连接到函数返回节点来完成序列。
在完成传送体积后,我们现在可以测试我们的传送能力,以及支持它的所有系统(抛物线线追踪和对象交互界面)。在你的测试级别中放下几个 TeleportVol 蓝图,尽情尝试!如果一切正常,我们可以继续构建原型拼图盒。
服务器 – 构建拼图盒
现在玩家有了触摸物体和在我们测试级别中传送的能力,现在是时候解决拼图盒了。在Server 17中,拼图盒是玩家远程入侵服务器并窃取数据的图形表示。最终版本可能包含多个需要解决的谜题和数十个需要完成的步骤。然而,对于这个第一个原型版本,我们将设计和构建一个简单的三步盒,如下所示:

此设计要求玩家找到并移除盒子上的一个面板,以露出一个解锁盒子前部的按钮。然后可以通过抓住它并将其移动到一边来移除该面板,从而露出另一个开关。最后一步是按下开关并找到可移除的面板以窃取数据。
在 Unreal Engine 4 中构建此功能将使我们创建一些东西:
-
我们需要为我们的界面目前未涵盖的手创建额外的功能,例如抓取、拖动和按下。
-
我们需要一个能够对玩家的触摸做出反应、可以被抓住、移动和扔出的对象。
-
我们需要可以触发事件的交互式按钮。
-
我们需要使用子演员和代码来构建盒子本身,以打开和关闭交互元素。
当完成时,我们将拥有一个令人惊叹的测试平台,可以用来证明服务器 17很有趣,并且可以由我们的目标受众进行测试。向前推进的第一步是构建第二个界面来容纳我们的按钮交互:

按钮交互界面
是时候创建按钮交互界面了:
-
前往内容浏览器,导航到我们的
Server17\Blueprints文件夹。在浏览器中右键单击,并导航到菜单中的创建高级资产部分。突出显示蓝图条目,并选择蓝图界面选项。将新界面命名为ButtonInteractionInterface。双击新界面以打开它。 -
在这个界面中,我们将创建九个不同的函数,代表我们创建的动作的不同状态。第一个是一个确定当我们把手悬停在某个东西上时要做什么的函数。在 My Blueprint 面板的函数部分找到并点击+按钮。将新函数命名为
OnHover。OnHover需要一个名为Interactor的输入,类型为 Interaction Component。它还需要一个名为 Return 的布尔类型输出,以便它能够正常工作。 -
下一个函数将被命名为
EndHover,它控制当玩家的手离开一个对象时会发生什么。它有与上一个函数相同的输入和输出。 -
现在,我们将创建
OnPickup函数来覆盖当我们想要拾取一个对象时会发生什么,这在 VR 中是一个非常常见的交互。它的输入和输出与最后两个函数相同。 -
接下来是
OnDrop函数;这个函数将给我们机会在玩家丢弃一个对象时创建额外的函数。它也有与最后几个函数相同的输入和输出。 -
接下来的三个函数控制对象的拖动,并允许我们通过这些动作创建更多的游戏玩法。创建名为
OnDrag、OnDragStart和OnDragEnd的函数。它们的输入和输出与迄今为止我们创建的所有其他函数相同。 -
最后,这里有一个不同!创建一个新的函数,并将其命名为
CanPickUp。这个函数控制一个布尔值,用于确定是否允许拾取某个东西。它只有一个输出:名为PickUp的布尔值。 -
这是最后一个。再创建一个函数。这个函数被命名为
OnUse,允许我们创建可以作为加成使用的对象。OnUse的输入和输出与OnHover相同。
现在我们有了第二个接口,它允许我们作为设计师有足够的自由,根据我们的想象力来创建交互。为了测试我们新的玩家能力,让我们构建一个基本的立方体,它可以以几种不同的方式进行交互:

与界面交互的立方体
为了测试我们的系统(并有点乐趣),我们将创建一个立方体,它利用了我们在本章中创建的许多新功能:
-
确保你处于
Server17\Blueprints文件夹中,并在内容浏览器中右键单击以创建一个新的蓝图类。在“选择父类”菜单中选择 Actor,并将我们的新蓝图命名为InteractCube。 -
在组件面板中点击“添加组件”按钮,并在菜单的通用部分选择立方体选项。在详细信息面板中,找到顶部附近的缩放值,并将值设置为 X=0.3,Y=0.3,Z=0.3。
-
是时候添加接口了。在屏幕顶部点击“类默认值”按钮。在详细信息面板中,在接口部分点击“添加”按钮。搜索并添加对象交互接口和按钮交互接口。
-
现在我们有很多功能可以玩!让我们从检测立方体被线迹击中的方式开始。打开我的蓝图面板的界面部分,并打开
TraceHitObject函数。在蓝图上右键单击并搜索材料节点上的“设置向量参数值(立方体)”。此节点允许我们访问立方体默认材质上的颜色参数。将其连接到函数节点和返回节点。接下来,将参数名称的值设置为颜色。最后,通过将参数值 X 的值更改为 1.0,让我们将立方体变成红色。现在,当立方体被线迹击中时,它将变成红色! -
让我们在线迹离开立方体时将颜色重新设置为白色。在界面菜单中双击
TraceLeaveObject函数。抓取我们之前使用的相同的“设置向量参数值(立方体)”节点,这次,将参数值的 X 设置为 1.0,Y 设置为 1.0,Z 设置为 1.0。 -
还在玩得开心吗?让我们从界面菜单中打开
TraceActivateDown函数。使用相同的前置技术,创建一个“设置向量参数值(立方体)”节点,输入参数名称的“颜色”,并将参数值的 X 设置为 0,Y 设置为 1.0,Z 设置为 0。现在,当我们点击按钮与之交互时,我们的立方体变成了绿色! -
如果我们使用
TraceActivateDown,我们可能还需要使用TraceActivateUp。让我们设置它,以便在释放按钮时将颜色重新变回红色。打开函数并创建另一个“设置向量参数值(立方体)”节点。将其连接到函数,将参数名称的值设置为颜色,并将参数值的 X 设置为 1.0,Y 设置为 0,Z 设置为 0。
想要在我们的函数中玩得更开心吗?使用添加冲量节点,其中节点的目标是场景组件,以创建突然向上的加速度。将设置向量参数值节点中的一个替换为添加冲量,并将冲量参数的 Z 值更改为 5000。确保勾选速度变化复选框。这样,当按钮被按下或释放时,立方体将向天空射击!
-
我们尝试让立方体可以被捡起和扔出去怎么样?为了创建这个机制,我们将让虚幻引擎对我们的立方体进行物理模拟,然后在捡起和放下立方体时切换这个功能的开和关。打开
OnPickup和OnDrop函数。在OnPickup中,在蓝图上右键点击并搜索设置物理模拟(立方体)的节点。将执行输入和输出连接到函数节点和返回节点,因为这将是我们在这个函数中使用的唯一节点。这样,当立方体被捡起时,将禁用物理模拟,这样它就不会从玩家的手中掉落或表现出其他奇怪的行为。现在打开OnDrop并创建另一个设置物理模拟(立方体)的副本。这次,在节点旁边的“模拟物理”选项旁边勾选复选框。这样,当立方体被放下时,将启用物理模拟,使其按我们的意图行事。最后,回到视口选项卡,点击立方体。检查详细信息面板,确保已启用模拟物理选项。如果未启用,请启用它。 -
还有一个函数需要设置。回到我的蓝图面板的接口部分,打开
CanPickUp函数。为了能够开关这个功能,我们需要一个可以切换的布尔值。在变量部分,点击+按钮并创建一个名为PickUpActive的新布尔变量。回到CanPickUp函数,拖入我们新变量的副本并从菜单中选择获取。将PickUpActive的输出连接到返回节点的PickUp输入。
要使用这些新功能,我们需要在交互组件中设置调用它们的方法,并将它们映射到Server17PlayerPawn中的按钮。这就像我们在本章早期创建对象交互接口时做的那样。回到内容浏览器,打开交互组件:

悬停交互组件代码
-
在交互组件内部,我们需要添加一些新的玩家功能,但当前的代码不允许这种扩展。为了改变这一点,我们将使用一个序列节点。序列节点是一个流程控制节点,它按照每个输出引脚连接的代码顺序执行。为了实现我们的新功能,我们希望在门之后添加序列节点。在蓝图上右键单击并搜索序列节点。一旦创建,点击节点左下角的“添加引脚”按钮。这将添加一个额外的执行输出。将序列直接连接在门之后,并将抛物线线迹节点连接到标记为 Then 0 的执行输出。
-
在开始实现悬停、拖拽和拾取功能之前,我们还需要一些额外的变量。在“我的蓝图”面板的变量部分点击“+”按钮并创建一个名为
InteractRadius的新浮点变量。编译蓝图并为其设置默认值 10。接下来,创建两个类型为 Actor 的变量,一个命名为HoverObject,另一个命名为HeldObject。最后,创建一个布尔变量并命名为Dragging。 -
让我们先实现悬停和拖拽行为。从我们新序列节点的 Then 1 输出拖动一条执行线并将其放置在蓝图的一个空白区域。我们要找的节点叫做 SphereOverlapActors,它被设计用来返回它在特定半径内检测到的对象。
-
SphereOverlapActors 需要四个不同的输入值。第一个是“球体位置”(或 Pos)。我们希望球体从交互组件所在的位置进行检测。从 Sphere Pos 输入拖动一条线并放下。搜索“获取位置”节点。这将给我们一个输入,表示交互组件在世界空间中的位置。
-
对于下一个输入,获取我们的 InteractRadius 变量的副本并从菜单中选择“获取”。这将为我们交互提供一个 10 厘米的检测半径,并通过变量轻松调整该半径。
-
现在,我们想要过滤玩家可以与之交互的对象。从对象类型输入拖动一条线并搜索“创建数组”节点。在节点上点击“添加引脚”按钮并使用两个下拉菜单选择“世界动态”和“物理体”选项。
-
最后,我们希望玩家忽略任何被归类为自身的对象,这样就不会出现一个动作控制器与另一个动作控制器交互的情况,例如。在蓝图上右键单击并使用搜索框查找“获取所有者”节点。这将返回一个指向玩家的 Actor 引用。由于 SphereOverlapActors 只接受一个数组作为忽略 Actors 的输入,我们需要将“获取所有者”的输出插入到一个数组中。从“获取所有者”的返回值拖动一条线并放下。找到“创建数组”节点并将结果数组连接到 SphereOverlapActors 的最终输入。
-
SphereOverlapActors 现在将返回一个符合我们标准的对象数组。使用输出,我们需要快速检查我们存储在变量中的 HoverObject 是否与我们刚刚获取的对象相同。如果它们相同,就没有继续的理由;这将节省我们一些性能。从 Out Actors 输出拖出,并搜索 Get 节点。这将检索索引 0(数组应只包含一个对象)区域的项目。接下来,获取我们的 HoverObject 变量的引用。最后,为了比较它们,从 HoverObject 变量拖出并搜索 NotEqual 节点。一个输入应该是我们的 HoverObject,另一个应该是我们的 Get 节点的输出:

Hover 代码的第二部分
-
NotEqual 将返回一个布尔值,因此我们需要一个分支来继续。将分支连接到来自 SphereOverlapActors 节点的执行输出。条件将是 NotEqual 节点的结果,并且只有当它是
True时我们才会继续前进。 -
如果我们的比较结果是 True,并且两个对象不相同,我们需要在当前对象上结束悬停行为,并设置悬停或拖动行为以与新对象一起工作。从分支的 True 输出拖出,并搜索我们的 Button Interaction Interface 中的 EndHover 函数。获取 HoverObject 变量的副本并将其插入到目标输入中,并使用 Self 引用作为交互者输入。
-
接下来,创建 EndDrag 函数的副本并将其连接到 EndHover 节点的执行输出。
-
现在,我们需要更新 HoverObject 变量,以包含我们正在悬停的新对象。拖入 HoverObject 的副本并从菜单中选择设置。输入将是之前 Get 节点的输出。
-
最后,我们将调用
OnHover函数来完成序列。创建OnHover函数的副本并将其连接到 set HoverObject 的执行输出。set HoverObject 的变量输出可以用作目标。使用 Self 引用作为交互者输入。
我们悬停的代码使我们能够检测玩家可能伸手去拿或手放在其上的对象,例如按钮或其他组件。接下来,我们将处理拖动:

拖动代码
对于拖动,我们需要创建两个可以从蓝图外部调用的自定义事件。这正是我们之前用来实现 ActivateUp 和 ActivateDown 行为的技术:
-
首先,我们将创建两个自定义事件。在蓝图上右键单击并选择添加自定义事件选项。将第一个命名为
StartDrag,第二个命名为EndDrag。 -
对于我们这两个新事件,我们基本上想要在调用 OnDragStart 和 OnDragEnd 函数的同时打开和关闭 Dragging 布尔值。让我们从 StartDrag 开始。获取我们的 Dragging 变量的副本,并从菜单中选择 Set。将事件的执行输出连接到 set Dragging,并点击节点上的复选框将其设置为 True。
-
现在我们将调用 OnDragStart 函数。从 My Blueprint 的接口部分拖动 OnDragStart 的副本到蓝图,并连接一个从 set Dragging 的执行线。将我们的 HoverObject 变量的副本作为目标,并使用 Self 作为交互者。
-
接下来,我们可以使用我们的 EndDrag 自定义事件。第一步是检查我们的 Dragging 布尔值是否为 True;如果是,那么我们可以结束当前的拖动。创建一个新的 Branch 节点,并将我们事件中的执行输出连接到它。获取一个 Dragging 的副本,并将其连接到 Branch 上的条件输入。
-
如果 Dragging 为 True,我们需要更新变量并结束拖动。创建 Dragging 的另一个副本,这次从菜单中选择 Set 并将其连接到 True 的分支输出。确保 Dragging 的复选框是空的。
-
是时候创建 OnDragEnd 函数的副本了。将 set Dragging 的执行输出连接到我们新节点的执行输入。就像我们之前为 OnDragStart 所做的那样,将 HoverObject 作为目标,并使用 Self 作为交互者。
-
是时候将这两个序列组合在一起了。我们将使用一个 Gate 来控制是否可以使用自定义事件拖动某个东西。创建一个新的 Gate 节点,将 OnDragStart 的输出连接到打开输入,并将 OnDragEnd 的输出连接到关闭输入。我们将连接早期 Sequence 节点的 Then 2 输入到进入输入。
-
完成拖动的最后一步是调用 OnDrag 函数。创建一个副本并将其连接到我们在上一步创建的 Gate 节点的退出输出。就像我们之前为拖动函数所做的那样,我们将 HoverObject 变量的副本连接到目标输入,并将 Self 作为交互者插入。
我们现在可以拖动物体了!这种行为可能在编程杠杆和其他可以移动但不必定能捡起的类似部件时很有用。说到捡起,我们将要解决的下个特性是捡起物品并扔掉的能力。与悬停或拖动类似,捡起和放下行为背后有一个类似的技巧。大多数物体,但并非全部,都会启用它们的模拟物理选项。这个技巧是这样的:当物体在 VR 中被捡起时,我们将物体附加到玩家的手上,这样它就不会从他们手中逃走。我们还将禁用物体的物理(我们在 InteractCube 的 OnPickup 函数中之前已经设置过)。当我们放下物体时,我们将它从玩家那里分离出来并重新启用物理。由于玩家从未看到任何变化,所以对他们来说,就像他们只是捡起物体然后放下一样。酷吧?
让我们开始构建:

捡起代码
正如我们对拖动行为所做的那样,我们首先创建两个自定义事件:一个用于捡起,另一个用于放下:
-
在我们的蓝图上右键单击,并使用添加自定义事件选项创建两个新的自定义事件。将第一个命名为“捡起”,第二个命名为“放下”。
-
我们首先构建捡起行为。我们需要首先检查玩家悬停的物体是否是有效的物体。在蓝图上右键单击并搜索 IsValid 节点。我们需要的是图标上带有问号的版本。将其连接到我们的捡起事件,并创建 HoverObject 变量的副本,它将作为节点上输入对象引脚的输入。
-
既然我们知道物体是否真实(从某种意义上说),我们可以检查我们是否可以捡起它!创建 CanPickUp 函数的副本,并将 HoverObject 作为目标连接。这将返回 True 或 False,这将决定玩家是否可以捡起该物体。
-
接下来,我们需要一个分支来处理从 CanPickUp 传来的布尔值。我们只希望当这个值为 True 时,玩家才能捡起物品。创建一个新的分支节点,并将其连接到 CanPickUp 的执行输出。条件输入将是来自 CanPickUp 的 PickUp 输出。
-
我们现在知道物体是否真实。我们也知道我们是否允许捡起它。现在是时候进行重头戏了。创建 HeldObject 变量的副本,并从菜单中选择设置。设置 HeldObject 的输入将是 HoverObject 的当前值。
-
接下来,创建一个 AttachToComponent 节点的副本,确保我们使用的是 Target 是 Actor 版本。将其连接到 set HeldObject 的输出。也将 set HeldObject 的变量输出传递到 AttachToComponent 的 Target 输入。现在创建一个 Self 引用并将其插入到 Parent 输入。最后,在节点中设置 Location Rule、Rotation Rule 和 Scale Rule 设置为 Keep World 选项。这将保持我们拾取的物体的基本属性,以便当我们放下它时,一切都将保持不变。
-
下一步是调用我们的 OnPickup 函数来触发我们可能想要为该对象执行的特殊行为。创建 OnPickup 的一个副本,并将 AttachToComponent 的输出连接到它。将 HeldObject 变量的一个副本连接到 Target 输入,并使用 Self 引用作为 Interactor。
-
现在我们有了拾取和放下物品的能力,我们可能想让玩家能够使用他们悬停的物品。为了创建这种行为,我们需要创建一个名为 UseHovered 的自定义事件。接下来,创建我们的
OnUse函数的副本并将其连接到我们的自定义事件。获取我们的 HoverObject 变量的一个副本作为 Target。完成节点时,使用 Self 引用作为 Interactor:

玩家 pawn 中的拾取/放下和悬停/拖拽代码
-
为了能够利用我们新的行为(悬停、拖拽、拾取和放下),我们需要将它们映射到玩家的运动控制器上的按钮。让我们将悬停和拖拽添加到触发器上。打开
Server17PlayerPawn并找到我们映射 ActivateUp 和 ActivateDown 到运动控制器触发器的位置。我们将从左侧触发器开始。从 ActivateDown 扩展一条执行线并将其拖动到搜索我们的 UseHovered 函数(引用左侧交互组件)。接下来,从 InteractionComponent_L 的引用拖动出来并搜索 StartDrag 函数。将其连接到 UseHovered 函数的执行输出。最后,再次从 InteractionComponent_L 的引用拖动出来并搜索 EndDrag。我们将将其连接到 ActivateUp 的执行输出。现在我们可以使用悬停的物体,以及开始和结束拖拽序列! -
为右侧运动控制器重复相同的步骤。
-
最后,是时候映射拾取和放下行为。许多游戏将这种行为映射到握把按钮,因为玩家似乎自然地倾向于使用这些按钮进行交互。在蓝图上右键单击并搜索 MotionController L Grip1 事件。从 Pressed 输出拖动一条执行线并搜索我们的 Pickup 函数(引用左交互组件)。现在从 InteractionComponent_L 的引用拖动出来并搜索 Drop 函数。将这个新函数连接到事件的 Released 输出。像之前一样,也为右侧运动控制器重复此过程。
完成!为了测试行为,让我们在我们的关卡中放置几个这些神奇的立方体并测试它们的功能。拿起它们,扔出去,观察它们改变颜色。很酷,对吧?想想你现在在这个立方体中拥有的游戏玩法可能性。这给你带来了灵感,不是吗?在我们开始实现这些想法之前,为什么不给我们工具箱中再添加一个按钮,让玩家可以按下它:

按钮按下代码
我们可以在视口(Viewport)中开始构建我们的按钮:
-
我们按钮将由三个不同的组件构建。在视口选项卡中,点击添加组件按钮并创建一个新的场景组件。将其命名为
ButtonRoot。接下来,创建一个圆柱体组件并命名为Button。我们需要将圆柱体缩小一点,所以将缩放属性的 X、Y 和 Z 值更改为 0.05。同时,将其碰撞预设更改为 OverlapAllDynamic。确保按钮是 ButtonRoot 的子组件。最后,创建另一个名为ButtonBase的圆柱体组件,并确保它是默认场景根的子组件。我们在这里也需要更改缩放属性。将缩放值更改为 X=0.15, Y=0.15, 和 Z=0.05。最后,将其碰撞预设设置为 NoCollision。 -
现在视觉组件已经创建,我们可以继续到蓝图。我们想要创建一个按钮,当玩家将手悬停在它上面时,它会稍微动画化,为此,我们需要一些变量。第一个我们将创建的变量名为
InitOverlapLoc,类型为 Vector。这将存储玩家手开始重叠的位置。第二个是MaxPressAmount,类型为 Float。这将是一个可调整的变量,这样我们就可以微调玩家需要按下按钮多少次才能触发它。编译蓝图并将此变量的默认值设置为 4。第三个我们需要变量名为PressAmount,类型为 Float。这将存储玩家按下的按钮量。最后,我们需要存储重叠按钮的组件名称。创建另一个变量并命名为OverlapComponent。确保其类型为 Primitive Component。 -
我们将开始我们的第一个代码序列,包含两个事件。点击我们之前创建的按钮组件,并转到详细信息面板。在菜单的事件部分列表的底部附近,有一组按钮,允许你创建几乎与我们的按钮兼容的每个事件。我们需要创建两个: OnComponentBeginOverlap 和 OnComponentEndOverlap。这将检测当玩家的手按下按钮时的情况。
-
在 ComponentBeginOverlap 事件开始似乎是个好地方,因为这个事件是启动一切的事件。第一步是检查重叠按钮的组件是否有效。创建一个 IsValid 节点的副本(记得那个带问号的?)。将其连接到事件并获取我们的 OverlapComponent 变量的副本以插入到输入对象引脚。
-
如果我们的 OverlapComponent 无效(意味着变量为空),我们需要将按钮重叠的任何内容存储为我们 OverlapComponent 的新值。获取另一个 OverlapComponent 的副本,但这次,从菜单中选择设置。新节点的输入将是事件节点中的 Other Comp 输入。
-
当 OverlapComponent 设置后,我们还需要在 InitOverlapLoc 变量中设置其位置。在蓝图上右键单击并复制一个 GetWorldLocation 节点,其中节点的目标是 Scene Component。将执行输入连接到设置 OverlapComponent 的执行输出。让 GetWorldLocation 的变量输入通过设置 OverlapComponent 的变量输出。
-
现在我们需要将新位置存储在 InitOverlapLoc 中。创建一个新的节点 InitOverlapLoc。这个新节点的变量输入将是 GetWorldLocation 的输出。别忘了连接执行连接!
-
是时候处理 OnComponentEndOverlap 事件了。当我们结束重叠时,我们只想检查从重叠中移除的对象是否与我们在 OverlapComponent 变量中存储的对象相同,以及我们是否想要清除该变量。通过创建一个分支并将其连接到事件的执行输出来启动序列。为了比较我们的对象,创建一个 Equals (object) 节点的副本。顶部的输入将是事件中的 OtherComp 输出。底部将是我们的 OverlapComponent 变量的副本。最后,将 Equals 节点的布尔输出连接到分支上的条件输入。
-
在这个序列的最后一步,创建一个 OverlapComponent 的副本并从菜单中选择设置。将其连接到分支的 True 执行输出。我们将留其变量输入为空,这样我们就可以清除其值。
-
我们代码的下一步是构建将控制简短动画的节点。我们从 Event Tick 节点开始。接下来,右键单击并创建一个 Sequence 节点,因为这将是一个两步过程。将 Sequence 连接到 Even Tick 的执行输出:

按钮下代码
-
从 Then 0 输出拖动连接,并将其放下,并使用搜索框找到 IsValid 节点(记得使用菜单中标记为 ? 的那个)。我们将检查这次输入对象是否是 OverlapComponent 的副本。我们将使用这个设置来确定玩家是否仍然与按钮重叠。如果是,我们将动画按钮向下移动。如果不是,我们将动画按钮,使其返回到原始位置。
-
接下来,拖入我们的 PressAmount 变量的一个副本,并从菜单中选择 Set 选项。将其连接到 IsValid 输出。我们现在将其变量输入留空。
-
为了进行实际的动画,我们需要创建一个 SetRelativeLocation 节点的副本。将其执行输入连接到设置 PressAmount 的执行输出,并使用按钮组件的引用作为目标。
-
当代码的基本设置完成动画后,是时候做一些快速的计算了。按下按钮是一个基本的动作,当玩家重叠时,涉及按钮组件在Z轴上上下移动。为了确定按钮需要移动多远,我们将重叠的演员移动的距离从该演员的初始位置中减去。我们将比较这个距离与我们的 MaxPressAmount,以确定按钮是否真正被按下。所有这些计算都将相对于按钮的位置进行,以确保它在您选择的任何情况下都能正常工作。我们将从将存储的 InitOverlapLoc 值转换为与按钮相关联开始。从组件面板获取 ButtonBase 的引用,从它那里拖动一个连接,并将其拖到打开搜索菜单。创建 GetWorldTransform 的副本。
-
从 GetWorldTransform 的返回值拖动一条线,放下连接,并搜索 InverseTransformLocation 节点。此节点旨在接收一个位置,并根据我们提供的转换将其从世界空间转换为本地空间。在这种情况下,我们将初始重叠位置转换为相对于 ButtonBase 的相对位置。获取我们的 InitOverlapLoc 变量的一个副本,并将其连接到 Location 输入。
-
现在创建一个
Vector - Vector节点,并将 InverseTransformLocation 的返回值插入到顶部输入。我们将在以下步骤中计算第二个值。 -
从 ButtonBase 的引用拖动另一个连接,并创建 GetWorldTransform 和 InverseTransformLocation 的另一个副本。
-
这次,我们需要获取存储在 OverlapComponent 中的组件的位置。获取 OverlapComponent 变量的一个副本,并从它那里拖动一个连接。放下它,搜索 GetWorldLocation。此节点的返回值将是我们在步骤 17中创建的 InverseTransformLocation 副本的 Location 输入。现在,InverseTransformLocation 的返回值可以连接到我们的
Vector - Vector的较低输入。 -
由于按钮只在Z轴上移动,我们只对减法节点的Z输出感兴趣。在
Vector - Vector的输出上右键单击,并拆分结构引脚。 -
为了确保Z值不会超出我们需要的范围,我们将使用一个 Clamp 节点来限制它。Clamp 节点接收一个值并告诉它它只能介于一定的最小值和最大值之间。在这种情况下,我们将引入Z值,并确保它不会低于零或高于我们的 MaxPressAmount。右键单击并创建一个 Clamp (float)节点。使用我们的
Vector - Vector的Z输出作为 Value 输入,并使用我们的 MaxPressAmount 变量的副本作为 Max。我们应该将最小值留在 0。 -
我们几乎完成了!我们的钳位浮点值现在可以成为我们之前创建的 set PressAmount 节点的输入。将 Clamp 的输出连接到 set PressAmount 输入。
-
由于按钮总是会向下移动以显示它被按下,因此我们需要传递给 SetRelativeLocation 节点的值应该是负数。从 set PressAmount 的变量输出拖出,并创建一个
Float * Float节点。将第二个值设置为-1.0。 -
为了完成动画,右键单击 SetRelativeLocation 的 New Location 输入并拆分结构。将
Float * Float的输出连接到 New Location Z 输入:

返回按钮起始位置的代码
-
一旦重叠完成,我们需要将按钮移回其原始位置。回到我们之前在步骤 11中创建的 IsValid,将 IsNotValid 输出的连接拖动,放下,并搜索节点 SetRelativeLocation 的副本。将我们的 Button 组件作为目标设置引用。
-
在蓝图上右键单击并搜索 VinterpTo。这将帮助按钮实现平滑的动画回到未按下状态。为了填充 Current 输入,获取按钮组件的引用并将其拖出连接。放下并搜索 GetRelativeLocation 节点。将此节点的输出连接到 Current 输入。
-
要填充 Delta Time 输入,将 Event Tick 节点中的 Delta Time 值连接过来。
-
最后,将 VinterpTo 的 Return Value 连接到 SetRelativeLocation 的 New Location 输入。
完成按钮的最后一步是处理按钮被按下时发生的情况。为此,我们将使用事件分发器。通过使用事件分发器,我们可以根据需要将不同的命令绑定到按钮上,从而为我们提供一个灵活且可重用的系统:

按钮被按下时会发生什么?
要继续,从 Sequence 节点的 Then 1 输出拖出,并创建一个新的 Branch:
-
现在是时候比较我们按下的按钮数量与最大按下数量的差异了。右键单击并创建一个
Float >= Float节点。获取 PressAmount 的副本并将其连接到顶部输入。获取 MaxPressAmount 的副本并将其连接到底部输入。最后,将节点的输出连接到分支的 Condition 输入。 -
现在创建一个 DoOnce 节点。此节点停止再次发生某些事情,直到它被重置。我们将从分支的 True 输出连接到执行输入,并将 False 输出连接到重置输入。
-
最后,创建一个事件分发器。在 My Blueprint 面板的事件分发器部分找到并点击+按钮。将新分发器命名为 Pressed。回到蓝图,从 DoOnce 的 Completed 输出拖动并搜索 Call Pressed 节点。
-
实现按钮按下行为的最后一步是在交互组件中添加一些代码。
我们现在有一个可以供玩家在 VR 中猛按的工作按钮。为了测试功能,将按钮的副本放入可以按下并打开关卡蓝图的关卡中。我们将从蓝图默认应该存在的 Event BeginPlay 节点开始构建。如果它不在那里,请继续添加一个。
在蓝图上右键单击,创建一个指向级别中 InteractButton 的引用。从输出拖动并搜索 AssignPressed 节点,将事件的执行输出连接到其输入。这将允许您为 Pressed 分配一个自定义事件。它可以是你想要的任何东西!我在上一个屏幕截图中创建了一个简单的方法来结束关卡。
哇!我们已经从起点走了很长的路。现在玩家有了手。他们可以使用它们来与周围的世界互动。他们可以触摸、拿起、扔、按按钮,以及我们可以想到的任何其他事情。他们还可以在关卡中传送以探索我们决定创建的内容——所有这些都有控制措施,以便他们只能做我们作为设计师允许他们做的事情。是时候将这种力量用于良好的用途了。
记得我们在本节开头设计的拼图盒吗?我们现在拥有了实现这一目标所需的所有部件。由于这是一个原型,我们将使用静态网格组件和另一种称为子演员的组件来构建盒子。子演员是类蓝图的副本,允许我们在类蓝图中使用其他演员构建对象,这意味着我们可以使用 Box 组件、InteractCube 的副本和几个 InteractButton 的副本来拼凑我们的简单拼图盒:

构建 PuzzleCubeTest
首先创建一个新的类蓝图:
-
在内容浏览器中的
Server17\Blueprint文件夹上右键单击,并基于 Actor 创建一个新的类蓝图。将这个新蓝图命名为 PuzzleCubeTest,因为这个拼图方块代表我们的第一个原型。双击它以打开它。 -
让我们创建视觉组件。最好的开始方式是从非交互式组件开始。在 My Blueprints 的组件部分,点击添加组件按钮,并创建一个新的 Box 组件。将缩放属性更改为 X=1.0,Y=2.0,Z=0.1。将此组件命名为
Bottom。 -
创建另一个盒子组件,并将其命名为
RightSide。将缩放属性更改为 X=0.5,Y=2.0,Z=0.1,并围绕Y轴旋转 90 度。将其沿底部组件的右侧对齐。 -
选择右侧组件并按Ctrl + W。这将创建一个副本,您可以将其与底部组件的左侧对齐。将这个新部件命名为
LeftSide。将其 Y 缩放属性更改为 1.5。这个空间将允许在后面隐藏一个隔间。 -
点击底部组件并按Ctrl+W创建一个副本。将这个新组件命名为 Top。将此组件沿正Z方向向上移动 60 厘米。
-
箱子正在慢慢组装起来,但现在我们需要一块背板来完成外壳。创建一个新的盒子组件,并将缩放属性更改为 X=0.9,Y=0.5,Z=0.1。将其命名为 BackPlate。最后,围绕X轴旋转 90 度。
-
现在我们将创建非交互式的底部和顶部支撑。创建一个新的盒子组件,并将其命名为 BottomSupport_1。将缩放属性设置为 X=0.5,Y=1.25,Z=0.1。将其移到 Bottom Component 下方,并与前部和侧面边缘对齐。
-
选择
BottomSupport_1,按Ctrl + W并命名这个新组件为BottomSupport_2。围绕Z轴旋转 90 度,并将其移回箱子底部的中心。这样做的一个简单方法是将其位置属性的Y值设置为 0。 -
创建 BottomSupport_1 的另一个副本,并将其命名为 BottomSupport_3。围绕Z轴旋转 90 度,并将此组件与底部组件的后边缘对齐。
-
通过单击第一个并按住Ctrl同时选择其他两个,在组件列表中选择所有 BottomSupport 组件。如有必要,旋转组件 90 度,使它们恢复到正确的方向。然后沿Z轴向上移动 80 厘米,使它们的底部边缘与 Top 组件的顶部边缘对齐。将它们命名为
TopSupport_1,TopSupport_2和TopSupport_3。 -
为了给箱子内的一个按钮提供一个放置的表面,我们将创建一个额外的非交互式组件。点击 BackPlate 组件,并使用Ctrl + W创建一个副本。将这个新组件命名为
MiddlePlate,并使其位置属性的 X=0,Y=60,Z=30。 -
为了在我们的拼图盒中创建交互式表面,我们将利用子演员组件,这些组件将是 InteractCube 对象的副本。这将使我们能够使用我们编程到 InteractCube 中的所有功能,并将它们作为拼图盒的组件使用,而无需在新的组件中重新编程它们(尽管我们可能在未来想要这样做)。创建一个新的子演员组件,并将其命名为
SideDecoy_R1。在详细信息面板中,将子演员类属性设置为 InteractCube。将缩放值设置为 X=0.5,Y=0.13,Z=0.7,并围绕Z轴旋转 90 度。将此组件移动到右侧顶部和底部支撑之间的位置,与 RightSide 组件的前边缘对齐。 -
使用Ctrl + W复制
SideDecoy_R1,并将新子演员命名为SideDecoy_R2。将其移动到右侧顶部和底部支撑的后方,与 Right 组件的后边缘对齐。 -
现在,对于左侧,使用Ctrl + W复制
SideDecoy_R1,并将新子演员命名为SideDecoy_L1。将其移动到盒子左侧的顶部和底部支撑之间,并与 LeftSide 组件的前边缘对齐。 -
复制
SideDecoy_L1并将其移动到顶部和底部支撑的中心位置。 -
诱饵组件是为了在玩家寻找要按下的按钮以解锁拼图盒时分散他们的注意力。当它们全部就位后,我们现在可以添加我们的交互式按钮组件。在拼图盒的右侧,创建一个新的子演员组件,并将子演员类属性设置为 InteractButton。将这个新组件命名为
Child Actor Button_Step1。最后,将其移动到中间顶部和底部支撑之间的中心位置,并通过围绕Y轴旋转-90 度与 RightSide 组件的表面对齐。玩家需要在揭开并找到这个按钮之前才能打开盒子的前面。 -
使用Ctrl + W复制 Button_Step1,并将新组件命名为 Button_Step2。将按钮的旋转属性设置为 X=0,Y=90,Z=90。将此按钮与 MiddlePlate 组件的中心对齐。此按钮位于覆盖拼图盒前部的板上,只有按下 Button_Step1 后才能将其移除。
-
现在,让我们遮盖那些按钮!复制
SideDecoy_R1并将其命名为ButtonCover_Step1。将这个新对象移动到覆盖 Button_Step1,通过在右侧顶部和底部支撑之间的中心位置对其进行对齐。 -
为了覆盖前面,我们需要一个新的大小的 Child Actor 来适应那个隔间。创建一个新的 Child Actor,将 Child Actor 类属性设置为 InteractCube,并将其命名为
ButtonCover_Step2。将缩放属性的值设置为 X=0.8, Y=0.5, 和 Z=0.1。围绕 X 轴旋转 90 度。最后,将其对齐以覆盖 Button_Step2 并使其与拼图盒的前面齐平。这将在玩家按下第一个按钮后解锁。 -
还有一个盖子。这个盖子将覆盖我们后面的隔间。点击 SideDecoy_L2 并使用 Ctrl + W 创建它的副本。将新副本命名为
CompartmentCover_Step3。将其对齐在后面顶部和底部支撑之间以覆盖隔间。这个盖子将在玩家按下第二个按钮时解锁。 -
如果拼图盒的结尾没有奖品,那还有什么意义?创建另一个 Child Actor 组件并将 Child Actor 类属性设置为 InteractCube。将其命名为 StolenData。这代表玩家追求的奖品。将缩放属性的值设置为 X=0.1, Y=0.1, 和 Z=0.1。将其放置在 CompartmentCover_Step3 覆盖的后部隔间内。
-
最后还有一个细节。为了让 InteractCube 作为我们稍后创建的更详细组件的替代品,我们需要打开 InteractCube 蓝图并关闭 Simulate Physics 选项。这将使得立方体不会默认启用物理,因此拼图立方体不会散开。一旦玩家与立方体交互,物理仍然会被启用。
根据我们的设计,原型拼图盒的代码需要能够完成以下几件事情:
-
在开始时禁用拾取按钮盖的能力
-
将正确的按钮按下事件绑定到我们的两个按钮上
-
在按下按钮 1 后启用前按钮盖
-
在按下按钮 2 后启用隔间盖
禁用玩家无法与之交互的组件可以通过切换它们的单个 PickUpActive 布尔变量来实现。这将保留它们的所有其他功能,但使得玩家在我们希望他们拥有这种能力之前无法移除它们。为了给他们这种能力,我们可以将几个自定义事件绑定到我们的两个按钮上,在按钮被找到并按下后重新启用它们。让我们转到 PuzzleCubeTest 的事件图并开始工作:

PuzzleBoxTest 代码
我们将从 Event BeginPlay 节点构建此代码:
-
在蓝图上右键单击并搜索 Sequence 节点。我们将使用这个节点来按正确顺序触发之前提到的步骤。点击节点的添加引脚按钮,直到我们有四个输出引脚可供使用。
-
首先,从 Then 0 输出拖动,并搜索 Cast To InteractCube 节点。对于 cast 的 Object 输入,我们需要引用我们的子演员 ButtonCover_Step2。然而,如果我们尝试插入一个简单的引用,cast 将无法正确工作。为了确保它引用正确的对象,我们需要添加一个 GetChildActor 节点。创建对 ButtonCover_Step2 的引用,并从其输出拖动一个连接。搜索 GetChildActor 节点,并将其输出连接到 cast 的 Object 输入。
-
在设置好 cast 之后,我们现在可以假装自己是按钮盖,并关闭其被拾取的能力。从 As InteractCube 输出拖动一个连接,并搜索设置 PickUpActive。在设置 PickUpActive 节点上,确保复选框是关闭的。
-
让我们重复这个过程为 CompartmentCover_Step3。通过在序列的 Then 1 输出上拖动创建 cast,并通过 GetChildActor 节点将子演员版本的 CompartmentCover_Step3 的引用连接到 Object 输入。作为子演员,通过在设置节点上关闭复选框将 PickUpActive 设置为 false。
-
接下来,我们需要将按钮按下与几个自定义事件连接起来。从序列节点上的 Then 2 输出拖动一个连接,并搜索节点 Cast To InteractButton。使用与之前相同的方法创建对子演员 Button_Step1 的引用,并将其连接到 cast 的 Object 输入。我们将使用作为按钮一部分创建的事件调度器来将自定义事件连接到按钮的按下。从 As InteractButton 输出拖动一个连接,并搜索绑定事件到 Pressed。
-
创建一个新的自定义事件,并将其命名为 UnlockStep2。从事件节点名称旁边的小方块拖动一个连接,并将其插入到 Bind Event to Pressed 节点的事件输入。
-
从步骤 3 的 As InteractCube 输出拖动一个连接,并创建一个设置 PickUpActive 节点。将其连接到 UnlockStep2 事件的执行输出,并确保设置节点上的复选框已打开。我们现在已重新启用了 ButtonCover_Step2。
-
重复这些步骤来设置重新启用 CompartmentCover_Step3 的能力。创建一个 Cast To InteractButton 节点,并传递对 Button_Step2 子演员版本的引用。作为按钮,创建一个绑定事件到 Pressed 节点,并将其连接到名为 UnlockStep3 的自定义事件。从步骤 4 的 As InteractCube 输出拖动,并创建一个设置 PickUpActive 节点。确保这个新节点上的复选框设置为开启。
就这样,我们得到了一个需要三步解决的实用拼图方块。使用我们的传送体积,创建一个可以在方块周围传送的空间,然后在中心放置一个拼图方块的副本。围绕它移动。测试步骤,看看它们是否工作。一旦一切正常,我有一个挑战给你。当我们开始这个部分时,我提供了拼图盒的计划草图,以及一个原型关卡供玩家测试。构建关卡并在其中测试你的拼图盒。想要更大的挑战吗?设计和构建你自己的拼图方块和测试关卡!
构建第一个工具站
Server 17 游戏的一个关键部分是工具站的概念,这是一个位于拼图盒附近的位置,包含某种工具、设备或提示生成器,玩家可以使用它来帮助解决当前的问题。每个站点的工具会有所不同,从可能以时间惩罚为代价解决拼图步骤的暴力破解工具,到可以突出显示玩家必须与之交互的下一个部件的简单提示工具。对于原型,我们将设计一个简单的旋转工具,以突出我们创建的工具的易用性。让我们看看原型视觉:

原型视觉
构建相对简单。使用内容浏览器,转到入门内容中的Shapes文件夹,并取一个圆柱体。使用缩放工具将其缩小一半。然后取一个楔形并将其放在圆柱体顶部。也将这个缩小一半。我还使用文本渲染器创建了图像中看到的文本。这个工具站最后的部件是一个新的类蓝图:

StationButton 代码
创建一个新的类蓝图并从菜单中选择 Actor:
-
创建新的类蓝图,从“选择父类”菜单中选择 Actor 类,并将其命名为
StationButton。 -
与我们的 InteractCube 类似,这个蓝图只包含一个组件。我使用了圆锥组件;然而,请随意使用您认为可能合适的任何组件。
-
现在,让我们为这个玩意儿编写代码!为了利用我们在这章中创建的一些功能,点击类默认按钮,并在详细信息面板中找到接口部分。点击添加按钮并选择我们的对象交互接口。
-
添加接口后,转到 My Blueprint 面板并打开菜单中的接口部分。双击并打开以下功能:
TraceHitObject、TraceLeaveObject、TraceActivateUp和TraceActivateDown。 -
让我们从
TraceHitObject开始。这涵盖了我们的线迹接触到对象时会发生什么。从组件面板中将圆锥的引用拖到蓝图,并从它拖出一条连接。创建SetVectorParameterValueOnMaterial的副本,并将其连接到函数节点和返回节点。将参数名称字段的值设置为 Color。最后,将参数值输入的Y值设置为 1.0。这将在我们对按钮进行线迹追踪时将其突出显示为绿色。 -
对于
TraceLeaveObject,我们需要创建一个变量来存储我们的按钮是否正在使用。转到 My Blueprint 的变量部分,创建一个新的布尔变量。将其命名为ButtonPressed,并将其默认值设置为 False。除了将按钮颜色变回默认的白色外,我们还想确保如果按钮不再被线迹触摸,它不再被注册为按下。重复前面的步骤创建SetVectorParameterValueOnMaterial节点,并将参数值输入的值设置为 X=1.0,Y=1.0,Z=1.0。这将使按钮颜色变回白色。在SetVectorParameterValueOnMaterial之后,连接一个设置ButtonPressed的节点,并确保复选框设置为关闭。将其连接到SetVectorParameterValueOnMaterial的输出和ReturnNode。 -
现在,让我们继续到
TraceActivateDown。当玩家激活按钮时,我们只想将ButtonPressed设置为 True。我们将在关卡蓝图中处理旋转。将设置ButtonPressed的副本连接到函数。确保节点上的复选框已打开。在设置节点之后,使用圆锥作为目标创建另一个SetVectorParameterValueOnMaterial的副本。按照本节前面的步骤设置它,并将参数值输入的X值设置为 1.0。这将使按钮在使用时变为红色。 -
最后,我们有
TraceActivateUp。当玩家释放扳机时,我们需要将颜色变回绿色(这是被线迹击中的颜色),并将ButtonPressed重置为 false。将SetVectorParameterValueOnMaterial的副本连接到函数节点,按照我们之前步骤设置好,并将参数值输入的Y值设置为 1.0。将set ButtonPressed的副本连接到SetVectorParameterValueOnMaterial节点的输出,并将其输出连接到返回节点,确保复选框设置为关闭。 -
在我们的关卡中,将
StationButton的副本添加到我们创建的工具站中,并将其命名为 StationButton_Rotate。
要使拼图盒在按下我们的站按钮时旋转,我们需要在关卡蓝图中构建一些功能,因为这种能力仅限于原型级别。使用蓝图按钮打开 Level Blueprint。

关卡蓝图代码
我们将使用一个自定义事件来开始构建这个序列:
-
在蓝图上右键单击并创建一个新的自定义事件。我们将将其命名为 ButtonCheck。
-
接下来,右键单击并创建一个新的 Branch 节点。这将读取按钮是否被按下。创建对 StationButton_Rotate 的引用并从它拖动一个连接。放下它并搜索 Get ButtonPressed。将此节点的输出连接到 Branch 上的条件节点。
-
现在,创建一个 Event Tick 节点。我们希望游戏每帧检查按钮是否被按下。从事件拖动一个连接并搜索我们的 ButtonCheck 函数以创建它的副本。
-
就像我们对之前的按钮所做的那样,我们将使用一个 Gate 来控制代码是否执行,基于 ButtonPressed 是 True 还是 False。创建一个 Gate 节点并将 Enter 输入连接到 ButtonCheck 函数的输出。将 Branch 的 True 输出连接到 Open 输入,将 Branch 的 False 输出连接到 Close 输入。
-
是时候创建旋转功能了。在“我的蓝图”的变量部分创建一个新的
Float变量,命名为 RotSpeed。这将控制谜题立方体的旋转速度。 -
接下来,右键单击并搜索 Make Rotator 节点。将 RotSpeed 的一个副本连接到 Z(偏航)输入。
-
我们需要的最后一个主要节点是 AddActorLocalRotation。将其连接到 Gate 节点的 Exit 输出,并使用关卡中谜题立方体的引用作为目标。
-
Delta Rotation 输入需要一点数学计算。从 Make Rotator 的输出拖动一个连接并搜索 ScaleRotator 节点。对于浮点输入,使用 Event Tick 的 Delta Seconds 输出。这将创建一个随时间平滑的旋转。
-
最后,将 ScaleRotator 节点的输出插入到 AddActorLocalRotation 的 Delta Rotation 输入上。
现在,我们有了旋转功能!显然,在Server 17中,关于黑客工具的想法还有很多可以做的,但这对我们的原型阶段来说已经足够了。看起来这个功能终于要完成了。尽管如此,我们仍然还没有创建最后一个游戏元素:我们的关卡计时器。
构建计时器
在Server 17的世界里,玩家破解公司服务器以寻找有价值的公司机密。然而,那些公司不会坐视不管!强大的系统管理员、公司黑客和 AI 反制措施动员起来阻止玩家,给他们有限的时间来破解每个服务器。为了在游戏中表示这一点,我们将在游戏中实现一个关卡计时器,当计时器达到零时,玩家将失败。由于这只是一个原型,当计时器完成时,游戏将退出。
由于这是一个独立于任何级别的游戏元素,我们将在自定义游戏状态中构建这个功能。前往我们的Sever17\Blueprints服务器,双击S17GameState以打开它:

在游戏状态中设置游戏计时器代码
让我们开始吧:
-
此功能利用了蓝图系统中内置的计时器系统。使用 SetTimerByEvent 节点,当计时器(以秒为单位)完成时,我们可以调用自定义事件。首先,创建 Event BeginPlay 节点的副本,并从输出拖动一个执行连接。搜索 SetTimerByEvent 节点,并从菜单中选择它。
-
接下来,创建一个名为
EndGame的自定义事件。通过将自定义事件旁边的方块引脚拖动并连接到 SetTimerByEvent 的输入引脚,将此自定义事件连接到 SetTimerByEvent 的事件输入。 -
将 EndGame 事件拖动开,搜索 ExecuteConsoleCommand 节点。在命令字段中,输入命令
quit。 -
要完成序列,我们需要为 SetTimerByEvent 上的时间设置一个值。为了保持系统的灵活性,我们将创建一个游戏时间的变量,可以从游戏状态外部轻松调整,以考虑诸如关卡难度或故事元素等因素。创建一个新的
Float变量,并将其命名为 GameTime。编译蓝图,并将默认值设置为 300。获取它的副本,并将其连接到 SetTimerByEvent 的时间输入。
太棒了!我们勇敢的玩家现在有五分钟的时间来完成我们的拼图魔方,否则将被踢出游戏。请随意调整 GameTime 的默认值,使其适合您的玩家或根据您的挑战进行调整。
摘要
哇!这是一段多么精彩的旅程。我们以一些关于游戏玩法的有意思的讨论开始这一章,并以从头开始创建整个游戏原型结束!在这一章中,我们探讨了成功实现 VR 独特功能的多种不同类型的游戏玩法。利用这些知识,我们设计了游戏元素,以充分利用玩家动作与游戏之间几乎一对一的转换,以及玩家能够从真正的第一人称视角与世界互动的能力。在本章的剩余部分,我们从手部交互到 VR 按钮,甚至为玩家在时间限制内解决谜题的谜盒,从头开始构建了所有系统。
在下一章中,我们将通过讨论我们迄今为止创建的用户体验以及如何通过使用用户界面来改进该体验来扩展我们的游戏玩法。我们将讨论在游戏世界中使用 2D 和 3D 元素的使用和可行性,以及什么最适合与 VR 一起使用。利用这些知识,我们将设计和构建我们自己的元素,用于在 Server 17 中使用。
第四章:VR 内的用户界面和用户体验
考虑我们的用户,并从他们的角度构建我们的游戏系统,这只是确保我们的游戏成为最广泛玩家群体享受体验的整体过程中的一小部分。以人为中心的设计(HCD)是更大设计过程,即用户体验(UX)设计的第一步。当我们思考整个用户体验时,我们实际上不仅关注使用我们游戏的人,还关注我们如何改进他们使用游戏的方式。通过 UX 的视角思考,我们希望构建一个满足所有玩家需求、易于使用且玩家不愿放下的游戏。
在本章中,我们将涵盖以下主题:
-
什么是 UX 设计?
-
UX 的七个方面
-
VR 中的用户界面
-
为Server 17设计 UI
什么是 UX 设计?
尽管近年来其使用变得更加普遍,但 UX 设计这个术语并不是一个新词。它是由 Don Norman 在苹果公司工作时提出的,是一个涵盖人们与机器互动的许多方式的通用术语。它还指从用户的角度看问题的想法。在游戏行业中,UX 通常与用户界面设计紧密相连,因为玩家通常只通过界面与软件互动。然而,虚拟现实的发展及其为玩家提供大量选择如何与游戏世界互动的能力,导致公司重新思考他们对这个领域的看法。当玩家可以无需传统 UI 与游戏世界互动时,你如何看待游戏中的用户体验?这正是 UX 设计师真正发挥其作用的地方。
UX 设计师是一个负责产品外观、感觉和可用性的设计师。然而,即使这个定义也有不足之处,因为直到最近,游戏中的 UX 设计基本上意味着专注于用户界面。在虚拟现实开发中,UX 设计涵盖了更多主题,如故事体验、控制方案、玩家安全、可访问性等。UX 设计师应该关注游戏的七个不同方面。游戏需要具备以下特点:
-
有用性:游戏是否为用户提供他们所寻找的体验?如果一个游戏或产品对某人没有用处,就没有理由将其推向市场。在 UX 设计中,我们使用 HCD 的发现和同理心阶段来发现我们正在设计的内容对我们用户的有用性。
-
可用性:用户在使用产品时能否实现他们的目标?如果一个游戏难以玩或理解,它就不会成功。有许多因素会影响视频游戏的可用性,例如控制、角色动画、难度等。
-
可访问性:可访问性一直是用户体验设计的重要组成部分。不同能力水平的用户都应能够玩你的游戏,你提供的服务应让任何人都能访问。当我们坐下来为可访问性设计时,我们经常发现我们为每个人改善了体验。一些组织已经发布了开发者资源,以帮助将更多可访问的游戏推向市场,例如
accessible.games/和gameaccessibilityguidelines.com/。 -
吸引力:吸引力指的是游戏或系列产品的市场营销、品牌和美学。当我们为吸引力设计时,我们希望创造一个玩家想要的图像或情感依恋。目标是创造一个玩家会吹嘘的游戏体验,并在他们的朋友中产生对该游戏的渴望。
-
可发现性:可发现性指的是游戏找到和购买有多容易,但也指的是在游戏中找到体验有多容易。想象一下,一个玩家购买了最新的竞技场射击游戏。他们很兴奋地想和朋友们一起玩,所以他们加载了游戏,但似乎找不到播放按钮或自定义控制选项。他们搜索了无数菜单,允许他们创建角色和调整音效,但就是无法达到可以和朋友们一起玩的地步。最后,他们只是放弃了,扔掉了控制器。作为开发者,我们最不希望的就是浪费玩家的时间并让他们感到沮丧。这就是为什么可发现性如此重要的原因。
-
宝贵:玩家通常会根据在线能力、游戏时长、宣布的功能和难度等因素来寻找价值。玩家在游戏中看到的价值越多,他们购买的可能性就越大。因此,我们设计师的主要目标是让玩家看到我们游戏的价值,并购买它们,这样我们才能继续设计和创造更多体验。
-
可信度:这是玩家信任开发者能够交付他们承诺的体验和功能的能力。可信度是游戏行业的重要商品,近年来我们看到了出版商/开发者将其视为理所当然。我们的游戏只有一次机会给玩家留下良好的第一印象,而大多数玩家永远不会给产品第二次机会。这对依赖声誉和口碑来销售游戏的独立小型开发者来说尤为重要。
这些七个方面的游戏设计共同构成了思考用户体验的基础。当我们考虑到玩家的需求、目标和整体体验来设计游戏时,我们创造出的产品在市场上既有趣、又难忘、又成功。带着这个想法,让我们继续创建Server 17的下一部分:那些对控制体验和向玩家传达必要信息至关重要的用户界面元素。
VR 中的用户界面
正如我们所见,VR 中的交互超越了我们在传统应用程序和视频游戏中的习惯。玩家在游戏世界中几乎完全沉浸其中,可以伸手触摸他们想要与之互动的许多物体。这种直接与环境互动的能力为界面设计开辟了许多途径,同时也提出了几个挑战。其中一个挑战是,显示在屏幕边缘的 HUD 元素在 VR 中会扭曲并偏离位置。这些元素也可能根据故事和设置破坏 VR 体验的沉浸感。为了解决界面问题,大多数 VR 开发者已经远离了这些界面,转而使用嵌入在游戏世界中的信息元素。这些可以归纳为以下三个类别:
-
诊断性
-
空间
-
Meta
让我们看看诊断性界面,如下面的截图所示:

Server 17 关卡时钟
诊断性界面元素存在于游戏世界中,并直接从环境中向玩家提供信息。玩家携带的地图,例如在Minecraft或Firewatch中,Isaac Clarke 在Dead Space套装中嵌入的能量条,以及玩家在Metro游戏中提到的手表,都是环境内部通过上下文线索向玩家提供信息的例子。在虚拟现实中,诊断性界面更受欢迎,因为它们促进了沉浸感,并且不会对玩家产生任何不良影响。
接下来是空间界面,如下面的截图所示:

Tribe XR 中的空间界面元素
有时向玩家提供信息最好的方式就是让它直接浮现在他们面前。空间界面元素在游戏世界中以指定的世界坐标浮动,等待玩家阅读或与之互动。我们在许多当前的 VR 游戏中看到这些界面——例如,Beat Saber中的歌曲菜单,Tribe XR中的曲目选择界面,以及Robo Recall中的弹药计数器。空间界面元素在虚拟现实中表现良好,因为它们与虚拟世界融合在一起,并且经常模仿玩家习惯使用的传统 UI 元素。
最后,我们有元界面:

Robo Recall中的界面元素
元界面元素被定义为在玩家视野上显示为叠加元素的两维元素,但它们不像标准界面那样持久。这些元素通常用于传达临时信息,例如伤害,而不在屏幕上留下更持久的存在。这种类型界面的最常见用途是在游戏如Gorn和Robo Recall中显示血迹或红色调的视野来显示伤害。
设计 Server 17 的 UI 元素
在了解用户体验和界面之后,让我们将这些知识应用到为Server 17创建一些 UI 元素上。对于我们的首次 VR 用户,我们的界面元素可能应该是对白的,以便于使用并保持我们在科幻环境中的沉浸感。我们需要显示关卡计时器,以便玩家知道他们还剩下多少时间。我们可能还需要重新思考工具站的使用方式,以简化站界面。一如既往,记得在整个过程中测试你的用户群体,并记住你正在设计游戏以最大化他们的乐趣!
显示关卡计时器
让我们从设计关卡计时器开始,如下面的图所示:

使用基本的 UI 线框软件创建的关卡计时器线框
为了使计时器尽可能简单,我们需要创建一个可以从关卡中的任何地方轻松看到且不会让玩家脱离体验的东西。首先,我们的计时器应该是数字的,以符合我们游戏的科幻背景。其次,它应该位于玩家觉得自然且易于在关卡中找到的位置。最后,它应该能够显示分钟和秒,而不仅仅是秒,以符合玩家对计时器的期望。这就是我认为的好解决方案。
创建一个对白计时器元素将保留玩家的科幻体验,同时也能创建出易于阅读的东西。我们可以将我们的小部件放置在谜题上方,并允许它旋转以始终面向玩家。这将满足我们所有的标准,并创造出既有趣又符合我们主题的东西。
我们的时间解决方案将包括两个不同的部分。第一部分是一个用于计算关卡时间的脚本化Unreal Motion Graphics(UMG)小部件。第二部分将是一个类蓝图,用于在 3D 关卡中显示我们的 2D 小部件。
在准备这本书时,有几个人要求我讨论 VR 中 2D 和 3D 界面资产之间的区别。2D 界面元素只要作为空间或对白组件存在,就可以在虚拟现实中工作。3D 组件,如我们在上一章中开发的按钮和工具站,同样有效,特别是因为它们本质上是对白的。始终最重要的考虑因素应该是你的玩家和他们的期望。研究和测试将始终帮助你设计出最佳解决方案。
我们将首先创建 UMG 小部件,如下面的截图所示:

UMG 小部件画布
要创建 UMG 小部件,请按照以下步骤操作:
-
在内容浏览器中右键单击,将鼠标悬停在“用户界面”选项上,并选择“小部件蓝图”。将新小部件命名为
LevelTimer。双击新蓝图以打开它。 -
我们的设计由两个文本组件组成,一个是标签,另一个是每帧更新以显示时间的组件。通过使用调色板面板找到文本组件并将其拖动到画布面板上创建第一个组件。
-
在详细信息面板中,将名称更改为
TraceLabel。 -
在详细信息面板的“槽”部分,点击“锚点”下拉菜单并选择居中选项。这将使它在我们的类蓝图内保持居中。
-
将“位置 X”值更改为-150,将“位置 Y”值更改为-125。
-
将“大小 X”值更改为 300,将“大小 Y”值更改为 100。
-
在详细信息面板的“外观”部分,将文本颜色更改为鲜艳的颜色。我选择了绿色,但您可以根据自己的喜好进行更改。
-
在“外观”部分的“字体”部分,将大小值更改为 48。
-
最后,将“对齐”选项更改为居中。
-
在设置好字体选项后,我们现在可以更新文本。在详细信息面板的“内容”部分,将文本值更改为
TRACE ACTIVE。 -
创建第二个文本组件并将其命名为
TimerDisplay。 -
就像之前一样,点击“锚点”下拉菜单并将其更改为居中选项。
-
将“位置 X”值更改为-150,将“位置 Y”值更改为-50。
-
将“大小 X”值更改为 300,将“大小 Y”值更改为 100。
-
在详细信息面板的“外观”部分,将颜色更改为与步骤 7 中使用的相同值。
-
在“外观”部分的“字体”部分,将大小值更改为 48。
-
将“对齐”更改为居中。
-
在面板的内容部分,将文本值更改为“MM:SS”。
-
现在我们需要编程文本值以更新并显示我们关卡中剩余的时间。为此,我们将创建一段称为绑定的编程。单击文本值右侧的“绑定”下拉列表,并选择创建绑定,如下面的截图所示:

显示级别时间绑定
-
让我们从一些基础工作开始。在“我的蓝图”面板的“函数”部分,右键单击我们绑定的名称,并选择重命名选项。将名称更改为“Display Level Time”。
-
此绑定需要能够接受以秒为单位测量的级别时间,将其转换为分钟和秒,然后将其作为字符串显示。第一步是创建一个新的浮点变量来存储我们的级别时间。在“我的蓝图”面板的“变量”部分创建一个新的浮点变量,并将其命名为“Level Time”。
-
将我们新变量的副本拖动到蓝图上,并从菜单中选择获取。从这里,我们将稍微深入了解 Unreal 的时间跨度系统。我们将使用一个名为 Time Seconds to String 的节点将存储在 Level Time 中的秒转换为以m:秒:毫秒格式的字符串。从那里,我们可以将新的字符串转换为文本并将其输入到我们的文本组件中。右键单击并搜索 Time Seconds to String 节点,并将 Level Time 连接到 In Seconds 输入。
-
从那里,将 Time Seconds to String 的输出连接到 Return 输入,Unreal 会为我们创建翻译节点,如下面的截图所示:

关卡计时器小部件的事件图
-
现在来完成计时器的设置。我们需要将
S17GameState转换为以检索 GameTime 的值,并将其作为 Level Time 的值。这允许我们在一个地方更改值,并自动影响计时器。切换到我们的小部件的事件图,从 Event Construct 节点拖出一条执行线。搜索 Cast To S17GameState。 -
让我们设置 cast。从对象输入拖动一个连接,并搜索 Get Game State 节点。定义好后,我们现在可以假装自己是游戏状态并检索 GameTime 的值。从
S17GameState输出拖动一个连接并搜索 Get Game Time 节点。 -
最后,我们需要将 GameTime 的值存储在我们的 Level Time 节点中。将 Level Time 变量的副本拖动到蓝图上,并选择设置选项,将其连接到我们的 cast 节点的执行输出,并将获取 GameTime 值的输出连接到 Level Time 的浮点输入。
-
为了最终使计时器工作,我们将在每个 tick 中快速计算 Level Time 的新时间。结合我们的 Bind 函数,这将显示当前剩余关卡时间在我们的小部件中。获取 Level Time 变量的副本并将其拖动到我们的蓝图上。从菜单中选择获取,并将其放置在 Event Tick 节点附近。
-
将 Level Time 的另一个副本拖动到蓝图上,这次从菜单中选择设置。将其连接到 Event Tick 的执行输出。
-
数学时间!我们将使用 Event Tick 的 In Delta Seconds 输出通过从 Level Time 的当前值中减去它来计算关卡剩余时间。从 get Level Time 节点拖出一条线并搜索
Float-Float。确保这个新节点中的第一个值是 Level Time,并将 In Delta Second 输出连接到第二个输入。将此节点的输出连接到 set Level Time 的浮点输入。
在我们的计时器功能全部设置完成后,剩下的就是将其显示在关卡中。为此,我们将使用一个包含 Widget 组件的类蓝图。该组件允许我们在 3D 空间中显示 2D 界面元素,通过将 widget 投影到一个平面上。然后我们可以将这个平面放置在谜题上方,并编程使其始终旋转以面对玩家。这样,我们可以保证玩家始终可以看到我们的倒计时时钟。
首先创建一个新的从 Actor 扩展的 Class Blueprint 并命名为 3dLevelTimer:

3dLevelTimer 函数
通过以下步骤添加 Widget 组件:
-
在我们的蓝图视图中,点击添加组件按钮并搜索 Widget 组件。将其添加到蓝图并命名为 Display Widget。
-
在组件面板中点击新的显示 Widget。在详细信息面板中,在菜单的用户界面部分找到 Widget 类选项。点击下拉菜单并选择我们之前创建的关卡计时器 widget。
-
接下来,我们将创建一个新的函数来处理 widget 的旋转。在 My Blueprints 面板的 Functions 部分中,点击 + 函数按钮。将新函数命名为 Update Rotation。
-
Update Rotation 函数的目的是找到玩家摄像机的位置,并将 widget 旋转以面对它,这样我们的玩家就可以始终看到剩余时间。通过从起始节点拖动一条线并搜索 IsValid 节点来启动该函数。我们只想让 widget 在玩家处于关卡时才关注旋转。
-
从 IsValid 拖动一条线从 IsValid 输出,并搜索引用 Display Widget 的 SetWorldRotation 节点。右键单击新旋转输入并拆分结构引脚。我们稍后会用到这个。
-
返回到 IsValid 节点,我们仍然需要找到输入对象。从输入拖动一条线,并搜索 Get Player Camera Manager 节点。此节点将为 IsValid 提供输入,以及我们将在下一步创建的节点。
-
从 Get Player Camera Manager 节点拖动另一个连接,搜索 Get Camera Location。这将是我们寻找注视旋转的起始位置。
-
从 Get Camera Location 的返回值拖动一条线并创建一个 Find Look At Rotation 节点。为了获取新节点的目标,我们需要从组件面板中将 Display Widget 的引用拖入蓝图。从那里,创建一个 Get World Location 节点并将其输出连接到 Find Look At Rotation 的目标输入。
-
几乎完成了!在
Find Look At Rotation节点的返回值引脚上右键单击,并拆分结构。我们只需要处理偏航值。如果我们以当前的形式处理偏航,我们的小部件将会反向旋转。我们可以通过一点数学来纠正这个问题。从返回值 Z 输出拖动一个连接并创建一个Float+Float节点。将第二个值设置为180。将输出连接到Set World Rotation节点的New Rotation Z输入。 -
是时候将新功能添加到事件图了。将
UpdateRotation函数的副本拖动到蓝图上,并将其连接到事件节点的副本。我们现在可以测试了!
编程完成!将3dLevelTimer的副本拖动到你的测试关卡中,并将其放置在谜题上方。现在亲自测试这个新功能,以及你的潜在用户。注意他们的反馈,并根据需要调整计时器的大小、颜色和位置。
重新设计工具体验
在将用户体验设计原则应用于我们的关卡计时器后,让我们回到上一章中创建的工具站。在测试中,我发现玩家每次想要旋转谜题时都必须前往工具站,这在计时体验中尤其不直观。当我们收到这样的反馈时,只有一个解决方案:重新设计!让我们看看以下图表:

新工具菜单线框
在与我的玩家进行几轮测试和访谈后,很明显,不得不回到工具站去旋转谜题(以及使用其他未来工具)增加了一个不必要的步骤。工具站本身是一个不错的主意,但它并没有为游戏体验带来任何额外的价值。事实上,它使体验变得不那么易用。新的目标将是重新设计这个站为一个与玩家控制器相连的工具菜单。这个菜单可以在任何时间或地点打开,并以与工具站相同的方式使用,无需移动。
它需要分为两部分来构建,类似于关卡计时器。第一部分将是一个包含所有功能的 2D 小部件。第二部分将是一个 3D 小部件,我们可以在按下控制器上的按钮时显示它。我们可以在以下屏幕截图中看到这个设置:

完成的ToolsWidget界面
首先创建一个新的蓝图小部件,并将其命名为ToolsWidget:
-
在内容浏览器中右键单击并创建一个名为
ToolsWidget的新蓝图小部件。双击蓝图以打开 UMG 编辑器。 -
这次当我们创建小部件时,我们将采取更组织化的方法。首先,在调色板中搜索图像组件。将其拖到画布面板上,并在详细信息面板中将锚点选项更改为居中。将其名称更改为
Background。这将给我们的菜单一个很好的背景颜色。 -
将位置 X 和位置 Y 的值更改为
-62.5。 -
将大小 X 和大小 Y 的值更改为
125。 -
是时候选择颜色了。使用颜色和透明度选项选择您的新颜色。将 Alpha 值设置为
0.5会给它一种不错的科幻科技感。 -
现在我们将创建一个垂直框组件并将其添加到画布面板中。别忘了将其锚点选项更改为居中。
-
与背景一样,将位置 X 和位置 Y 的值更改为
-62.5,以及大小 X 和大小 Y 的值更改为125。 -
垂直框将我们放置其中的每个组件在垂直空间中均匀组织。我们将使用这一点来确保我们的按钮在菜单中完美分布。首先,将文本组件拖到垂直框中。将水平对齐设置为居中,字体大小设置为
14。将文本设置为工具菜单。 -
现在我们将添加一些按钮。其中两个将用于左右旋转拼图。我们还将构建两个按钮,将来可以用来编程额外的工具。转到调色板并搜索按钮组件。将两个副本拖到垂直框中。在详细信息面板中,将两个按钮的填充值更改为
5。 -
将第一个按钮命名为
Tools1_BTN,第二个按钮命名为Tools2_BTN。 -
每个按钮现在都需要一个文本标签。使用调色板找到文本组件,并将其拖到每个按钮上。对于每个按钮,将字体大小更改为
10。将文本更改为工具 1 和工具 2。 -
现在,我们将使用水平框来组织我们的旋转按钮。使用调色板面板中的搜索框找到水平框,并将其拖到我们的垂直框中。在详细信息面板中,将大小选项设置为填充。
-
将两个按钮组件拖到水平框中。将第一个按钮命名为
Left_BTN,第二个命名为Right_BTN。设置填充为5,大小选项为填充。 -
为每个按钮添加标签。将文本组件拖到左侧按钮上。将字体大小设置为
10,并将文本值更改为<---。 -
对右边的按钮也进行相同的操作,但将文本值设置为
--->。
我们现在有一个基本界面,准备好进行编程。计划以与之前创建的工具站类似的方式编程左右拼图旋转按钮。我们创建的额外工具按钮将暂时不添加功能。现在是时候查看以下截图所示的代码了:

ToolsWidget 编程蓝图
要能够旋转我们的拼图,我们首先需要通过以下步骤找到它的引用:
-
我们需要在界面创建时就能找到我们关卡中的谜题。为此,我们将使用事件构造节点。转到图,如果还没有,使用调色板面板创建一个。
-
要找到谜题,我们将使用一个名为 Get All Actors of Class 的节点。这个节点能够找到你关卡中特定类的每个副本,并将其放入一个临时数组中。从事件构造拖一条执行线,并将其放下。搜索“Get All Actors of Class”,并将 Actor Class 选项设置为 PuzzleCubeTest。
-
Out Actors 输出给我们一个包含 PuzzleCubeTest 任何实例的数组。在这种情况下,只有一个。要访问它,我们将使用 Get (a copy)节点。从 Out Actors 输出拖一条线,并使用菜单创建一个。该节点将访问索引 0,数组中的第一个槽位,应该包含我们对 PuzzleCubeTest 的唯一引用。
-
从获取节点的输出拖一条线,并从菜单中选择提升到变量。这将把我们的谜题引用放在一个我们可以使用的变量中。将新变量命名为 ActivePuzzle。将事件的执行输出连接到为我们新变量创建的设置节点。
-
在放置好谜题引用后,现在是我们创建旋转的时候了。就像我们创建 3D 按钮时一样,我们将使用节点的 In Delta Time 输出来控制我们的旋转。如果蓝图里还没有,创建一个事件计时节点。
-
由于我们必须检查左右按钮的按键,我们需要使用一个序列节点。从事件计时节点的执行输出拖一条线,并创建一个序列节点。
-
Then 0 分支将处理左键,我们将使用创建原始旋转代码时使用的相同技术。从 Then 0 输出拖一条线,并创建一个门节点。
-
现在我们需要一个按下和释放的事件来控制门的开启和关闭。转到我的蓝图面板的变量部分,点击 Left_BTN。滚动到详细信息面板的底部,点击 On Pressed 和 On Released 选项旁边的+按钮。将 On Pressed 事件连接到门的打开输入。最后,将 On Released 事件连接到关闭输入。
-
我们需要使用 AddActorLocalRotation 节点来编程实际的旋转。从门的退出输出拖一条线,并创建该节点。对于目标,转到变量部分,获取 Active Puzzle 变量。将其拖入蓝图,并从菜单中选择获取。将其插入到 AddActorLocalRotation 的目标输入上。
-
要获取我们的 Delta 旋转,我们将时间变化乘以旋转速度。创建一个新的 Float 变量,并将其命名为旋转速度。将其拖入蓝图,并选择获取选项。从其输出拖一条线,创建一个 Make Rotator 节点。最后,将连接从 X 输入移动到 Z 输入。
-
从 Make Rotator 的输出拖动并连接一个 Scale Rotator 节点。此节点接受一个旋转器并将其乘以一个浮点值。将浮点输入连接到 Event Tick 的 In Delta Seconds 输出。
-
最后,将 Scale Rotator 的输出连接到 AddActorLocalRotation 的 Delta Rotation 输入,如图所示:

ToolsWidget 拼图旋转代码
- 对于 Then 1 分支,重复从步骤 7 到步骤 12 的步骤来编程右键,但这次在 Rotation Speed 和 Make Rotator 节点之间添加一个
Float * Float节点。将第二个值设置为-1 以改变旋转方向。
工具 Widget 构建和编程完成后,是时候构建 3D 小部件以在级别内显示它了。我们将使用与在级别中显示计时器小部件相同的流程,但这次,我们将 3D 小部件附加到动作控制器上,以提供手腕式显示的感觉,如图所示:

工具 Widget 在实际操作中,安装在玩家的手腕上
在内容浏览器中右键单击并创建一个新的基于 Actor 的 Class Blueprint。将其命名为3dToolsWidget,双击打开它,然后执行以下步骤:
-
在组件面板中,使用添加组件按钮向视图中添加一个 Widget 组件。
-
在“详细信息”面板中,将其重命名为
ToolsWidget。 -
在“用户界面”部分的“详细信息”中,单击 Widget Class 下拉菜单,并选择我们的
ToolsWidget界面。 -
将绘制大小 X 和 Y 值设置为
125。 -
是时候将界面添加到我们的
Server17PlayerPawn中了。在内容浏览器中找到玩家角色,双击它以打开它。 -
让我们将界面添加到动作控制器中。单击添加组件按钮并搜索子演员组件。将其命名为
ChildActor_ToolsWidget并将其设置为层次结构中的MotionController_L的子项。 -
在“详细信息”面板中,将“子演员类”选项设置为
3dToolsWidget。 -
在视图中,使用移动和旋转工具将小部件定位在左侧手腕处,类似于手表。如果您喜欢自己输入值,将位置值设置为X=5,Y=-5,和Z=0。对于旋转值,使用X=180,Y=0,和Z=-90。
-
由于我们的界面显示在玩家手腕上相当大,将缩放值更改为0.2。
默认情况下,动作控制器没有与 2D 界面元素交互的方式,因为它们最初是为鼠标交互设计的。为了添加此功能,我们需要使用一个 Widget Interaction 组件。这个组件是由 Epic Games 设计的,作为 VR 控制器和传统界面之间的桥梁,并且只需要一点设置即可使用。首先,将一个 Widget Interaction 组件添加到Server17PlayerPawn中,如图所示:

Widget Interaction 组件
我们将向每个运动控制器添加以下组件之一:
-
在组件面板中,使用添加组件按钮添加两个 Widget Interaction 组件的副本。将一个作为 MotionController_L 的子组件,命名为 Widget Interaction L。将另一个作为 MotionController_R 的子组件,命名为 Widget Interaction R。这将赋予双手功能。
-
Widget Interaction 组件允许我们模拟鼠标指针。我们还可以通过一点代码模拟鼠标点击。前往我们的玩家 pawn 的事件图,并定位我们的触发代码。我们可以在现有序列的末尾添加功能。从左触发序列的末尾拖动一条执行线,放下它,并搜索 Press Pointer Key (Widget Interaction L) 节点。使用键下拉列表选择左鼠标按钮。
-
从我们刚刚创建的 Widget Interaction L 的输出拖动一条线,并搜索 Release Pointer 键节点。将键下拉菜单设置为左鼠标按钮,并将执行线连接回 End Drag 节点。
-
为我们的右触发器重复此过程,以赋予相同的函数功能。
到目前为止,我们可以测试我们的界面。确保旋转功能正常工作,并且左手腕的位置正确。为了完成此菜单,我们还需要进行一些编程。让我们使其可以通过运动控制器上的菜单按钮切换开和关。这样,玩家可以在不使用时隐藏它。为此,我们需要使用 MotionController (L) 肩部事件,如下面的截图所示:

工具小部件切换代码
首先通过以下步骤创建事件节点:
-
在 Event Graph 的
Server17PlayerPawn上右键单击,并搜索 MotionController (L) 肩部事件。创建它并将其放置在我们按钮代码的附近。 -
从按下输出拖动一条执行线并将其放下。搜索 Toggle Visibility (ChildActor_ToolsWidget) 并将其放置在蓝图上。
-
返回我们的玩家 pawn 视口,并点击 ChildActor_ToolsWidget。在详细信息面板中,关闭可见性选项。
完成了!我们现在已经构建了两个不同的界面元素,它们更多地使用了 2D 菜单组件,并且我们学习了如何在 3D 空间中显示它们。我们编程了它们以与世界交互,并添加了模拟鼠标交互的能力,以便我们可以使用它们。只需想想你现在可以为你的虚拟现实体验创建的所有伟大的交互式菜单,无论是使用 3D 对象还是 2D 界面!
摘要
在本章中,我们学习了用户体验以及 HCD 只是这个更大领域中的一小部分。我们探讨了用户体验设计的领域,以及 VR 如何使其超越了传统的界面交互。我们还学习了用户体验的七个重要方面。从那里,我们审视了不同类型的用户界面,并了解了在虚拟现实中什么最有效。最后,我们将我们对用户体验和 VR 兼容界面的新知识应用于设计和创建我们级别的界面小部件。
在下一章中,我们将讨论如何为虚拟现实游戏创建令人眼前一亮的视觉元素。就像游戏玩法元素和用户界面一样,VR 游戏的美术创作与传统视频游戏美术创作有不同的要求。优化是保持性能高和玩家舒适的关键。我们将探讨一些创建我们的照明和视觉效果的技术,以及充分利用我们的静态网格和材料所需的内容。
第五章:在 UE4 中为 VR 创建优化的游戏艺术
服务器 17已经取得了长足的进步。我们已经将游戏从简单的谜题想法发展成了一个可工作的游戏原型。在这个阶段,我们有一个样本关卡,自定义游戏玩法和自定义界面。我们已经构建了可以扩展以添加更多游戏机制的交互系统。然而,游戏看起来并不怎么样,对吧?我们的照明很基础。我们仍在使用许多事物的默认纹理。这没有问题,因为这是一个游戏原型,但如果我们想将这个游戏展示给家人和朋友之外的人,它将需要一些美化。
为 VR 创建 3D 游戏艺术与为其他游戏创建游戏艺术不同。尽管我们使用了许多相同的程序,但 VR 要求我们在艺术上保持保守。VR 的性能要求我们需要保持多边形数量低,使用技巧来消除高级照明技术的使用,并重新思考我们的艺术方法。尽可能伪造一切,并将你的游戏艺术视为 90 年代末的作品。
在本章中,我们将涵盖以下主题:
-
性能是关键
-
VR 中的艺术限制
-
性能提升技巧
-
测量游戏中的性能
性能是关键
反复提及 VR 中的性能主题。我们第一次讨论它是在第一章,“在虚幻引擎 4 中介绍 VR 技术”时,我们讨论了 VR 不适感。我们再次在第三章,“探索虚拟现实中的扣人心弦的游戏玩法”中提及了它,当时我们编写了使我们的游戏运行起来的交互系统。那么,为什么我们一直谈论这个话题呢?性能是享受 VR 体验的核心。高端视觉效果有助于我们的玩家沉浸在我们的数字环境中,而保持稳定的帧率是最大化玩家舒适度和减少 VR 不适感的最佳方式。那么,我们如何平衡这两者呢?
在本章中,我们将讨论与 3D 游戏艺术、照明和视觉效果相关的性能。在 VR 中,管理你的资源和细节级别变成了一场平衡游戏。我们如何提供吸引玩家的视觉效果,同时保持 90 FPS 或以上的帧率以保持他们的舒适度?简单的答案是理解和周密的计划。有了正确的规划和一些技巧,我们可以保持高 FPS,并交付玩家所要求的体验。
首先,让我们讨论一下 VR 如何渲染我们在屏幕上放置的元素的重要要点。我们创建并添加到环境中的每个对象都需要经过绘制到屏幕的过程。对象越详细(以多边形计数或三角形计数来衡量),所需的处理就越多。处理需要时间。我们还要注意绘制调用。每次我们更新屏幕时,对象都需要再次绘制,以及它所使用的每种材料。我们可以这样描述:
绘制调用 = 屏幕上网格的数量 * 每个网格的材料数量
我们还必须记住,VR 头戴式设备渲染到两个屏幕(每个眼睛一个)。因此,绘制调用的数量实际上是翻倍的!这就是规划可以发挥作用的地方。以下是一些有用的做法和禁忌:
-
做法:
-
规划我们的环境,以便我们可以最小化屏幕上的网格数量。确保每个网格都有目的,并且通过存在为关卡增添内容。
-
最小化每个对象上的材料数量。可以创建一个大的材料,可以与几个不同的网格一起使用。然而,将每个对象上的材料数量保持在尽可能低的工作效果同样好。
-
禁忌:
-
仅放置网格以填充空间。这使得关卡感觉杂乱无章。这也增加了每帧的绘制调用数量。
-
依赖高级渲染技术来显示网格和效果。如透明度、屏幕空间反射和法线贴图等特性在 VR 中表现不佳。要么它们太资源密集,要么显示不正确。
这些只是知识和规划如何帮助性能的一两种方式。通过理解 VR 的需求并规划我们的艺术方法,我们可以尽最大努力减少绘制调用。
VR 中的艺术限制
现在我们有了基本理解,是时候具体分析了。我们在 VR 中面临的限制如何影响以下游戏艺术类别的每个类别?:
-
静态和骨骼网格
-
材料图
-
灯光
-
视觉效果
每个类别代表在为我们的游戏创建视觉效果时需要考虑的不同限制。
静态和骨骼网格的限制
让我们先从静态网格和骨骼网格开始看:

Unreal 4 中的静态网格编辑器
静态和骨骼网格代表了在 Unreal Engine 4 中创建游戏所投入的大部分艺术。这些是你的 3D 模型,通常被归类为环境、角色、武器、车辆等。回到 20 世纪 90 年代末,当计算机资源更加有限时,艺术家在多边形或三角形计数方面必须遵守严格的限制,但那些限制已经成为过去。现代游戏硬件可以毫无问题地将数百万个三角形推送到屏幕上。然而,由于 VR 对性能的需求如此之高,现在是时候像 1999 年一样创建我们的模型了!
对于我们这些较老的人来说,我们能够记住那些限制是什么样的。对于其他人(年轻的读者)来说,我们需要稍微回顾一下这意味着什么。今天的三角形计数标准相当高。第一人称武器可以有 30,000 个三角形或更多。角色拥有高达 120,000 个三角形的三角形计数并不罕见。然而,任何额外的细节都会影响性能。如果没有法线贴图帮助我们伪造细节并减少这些数字,我们如何保持高端环境所需的细节水平?一种常见的做法是在玩家看不到的地方删除对象上的多边形。当玩家可以看到我们对象的四面八方时,我们如何做到这一点?
材质限制
接下来,让我们讨论材质:

材质编辑器的一瞥
场景材质的复杂性和数量往往是 VR 中性能不佳的主要原因。我们应用到网格上的每种材质都会给我们的游戏增加一个绘制调用,迫使计算机在每一帧都要更加努力工作。这可能导致性能变慢,无法达到我们 90 FPS 的目标。还有透明度和反射的问题。透明和不透明的材质是一种很棒的效果。然而,在 VR 中,这些材质的成本很高,因为材质必须每帧重新评估和重新绘制。反射是另一种很棒的效果,它有助于让世界感觉更加真实和沉浸。然而,这些效果也非常消耗资源,需要复杂的计算。
光照限制
现在是时候看看光照了:

UE4 中的光照示例
许多现代游戏利用动态光源为玩家提供一个活生生的世界。阴影随着太阳在天空中移动而移动。NPC 在街灯下移动时投下阴影。这种光照和阴影让我们感觉在这个世界中扎根,是任何游戏不可或缺的一部分。然而,动态光照对每一帧的计算成本非常高。那么,我们如何使用阴影来保持虚拟世界中的真实感呢?
视觉效果(VFX)限制
最后,我们来谈谈视觉效果:

Unreal Engine 4 Cascade VFX 编辑器
正确的视觉效果可以为许多不同类型的游戏带来冲击力和兴奋感。它们在动作游戏中至关重要。它们为体育游戏带来冲击感。它们甚至增加了模拟的真实感和感觉。然而,就像动态光照一样,它们在性能方面成本很高。透明和不透明材质的限制也适用于这里。一些技术,如使用 SubUV 纹理(在网格中排列动画粒子的帧),变得无效。许多粒子还包含动态光源。
性能提升技术
考虑到玩家的舒适度和所有这些限制,设计师们是如何为 VR 用户创造令人惊叹的 3D 世界的?答案可以总结为阶段fake it till you make it。VR 背后的技术正在快速发展。处理我们提到的问题的新技术正在被集成到下一代游戏引擎中。然而,目前,艺术家们正在使用一些最佳实践来提供令人惊叹的视觉效果,例如 Epic Games 的决战演示:

Epic Games 的决战演示
这些人能够在 90 FPS 的帧率下渲染出这个惊人的场景。这里有透明度、光照和视觉效果。所使用的模型来自各种其他 UE4 演示,并且没有降低质量。以下是他们是如何做到的一些例子。
静态和骨骼网格技术
要在性能方面最大限度地提高你的 VR 环境的成功率,你需要有计划地处理场景,并尽可能保守。由于玩家有机会比在传统游戏中更仔细地检查和与环境互动,因此 VR 准备好的资产需要为平均身高的用户进行缩放。网格也必须是多面的。这意味着要确保对象是完整的,网格中没有缺失的多边形,因为玩家通常可以从任何角度查看对象。以下是一个为Tribe XR创建的 DJ 台示例:

在 Tribe XR 中使用的先锋 CDJ 在 Autodesk Maya 中打开。这个网格只有 2,001 个三角形
在他们的游戏Tribe XR中,团队计划和专注于他们的 VR DJ 体验,以便在一个房间里进行,这样他们就能最大限度地提高创造沉浸式科幻环境的能力。受到像Overwatch和Team Fortress 2这样的游戏的启发,他们创建的休息室中包含足够的网格,以营造出一种居住感。每个网格都经过精心放置,以创造一种轻松和逼真的氛围。他们使用的网格具有低多边形/三角形计数,并且 UV 贴图经过优化以减少冗余的绘制调用。结果是流畅的 VR 体验,保持高帧率,让玩家沉浸在音乐中。
游戏优化的标准方法,如使用细节级别(LOD)网格,在 VR 中仍然有效,应该仍然被考虑。
材料技术
如前所述,VR 的材料应该有计划地设计和规划,以最大限度地减少每帧所需的绘制调用次数。每个网格应包含尽可能少的材料以减少这些绘制调用。在决战演示中,Epic Games 创建了许多专门设计的道具,尽管其中一些是从以前的演示中借用的,例如 Samaritan 演示。为 VR 构建的网格都遵循类似的模式。所有网格都经过优化,三角形数量低,大多数只使用一种材料。
透明度/透明度被控制在最低限度,并且仅在少数几个位置使用,例如车辆中的玻璃。周围建筑中的玻璃是通过使用的纹理图像伪造的。在需要透明度的位置,可以使用 DitherTemporalAA 材质节点来使不透明度蒙版对象看起来像透明对象。以下是一个使用火箭烟雾尾迹的示例:

对决演示中的 RocketTrail 材质
具体来说,使用 DitherTemporalAA 节点有助于消除像素过度绘制,从而提高性能。
最后,我们有一个强大的工具可以帮助我们微调场景中的材质:着色器复杂度视图。通过视口中的视图模式下拉菜单访问,此视图以绿色和红色着色场景,绿色表示较简单,红色表示较复杂。让我们看看这个来自对决演示的截图:

启用着色器复杂度的对决演示
大多数场景都叠加在绿色上,显示该级别中大多数对象的着色器指令数量最少。我们看到红色的地方是透明的挡风玻璃和从渗透者演示中获取的角色的着色器(这些没有针对 VR 进行优化)。
照明技术
照明级别是每帧更新中最资源密集的过程之一。当动态灯光移动时,它会改变阴影、反射和散射光,这些都必须重新计算和重新绘制。为了补偿这一点,虚幻引擎 4 为每个对象提供了烘焙光图选项。这通过将照明数据烘焙到每个对象上的光图中来创建静态照明。光图不能像动态照明那样逼真,但性能差异非常明显。这并不是说动态照明在 VR 项目中没有位置。动态灯光仍然可以有限地使用,并且它们永远不应该相互接触。如果你正在创建的场景是户外场景,尝试将你的方向光(太阳或月亮)设置为动态,然后使用最简单的设置使用级联阴影图。
然而,使用静态照明的缺点是动态对象会失去阴影。例如玩家、敌人以及交互对象似乎只是漂浮在你的虚拟空间中,没有阴影来固定它们。这给空间带来了一种不太自然的外观。为了解决这个问题,我们可以使用一种可以创建伪造 blob 阴影的技术。我们可以在对决演示中看到它:

一个带有伪造的 blob 阴影的静态网格
最后,反射可以极大地增强一个区域的感觉复杂性和现实感。然而,实时反射消耗了大量的资源,并且不适合 VR 游戏。本着“假装做到你做到”的精神,Epic Games 的设计师创建了反射捕获演员。这些演员从它们的影响区域内捕获反射并将它们编码到静态立方图中。这些立方图可以由材质用于在关卡中创建和微调反射。由于这些立方图是在游戏开始之前创建的,它们对性能的影响非常小。
视觉效果技术
如果你做过很多游戏开发工作,无论是二维还是三维,那么你应该知道用于创建粒子效果的传统的 SubUV 技术。这项技术涉及创建一个代表粒子(如火焰或烟雾)的精灵图集,并让游戏引擎通过单元格进行动画处理。这创建了一个看起来是三维的动画粒子,但实际上是一个 2D 纹理。以下是一个烟雾的例子:

使用 SubUV 技术创建的烟雾粒子
你的第一个想法可能是这种类型的粒子对于 VR 来说非常理想,因为我们正在使用 2D 纹理投射烟雾粒子。然而,由于这项技术依赖于透明度,在 VR 中创建这种粒子对我们的性能来说将是艰难的。我们还需要考虑 VR 中的玩家可以从许多不同的角度观看粒子。正因为如此,使用 SubUV 技术创建的粒子最终看起来很平面且缺乏趣味。设计师可以通过专注于使用小网格作为粒子,并创建接近摄像机的效果来解决这个问题:

在 Cascade 中打开的 VR 友好型粒子
在对决演示中,烟雾在关卡中的几个地方被使用,例如翻转汽车的车辆爆炸的一部分。为了最小化对性能的影响,设计师创建了一个动画烟雾材质,他们可以将它放置在 3D 带状网格上,然后作为效果的一部分与几种不同类型的混凝土块一起发射。当启用 Shader Complexity 视图查看演示时,这些烟雾效果以绿色调显示,这意味着它们已经优化并且对帧率的影响很小。这也允许设计师在摄像机附近或正前方部署这些效果——这是 SubUV 粒子可以很好地做到的事情。
测量游戏内性能
反复提到性能是 VR 游戏最重要的考虑因素。然而,我们还没有讨论如何在 Unreal Engine 4 内部测量性能,以便我们知道我们是否进行了良好的优化。让我们看看我们有哪些可用的工具。
UE4 作为游戏引擎的一部分提供了大量的性能分析工具,多到我在这个快速入门指南中无法全部讨论。然而,我确实想讨论几个与我们讨论相关的内容:Stat 命令和 GPU 可视化器:

Stat 命令统计信息
有几个 Stat 命令对我们确定性能很有用。它们可以通过按波浪线键打开控制台并输入以下命令来访问(它们不区分大小写):
-
Stat FPS:此命令显示当前帧率以及渲染一帧所需的时间(以毫秒为单位)。请记住,我们针对 HTC Vive 和 Oculus Rift 的目标是 90 FPS。
-
Stat Unit:以毫秒为单位显示渲染帧、游戏计算、绘制调用和 GPU 计算时间。
-
Stat SceneRendering:显示一般的渲染时间统计信息。当性能开始下降时,此面板可以显示罪魁祸首。
这些统计数据可以帮助我们了解我们的游戏是受 CPU 限制还是受 GPU 限制。受 CPU 限制意味着我们的游戏有太多的复杂计算,性能目前正受到 CPU 的瓶颈限制。当我们的游戏受 GPU 限制时,这意味着我们有太多的绘制调用、灯光或复杂的视觉效果,我们的性能正受到图形处理器的限制。
判断我们是 CPU 受限还是 GPU 受限的另一种简单方法是降低游戏图形质量,并观察对帧率的影响。如果当前 FPS 没有变化,那么我们就受 CPU 限制。
我们拥有的另一个工具是 GPU 可视化器:

GPU 可视化器
这是一个可视化界面,允许我们查看用于绘制每一帧的渲染通道的 GPU 成本。尽管一开始可能看起来很复杂,但这个界面可以显示哪个视觉元素或功能导致了性能的最大下降,正如该功能在渲染时占用最多的毫秒数所指示的那样。有了这些知识,我们可以优化特定的功能或完全删除它。有关此主题的更多信息,请参阅位于docs.unrealengine.com/en-us/Engine/Performance的 Unreal Engine 4 文档中的性能和性能分析部分。
摘要
在本章中,我们学习了与静态和骨骼网格、材质、光照和视觉效果相关的 VR 游戏中性能问题的几个已知原因。在讨论了原因之后,我们通过一些来自 DJ 模拟器Tribe XR和 Epic Games 自家的电影级 VR 演示对决的例子,探讨了几个不同的解决方案。最后,我们讨论了如何对自家的游戏性能进行剖析,以确定游戏是否受到 CPU 或 GPU 的限制,以及我们如何利用这些数据来调整和优化我们的应用程序,以实现最大程度的玩家舒适度。
在最后一章中,我们将讨论游戏测试在用户体验设计过程中的重要性,以及如何收集这些数据以做出进一步的设计决策。我们还将学习如何通过烹饪过程最终确定我们的游戏,并为其分发做准备。最后,我们将讨论我们所学到的一切的重要性,如何继续进行我们创建的游戏原型,以及查看进一步开发 VR 应用程序的资源。
第六章:完成我们的 VR 游戏及下一步计划
服务器 17 从我们在第一章,“在虚幻引擎 4 中引入 VR 技术”中构思的想法已经走了很长的路。从我们的初始设计和与用户的讨论中,我们创建了一个以玩家乐趣和舒适度为首要考虑的游戏原型。在第五章,“在 UE4 中为 VR 创建优化的游戏艺术”,我们讨论了 VR 中游戏艺术的局限性以及可以用来构建游戏所需视觉效果的某些最佳实践。那么,我们接下来该怎么做?是时候进行测试了!
一旦我们有了可工作的原型,就是时候与我们的用户进行测试,看看我们的某些设计想法是否正确。只有玩家才能告诉我们我们是否走上了正确的道路。没有任何设计在出厂时就是完美的,我们的游戏通常需要经过几轮测试和重新设计,我们才能达到一个成功的版本。这就是为什么我们必须测试,并且经常测试。
在本章中,我们将涵盖以下主题:
-
游戏测试的重要性
-
收集测试数据
-
准备分发
游戏测试的重要性
测试阶段是 HCD 过程的重要组成部分:

测试是 HCD 过程的第五步
在这个阶段,目标是收集玩家真实而有意义的反馈,希望借此改进我们的设计。这个过程是最重要的部分,因为没有让玩家实际体验游戏,我们就无法知道游戏是否有趣。这也是许多新手开发者感到挣扎的阶段。许多创作者非常认同自己的作品,甚至将批评(即使是建设性的批评)视为对他们或他们能力的批评。重要的是我们要从作品中抽离出来,以便接受能改进项目的想法。以开放的心态对待所有反馈,并理解最终我们有权决定哪些反馈采用,哪些忽略。我们的目标始终是使产品更好。
收集测试数据
测试数据可以通过以下方式收集:
-
可用性测试
-
卡片分类
-
专家评审
我们可以使用几种方法来收集关于我们游戏的反馈。这些方法都是为了收集玩家可以用来改进游戏各个方面的具体数据,从用户界面到关卡设计。其中一些是高科技的,利用了记录玩家的先进方法,如眼动追踪测试。其他方法则非常低科技,可能涉及让玩家将带有特定关键词的卡片分类,或者简单地让玩家尝试游戏,同时你和你的同事记录结果。
可用性测试
我们大多数人最熟悉的第一种方法是可用性测试。可用性测试包括将测试人员带到你的位置,让他们在你和其他人观察和记录结果的同时测试你的设计。观察测试类似于在面试期间记录笔记。注意用户说什么,也要注意他们的肢体语言和他们所做的事情。
这里有一个例子:假设你创建了一个发生在迷宫中的解谜游戏。你给十个符合目标受众的玩家机会在 alpha 阶段尝试游戏。在测试期间,几乎每个玩家都决定尝试跳过一个放置在关卡中的障碍。在之后询问他们时,有四个玩家表示他们无法越过障碍感到沮丧,因为他们认为这可能是一个捷径。几乎所有玩家都认为这个障碍看起来像是他们应该能够通过的东西。我们如何利用这些信息来改进游戏?一个选择可能是改变障碍的外观,使其看起来更难以逾越——也许是一堵墙而不是栅栏。这当然可以解决问题,但它并没有真正利用我们所学的。我们可以在该区域构建一个秘密奖励。我们可以稍微调整障碍,使其对决心坚定的玩家可通行,并在另一侧隐藏一个奖励。我们还可以通过在障碍物之后添加一个捷径来迎合玩家的期望,如果他们能通过它。最后,如果我们认为这种变化是不必要的,我们总是可以选择忽略玩家反馈的这部分。你会选择哪一个?
卡片分类
另一种旨在收集玩家数据的方法是卡片分类,这对于理解玩家对关键功能的看法或决定游戏应该关注什么非常有用。这种方法来自心理测试领域,旨在帮助我们理解用户期望和他们对你的想法的理解。卡片分类涉及在几张便签上写下 50-60 个关键词或想法,并要求玩家将它们分类或根据其重要性进行排名。这些可以是游戏功能,如升级、武器修改或多人游戏。它们也可以是更抽象的想法,例如可接受的游戏时间是什么,或者玩家舒适度对体验有多重要。
卡片分类活动有两种类型。开放式卡片分类要求玩家将卡片组织成类别,并按准确描述其中内容的名称命名这些类别,而封闭式卡片分类是一种玩家将卡片分类到预定义类别中的方法,以展示他们对内容的了解。这两种方法都有其优点。第一种方法允许你测试玩家如何理解你的想法。第二种方法允许你标记想法并测试你的标记,使用真实用户。
专家评审
我要讨论的最后一个方法是专家评审。这涉及到将你的游戏发送给专家并收集他们的意见。在游戏中,这可能意味着让几个不同的专家尝试你的产品,并就特定功能提供反馈。这包括让关卡设计师测试你的游戏并提供关于关卡反馈,或者 UX 设计师就你的界面和交互系统提供反馈。这种方法允许你在团队设计专长可能较弱的地方获得专业见解,因为小型团队的成员经常身兼数职,并被要求承担他们不太了解的任务。然而,重要的是要注意,这不能替代让玩家测试我们的游戏,因为玩家反馈是我们能获得的最重要的见解类型。
准备分发
因此,我们已经收集了玩家数据,重新设计了游戏,并再次进行了测试。我们可能已经多次通过了这个设计周期的一部分,但现在我们的游戏已经完美,准备向大众发布。所以,现在是时候制作和打包应用程序的发布版本了。这个过程有几个步骤:
-
调整我们的项目设置。
-
启动项目启动器。
-
设置自定义启动配置文件。
-
测试游戏构建。
我们发布之旅的起点是通过调整项目设置中的一些打包设置:

项目设置,显示蓝图本地化选项
让我们打开蓝图本地化。此选项将我们的现有蓝图转换为 C++代码,这将提高我们的性能:
-
在项目设置菜单的打包部分找到蓝图本地化选项。
-
点击下拉菜单并选择包容选项。
接下来,在左侧菜单的平台部分选择 Windows 选项。在这里,你可以为你的游戏使用自定义的启动画面或自定义图标。这绝对是我们游戏发布前应该做的事情。
现在,我们需要转到项目启动器:

项目启动器窗口
从这里,我们可以为我们的启动偏好选择一个默认配置文件,或者我们可以创建自己的。尽管 WindowsNoEditor 选项可能对我们适用,但让我们尝试在底部创建自己的自定义配置文件:
-
在启动器底部,点击+按钮创建自定义启动配置文件。
-
在窗口顶部,双击“New Profile 0”名称,并将此配置文件命名为
VRQuickStart。 -
双击“Enter a description here”文字,并为此配置文件提供描述。
-
在项目部分,我们可以指定我们希望为特定项目使用此配置文件。现在请保持默认选项。
-
前往烹饪部分,并使用下拉菜单选择按部就班选项。烹饪我们的内容会移除任何未使用的内容,并为指定平台准备我们的文件。
-
在烹饪平台部分,确保选中了 WindowsNoEditor 的复选框。
-
在烹饪文化部分,确保选择了你想要本地化的任何文化。我选择了 en-US。
-
对于烹饪地图,选择你创建的原型地图。
-
在菜单的部署部分,确保选择了你的电脑。在变体下,选择 WindowsNoEditor 选项。
-
最后,点击后退选项返回到项目启动器。
在我们的个人资料设置完成后,终于到了启动我们的自定义个人资料并允许虚幻引擎为分发准备我们的游戏的时候了!点击自定义个人资料的启动按钮,并观察我们的游戏通过这个过程。由于我们的游戏目前只是一个原型,所以整个过程只需几分钟。当我们拥有一个包含艺术作品、多个关卡和更高程度自定义代码的更完整游戏时,这个过程将需要更长的时间。一旦完成,就是享受我们的演示的时候了!
摘要
恭喜!我们现在有一个打包并完整的游戏原型!那么,接下来我们该做什么呢?通过本书各章节所学到的知识,我们游戏的未来似乎广阔无垠。对于一些人来说,你们可能想要继续使用我们与Server 17一起创建的主题。对于其他人来说,目标可能是将我们设计和构建的系统用于启动一个新游戏。我们在第三章,探索虚拟现实中的引人入胜的游戏玩法和第四章,VR 中的用户界面和用户体验中编写的每个系统都被设计成通用和灵活的,允许读者将它们引导到他们希望的方向。我可以看到我将这些系统解释为非常适合破解谜题游戏,并重新构思用于射击游戏、车辆体验,甚至卡通烹饪游戏。一旦你确定了设计,就构建你的游戏玩法并与你选择的玩家进行测试。完善你的想法,制作一些令人惊叹的艺术作品,并将其发布到野外。
最终,决定这个原型的命运取决于你。然而,无论你决定做什么,永远不要停止创造。我们擅长我们所花时间做的事情。如果你想扩展你的游戏设计技能,永远不要停止制作游戏。


浙公网安备 33010602011771号