书名:Unreal Engine 4蓝图可视化编程
ISBN:978-7-115-45304-4
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
• 著 [美] Brenden Sewell
译 陈东林
责任编辑 胡俊英
• 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
• 读者服务热线:(010)81055410
反盗版热线:(010)81055315
Copyright © 2015 Packt Publishing. First published in the English language under the title Blueprints Visual Scripting for Unreal Engine.
All rights reserved.
本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。
Unreal游戏引擎的蓝图是一个强大的系统,它可以帮助用户构建出功能齐全的游戏。本书以一个射击游戏为背景,从基本的游戏力学原理、用户界面等内容讲解了游戏开发的全过程。
本书分为8章,分别介绍了使用蓝图进行对象交互、升级玩家的技能、创建屏幕UI元素、创建约束和游戏性对象、使用AI制作移动的敌人、升级AI敌人、跟踪游戏状态完成游戏体验、打包与发行等内容。
本书由经验丰富的技术达人撰写,非常适合游戏专业人士、计算机专业的学生以及想要从事游戏设计相关工作的读者阅读。
本书深入浅出地引领开发者进入到3D游戏乃至VR领域开发的浩瀚知识海洋中。作者仅用了一个看似简单的射击游戏案例,将虚幻蓝图脚本编程的复杂逻辑如画卷般地向读者徐徐展开。本书称得上是一本难得的通俗易懂的虚幻引擎4(UE4)编程参考读物。
——郭奕,北京唯幻科技CTO,苏黎世大学计算机博士
本书译者陈东林,于2013年毕业于江西师范大学通信系,曾先后任职中国联通主干网络维护工程师、游戏蛮牛技术编辑。现为北京服装学院研究生,研究方向为虚拟现实。同时担任UE4软件工程师一职,致力于使用虚幻引擎4完成VR中的交互,并研究VR中的渲染优化问题。
书中连接蓝图的步骤比较详细,读者对蓝图比较熟悉后可以根据截图进行连接,从而节省阅读时间。本书的工程文件可在人民邮电出版社网站下载,下载地址是:http://www.epubit.com.cn/book/details/4605。
本译文仅供大家参考,由于引擎版本的不同(译者使用的是4.12.5版本),或者每个人操作顺序或操作习惯的不同,会遇到不同的问题,希望大家能够一步步调试,得到想要的结果。在调试的过程中,你会学习到更多内容。
关于第8章,由于译者使用的是Windows平台,与原作者使用的Mac平台不同,因此截图为Windows平台。操作基本上类似,可供读者参考。
感谢我的导师刘昊副教授对我的指导,感谢郭仲倩女士对本书翻译提供的建议,感谢人民邮电出版社胡俊英编辑给我翻译本书的机会,感谢张伯爵、崔洪志(Alex_Tsui)、尹硕朋、王旭对本书部分内容的审校做出的贡献。特别感谢苏黎世大学博士郭奕先生为本书撰写推荐语。
——陈东林,2017年3月
Brenden Sewell是E-Line Media的游戏主策划。在过去5年里,他设计并制作了许多既有趣又有教育意义再或社会影响力的游戏。自2002年以来,他一直在创作游戏,其中“Neverwinter Nights”项目在游戏设计表现力上给了他宝贵的经验。2010年,他获得了印第安纳大学认知科学学位。从那时起,他一直专注于增强自己的游戏设计能力,同时为大家分享设计与开发的经验,让更多的人享受游戏行业的欢乐。
我想感谢以下这些人对本书出版过程的贡献。感谢Steve Swink(@steves- wink
)、Jake Martin、Demetrius Comes和Graeme Bayless在设计原则方面给予我的精确指导。感谢Logan Barnett(@logan_barnett
)和David Koontz(@dkoontz
)帮助我对脚本开发有了更加广泛的理解。感谢Packt出版社的所有成员及我的技术审稿人员,是你们帮助本书能顺利出版。感谢Unreal开发社区提供的支持和丰富的信息,让我们能够更好地掌握这门技术。最后,还要感谢我的女朋友Michelle、我的父母及我所有的朋友们。
——Brenden Sewell
Faris Ansari是来自巴基斯坦的IT专业人员,他熟悉和感兴趣的领域有Unity 3D、虚幻引擎、Cocos2d、Allegro、OpenGL及其他游戏开发环境。他以一个独立游戏开发者的身份开始了他的职业生涯,并通过开发一些成功的游戏,得到了不菲的收入。他拥有良好的开发技术,喜欢承担新挑战的同时研发新技术(特别是开源技术)。
Faris已经审阅的书有Learning NGUI for Unity。
他的爱好包括玩游戏、学习新知识和看电影。他非常喜欢与同事、朋友们一起讨论创新的想法。他的口头禅是:“每个专家都曾经是一个初学者。”
你可以随时通过领英与他联系并讨论创新的想法。
感谢我的朋友们和我的家人,感谢你们对我无限的支持和帮助。
——Faris Ansari
Scott Hafner是一位专业的游戏策划,在游戏行业拥有超过10年的经验。在他的职业生涯中,他曾任职游戏制作人、游戏策划和关卡设计师,创作了很多游戏,包括MMO、FPS、RPG等。
感谢未婚妻对我无限的支持和鼓励。
——Scott Hafner
MarcinKamiński是CTAdventure的高级程序员,拥有自己的公司——Digital Hussars。此前,他曾在Artifex Mundi、CI游戏和Vivid Games工作。他擅长的领域是人工智能和网络编程。在过去的14年中,他已经协助开发了许多PC、游戏机和手机游戏。
Marcin同时也是Unity iOS Essentials和Unity 2D Game Development Cookbook的审稿人。
Alankar Pradhan来自印度孟买。他是一个雄心勃勃的人,喜欢与新人交流、热爱跳舞、跆拳道、旅行,在闲暇时光喜欢与朋友一起玩耍,也喜欢在PC和手机上玩游戏。游戏一直是他生活的激情所在。他不只是喜欢玩游戏,还对游戏的工作原理充满好奇心。因此,他决定选择游戏行业。Alankar获得了英国谢菲尔德哈勒姆大学软件开发专业理学学士学位。Alankar在印度华特迪士尼担任游戏编程实习生,在实习期间,他参与了一个名为“Hitout Heroes”的现场编程项目。他还是DSK Green Ice Games的一名游戏程序员,作为视频游戏程序员开发一个针对PC和游戏机的游戏。Death God University(D.G.U)这个游戏于2015年7月1日发布。他正在研究的另一个项目是The Forsaken Mountains。
Alankar曾在团队中参与过许多小型项目,也曾在C#、C++、Java、Unreal脚本(虚幻引擎4之前的Unreal Script)、Python、Lua、Groovy/Grails、HTML5 / CSS等各种语言方面提升自己的技能。他熟悉的引擎有:Unity3D、虚幻开发工具包、Visual Studio以及SDK(如NetBeans、Eclipse和Wintermute)。2013年,他的著作Comparison between Python and Lua in Gaming Industry得以出版。此前他曾是Packt出版社的一名技术审稿人,审阅了Creating E-Learning Games With Unity和Learning Unreal __Engine iOS Game Development两本书。
除此之外,Alankar喜欢阅读、听音乐、写诗和短篇故事。他有自己的网站,
还出版了The Art Of Lost Words一书,可在Amazon.com上找到。
他的电子邮件地址是alankar.pradhan@gmail.com
。你可以访问他的作品网站alankarpradhan.wix.com/my-portfolio
或在Facebook上联系他。
我们总是忙着赶路而忘记了欣赏沿途的风景,尤其是忽视了那些我们在路上遇到的人们。欣赏是一种美妙的感觉,如果我们不忽略它的话,那将是一种非常好的享受。我想在此感谢那些曾指导和鼓励我的人们。
我想向我的父母表示真挚的感谢,感谢他们对我的谆谆教诲和无限信任。感谢我的朋友们,是你们一直支持和鼓励着我,并帮助我达到了现在的水准。
最后,我还要感谢所有直接或间接为本书的出版做出贡献的人们。
——Alankar Pradhan
Matt Sutherlin过去10年一直在游戏行业工作,他从一名质检员(Quqlity Assurance,QA)和码农成长为一名引擎程序员与技术专家。最近,他一直非常专注于图形学技术,致力于AAA级别游戏(如《风暴英雄》和《光环5:守护者》)的引擎渲染器、渲染管线和着色器的研究。
感谢我的妻子(Megan)和我的父母(Mike和Mary Lynn)多年来的支持、耐心和理解。我成长的每一步都离不开你们的支持。我还要感谢Alan Wolfe在编程技巧和算法设计方面给予我的指导,我真的非常有幸结识了这样一位优秀的朋友。
——Matt Sutherlin
游戏引擎(例如虚幻引擎4)作为强大的商业游戏的制作工具,越来越受传统游戏工作室以外的新老游戏开发者所欢迎。虚幻引擎为过去10年中发布的许多最受欢迎的控制台和PC游戏提供了动力,最新版本的虚幻引擎尽可能地包含了开发者所需的工具。这些工具中最具变革性的是蓝图可视化编程系统,其允许非专业程序人员创建和实现游戏机制、用户界面(User Interface,UI)和交互。
本书采用分步方法,指导读者使用可视化的蓝图节点构成蓝图行为,并将它们链接在一起以创建游戏机制、UI等。在这个过程中,读者将学习所有使用蓝图在虚幻引擎4中开发游戏的必要技能。
我们从基础的第一人称射击模板开始,每个章节将扩展原型,以创建一个越来越复杂和稳定的游戏体验。从创造基本的射击机制逐渐过渡到更复杂的系统,将生成用户界面和智能敌人行为。学完这本书时,你将完成一个功能齐全的第一人称射击游戏,在游戏开发过程中学会一些必要的技能。
第1章,使用蓝图进行对象交互,首先介绍如何将新对象导入到关卡中,以构建游戏世界。然后修改对象的材质,通过材质编辑器进行设置,在运行时通过蓝图改变对象的材质。
第2章,升级玩家的功能,教你如何使用蓝图在游戏过程中生成新对象,并将蓝图中的动作链接到玩家控制输入。你还将学习创建蓝图,允许对象对发射的子弹做出碰撞反应。
第3章,创建屏幕UI元素,设置图形用户界面(Graphical User Interface,GUI),跟踪玩家的血量、体力、弹药数量和游戏目标的数值。在这里,读者将学习如何使用虚幻引擎的GUI编辑器设置基本用户界面,以及如何使用蓝图将界面链接到游戏中的数值。
第4章,创建约束和游戏性对象,内容包括如何限制玩家的能力,定义游戏关卡的游戏目标,并通过与上一章中创建的GUI元素交互的蓝图跟踪这些目标。我们通过设置可收集的弹药包,填充玩家的弹药,并且利用关卡蓝图来定义游戏的胜利条件。
第5章,使用AI制作移动的敌人。这是关键的一章,涵盖了如何创建一个敌人的AI,敌人将在关卡中追逐玩家。我们通过在关卡中设置一个导航网格,并使用蓝图让敌人在巡逻点之间徘徊。
第6章,升级AI敌人,通过修改敌人的AI,赋予敌人一定的判断能力,以创建一个有趣的游戏体验。在本章中,我们使用视觉和听觉检测来设置敌人的巡逻、搜索和攻击状态。此外,我们还介绍了游戏过程中逐渐生成新的敌人的方法。
第7章,跟踪游戏状态和完成游戏体验,在确定游戏的最终发布版本之前,我们添加了一些关键的游戏体验。在这一章中,我们创建了使游戏难度逐渐加大的机制,增加了保存游戏的功能,使玩家能够保存他们的游戏进度并回到该进度继续玩耍。本章还添加了玩家死亡机制,使游戏挑战更有意义。
第8章,打包与发行,介绍如何优化图形设置,以获得最佳的游戏性能和视觉效果。然后讲解如何创建一个共享的游戏,并分享一些建议,促使读者超越本书的限制,成为一个成熟的游戏开发者!
当开始开发一个游戏时,你想到的第一步应该是建立一个原型。幸运的是,虚幻引擎4和蓝图让基本的游戏功能实现起来比以往任何时候都更容易。这样用户便可以很快地开始测试自己的想法。为了让大家熟悉虚幻编辑器(Unreal Editor)和蓝图(Blueprint),我们将使用一些自带的资源和蓝图建立游戏玩法机制。
本章我们将学习以下内容。
在开始创造游戏元素之前,我们需要创建一个项目,这个项目将包含游戏的内容。为了获取虚幻引擎4(Unreal Engine 4,以下简称UE4),并开始设定我们的项目,需要打开Epic Games launcher,通过它便可以从UE4官网下载UE4引擎。单击Epic Games launcher的UE4标签。如果你是第一次在你的计算机中使用虚幻引擎,你将会看到灰色的未安装(Not Installed)按钮。在Launcher的左侧,会看到一些选项。
工作标签可以让你选择已经安装的引擎版本及已经创建好的项目。现在请单击工作标签,找到黄色的安装按钮并单击,如图1.1所示。
图1.1 安装引擎
当引擎已经完成安装时,安装按钮将会变成启动按钮,如图1.2所示。单击任意一个启动按钮即可启动引擎。
图1.2 启动引擎
单击启动按钮后,虚幻项目浏览器(Unreal Project Browser)就会呈现在你眼前。默认显示的是项目标签,它呈现的是已创建的所有工程的缩略图,同时也展示示例工程模板。我们的目的是要新建项目,因此单击新建项目标签。[1]
从新建项目标签下,你可以选择一个模板,这个模板将为游戏项目提供初始的资源;或者用户也可以选择空白(不使用模板)开始你的项目。在新建项目标签下,用户会发现有两个子标签:蓝图和C++。蓝图标签用自身提供的模板创建项目,且该项目自带的蓝图具有一些基本的行为。通过C++ 标签下的模板创建项目,其核心的一些行为都是通过C++ 语言编写的。因为我们想快速启动和运行第一人称射击游戏的原型,而不是从头开始创建基本的控制功能,所以我们要确保已经选择了蓝图标签,然后选择First Person模板,如图1.3所示。
图1.3 选择FirstPerson蓝图模板
下一步是根据我们的偏好调整项目设置。在模板选择器下有3个灰色的选项,允许我们选择目标平台(桌面/游戏机、移动设备/平板电脑)、图像级别(最高质量、可缩放的3D或2D)、是否具有初学者内容。这里我们保持默认设置(桌面/游戏机,最高质量,具有初学者内容)。在这3个灰色选项的下面,用户将看到文件存储路径,可以根据自己的偏好将项目存储到硬盘相应路径下,项目名输入框内则需要你输入项目的名称。在这里将项目命名为BlueprintScripting
,并将项目保存到操作系统虚幻项目的默认文件夹,如图1.4所示。
图1.4 设置项目路径
既然我们已经选择了模板,并且将项目按自己的偏好设置好了,那么我们就可以单击绿色的创建项目按钮创建项目。当引擎初始化资源和设置项目进程完毕后,虚幻编辑器便会打开关卡编辑器,在关卡编辑器中,你可以创建并预览关卡,放置和修改对象,如果你修改了项目,还可以及时测试。
按下工具栏顶部的播放按钮,如图1.5所示,用户将可以试玩第一人称模板内置的游戏。这个游戏包括了角色移动、发射子弹、使用子弹给立方体对象施加力。在游戏模式中,播放按钮将会变成暂停按钮和停止按钮。用户可以单击暂停按钮暂停游戏,当用户在运行游戏的时候,如果希望知道一个交互或者actor属性,暂停游戏将会很有用。单击停止按钮将会停止运行游戏并返回编辑模式。在继续创作之前,先试玩一下游戏吧。
图1.5 工具栏
现在我们希望添加自定义对象到关卡中。在关卡编辑器的中心面板是3D视口,视口为我们呈现游戏的3D内容。这时,熟悉在3D视口中的移动很重要。可以通过使用鼠标按键和拖动鼠标向周围移动控制摄像机来改变视角。在视口中按住鼠标左键并拖动光标将可以操控摄像机向前后左右移动,按住鼠标右键拖动光标将会旋转相机的视角。最后,按住鼠标中键并拖动光标将可以调整相机的位置(上下左右)。[2]
UE4中最简单的对象称为actor。它可以被拖曳到游戏世界中。actor是最基本的对象,除了能够旋转、移动、缩放之外,没有继承其他的行为,但是可以通过添加组件来获取更多的复杂的行为。我们的想法是创建一个简单的目标actor,当使用枪发射子弹击中它时能够改变自身颜色。我们可以在模式(Modes)面板创建一个简单的actor,选中模式(Place)标签,单击基本(Basic)然后拖曳Cylinder到3D视口当中。这个操作将创建一个新的圆柱体(cylinder)actor并置入到关卡中,你将在3D视口和世界大纲视图中看到它,单击鼠标右键>>编辑>>Rename将默认的名称Cylinder重命名为CylinderTarget
,如图1.6所示。[3]
图1.6 将新建圆柱体重命名为CylinderTarget
之前我们设定的目标是:当圆柱体被射弹击中后,能够改变自身的颜色。因此,我们需要改变圆柱体actor的材质。材质是一种资源,能被添加到actor的网格当中(网格定义了actor的物理形状)。可以认为材质就像油漆一样作用于actor的网格或外形之上。因为actor的材质决定了它的颜色,所以改变actor颜色的方法之一就是将原先的材质替换为另一种颜色的材质。因此,我们首要任务是创建材质,这个材质将使actor呈现红色。
在内容浏览器里找到FirstPersonBP文件夹,创建子文件夹并命名为Materials
,进入Materials目录,在空白处单击鼠标右键,在弹出的菜单表中选择创建高级资源>>材质&贴图>>材质,将新建的材质命名为TargetRed
。
双击TargetRed材质,打开编辑标签,如图1.7所示。
上图所示为材质编辑器,其与蓝图拥有同样的特性。这个屏幕的中心称为网格(grid)。我们可以将所有的定义蓝图多级的对象放置到网格上,该网格的标签名为material,术语称为节点。在之前的截图中,有一系列的输入引脚,这样其他的材质节点可以添加到上面,也因此可以定义它的属性。
图1.7 TargetRed材质
为了将颜色赋给材质,我们需要创建一个节点。该节点将为节点中基础颜色(Base Color)输入给出颜色的信息。在节点附近的空区域单击鼠标右键,将出现一个菜单,它包含搜索框和一个可扩展的选择列表。这个展示了所有的我们可用添加到这个材质蓝图的可用蓝图节点选项。搜索框对文本很敏感,我们键入搜索对象的前几个字符就能看到一系列的搜索结果,在这里我们搜索的是VectorParameter,如图1.8所示。
图1.8 搜索VectorParameter
材质编辑器(Material Editor)中的VectorParameter用于定义颜色,我们可以将它添加到材质编辑器的基础颜色输入节点。首先需要给VectorParameter节点选择一个颜色,双击Color节点的黑色区域,打开颜色选择器(Color Picker)。当目标被选择时,我们希望它显示亮红色,在色盘中手动选择颜色,如图1.9所示,选择完毕后单击好按钮,稍后你将发现原来VectorParameter节点的中间黑色的部分已经变成了红色。
图1.9 颜色选择器
为了帮助我们记忆在材质中VectorParameter会哪些参数或属性,我们需要将Vector Parameter重命名为Color
。当选中节点时,该节点会被金色的边框包围,查看细节面板(Details)里的内容,在通用 >Parameter
处键入“Color”。这时VectorParameter节点名称自动地由None改为Color。
最后一步,连接Color VectorParameter
节点与基础颜色节点。在蓝图中,可以通过单击和拖曳输出引脚至输入引脚将节点连接起来。输入引脚在节点的左侧,输出引脚在节点右侧。连接两个节点的细线称之为引线(Wire)。从Color输出引脚拖出一根引线至材质节点的基础颜色输入引脚,如图1.10所示。
图1.10 连接Color节点与TargetRed节点
我们可以通过材质节点的其他输入引脚,给材质添加一些光泽。如果使用单一颜色、平整的材质,3D物体看起来就会很不真实,可以在金属(Metallic)和粗糙度(Roughness)引脚设置值来改善这一情况。在空的网格区域单击鼠标右键,在搜索框中键入“scalar
”,找到ScalarParameter
节点,如图1.11所示。
图1.11 搜索ScalarParameter节点
找到ScarlarParameter并选择,转到细节面板(Details),由于任何叠加性的影响对材质都很微妙,设置Default Value为0.1。将节点重命名为Metallic。最后,拖出引线连接Metallic的输出引脚和材质的金属输入引脚。
还需要连接粗糙度参数,在刚才创建的Metallic节点上单击鼠标右键,选择Duplicate。这个操作将生成Metallic节点的复制,唯一不同的时它没有引线与材质连接。选中这个复制节点,然后在细节面板中重命名为Roughness。保持Roughness节点中的Default Value值为0.1不变,拖出引线连接Roughness的输出引脚和材质的粗糙度输入引脚。如图1.12所示。
图1.12 连接金属和粗糙度后的材质
至此,我们已经创建了一个亮红色的材质,当目标被选择时就会用它来突出显示。单击编辑器左上角的“保存”按钮保存资源,然后关闭材质编辑器返回关卡。
现在游戏世界中放置了一个圆柱体,在当圆柱体被击中时,我们需要为圆柱体赋上前一节创建的材质。最后一个交互是游戏逻辑判断圆柱体被选择,然后将圆柱体的材质改变为红色材质。为了创建这一行为并添加到圆柱体上,我们需要创建一个蓝图。创建蓝图的方式有很多种,但是为了简便,我们可以创建蓝图并直接添加给圆柱体。为此,确保在场景中选中了CylinderTarget对象。单击细节面板顶端的蓝色蓝图/添加脚本(Blueprint/Add Script)按钮,将可看到路径选择窗口。
在这个项目中, 我们把所有的蓝图存放在FirstPersonBP
文件夹的子文件夹Blueprints
下,因为这个蓝图是为CylinderTarget actor
创建的,所以文件名为默认的Cylinder- Target_Blueprint
即可,如图1.13所示。
图1.13 创建蓝图
现在内容浏览器(content browser)的FirstPersonBP >> Blueprints
文件夹中可以看到CylinderTarget_Blueprint
。双击打开该蓝图的蓝图编辑器,我们将看到圆柱体的视口视图,如图1.14所示。我们可以操作actor的一些默认属性或者增加更多的组件,它们都包括很多自有逻辑使得actor更加复杂。我们将在后续章节中探讨组件(components),现在,我们需要创建一个简单的蓝图并直接赋给actor。为此,单击视口标签旁边的事件图表(Event Graph)标签。
图1.14 视口视图
事件图表(见图1.15)看起来应该很熟悉,因为它与我们之前使用的材质编辑器的视觉效果和功能上由很多相似之处。默认情况下,打开事件图表时会有3个未连接也未使用的事件节点。事件(Event)指游戏中的一些动作(action),它作为蓝图做某件事情的触发器。大多数蓝图遵循如下结构:事件(when)|条件(if)动作(do)。这个可以被文字描述为:当某件事情发生时,检查X、Y、Z是否为真,如果为真,完成这一系列的动作。举个例子,蓝图定义了我是否开枪,流程如下:当(when)扣动扳机,如果(if)子弹已上膛,进行(do)射击。
在事件图表中,列出的3个默认事件节点是用的最多的事件触发器。当玩家(Player)第一次开始玩游戏时触发事件Begin Play。当另一个actor开始与蓝图控制的现有actor发生触碰或重叠时触发事件Actor Begin Overlap。事件Tick在游戏运行的每一帧触发与之关联的动作,帧率决定于电脑的配置,也影响着事件Tick触发动作的频率。
我们希望每次射弹击中圆柱体时,都能触发“改变材质”这一动作。可以使用事件Actor Begin Overlap节点来探测射弹对象与目标的网格是否重叠。我们将通过仅当另一个actor触发目标actor时检测来简化这些。以一个简洁的页面开始吧,选中所有的默认事件,单击键盘的[delete]键把它们都删除。
图1.15 默认的事件图表
为了创建检测事件,在图表空白区域单击鼠标右键,搜索框中输入“hit
”,如图1.16所示。找到事件Hit(Event Hit)节点并选择,当这个蓝图控制的actor
被另一个actor
触发的事件Hit。
当在事件图表中添加了事件Hit节点后,会看到事件Hit节点上有许多颜色各异的输出引脚。首先注意到节点右上角的白色类似三角形的引脚,这是执行引脚(execution pin)。它定义了动作序列中下一步要执行的动作。将不同节点的执行引脚连接在一起便组成了所有蓝图的基本功能。既然拥有了触发器,那我们就需要找到一个动作,这个动作可以让我们能够改变actor
的材质。
图1.16 搜索事件hit
从执行引脚拖出一根引线至节点右端的空白区域,将自动出现一个搜索窗口,允许我们创建一个节点并将它与执行引脚相连。确认搜索框内勾选了情境关联(Context Sensitive)。这样将搜索结果限制在能够被添加的节点中。在搜索框内键入“set material
”,选取Set Material(StaticMeshComponent)节点。
小贴士
如果在勾选了情境关联的情况下搜索不到想查找的节点,可尝试去勾选重新搜索。即使这个节点在情境关联搜索时查找不到,它仍有可能可以添加到蓝图逻辑当中。
事件Hit节点中的动作(action)如图1.17所示。
图1.17 添加Set Material(StaticMeshComponent)节点
当用户将Set Material节点放置到蓝图中后,便会注意到它已经与事件Hit节点的执行引脚连接。当蓝图的actor被另外的actor击中时,蓝图现在将会执行Set Material动作。然而,我们现在还没有设置响应Set Material时将要调用的材质。
为了设置将调用的材质,单击Set Material节点中的选择资源(Select Asset),调出下拉列表,在搜索框内输入“red
”来搜索之前创建的TargetRed材质。查找到该材质后单击它,将该资源添加到Set Material节点的材质区域,如图1.18所示。
我们现在已经做好了改变目标圆柱体颜色的蓝图的准备工作,但是在保存(Save)蓝图之前,还需要编译一下。编译是将蓝图语言转换为告诉计算机该如何操作的机器指令。单击编辑器工具左上角的编译(Compile)按钮,然后单击保存。
图1.18 添加TargetRed材质
既然我们已经设置了一个基础的游戏交互,运行测试一下,确保结果是否达到预期。关闭蓝图编辑器,在UE编辑器中单击播放按钮运行游戏,尝试跑动和射击,与我们之前创建的CylinderTarget
actor
发生碰撞,如图1.19所示。
图1.19 目标圆柱体改变颜色
当运行游戏的时候,会看到目标圆柱体在被子弹击中时,确实改变了颜色。这是游戏框架的初始,它可以用于接收敌人对于玩家(Player)动作的反馈。你或许也会注意到玩家碰到目标圆柱体时,圆柱体也会改变颜色,然而我们希望只有子弹击中目标时才改变目标颜色。这些不可预见的结果在游戏开发过程中很普遍,最好避免的方法就是频繁地测试我们正在创建的游戏。
为了修正蓝图中的bug,让它仅对子弹击中时做出反应并改变目标颜色,回到Cylinder Target_Blueprint蓝图,继续查看事件Hit节点。
在事件Hit节点上余下的输出引脚,它们是存储着关于事件数据的一些变量。这些数据可以被传递到其他节点。引脚的颜色代表着变量的数据类型,蓝色引脚传递对象(object),例如actor;红色引脚包含一个布尔型(true/false)变量。
随着接触更复杂的蓝图,我们将学习更多的引脚类型,但现在,我们只需要关心蓝色的Other输出引脚。它包含其他actor的碰撞触发事件Hit的数据。这个在确保目标圆柱体只在被子弹击中时改变颜色很有用,而其他的actor碰到目标时不改变颜色。
为了确保仅对子弹做出响应,断开事件Hit节点与SetMaterial节点之间的连接(断开引脚连接的方法之一为:按住[Alt]键,然后鼠标左键单击连线),从Other引脚拖出一根引线到空白区域,在搜索框内键入“projectile
”,找到“类型转换为FirstPersonProjectile(Cast To FirstPersonProjectile)”并选择,如图1.20所示。
FirstPersonProjectile
是UE4引擎的第一人称模板中的蓝图,控制着子弹的行为。该节点使用类型转换来确保:只有在子弹actor击中与转换节点引用的对象相匹配的圆柱体时,才将动作附加到节点的执行引脚。
图1.20 搜索projectile
当节点出现时,可以看到在事件Hit的Other输出引脚和转换节点的Object引脚之间有一根蓝色的线相连(如果没有自动连接,可以手动连上)。连接FirstPersonProjectile节点的输出执行引脚和Set Material节点的输入执行引脚,如图1.21所示。
图1.21 连接相关引脚
现在编译、保存,关闭蓝图界面后单击播放按钮进行测试。现在你会发现玩家碰到目标圆柱体时不会改变圆柱体颜色,只有子弹击中时才可以。
既然我们有目标来响应玩家的射击,则可以添加一些挑战性的东西来让项目像一个游戏,一个简单的方法是为目标制作移动的标靶。为了完成这个功能,首先我们需要将目标actor是设为可移动的,然后需要通过蓝图设置逻辑。这样便可以控制目标移动。我们的目标是使目标圆柱体在关卡中来回移动。
为了让目标移动,首先需要改变actor的移动性(Mobility)为可移动(Moveable)。这个操作将允许对象在玩游戏时可以被操纵。在UE4编辑器的世界大纲视图中,选中CylinderTarget_Blueprint
,查看细节(Details)面板。在变换数值(Transform values)的下方,可以看到可移动的开关,单击将状态由静态(Static)转变到可移动,如图1.22所示。
图1.22 设置可移动属性
批注
默认情况下, 放置在世界中/场景中的actor为静态(Static)。“静态”表示这个对象在游戏运行时不能移动或被操作。静态对象着重对少量的关键资源进行渲染,应该将其作为非交互对象的默认选择,以便最大化帧率。
注意:关卡中目标圆柱体的版本仅仅是我们为目标圆柱体创建的蓝图模板的一个实例。这个实例是指一个已经被创建的真正的对象,而蓝图是各种特征的描述,一旦使用蓝图创建了实例,该实例也会拥有这些特征。
在关卡中我们做的改变仅仅是针对目标圆柱体的,为了对其他目标也做一些改变,就需要直接修改蓝图了。为此,打开CylinderTarget_Blueprint
。
随着蓝图的打开,我们希望查找工具栏下方的视口标签。在左侧,可看到组件(Components)面板列出了组成这个蓝图的所有组件。由于我们想编辑物理对象、网格的属性,所以单击组件StaticMeshComponent(继承)。在蓝图编辑器的右边会看到细节面板与UE4关卡编辑界面中的细节面板有着相同的属性和分类。在这里,我们同样地将变换(Transform)下方的移动性开关设为可移动。这个操作将确保由这个蓝图创建的目标是设置为可移动的。
因为我们希望子弹能够将对象作为射击目标,所以需要确保目标能够发生碰撞,以至于子弹不会穿过目标。仍然是在细节面板中操作,找到Collision分类,然后在下拉菜单中找到碰撞预设值(Collision Presets)。在这个菜单中会有很多的选项。选择Custom时,用户可以将对象与不同对象之间的碰撞交互进行个性化设置。为了达到项目预期,我们仅需要选择BlockAllDynamic。这样网格会记录它与其他拥有碰撞器的对象之间发生的碰撞。如图1.23所示。
图1.23 设置StaticMeshComponent
既然我们将目标设置为可移动的,接下来需要让蓝图控制目标圆柱体如何移动。为了移动一个对象,需要3个三方面的数据。
为了理解对象现在的位置,我们需要知道更多的信息,特别需要留意圆柱体在世界中的坐标。需要将速度和方向的值提供给蓝图,通过一些必要的计算将这些值算出来,为蓝图移动对象提供有用的信息。
第一步就是创建两个变量:方向(direction)和速度(speed)。找到我的蓝图(My Blueprint)面板,你会看到一个叫作变量(Variables)的分类,这个分类里暂时没有其他数据,可单击这个分类右边的加号(+)来创建变量。
创建变量后,单击该变量,在蓝图编辑器右边的细节面板中,用户将看到一系列的区域可以用于编辑这个变量,我们需要编辑其中的4个:变量名称(Variable Name)、变量类型(Variable Type)、可编辑(Editable)和默认值(Default Value)。我们希望第一个变量用于控制移动速度,因此重命名该变量名为Speed
。对于变量类型,我们希望这个变量能够用于存储期望的速度数值,因此在下拉列表中选择浮点型(Float)。
勾选可编辑使得能够在此蓝图之外编辑该变量。这样在测试游戏的时候能够很方便快速地调整变量的数值。默认值分类看起来没有可编辑区域,但是有一条提示信息请编译此蓝图(compile the Blueprint),编译蓝图之后,出现一个可以输入初始值的区域,将默认的值0.0
改为200.0
,如图1.24所示。
图1.24 设置变量Speed
通过同样的步骤,创建Direction
变量,变量类型选择Vector。Vector包含X、Y、Z坐标轴的信息。在这个情况下,我们需要指明对象移动的方向。设置Direction变量为可编辑,并且将默认值设为(0.0,-10.0,0.0
)。
为了得到需要为移动所提供的信息,现在将继续探索。或许最初的时候看起来有些难,但是通过分解每个功能并将各个节点组合起来,就可以完成最终的目标。
我们需要完成的第一个计算工作就是取出direction变量的向量并进行归一化(normalize)。归一化是向量运算中的一个常见步骤,可将向量的长度转换为一个单位长度,可与其余部分的计算兼容。有一个专门的蓝图节点来完成此操作。
选中我的蓝图(My Blueprint)面板上的Direction变量,将它拖曳至事件图表(Event Graph)中的空白区域。这个操作将生成一个小的节点,提示用户选择获得(Get)或者设置(Set)。我们想取出direction
的值,所以选择获得来创建这个节点来存储变量的值。单击Direction节点的输出引脚至事件图表的空白区域,在搜索框内键入“normalize
”,在搜索结果中选择Vector分类下的Normalize节点,如图1.25所示(选中需要注释的两个节点后按[C]键添加注释)。
图1.25 归一化方向向量
如果发现搜索不到节点,请将Vector数组改为Vector,如图1.26所示。
批注
在蓝图上留下注释是一个很好的习惯,注释能够帮助描述这一组蓝图完成什么样的功能。在编辑完这个蓝图后,过一阵子回过头来看的时候能够快速地知道它的功能。
图1.26 单击修改Vector与Vector数组类型
为了让速度值与方向相关,首先需要将速度变量与delta time相乘。delta time是以秒计算,完成最后一帧的时间。它与游戏的帧速率不同。速度变量的值与delta time相乘后,可以确保游戏中的对象的速度都是一致的,与游戏的帧速率无关。[4]
将Speed变量拖入事件图表,选择“获得”来创建这个速度节点。在事件图表空白区域单击鼠标右键进行搜索,找到Get World Delta Seconds选项。最后从Speed节点或Get World Delta Seconds节点的输出引脚拖出引线,调出搜索框,键入“*”(星号)或“multi”,找到并选择float*float节点。最后,将另外一个节点(Speed节点或Get World Delta Seconds节点)输出引脚至float*float节点的另一个输入引脚,这样就做到了使两个数值相乘,如图1.27所示。
图1.27 speed*deltatime
既然已经有了归一化的方向向量和与时间相关的速度值,就需要将这两个数值相乘并将结果赋给对象目前的坐标。首先,在组件面板找到StaticMeshComponent,并将它拖曳到事件图表。这个操作将生成一个节点,从这个节点中我们可以提取包含在对象的网格组件(mesh component)中的任何数据。
下一步,我们希望获取网格的位置坐标。方法之一就是查看对象的变换属性并提取位置(location)信息,从蓝色的输出引脚拖出引线,在搜索框内键入“Get World
”,选择Get World Transform选项创建节点。除了位置坐标,变换还包含对象的旋转和缩放信息。这一点很有用,因为我们希望目标移动的时候仍然具有旋转和缩放属性,并且从新的移动信息中获取信息来创建变换 的值。
现在我们希望将变换分解到组件部分。这样就可以在计算中仅使用变换的位置信息,同时保留旋转和缩放不变。从Get World Transform节点的输出引脚拖出引线,搜索Break Transform节点并添加到事件图表中。
现在我们需要添加必要的节点为刚提取出来的位置信息添加速度和方向。右键单击事件图表的空区域,搜索并选择Make Transform节点。这个将时我们运算的最后一个操作,所以将它放在蓝图靠右的位置。Make Transform节点有3个输入:Location、Rotation、Scale。将Rotation、Scale与之前创建的Break Transform节点的对应输出引脚相连。
下一步,我们需要将方向向量与浮点型的速度数值相乘。从Normalize节点的输出引脚拖出引线,搜索“*”或“multi”,选择Vector * Float,然后将绿色的输入引脚与速度乘以deltatime的节点的输出引脚相连。
最后一步是将速度与方向添加至运算得到的当前位置。单击最新创建的乘法节点的黄色向量输出引脚,拖出引线至事件图表空白区域,搜索“+”,选择Vector+Vector节点。确保向量相加的输入引脚与乘法节点的输出引脚相连后,将另外一个输入引脚与Break Transform节点的Location输出引脚连接。最后,将向量加法节点输出引脚与Make Transform节点的Location输入引脚相连接,如图1.28所示。
图1.28 最终的蓝图
既然我们计算了变换,就可以通过变换的值来调整目标actor的位置了。前面已经使用deltatime使得速度和方向的变化与帧率无关,因此可以简单地使用事件Tick节点来实现每帧都触发移动(move)动作。鼠标右键事件图表的空白区域,搜索“Event Tick”并选择事件Tick,将它放置在Make Transform节点的右边。
为了移动目标actor,我们需要使用Set Actor Transform节点。从事件Tick的执行引脚拖出引线至图表的空区域,查找Set Actor Transform
并选择节点。然后连接Make Transform节点的Return Value输出引脚至Set Actor Transform节点的New Transform输入引脚,如图1.29所示。
图1.29 每帧更新位置
如果现在编译、保存蓝图,然后开始测试游戏,你期待看到什么结果?目标圆柱体会在游戏开始的时候根据设定的速度和方向移动。然而,由于我们没有任何引起目标停止运动的指令,所以目标圆柱体将随着游戏运行一直移动,甚至会穿越场景中的对象。为了解决这个问题,我们需要一个逻辑来周期性的改变目标的方向。这将使目标像移动的标靶一样,在两点之间规律地来回移动。
我们需要设置两个节点,为方向变量设置两个不同的值。拖曳direction变量至事件图表的空白区域并选择设置,生成一个有X、Y、Z坐标的节点。我们可以用它来改变direction变量的值,使这个值与我们赋予的初始值不同。我们希望有两个这种类型的节点,再拖曳direction变量至空白区域生成另一个节点,将这两个节点的Y轴的值分别设为10
和-10
。
现在我们需要一个方法在这两个节点之间转换,使方向就会重复地改变。希望两组动作(action)在每次切换之前交替执行一次时,可以使用FlipFlop节点。这适用于我们这个项目,所以鼠标右键单击事件图表的空白区域搜索“FlipFlop
”,选择并放置好节点,然后与刚创建的两个direction节点连接。
最后,我们需要确保在执行方向转换之间有一些延迟。否则,方向将会在每一帧都改变,目标对象也就不会移动了。为了实现这一步骤,从FlipFlop节点的执行引脚拖出引线至空白区域,搜索Delay节点。这个节点将允许我们设置一个以秒为单位的延迟时间,而在这个节点以后的执行命令将会被延迟这段时间后执行。将Delay节点放在Set Actor Transform节点和FlipFlop节点之间,设置延迟时间为6秒。在6秒的延迟后,执行FlipFlop的转换功能。最终的结果如图1.30所示,如果你完成了,请编译并保存蓝图。
图1.30 改变方向蓝图
现在我们已经将蓝图更新过了,可以测试查看目标圆柱体对象是否按照预期进行移动。首先,我们需要将目标圆柱体对象放在Y轴方向上没有障碍物的地方,确保在Y轴上运动不会与其他物体碰撞,这里采用的坐标是(410,680,180),仅供大家参考。
单击播放按钮,如果蓝图正常工作的话,你将会看到圆柱体在两个定点之间来回的移动。
使用蓝图的优点之一是它创建了一个功能性模板,这个模板可以被场景中的很多对象进行使用。在Blueprints
文件夹中找到CylinderTarget_Blueprint
并将它直接拖到3D视图中,可创建另一个继承原始目标圆柱体功能的圆柱体。通过这个方法,我们通过仅仅使用设置蓝图逻辑,就可以快速地创建很多移动的目标。
本章通过UE4蓝图创建了第一个原型,迈出了游戏开发的第一步!
在本章中,利用FPS模板创建了一个工程和一个初始关卡。然后设置了一个目标,通过改变自身颜色来响应子弹的射击。最后,设置了一个蓝图,能够快速地创建很多移动的目标。读者在本章所学到的这些技巧,将为后续章节创建更加复杂的交互性行为打下扎实的基础。
你或许会希望花更多的时间来调试游戏原型,包括布局、目标移动速度。由于我们将继续游戏开发,后续很多时候都在候选效果面前徘徊并作出选择。蓝图可视化编程最好的地方就是可以快速地让用户测试自己的想法。用户学到的每个技巧都将让你获得更多的游戏开发经验,而这些经验可以在探索和做原型时获取。
在接下来的第2章中,我们将利用FPS模板进一步地了解玩家控制器(player controller),将拓展控制角色移动和射击的蓝图,并且制作更多有趣的视觉冲击和音效。
[1] 译者注:目前通过Epic Games launcher启动Unreal编辑器速度有些慢,建议读者找到安装目录,比如“C:\Program Files (x86)\Epic Games\4.11\Engine\Binaries\Win64”,将UE4Editor.exe发送快捷方式到桌面。
[2] 译者注:Viewport(视口)是虚幻编辑器的一个显示窗口,可以显示前视口、侧视口、顶视口和透视口。关于Virewports(视口)的更多信息,请参照Trash.虚幻编辑器用户指南文档。
[3] 译者注:Actor是一个可以放置在世界中或者在世界中产生的对象。这包括类似于Players(玩家)、Weapons(武器)、StaticMeshes(静态网格物体)等。
[4] 当速度与delta time相乘后,假设speed为10,即为每秒10米,而不是每帧10米,能够防止帧速率不稳定时出现的状况。
在本章中,我们将通过修改玩家控制器(player character)蓝图,来扩展在第1章中创建的射击交互的核心部分。FPS模板中玩家控制器的蓝图——特别是当它与上一章相对简单的目标圆柱体的蓝图比较时——乍一看很复杂。我们将会分析玩家控制器蓝图并将它分解为很多部分,弄明白每个部分的功能,以及它们组合在一起时能够控制角色和射击的原因。
我们可以很容易且快速地使用现有的资源来照着搭建这个蓝图,甚至不需要花时间去思考它是如何完成这些功能的。但是,我们必须确保当问题出现时能够尽快尽量完美地解决,并且能够扩展玩家控制,以便更全面地符合我们的需要。所以,花点时间来理解这些内部资源有助于将来项目的推动。
在本章的结尾,我们希望能成功修改角色控制器。这样我们就可以为角色添加冲刺、摧毁游戏对象功能,在摧毁对象时附加爆炸效果和声音效果。随着我们达成这些目标,也就完成了一下技能的提升。
开始探索FirstPersonCharacter
蓝图,以使玩家在关卡中移动时拥有更多的战术选项。没做修改前,玩家还只是局限在一个速度进行移动。我们可以通过蓝图节点监听按键的动作来进行调整,将调整移动速度功能附加在CharacterMovement
蓝图组件中。
现在我们打开FirstPersonCharacter
蓝图。它与第1章的Cylinder- Target_Blueprint
蓝图位于同一个文件夹下。在内容浏览器中找到FirstPerson- Character,双击打开蓝图。选中事件图表标签,你将发现有许多的蓝图节点。我们首先研究的是加了Stick input注释的这一组节点,如图2.1所示。
图2.1 输入控制蓝图模块
红色的触发器节点在每帧都被调用,并将TurnRate和LookUpRate节点的值分别通过一个控制器输入传递出去。这些值通常与我们实际用的模拟摇杆的上/下、左/右轴相匹配。注意到这里只有两个输入轴触发器,却完成了向上看/向下看、向左转/向右转等4个功能,因为向上看与向下看传递的Axis Value为正值和负值,向左转和向右转也是一样,传递的Axis Value为正值和负值。
然后,两个轴向触发器的值都与一个变量相乘,表示玩家转身或向上(下)看的速率。这些值也都与world delta seconds相乘来归一化,避免了不同帧率的影响,尽管触发器会每帧都调用,但是玩家速率保持不变。3个输入引脚的乘积分别传递给Add Controller Yaw Input函数和Add Controller Pitch Input函数。这两个函数将控制器输入转化为展现在玩家摄像机中的效果。
在Stick Input蓝图节点组下方有另外一个注释为Mouse Input的节点组,它们看起来很像。Mouse Input转换鼠标移动输入,与轴向摇杆控制器不同,它直接获取数据然后将这些值直接传递给相应的Add Controller Yaw Input函数和Add Controller Pitch Input函数,而不需要像模拟输入做那些计算。
管理玩家移动功能的节点组的设置与摇杆和鼠标输入节点组有些类似。从输入轴MoveForward和输入轴MoveRight获取的轴值是由控制器或键盘输入的。与Stick input节点组一样,Movement input节点组也通过返回负的轴值来完成向后和向左运动的功能。在运动转换中很大的不同是我们需要actor移动的方向,这样移动转换可以被应用正确的方向。方向由Get Actor Forward Vector节点和Get Actor Right Vector节点获取并附加给Add Movement Input节点的World Direction输入引脚,如图2.2所示。
在新的版本中,添加了对VR支持,新增了First Person Camera、Get Forward Actor、Get Right Actor、Is Head Mounted Display Enabled等节点,如图2.3所示。
图2.2 Movement input节点组(4.7.6版本)
图2.3 Movement input节点组(4.12.5版本)
最后一个与移动相关的就是注释为Jump的节点组。它由输入动作Jump触发节点、Jump函数和Stop Jumping函数组成,输入动作Jump触发节点检测与跳起事件绑定的按键的按压和释放,并且从按键按下至抬起的时间内都有相关函数进行事件监听。
在FPS模板在蓝图中,绑定了一些玩家输入动作来产生一些行为,比如向前移动或跳跃。为了创建新类型的行为,我们将新的物理控制输入添加到玩家动作当中。为了改变游戏的输入设置,单击虚幻编辑器菜单的编辑按钮,找到项目设置(Project Settings)选项。在弹出的窗口左侧,引擎分类下找到输入(Input)选项并选择。
在引擎-输入(Engine-Input)菜单中的Binding(绑定)分类下,有两个部分,分别称为:Action Mappings和Axis Mappings,其中,Action Mappings监听按键和鼠标单击事件触发玩家动作;Axis Mappings针对的玩家移动和事件都有个范围,比如W键和S键同样影响Move Forward动作,但是结果不一样(控制角色向前走/向后走)。比如冲刺和变焦功能,它们有启用或不启用等两个选项,所以我们会将它添加作为Action Mappings。动作和按键映射(Action Mappings,Axis Mappings)提供了一种通过在输入行为和激活行为的按键之间插入一个中间层来方便地映射按键和坐标轴的机制。动作映射针对按键按下和释放,而坐标轴映射则针对具有连续范围的输入。
单击Action Mappings右侧的加号两次添加两个Action Mappings,将第一个重命名为Sprint,从下拉菜单中选择左Shift键与Sprint事件绑定。将第2个动作命名为Zoom,然后将它与鼠标右键绑定,如图2.4所示。
图2.4 按键绑定
上面我们讲到了输入节点接收控制器输入并将它应用到游戏角色中,既然有了基本的了解,接下来将为玩家扩展冲刺(Sprint)功能。我们需要在FirstPerson- Character
蓝图中设置好一系列的节点。
首先我们需要创建触发器来激活冲刺功能。回想我们之前将冲刺动作映射到左Shift键,为了进入那个输入触发器,需在事件图表的空白区域单击鼠标右键,搜索Sprint,选择输入>>动作事件>>Sprint(InputAction Sprint)事件节点。
现在我们希望修改玩家的移动速度。如果你尝试添加一个新的节点,在搜索框内搜索speed并且勾选情境关联(context-sensitive),那你将仅仅只能找到检测最大速度并校验是否超过最大速度的节点,而并不能设置最大速度。所以,我们需要检索到一个值,从FirstPersonCharacter
附加到actor的角色运动组件上。查看组件面板并选中CharacterMovement(继承)。之后,细节面板将会出现一系列的变量,如图2.5所示。
在这个变量列表中,你可以找到Max Walk Speed。这个变量的值定义了玩家可以达到的最大移动速度,并且它需要成为冲刺(Sprint)函数的目标。我们希望从角色移动组件中获取这个值并添加到事件图表中。为了做到这个,在组件面板选中CharacterMovement(继承)组件,将它拖曳到事件图表中,放置在左Shift节点附近。这个操作将生成一个Character Movement节点,如图2.6所示。
图2.5 CharacterMovement(继承)的细节面板
图2.6 Character Movement节点
从Character Movement节点的输出引脚拖出引线至事件图表的空白区域,确保勾选了情境关联,在搜索框内键入“walk speed”,找到设置Max Walk Speed,将输出动作Sprint节点的Pressed输出执行引脚与设置Max Walk Speed节点的输入执行引脚连接,使得按下左[Shift]键的时候能够修改最大移动速度。最后,在输出动作Sprint节点中改变Max Walk Speed的值,将默认数值由0.0改为2200。
我们同样需要确保玩家能够在左[Shift]按键抬起的时候减速。为了做到这个,再次从Character Movement节点的输出引脚拖出引线,然后在搜索并添加另一个设置Max Walk Speed节点。这次连接输出动作Sprint节点的Released输出执行引脚和新的设置Max Walk Speed节点的输入执行引脚,然后将Max Walk Speed的默认值0.0改为600。为了养成良好的添加注释的习惯,选中本节我们添加的所有节点后,按下[C]键(或者单击鼠标右键,选择注释组>>从选中项中创建注释,将注释命名为Sprint
,如图2.7所示。
图2.7 Sprint蓝图
现在编译、保存,最小化蓝图后在UE4编辑器中按下播放按钮进行测试。你将会注意到当你在移动过程中按下左[Shift]按键时,玩家角色显著地提高了速度。
现代FPS的核心元素就是以瞄准镜的形式将FOV(field of view,视野)呈献给玩家。这是一个很重要的因素,给游戏带来了精确感和控制感。现在将这一功能的简化版添加到项目中。
在事件图表中靠近Mouse input节点组的空白区域单击鼠标右键,搜索输入动作Zoom(InputAction Zoom)触发器节点并添加。我们希望修改FirstPerson- Camera组件中的FOV值,于是到组件面板中找到FirstPersonCamera并将它拖入事件图表中。
从FirstPersonCamera节点的输出引脚拖出引线,搜索Set Field Of View(变量)节点并选择。减小视野给我们一种效果:在屏幕的中央将一小块区域进行放大。由于我们默认的视野值是90,处理后的视野值在节点中设为45,如图2.8所示。
图2.8 Zoom view蓝图
连接输入动作Zoom的Pressed输出执行引脚和设置节点的输入执行引脚。编译、保存,最小化蓝图后在UE4关卡编辑器中单击播放进行测试。你将注意到当你右键单击鼠标时,视野会缩小,看到的物体也感觉更近了。由于主摄像机中为角色剔除的对象会产生变形,所以接下来我们将优化这一行为。
为了让视野平滑转换,我们需要创建一个将变化逐渐地显示出来的动画。为了实现这个效果,先回到事件图表中的FirstPersonCharacter蓝图。
按住[Alt]键并单击输入动作Zoom节点的Pressed输出执行引脚的连接,从Pressed引脚拖出引线,搜索并选择添加时间轴(Add Timeline)来添加一个时间轴(timeline)。时间轴允许我们在指定的时间内改变一个值(比如摄像机的视野)。
批注
组件末尾的(继承)标签告诉我们这个组件的功能在C++ 脚本中定义,而不是在蓝图中定义的。组件的功能直接在C++ 脚本中定义,这在进行移动或网格定义的物理计算等复杂工作的组件中很常见的。如果你对继承组件的代码感兴趣,你可以右键单击该组件打开“.h”头文件。
为了改变时间轴的值,双击时间轴节点,打开时间轴编辑器。在编辑器左上角你将看到4个按钮,每个按钮都能添加一个不同类型的并且将在时间轴过程中改变的值。因为视野被一个数值所表示,所以我们单击[f]按钮(添加浮点型轨迹(Add Float Track)),将添加一个时间轴,并且光标闪烁提示修改标签名,我们将它命名为Field of View。现在我们需要在不同的时间间隔上编辑数值,如图2.9所示。
图2.9 编辑时间轴Field of View
为了完成上图所示的时间轴,我们需要按住[Shift]键并在(0,0)点附近单击(也可以在(0,0)点附近单击鼠标右键>>添加关键帧),你将看到在图表的左上角出现时间和数值区域。这允许我们根据数值精确地调整时间轴。确保时间设置为0.0
,默认视野(FOV)的值为90
,即(时间,值)为(0.0,90)。
我们希望放大的动画尽量快速完成,所以在时间轴编辑器的顶端,找到长度(Length)右边的输入框,将默认值修改为0.3。这个操作可以将动画限定在0.3秒内完成。现在按住[Shift]键并单击图表白色区域的末端,仔细地将Time区域设为0.3,值区域设为45(如果觉得手动调比较麻烦,可以在输入框内直接输入)。这时数值区域内所有的数值都自动地被转换为近似的浮点型数值,如图2.10所示。这些数值差并不能产生更多的视觉上的差异,所以我们并不需要太关心这个数值转换。
图2.10 数值转换
注意到时间线反映的值逐渐地从90减小到45,这意味着当这个动画被调用时,玩家的视野将会平滑地放大,而不是迅速地从90直接变为45。这样就利用时间轴来设置数值在一段时间内的改变。
现在转到事件图表,我们希望用时间轴来设置视野,如图2.11所示连接蓝图。
图2.11 连接时间轴节点
从时间轴_0节点的Field of View输出引脚拖出引线至设置(Set)节点的输入引脚。这样将重写之前写死的值45。然后,将时间轴节点的Update输出执行引脚与设置节点的输入执行引脚相连。这个操作将使得视野值(FOV value)通过传递新的值给执行函数来随时更新。由于在时间轴里已经设置过了,视野的值在这里就不需要设置了,时间轴使得视野值从90到45在0.3秒内逐渐变化。
批注
UE4中有两种基本的方法实现动画。时间轴非常适合于简单的改变数值,比如门绕着轴旋转,对于更复杂的、基于角色的或者是电影般的动画,可以使用引擎内建的动画系统。Matinee(关键帧)和复杂的动画超出了本书的范围,但是有很多专用的学习资源是使用matinee的。建议从Unreal wiki相关的教程开始,参见
wiki
网站的相关介绍。
最后,我们希望当鼠标右键松开时,取消放大视野。为了实现这个效果,从输入动作Zoom节点的Released引脚出出引线连接至时间轴节点的Reverse引脚。这个操作将使得松开鼠标右键时,原来拉近视野的动画倒着播放(即拉远视野),保证在还原到原来视野时有一个平滑的切换动画。同样地,选中完成该功能的所有节点,为节点组添加注释,可方便后续查阅时能够快速了解这个节点组完成的功能。
现在请编译、保存蓝图,最小化蓝图编辑器后,在UE4编辑器中按下播放按钮进行测试,当你按下和松开鼠标右键时,视野是否能相应地拉近和拉远。
我们已经给玩家角色增加了新的玩法,现在需要将关注点回到游戏的射击机制上。从刚才测试的情况来看,从枪口射出的子弹(小球)在空中的运动轨迹是一条速度相对较慢的弧线,我们希望适当加快子弹的速度让它看起来更像。
为了更改子弹的属性,我们需要打开同样位于Blueprints
文件夹中的蓝图FirstPersonProjectile
。打开蓝图后,找到组件面板里的并选择Projectile。这是一个控制子弹移动组件,其已经被添加到球体网格和碰撞器,用于定义当子弹在游戏世界中被创建后怎样运动。
在细节面板中,Projectile具有一系列的属性值。这些值与移动相关并可以被修改,我们将修改其中部分属性值,如图2.12所示。
图2.12 修改projectile的属性值
首先,找到Initial Speed和Max Speed属性,默认值为3000。Initial Speed定义了当子弹被创建时的飞行速度。Max Speed定义了在子弹创建之后给子弹施加力,子弹能够达到的最大速度。如果我们有一个火箭,或许会期望在火箭发射后给它施加一个加速度来表示推进器工作。然而,由于我们正在表示从枪口射出的子弹,将初设速度设置的最快更有意义,因此将Initial Speed和Max Speed都设为之前速度的两倍,即6000。
此外,你或许注意到当子弹碰到墙和其他物体时会反弹,就像碰到了一堵橡皮墙一样。然而我们要模仿一个更硬、更强力的冲击弹丸。为了移除反弹属性,在细节面板中找到Projectile Bounces分类,将Should Bounce取消勾选。其他的选项只有在Should Bounce勾选时才起作用,所以不需要去调整它们。
现在请编译、保存蓝图,最小化蓝图编辑器后,在UE4编辑器中按下播放按钮进行测试,你将发现枪口射出的子弹打得更远、更有力度。
既然我们已经拥有了更好的玩家移动和射击属性,将我们关注的点转向敌方目标。现在的效果是射击目标圆柱体后圆柱体会变成红色。然而,目前没有任何可以被玩家完全摧毁的目标。
我们可以通过添加蓝图逻辑来添加更多的与目标之间的交互,比如在击中目标两次及以上时销毁物体,同时增加玩家的奖励,一旦目标被摧毁,就产生一个令人满意的声音和视觉效果。
我们需要保证CylinderTarget
蓝图中有使目标圆柱体的状态变化逻辑。打开Blueprints
文件夹中的CylinderTarget
蓝图,找到事件Hit节点组。当我们的子弹击中目标圆柱体时,这些节点通知圆柱体改换红色材质。为了在圆柱体被击中两次后给圆柱体添加改变圆柱体行为的能力,我们需要为蓝图添加一个检查圆柱体被击中次数的计数器,然后根据状态显示不同的结果。如图2.13所示操作,可以帮助我们完成这个功能。
图2.13 添加计数器
图2.13 添加计数器(续)
为了在蓝图中创建多结果的条件逻辑,我们要利用分支(Branch)节点。这里的分支节点使用一个布尔变量作为输入。由于布尔变量的值只有真或假两种情况,分支节点只能产生两种结果。这两种节点可以通过连接的其他节点的输出执行引脚执行,代表真通道和假通道。
创建分支的第一步时定义你的布尔变量代表什么,并且哪些情况会将条件值从假改变为真。我们要创建一个表示目标被击中的初始状态,然后当目标再次被击中时被摧毁。开始创建一个Primed
布尔变量吧。
回忆之前在我的蓝图(My Blueprint)面板定义的变量,读者应该看到了之前为速度和方向定义的两个变量。单击加号(+)按钮添加变量,新的变量类型默认为布尔型,所以就不用去修改了,将它重命名为Primed并勾选可编辑使得在外部也能修改变量。最后,编译、保存蓝图。因为我们并不希望目标在一次都没被击中的情况下就处于primed状态,所以我们将变量的默认值设为假(false)(编译保存后默认值的勾选框不勾选)。
既然有了Primed布尔变量,将它从我的蓝图面板拖放到事件图表中,选择获取选项。这个将从变量中获取状态数据(真/假),同时也让我们能够在蓝图中使用它。从Primed节点的输出引脚拖出红色引线到事件图表,搜索并添加分支节点。
最后,我们可以将分支节点添加到事件Hit蓝图节点组。按住[Alt]键单击节点之间的连线,断开类型转换为FirstPersonProjectile节点和Set Material节点的连接。先将Set Material节点暂时拖放到一边,然后连接类型转换为FirstPersonProjectile节点的输出执行引脚和分支节点的输入执行引脚,现在将在目标圆柱体被击中时都调用分支来进行判断。
既然我们已经将分支节点激活,就需要给目标圆柱体一些指令来响应每个状态。我们希望创建的目标可以有这3种状态:默认(Default)、击中一次(Primed)和销毁(Destroyed)。由于销毁一个actor不能执行任何行为,所以在目标销毁后就不能有任何行为发生。因此,我们只需要关注默认、击中一次这两种状态。
首先来完成默认状态。由于这个分支监听当圆柱体在每一次被击中后圆柱体上发生的事件,所以我们希望执行之前添加的改变材质事件。如果目标现在还没有被击中,并且现在被第一次击中,那么我们需要将材质转换为红色。此外,我们也要将Primed布尔变量设为真(True)。通过上述设置,当目标圆柱体再一次被击中时,分支节点把行为传递到其他执行队列,节点的假(False)执行队列如图2.14所示。
图2.14 执行队列
将Set Material节点移到分支节点的右边,将分支节点的假(False)输出执行引脚与节点Set Material的输入执行引脚相连。从我的蓝图面板中拖出Primed变量至事件图表,选择设置选项,将Primed变量的输入执行引脚与Set Material节点的输出执行引脚相连,并勾选设置节点的Primed选项。这个操作将保证当目标被第二次被击中时,分支的判断结果为真。
下一步就是去定义从分支节点的真分支触发的动作序列。在之前早些时候,按照我们的定义,正在摧毁一个目标时希望能够完成3件事:听到爆炸声响、看到爆炸的效果、将目标对象从游戏世界中移除。我们先从经常忽视的,但是非常关键和影响游戏体验的声音元素开始。
我们可以设计的最基本的交互就是:在游戏世界中的某个位置立即播放一个“.wav”声音文件,并且这个功能可以完美地按照我们的意愿运行。从分支节点的真执行引脚拖出引线,搜索Play Sound at Location节点,如图2.15所示。
图2.15 添加Play Sound at Location节点
Play Sound at Location是一个简单的节点,其承载一个声音文件和一个位置输入,并在该位置上播放声音。这个项目中有几个默认的声音文件资源,单击Sound输入引脚旁的选择资源,用户可以从下拉菜单中看到声音文件的列表,找到并选择Explosion01作为爆炸音效。
既然我们已经选择了声音资源,就需要定义在哪里播放这个声音。还记得怎样通过设置目标圆柱体的网格组件设置视野吗?可以使用与之相类似的方法来进行设置,提取出位置信息,然后将位置向量与声音节点直接连起来。然而,事件Hit触发器把这个事情简化了。
事件Hit节点的输出引脚之一为Hit Location。这个引脚包含了被事件Hit节点检测到的游戏世界中两个对象发生碰撞的位置信息。这个位置是产生爆炸效果的绝好位置,从事件Hit节点的Hit Location引脚拖出引线与Play Sound at Location节点的Location引脚相连接。
编译、保存、最小化蓝图后,在UE4编辑器中单击播放按钮进行测试。射击某一个移动目标使它变红,然后接下来的每一次射击都将产生一个爆炸音效。
既然我们的爆炸声音效果已经起作用了,现在开始添加视觉效果并销毁圆柱体,参照图2.16进行设置。
图2.16 设置爆炸效果
从Play Sound at Location节点的执行引脚拖出引线至事件图表的空白区域,搜索并添加Spawn Emitter at Location节点。
Spawn Emitter at Location节点看起来与Play Sound at Location节点类似,不过它还多了旋转输入引脚和Auto Destroy开关。在Emitter Template下方的下拉菜单中,找到并选择P_Explosion效果。这是FPS模板中自带的另一个标准资源,它将在发射器被添加的地方产生一个令人满意的爆炸效果。
因为我们希望爆炸效果与爆炸音效在同一个位置生成,从事件Hit节点的Hit Location引脚拖出引线与Spawn Emitter at Location节点的Location引脚相连接。爆炸是一个从所有的角度看都一样的三维效果,所以我们可以暂时不管Rotation输入引脚。Auto Destroy开关决定粒子发射器是否能被多次触发,一旦这个粒子效果被创建,我们就将包含这个粒子发射器的actor销毁,所以我们勾选Auto Destroy。
最后,我们希望在爆炸的声音和视觉效果完成后,从游戏世界中移除目标圆柱体。从Spawn Emitter at Location节点的输出执行引脚拖出引线,搜索Destroy Actor节点(为了找到这个节点,你可能需要暂时地将情境关联取消勾选)并添加。这个节点只有一个目标输入,默认为self。由于这个蓝图包含了我们想摧毁的圆柱体对象,并且self就是我们想摧毁的,所以我们不需要对这个节点进行设置。
提示
Emitter(发射器)是一个在特殊位置产生粒子效果的对象。粒子效果收集了很多小的对象,将它们结合起来创建液体、气体或其他不能触摸的效果,比如水的冲击、爆炸或光束。
扩展整个事件Hit节点序列的注释,并且更新上面的文本描述,写清楚这个节点组完成了哪些功能,如图2.17所示。
图2.17 最终的蓝图节点组
当你完成了注释的添加,请编译、保存,最小化蓝图后,在UE4编辑器中单击播放按钮进行测试。当你使用枪发射子弹击中目标圆柱体两次后,将看到爆炸效果并听到爆炸音效。
游戏中添加音效和视觉效果,是射击游戏中主角该有的能力。同时,本章的游戏还添加了目标与玩家之间的交互。我们可以将前两章的技巧结合起来,创建更复杂、更有趣的行为。
在本章中,我们创建了一些自定义的玩家控制来允许玩家加速冲刺和拉近的视野。在这个过程中,我们探索了移动控制器时怎样将玩家的输入转换为游戏体验。我们也通过使用时间轴创建了简单的动画。然后通过为敌方目标添加爆炸效果和音效,在目标被击中两次时添加不同的状态,增加了更多的玩家与环境的反馈。
在第3章中,我们将探索添加用户界面(User Interface,UI),为玩家提供游戏世界中的状态反馈。