Blender-和-Godot-游戏开发-全-
Blender 和 Godot 游戏开发(全)
原文:
zh.annas-archive.org/md5/27012afe29617ebdbb939cc1841e6f8d译者:飞龙
第一章:前言
使用 Godot 和 Blender 进行游戏开发 是一本全面介绍,适合那些刚开始构建 3D 模型和游戏的新手,让你能够利用这两种技术的功能来创建动态、交互性和吸引人的游戏。
本书将从介绍低多边形建模是什么开始,然后深入探讨如何使用 Blender 创建、绑定和动画化我们的模型。我们还将确保这些资产是游戏就绪的,这样你就可以轻松地将它们导入 Godot,并有效地使用你的资产。然后,在 Godot 中,你将使用游戏引擎来设计场景,处理光影,并将你的 3D 模型转换成交互式、可控制的游戏资产。
本书结束时,你将在 Blender 和 Godot 之间拥有一个无缝的工作流程,这个流程专门针对游戏开发,并会根据我们的指导和说明创建一个点选冒险游戏。在此之后,你应该能够利用这些新获得的技术从构思到完成创建自己的 3D 游戏!
本书面向的对象
本书是为那些希望从 2D 游戏过渡到 3D 游戏的游戏开发者而写的。你应该对 Godot 有基本的了解,能够导航用户界面,理解检查器面板,创建场景,为游戏对象添加脚本等。有 Blender 的经验有帮助,但不是必需的。
本书涵盖的内容
第一章,创建低多边形模型,涵盖了在 Blender 中创建低多边形模型的过程。你还将了解如何利用修改器来加速这个过程。
第二章,构建材料和着色器,展示了如何为你的模型创建和分配不同的材质,并了解着色器在哪里发挥作用。
第三章,添加和创建纹理,教你如何为你的模型准备纹理。本章还涵盖了应用第三方纹理和创建自己的纹理。
第四章,调整摄像机和灯光,介绍了不同的灯光类型以及如何捕捉场景的镜头。你将在 第十章,利用光影使事物看起来更好,在 Godot 的背景下回顾这些概念。
第五章,设置动画和绑定,讨论了动画的概念,以及是否在 Godot 或 Blender 中进行动画是正确的选择。一旦我们确定在 Blender 方面的优势,你将绑定并动画化一个简单的模型。
第六章,导出 Blender 资产,探讨了最关键且经常被忽视的话题:从 Blender 导出你的模型。你将特别展示一个最适合 Godot 引擎的格式。
第七章,将 Blender 资源导入 Godot,方便地展示了如何将你的模型导入 Godot。不同应用程序之间的转换并不总是顺利的,因此你还将了解到不足之处和解决方案。
第八章,添加声音资源,探讨了在 Godot 引擎中使用声音。在发现引擎支持的不同类型的音频文件后,你将参与一个简短的练习来播放声音文件。
第九章,设计关卡,将是构建点按冒险游戏的一系列练习的开始。为了启动这项工作,你将使用 GitHub 仓库中的模型来设计关卡。
第十章,使用灯光和阴影使事物看起来更好,介绍了你可以在关卡中部署的不同灯光类型,以增强游戏的外观和感觉。为了进一步完善场景,你还将发现全局照明和后期处理效果的使用。
第十一章,创建用户界面,讨论了用户界面的必要性。然后,你将利用一系列 Godot UI 组件来组成一个笔记。最后但同样重要的是,你将研究在 Godot 中创建主题可能如何节省时间。
第十二章,通过摄像头和角色控制器与世界交互,介绍了不同游戏平台上的不同摄像头类型和设置。在获得对游戏世界的基本了解后,你将继续进行检测用户输入,这对于你正在构建的游戏类型至关重要。最后,你将使用这些信息将游戏角色移动到指定的位置。
第十三章,以声音和动画结束,完成了我们小游戏的核心机制。为此,你将为某些游戏对象添加音效和动画。此外,你将在 Godot 中创建一个简单的动画,并为玩家触发此动画创建必要的条件。一旦完成所有游戏内要求,你将为玩家加载一个新的关卡。
第十四章,结论,展示了如何将你的游戏导出到 Windows,以便你可以与世界分享。你将通过了解 Godot 还能为你提供什么来结束这一章和整本书。
为了充分利用这本书
你需要在你的计算机上安装 Blender 2.93 和 Godot 3.4.4 的 Windows 版本。所有视觉示例和代码样本都已针对这些版本进行测试。如果你安装了较新或较旧的版本,你可能会注意到差异。

了解如何使用 GitHub 的基本知识可能会有所帮助。或者,您可以下载整个仓库并使用本地副本进行工作。
如果您正在使用本书的数字版,我们建议您亲自输入代码或从本书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将有助于您避免与代码复制粘贴相关的任何潜在错误。
下载示例代码文件
您可以从 GitHub 下载本书的示例代码文件github.com/PacktPublishing/Game-Development-with-Blender-and-Godot。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有其他来自我们丰富的图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!
下载彩色图像
我们还提供了一份包含本书中使用的截图和图表彩色图像的 PDF 文件。您可以从这里下载:packt.link/0KyZi。
使用的约定
本书使用了多种文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号。以下是一个例子:“如果您将半径增加到10.0,会发生一些有趣的事情。”
代码块设置如下:
extends AudioStreamPlayer
func _unhandled_key_input(event: InputEventKey) -> void:
if event.is_pressed() and event.scancode == KEY_SPACE:
stream_paused = false
else:
stream_paused = true
粗体: 表示新术语、重要单词或屏幕上出现的单词。例如,菜单或对话框中的单词会以粗体显示。以下是一个例子:“当你应用了Solidify修改器时,你一定看到了还有很多其他的修改器。”
小贴士或重要注意事项
看起来是这样的。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送至 customercare@packtpub.com,并在邮件主题中提及书名。
勘误: 尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata并填写表格。
盗版:如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过 copyright@packt.com 与我们联系,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为本书做出贡献,请访问authors.packtpub.com。
第一部分:使用 Blender 的 3D 资产
本书本部分为您详细介绍了如何在 Blender 中创建模型、纹理和动画。到本部分结束时,您将能够创建游戏准备资源。
在本部分,我们涵盖了以下章节:
-
第一章**,创建低多边形模型
-
第二章**,构建材质和着色器
-
第三章**,添加和创建纹理
-
第四章**,调整摄像机和灯光
-
第五章**,设置动画和绑定
第二章:创建低多边形模型
Blender 是一个经过多次迭代才达到现在水平的复杂程序。越来越多的不同行业的专业人士正在将其作为其他知名 3D 应用程序(如 Maya、3ds Max、ZBrush 和 Modo)的替代品进行调研。此外,Blender 对于业余爱好者和负担不起上述软件许可费用的人来说是一个很好的起点。此外,Blender 拥有一个有帮助且庞大的社区,该社区创建课程和教程。Blender Conference(BCON)是一个年度活动,您可以在这里见到专业人士。
在您开始使用任何类型的软件创建 3D 内容之前,您必须做出的一个重要决定是您打算在哪里使用您的资产——这直接影响到您完成任务时将遵循的风格和工作流程。一种工作流程被称为低多边形建模,通过这种建模方式,您可以创建具有最少细节的 3D 资产。
在本章中,我们将讨论为什么与其它工作流程相比,低多边形建模可能是有益的。在了解优势之后,您将学习如何使用不同的技术创建低多边形资产。我们将通过介绍一些可能证明不可或缺的修改器来结束本章。
在本章中,我们将涵盖以下主要主题:
-
理解低多边形模型
-
低多边形模型的优势
-
创建低多边形桶
-
使用修改器自动化
技术要求
要遵循涉及 Blender 的章节中的说明,您必须在您的计算机上安装必要的软件。Blender 网站——更具体地说,他们的下载页面www.blender.org/download/——包含了针对您平台的说明和链接。在这本书中,我们使用的是 Blender 2.93。尽管版本 3.0 将为新用户和现有用户提供有趣和令人兴奋的选项,但当前版本完全能够为您的项目创建游戏资产,并涵盖本书中的主题。
本书使用 GitHub 存储将在 Godot 章节中使用的代码。然而,相同的仓库(github.com/PacktPublishing/Game-Development-with-Blender-and-Godot)也托管了相关章节中使用的 Blender 文件。在合理的地方,仓库将包含每个特定章节内的Start和Finish文件夹,以便您从头开始或比较您的工作进度。
理解低多边形模型
简而言之,一个 3D 模型被认为是低多边形的,当它使用最少的面数来制作其最显著的特征,主要是其外观和感觉。然而,让我们更详细地看看它们。
在实时应用,如游戏引擎中,你的计算机的中央处理器(CPU)和图形处理器(GPU)负责处理你在屏幕上看到的视觉信息。在过去二十年里,趋势明显倾向于 GPU 一侧,因为 GPU 专门用于一项主要任务:处理图形。
在这方面,GPU 比 CPU 有优势,并且它们不会区分 2D 和 3D 图形。然而,虽然 2D 图像包含像素信息,3D 对象是由包含定义对象所需坐标信息的数据表示的。
尽管立方体在你屏幕上渲染后仍然是一堆像素,但定义立方体的数据本质上是由八个点组成的,这些点被称为顶点。为了演示目的,在以下截图中,Blender 的顶点大小设置已被更改,以便你可以更容易地看到这些顶点所在的位置:

图 1.1 – 立方体的八个顶点
这两个立方体是同一个对象,但可以以两种不同的方式渲染相同的八个顶点及其相互关系:一种看起来像一个实体对象(在左侧),另一种看起来是透明的(在右侧)。所以,请记住,顶点是定义对象形状的数据点,而不是它的外观。在本章的后面部分,你将学习如何使对象看起来不同,类似于前面截图所示。
在我们讨论是什么使模型成为低多边形之前,我们必须了解一些其他基本部分。你已经看到顶点是至关重要的组成部分,但还有两个概念你必须了解:
-
边
-
面部
让我们看看这些如何与顶点相关联。通过这样做,我们将朝着理解是什么使模型成为低多边形迈进。
3D 模型的组成部分
边是连接两个顶点的部分。就是这样简单。如果你再次查看图 1.1,你会看到并不是所有的顶点都是连接的。然而,当它们连接时,就称为边,并且 Blender 通过从一顶点到另一顶点的直线来表示它。
面,正如你可能已经推断出的,当你以某种模式排列顶点或边时,是一个逻辑结果。例如,一个立方体或六面骰子有六个面。尽管图 1.1让你看起来需要四个边来形成一个面,但有一个更简单的方法——那就是三个边就足够形成一个面。所以,三角形是最简单的面,也称为多边形。
低或高,有什么区别?
当你设计一个模型时,你正在创建多边形。模型中多边形的密度决定了模型是否可以被认为是低多边形。以下图显示了由 Sketchfab 用户MohammadRezae和DJS_05提供的低多边形和高多边形作品样本:

图 1.2 – 低面片与高面片模型的示例
你可以在 Sketchfab 等网站上找到许多不同面片数量的示例。
在行业中,如果你要求为你设计一个模型,你可能想要提到你希望以低面片形式完成。通常认为,如果你不提这一点,人们会假设它将尽可能多地使用面片,因为你希望你的模型尽可能详细,拥有足够的面片。所以,当你剪掉那些面片时,而不是当你保留它们时,这种区别才会被明确。
让我们再次关注我们的默认立方体。它是低面片还是高面片?它可能两者都是。虽然我们知道只需要八个顶点来创建一个立方体,但我们可以在连接原始角顶点的边缘上有更多的顶点。然而,这并不会对渲染结果产生任何影响。也就是说,电脑将需要更长的时间来渲染相同的视觉效果。
因此,如前所述,当你的模型只有足够多的面片来理解你想要设计的对象时,你将拥有一个低面片模型。
虽然 GPU 现在运行得很快,并且能够出色地渲染数百万个面片,但降低面片数可能感觉像是在走捷径,但有一些很好的理由说明为什么你可能不希望在项目中使用那么多面片。
低面片模型的优点
这里是一个遵循低面片建模实践的益处的快速列表:
-
面片更少
-
文件尺寸小
-
一种特定的艺术风格
-
容易原型化
-
无纹理或纹理最少
使用更少的面片当然意味着需要改变和担心的事情更少。简而言之,你将学习如何创建一个桶形模型,并在完成该练习后,你的模型将接近一千个面片。这个数字一开始可能看起来很高,但想象一下使用一个具有超过 10,000 个面片的 hi-poly 桶形模型。因此,如果你是 3D 建模的新手,低面片建模是一个很好的起点。
如果你决定修改你的模型,使用更多的面片数量将迫使你更加小心。所以,本质上,拥有更少的面片是令人安慰的,因为你将感觉你对你的创作有更多的控制。自然地,更少的面片也会导致文件尺寸更小。
艺术风格优势是优势列表中的一个非技术性项目。尽管如此,它可能是一个重要的决定。让我们专注于图 1.3,例如。你会看到为什么缺乏细节并不总是意味着缺乏想象力:

图 1.3 – 低面片模型景观
在这里,你可以看到足够的细节,可以判断出这里有一座教堂。也许这座教堂正对着一个城镇广场。山顶有一些雪。这是一个对冬季运动有吸引力的和平小镇吗?也许镇民们目前正在教堂里躲避一个恶棍?我们的想象力填补了细节。无论情况如何,无论游戏类型如何,3D 模型的低多边形方面并不会对创造力产生惩罚。事实上,在过去的几年里,我们看到越来越多的使用低多边形资产的游戏成为头条新闻。
如果你在一个小型游戏开发团队工作,或者你是唯一的开发者,你有时会想先专注于游戏机制,看看这个想法是否有趣。在这些情况下,你将希望快速原型化对象,以便将它们嵌入到你的代码中。当你正在工作的模型具有你想要设计的对象的通用形状并且有足够的细节时,你可能就完成了。这就是为什么它成为独立开发者中高度追捧的选择,因为你可以快速进入下一个模型,然后是编写你的游戏。本质上,低多边形建模就像原型设计,但它比放置一个圆锥体作为树、一个圆柱体作为桶或一个立方体作为箱子要精细几步。
列表中最后一个是纹理。这是一个过程,你给你的模型赋予一定的外观和感觉。沙质海滩通常看起来是黄色的。如果是岩石海滩,那么岩石可能会有不同的灰色调。因此,这主要是将颜色信息应用到你的模型表面。有时,这种颜色信息将由额外的数据补充,例如反射率、金属质感和粗糙度。我们将在下一章中了解所有这些内容。
人们常说,计算机世界中的大多数事物都是一种权衡。速度、质量和价格之间的权衡是一个常见的例子,你很可能只能得到其中的两个,而不是全部三个。尽管低多边形工作流程提供了许多好处,但也有一些局限性,但认识到这些局限性将帮助你找到解决方案或提前规划。
低多边形模型的局限性
如果你的模型需要显示损坏,如边缘缺失的部分或从表面吹出的某些块,那么你需要在这些区域引入更多的多边形。这仍然不会使其成为高多边形模型,但如果你喜欢一些动态细节,你必须考虑额外的多边形。
此外,如果你决定为你的低多边形模型动画,你需要在弯曲和扭曲(取决于你正在动画化的模型)的区域添加更多的多边形。
此外,由于多边形较少,你可能需要在场景的照明上发挥创意,以产生细节的错觉。尽管 图 1.3 中水的颜色在整个构图中都是相同的,但设计师使用了一些巧妙的方法来使场景看起来更有趣。首先,水的表面看起来是断裂的。这给人一种这种水体内有轻微运动的感觉。也许有一阵轻风。其次,一些断裂处应用了反射材料。这使得表面反射出地平线上更远处的物体。
我们将在接下来的章节中探讨如何克服这些限制,但就目前而言,让我们学习如何创建一些我们自己的低多边形模型。
创建低多边形桶
每个学科都有一套适合初学者的惯例。如果你正在学习一门新的编程语言,将“Hello World”输出到屏幕上是一个经典的例子。学习如何使用 3D 建模软件也没有什么不同。例如,一个桶、一个药水瓶或一个甜甜圈都可以从你熟悉的基本形状开始,比如圆柱体、圆锥体或环面。
在本节中,你将学习如何设计一个桶形,但首先,这里有一些有用的快捷键,它们将帮助你在这个章节中导航并完成我们将要介绍的任务:
-
旋转:中鼠标按钮 + 拖动鼠标
-
缩放:向前/向后滚动鼠标滚轮
-
移动:Shift + 拖动鼠标
Blender 中有很多快捷键,一旦你积累了更多经验,就可以根据自己的喜好更改它们。说到快捷键,这本书只列出了 Windows 快捷键。然而,当你看到 Ctrl 键时,它指的是 macOS 中的 Command 键。
当你第一次启动 Blender 时,你会看到一些选项。一个重要的选项是决定使用哪个鼠标按钮来选择对象。从历史上看,默认使用的是右鼠标按钮,但你可能会觉得这不太常见。如果你已经关闭了那个初始屏幕,并且对选择操作的鼠标按钮分配不满意,你仍然可以通过转到 编辑 菜单并选择 Blender 首选项 来更改它。在 快捷键 部分,展开 首选项 部分,如图下所示;你将能够更改包括 使用鼠标按钮选择 在内的许多设置:

图 1.4 – Blender 的首选项窗口
说到选择按钮,无论你选择哪一侧,另一侧将保留用于将3D 游标移动到新位置。3D 游标是在世界中放置的一个视觉标记。当你需要在特定位置添加新元素,或者需要将某些东西对齐到某个点时,3D 游标就是那个点。我们可能会在大多数练习中保持 3D 游标的位置不变,但请记住,如果左键用于选择,那么右键用于 3D 游标,反之亦然。
官方手册
由于这本书是关于游戏开发的,我们将专注于 Blender 的一个小而相关的部分。然而,有时候查看官方手册可能是个好主意,特别是对于快捷键。Blender 网站有一个不错的用户手册:docs.blender.org/manual/en/2.93/。
建模是一个多步骤的过程。它涉及从基础开始,并在过程中添加更多细节。以下是我们将如何设计一个桶:
-
从原始形状开始
-
编辑模型
-
塑形身体
-
分离盖子
-
完成身体
-
放置金属环
-
完成盖子
这个列表仅仅是一个示例工作流程,突出了 Blender 中的一些有用部分。当你获得更多经验并找到完成你心中想法的不同顺序时,你可以以任何对你有效的方式工作。然而,你很可能会从基本形状开始。
从原始形状开始
Blender 中的新场景包含一个立方体、一个相机和一个光源。由于我们将要创建一个更像圆柱体的桶,我们应该去掉那个立方体:
-
选择立方体,然后在键盘上按X键删除它。
-
触发位于对象菜单左侧的添加菜单。
-
在网格组下选择圆柱体。
添加新对象的快捷键是Shift + A,这将显示相同的选项列表。如果你想要删除其他默认对象,请随意操作,因为你总是可以使用添加菜单稍后添加它们。以下截图显示了你可以找到它的位置:

图 1.5 – 你可以向场景添加许多类型的原始形状
一旦你将圆柱体添加到场景中,你会看到圆柱体带有许多侧面;确切地说有 32 个。对于一个低多边形桶,这有很多面可以被减半,而你仍然会得到一个看起来不错的桶。
当你添加一个新对象时,屏幕左下角会出现一个面板。这个面板的标题将反映你当前尝试完成的事情。在这种情况下,它应该显示添加圆柱体。如果它看起来是关闭的,点击标题,它将展开以显示你可以更改的圆柱体属性。
默认选项都很好,除了顶点数量。然而,这也是一个尝试不同值并立即看到变化的好机会。当你做所有这些时,如果你点击离开你的圆柱体,该面板可能会消失。要将其恢复,请点击编辑菜单下的调整最后操作。当你觉得你已经掌握了编辑新对象属性的方法时,你可以设置相关值,如下面的截图所示:

图 1.6 – 16 个面就足够创建我们的桶
添加一个如圆柱体这样的原始形状会将一个新对象引入你的场景。你已经更改了它的基本属性,如顶点数量。这个数字定义了构成顶部和底部圆的点的数量,如前面的截图所示。所有这些都是在对象级别完成的;因此,你一直在对象模式下工作。现在,是时候深入挖掘并编辑这个圆柱体的更细微的细节了。
编辑模型
每次你更改某个值时,可能感觉就像是在编辑模型。然而,从 Blender 的角度来看,并非所有编辑都是相同的。当你从原始形状开始时,你可以执行更高级的操作,例如更改定义原始形状顶点数量的数量。这正是你所看到并做到的——你一直在编辑对象。
当你想对构成对象的顶点、面和边有更多控制时,你应该切换到另一个模式,这样你就可以使用这些属性,从而可以对模型的形状有更精细的控制。
Mac 快捷键
你可以使用菜单、按钮和其他界面元素来完成工作,但最终你将依赖于快捷键。如果之前提到的快捷键对你不起作用,那么你可能需要查看 Blender 的手册,以找到适合你平台的正确组合:docs.blender.org/manual/en/2.93/interface/keymap/introduction.xhtml。
选择桶并按Tab键。这将打开编辑模式。如果你继续按Tab键,你将在对象模式和编辑模式之间来回切换。你还会看到,Blender 的 UI 会根据当前激活的模式隐藏一些按钮和菜单或显示一些新的按钮,这意味着某些选项仅在特定模式下可用。这意味着一些选项仅在特定模式下可用。如果你想知道你刚才看到的那东西去哪了,请确保你处于正确的模式。
然后,在编辑模式下,按Ctrl + R触发循环切割和滑动。这是一个上下文相关的操作,所以如果你没有看到任何动作,那是因为鼠标没有位于该工具可以操作的面。将鼠标悬停在圆柱的不同部分上。你会看到一个黄色的线贯穿整个圆柱;线的方向取决于你的光标在那个面上的位置。当鼠标仍然悬停在侧面面上时,通过将鼠标滚轮向上滚动两次来增加切割数量到 3。这是循环切割的预览,但它们还不是圆柱的一部分。
一个循环切割需要两次鼠标点击,无论你想要多少个循环。通过第一次点击,你告诉 Blender 你想要引入一些切割;在这种情况下,3。第二次点击将最终确定这些切割的位置,但你可以通过在桶的侧面上下移动鼠标来更改它。因此,在第一次和第二次点击之间,你有一些自由度来定位切割。下面的截图显示了我们要达到的效果:

图 1.7 – 使用精确值添加更多边
如果你意外地在两次点击之间移动了光标,这可能会移动切割的基线,请不要担心。一旦添加了边缘,操作细节将会显示出来,这样你就可以微调切割在模型中的位置。重要的是将因子设置为0,以便在中间获得完美的切割。如果你在切割之前最后一刻做了更改,你也可以调整切割的数量。
你切换到编辑模式的主要原因是为了更好地控制你对象的形状。在仍然处于编辑模式的情况下,你现在将学习如何使用这些循环切割给你的对象一个桶的形状。
成形桶身
桶是一个如此通用的概念。然而,我们还没有讨论我们将要工作的桶的类型。从技术上讲,我们离油桶并不太远,因为它们通常看起来是圆柱形的,并且有两个圆滑的棱。然后,还有你在花园里看到的用于收集雨水的塑料桶。这些桶通常有一个平面的侧面,顶部和底部略微收缩,或者中间部分略微膨胀,具体取决于你从哪个角度看它。
我们将选择一个更经典的桶:木桶。因为我们已经有了基本形状,现在我们可以开始给我们的桶添加更多细节了。两个想法很容易浮现在脑海中。大多数桶都有几个金属环——在中间、靠近底部和顶部——以承受它们所承受的压力。此外,盖子很少与侧面齐平,更可能是内凹的,所以也许我们应该单独处理这部分。让我们一次解决这些问题。
你的 3D 对象看起来扁平吗?
如果所有这些灰色看起来没有生机!如果你觉得 3D 对象的默认外观太平,而你更愿意看到边缘像图片中那样被强调,这里有一个技巧。在3D 视图的右上角有一个向下看的图标按钮。如果你点击该按钮并展开视图着色面板,你可以将照明切换到材质球,并在面板中打开阴影和凹槽选项。将凹槽类型选择为两者也可能是一个好选项。根据你的需要调查不同的值,这样你就可以更容易地处理你的模型。
我们的桶需要一个腹部。我们需要将我们刚刚引入的这些环变宽,以创建桶的经典形状。在仍然选择这三个边缘的情况下,按S,输入1.1,然后按Enter以将其放大 10%。像往常一样,如果你在完成操作后想调整值,最后操作的高级调整设置将会显示。现在,我们只需要使中间环稍微大一点。
虽然我们到目前为止一直在编辑模式中,但我们还没有研究你可以编辑的内容。在3D 视图的左上角,编辑模式下拉菜单旁边,从左到右你会看到顶点、边缘和面图标。这些按钮分别有1、2和3作为快捷键。
通过按下中间图标或2切换到边缘编辑模式。为了创建桶的腹部,你需要选择并放大组成中间环的所有边缘,但你可能不想逐个对每个边缘进行操作。因此,我们需要看看如何选择边缘环。
选择边缘环有两种方法。第一种方法使用键盘快捷键:
-
按住Alt键。
-
点击一个边缘。
这应该会选择与您刚刚点击的边缘相连的所有边缘,如下面的截图所示:

图 1.8 – 选择构成环的所有边缘很容易
第二种方法如下:
-
选择一个边缘。
-
前往选择菜单。
-
展开选择环并选择边缘环。
无论你选择哪种方式,在选择了中间边缘环之后,你必须执行以下操作:
-
通过按S来缩放它。
-
输入
1.05。 -
按下Enter。
这应该会得到一个经典的桶形状。
然而,顶部面仍然属于圆柱体。虽然从概念上讲,盖子可能被认为是桶的一个基本部分,但从编辑的角度来看,它必须被视为一个单独的对象。让我们学习如何分离部分以单独编辑它们。
分离盖子
要创建盖子,首先,确保你仍然处于编辑模式。通过点击编辑模式下拉菜单旁边的第三个图标或按3切换到面选择模式。然后,执行以下操作:
-
选择顶部面。
-
按下P。
-
选择选择。
这将分离顶部面并使其成为一个单独的对象。
或者,你可以在网格菜单下的分离组中展开。以下截图显示了如果你使用菜单进行分离,你可以找到此选项的位置:

图 1.9 – 分隔事物有时是必要的,实际上也是有益的
快捷键
到目前为止,你可能已经注意到 Blender 使用了很多快捷键。一开始可能很难学习和记住所有这些快捷键。如果你对想要对网格、顶点、边等操作有一个大致的想法,你应该查看顶部附近的相关菜单,看看有哪些操作可用。按下一个快捷键将只显示那些菜单的相关部分,但调查这些菜单并查看快捷键可能是一个很好的练习。
例如,P 键用于分隔事物,但存在三种分隔类型,所以你仍然需要最终决定类型。然而,使用快捷键仍然比展开菜单要快。
现在可能是介绍你认识大纲(位于右上角)的好时机。以下截图显示了你现在场景中存在的所有对象:

图 1.10 – 盖子和主体应该是两个独立的对象
如果你保留了场景中的相机和光源对象,你可以忽略它们,因为我们将在本书的后面部分发现这两个对象的作用。随着时间的推移,当你创建更多对象时,你将想要给你的对象命名,这样你就可以在大纲中轻松找到它们。
现在我们来试试。双击主体的标签。对Cylinder.001也做同样的事情,将其标记为盖子。你还会注意到,在大纲中单击标签将选择3D 视口中的对象,反之亦然。最后,点击那个眼睛图标暂时隐藏盖子。一旦我们处理完主体,我们将最终确定盖子。
完成主体
你认为桶体有什么问题或缺失的地方?它看起来很薄,不是吗?如果有一种方法可以拉伸每个面,并填充间隙,使其看起来更坚固,那会怎么样!到目前为止,你一直在选择边和面。你可以遵循类似的流程选择一些面,复制它们,并将它们移动到周围以给主体增加厚度。这很有吸引力,但让我们找到一种简单的方法来使主体变得坚固。
对于这个,你需要启用修改器面板。修改器是一种提供非破坏性更改对象的方法。你将在使用修改器自动化部分了解到其中的一些。
在3D 视口右侧有一个扳手图标,它将允许你添加修改器。以下是你必须采取的步骤,以赋予桶体实质内容:
-
切换到对象模式。
-
选择主体对象。
-
打开修改器面板。
-
从添加修饰符下拉菜单中选择实体化。
修饰符会改变对象,所以即使你在编辑模式下工作,使用修饰符时也会看起来像你正在对象模式下编辑该对象。你将在本章后面部分发现一些修饰符。现在,下面的截图显示了实体化修饰符正在做什么。Blender 中的大多数东西都附带了很多可以调整的值,但你现在只需要更改实体化选项中的厚度值:

图 1.11 – 我们的大炮开始看起来更坚固
0.03 m或0.04 m的值可能多大才合适。你可以选择行业标准厚度,或者选择一个看起来视觉上吸引人的值。根据你正在制作的游戏类型,或者你是否在为客户创建资产,你可以选择最适合资产的方法。
关于单位的讨论
如今,世界上大多数地方都在使用公制系统。然而,如果你是因为默认选项或者个人偏好,你可能在你的 Blender 副本中设置了英制单位。在这本书中,我们将使用公制系统。你可以在右侧第五个标签页内找到单位面板。这个标签页包含一个圆锥形、一个球体以及看起来像点的图标。
修饰符非常有帮助,但有时你需要亲自动手。这意味着修饰符能为你做的事情有限。例如,我们现在需要在身体周围放置金属环。没有修饰符能为你做这件事。不过,我们仍然可以利用修饰符。但首先,让我们创建一些金属环。
放置金属环
现在的大炮有一些实体感,但缺少金属环。创建另一个圆柱体并调整其大小以看起来像环需要太多工作,并且需要精确度。有一个更简单的方法可以利用大炮的几何形状。你将使用你已经看到的一些熟悉的方法:环切、环选择和分离。
在编辑模式下,在身体底部和第一个边缘环之间创建一个环切。对于另一个环切,你将在顶部边缘和下面的环之间创建切割。最终,你将创建两个环切,如下面的截图所示

图 1.12 – 从两端分别切下的两个部分,接近中间部分
你已经看到了如何选择边缘环:这涉及到按住Alt键并点击一个边缘。你将做类似的事情,但这次是为了选择面环。为此,确保在编辑模式下点击了面图标。或者,在编辑模式下,你可以按3。
当您按住 Alt 键并点击一个边缘时,您将选择与您刚才点击的边缘相邻的面。它还会选择其他方向相似的面以完成一个循环。尝试用水平和垂直边缘进行几次操作,看看循环的方向如何相应地改变。
您必须做的是选择组成靠近桶体顶部和底部的两个环的所有面。以下截图显示了应该选择哪些面,以便您可以将其分离形成金属环:

图 1.13 – 您必须选择某些内容,以便您可以将其分离
一旦您选择了第一个环,您可以按住 Shift 并重复之前的操作,以继续向您的选择中添加更多环。
现在,您已经准备好分离那些面了。按 P 键调出 Separate 选项并选择 Selection。现在,您可以重命名新创建的 Ring 对象。如果您回到 Object Mode,您会看到您可以单独选择每个对象。选择环;您会看到这个新对象也存在 Solidify 修改器。这不是很方便吗?
修改器中的厚度值是相同的,但如果我们改变该值的符号会发生什么?如果您点击 -0.04 m。如您所见,它仍然是相同的厚度,但在相反的方向上——看起来我们终于在我们的桶体周围有了那些金属环!
现在,让我们学习如何为中间部分添加另一个环。您可以遵循类似的步骤创建两个额外的环,一个在中心环上方,一个在下方。然而,您可以做得更好。
通过执行边缘环操作并触发 Edge 菜单下的 Offset Edge Slide 选项或按 Shift + Ctrl + R 来选择中间边缘。这与 Loop Cut and Slide 非常相似,但它有两个主要区别。首先,此操作将考虑一个边缘为其基线,并将新边缘向相反方向移动。其次,当您对新的边缘位置满意时,您只需点击一次。如果您在使用鼠标时遇到灵敏度问题,将操作属性中的 Factor 值设置为 0.1 可能是一个不错的选择。
我们将遵循类似的程序:选择和分离。在面编辑模式下,您将通过点击位于您新环之间的一侧垂直边缘,使用 Alt + Shift 的组合。在您分离中间面之后,您将面临一个重要的决定:您是否应该重命名您的新对象,并在其修改器中反转厚度的方向,就像您为上环和下环所做的那样?本质上,您希望您的新对象与它的同伴结合。这正是您接下来将要做的,但会使用一个巧妙的技巧,而不会重复自己。
哪种模式?
在建模过程中,有时你需要编辑模型的一部分。在这种情况下,处于 Edit Mode 将是必要的。然而,当你从模型中分离出块时,你很可能想回到 Object Mode 来处理这个新对象。所以,在这些模式之间来回切换将是必要的,并且一段时间后感觉会很自然。
在 Object Mode 中,首先,你必须选择你刚刚创建的中间环形。你不需要重命名它;你很快就会明白原因。你必须通过按住 Shift 并在 3D Viewport 中点击环形来添加一个额外的对象到你的选择中。确保你的最后一次点击是在你之前创建的环形对象上。在这个时候,点击的顺序很重要。你最后与之交互的对象将被 Blender 视为活动对象。它将用黄色轮廓标记,与橙色轮廓的对象相比,这些对象是选择的一部分,但不是活动对象。
一旦你以正确的顺序选择了环形,你必须通过按下 Ctrl + J 来将它们连接起来。你注意到刚才发生了什么吗?让我们来分析一下:
-
你在 Outliner 中再也看不到 Body.001 了
-
Ring 对象已将 Body.001 接纳为同伴
-
Ring 对象的 Solidify 修改器已应用于 Body.001
由于所有这些单独的部分现在被视为一个整体,因此不再有单独的部件,如下面的截图所示:

图 1.14 – 一枚戒指将它们全部连接在一起
随着你对 Blender 的信心增强,你会发现你可以遵循不同的方法来实现相同的结果。没有正确或错误的方法,而是节省时间的习惯,你将发展出自己的习惯,因为每个设计师都有他们做事的偏好方式。有时,其他技术或艺术上的考虑会限制你的工作流程。然而,作为一个初学者,你应该观察其他艺术家是如何创建类似对象的。幸运的是,有很多例子可以参考,所以学习、实验,并在过程中进行创新。
之前,你需要分开盖子。之后,你对主体进行了修改,甚至添加了环形。现在,是时候给你的桶盖上盖子了。
完成盖子的制作
如果你分开盖子后将其隐藏,你可以点击 Outliner 中的眼睛图标来打开它。你需要进行基本的缩放操作来将盖子放置到正确的位置。为了实现这一点,首先,选择盖子,然后执行以下操作:
-
按下 S 键。
-
输入
0.96。 -
按下 Enter。
为什么是这样一个精确的值?因为我们一直在使用 Solidify 修改器中的 0.04 m。所以,我们应该将盖子的比例减少 4%。这将使我们免于对齐盖子的所有边缘,使它们与桶的内侧齐平。如果你在你的修改器中使用了不同的值,你必须在这个步骤中补偿你的比例值,以便最终两者相加等于 1。
你做到了!当盖子放在正确的位置,看起来正好在边缘水平以下时,桶就完成了。查看以下截图,并将其与你的作品进行比较:

图 1.15 – 一只木制的——相当灰的——桶,光彩照人
如果你决定再次从头开始创建这个桶,也许你可以将上环放在盖子附近,下环放在底部。放置五个环也是可能的,但你可能需要调整每个环的高度,以防组合看起来过于杂乱。
到目前为止,你已经使用了一个修饰符,并且它已经很好地为你服务了。让我们深入了解更多的修饰符,看看它们可以有多么强大。
使用修饰符自动化
修饰符是一种非破坏性的方法,可以将操作应用于对象以改变其几何形状。当你不想重复执行步骤或操作足够复杂以至于不想直接改变对象的几何形状时,这通常是一个更好的选择。
当你应用了实体化修饰符时,你可能已经看到还有很多其他的修饰符。你能想象每个修饰符能做什么吗?你打算连续使用几个修饰符吗?是的,你没有看错。你可以堆叠很多修饰符,并且几乎不费吹灰之力地创建出复杂的形状。
然而,有一个重要的细节你必须注意——它们的顺序很重要!新的修饰符总是添加到堆栈的底部,并且它们与堆栈中之前的修饰符一起工作。因此,效果是累积的。如果你逻辑地堆叠你的修饰符,你可以在很短的时间内仅使用几个原始对象就创建出像以下截图中显示的如此复杂的形状:

图 1.16 – 修饰符帮助你轻松创建出如此复杂的形状
这个对象使用了诸如圆柱体和立方体这样的原始形状,但结果看起来很有趣。这要归功于一个长长的修饰符列表以及它们的使用顺序。一些修饰符已经以不同的值应用了多次,但这里有一个列表:
-
细分
-
减少
-
布尔运算
-
斜面
-
镜像
-
加权法线
在撰写本文时,Blender 有超过 50 个修饰符。描述每一个可能会填满一本书。你很可能会坚持使用生成类别中的修饰符。以下是一组你将经常使用的修饰符:
-
布尔运算:这是那些经常使用且包含三种子模式的修饰符之一:
-
差异:从一个对象的值中减去另一个对象的值
-
并集:将两个对象合并
-
交集:只保留两个网格共有的部分
-
-
斜面: 有时候,你可能想要更多的细节,尤其是在尖锐的边缘,这样它们看起来不会太生硬——光源可以反射的表面越多,眼睛看起来就越真实。如果你想要软化那些尖锐的角落,这个修改器也可以应用于顶点。
-
阵列: 这会在不同的轴上复制分配给它的对象,如果你愿意,可以带有一些偏移。你可以有固定数量的副本,或者用尽可能多的副本填充特定的长度。
-
镜像: 这就像阵列修改器一样,但它沿着你选择的轴创建一个副本。你可以选择多个轴。因此,你可以从一个对象的四分之一开始,并在 X 轴和 Y 轴上镜像它,这样你就有了一个完整的对象。这允许你将更改保持在原始四分之一的最小范围内,以便你可以将更改镜像到网格的其余部分。
当你添加修改器时,有时并不明显应该按什么顺序堆叠它们。幸运的是,你可以通过使用修改器标题部分中的按钮来改变它们的顺序或暂时禁用它们。
在创建桶的环时,你可以使用不同的技术来达到相同的结果:挤出。这需要你选择需要挤出的内容——在这种情况下,构成环的所有面——并沿着每个面的外向方向挤出。本质上,挤出是一个术语,用于移动顶点、面或边。
与经典方法(如推拉顶点和面)相比,修改器有一个很大的优势。如果你稍后回来进一步微调你的更改,这不是很方便吗?如果你现在选择了盖子,然后回到主体对象,修改器仍然会在那里。你不会在永久网格修改技术(如挤出)中有这种灵活性。
摘要
在本章中,你了解了低多边形建模的好处。然后,你从一个原始圆柱体创建了一个木桶,并加入了修改器。尽管纹理可以使你的模型看起来更真实,但你也知道你可以不使用它们。
作为练习,你可以自由地创建一个药水瓶。你可以从一个圆柱体开始,就像你为桶做的那样。通过切割环和调整缩放值,你可以给它一个锥形形状。这是你练习修改器的机会。如果你想看看完成的例子并比较你的作品,这本书的 GitHub 仓库中有一个完成的药水瓶等你查看。
在建模过程中,许多专业人士经常使用一些快捷键。以下是你已经使用过的列表:
-
Shift + A: 添加一个对象
-
Tab: 在编辑模式和对象模式之间切换
-
Ctrl + R: 引入环切
-
Ctrl + J: 连接
-
S: 缩放
-
P: 分离
在下一章中,你将学习如何将材质应用到你的模型上,这样你的模型的部分仍然可以有不同的外观和感觉,即使没有纹理。
进一步阅读
该章节的标题建议阅读资料,但有时亲眼所见甚至更佳。正如一张图片胜过千言万语,一段视频可能胜过千张图片。因此,这里有一份可能对所有级别 Blender 实践者有用的视频内容 URL 列表:
第三章:构建材质和着色器
根据维基百科,材质是由物质或物质的混合物构成的,这些物质构成了一个对象。这个定义对于现实生活中的对象也是正确的,对于你使用一些额外技术细节创建的电子模型也是如此。让我们看看我们这个环境中材质的定义。
在 Blender 中,材质基本上是包含大量数字、颜色和纹理以及其他有用内容的容器,最重要的是着色器本身。着色器是一段代码,它告诉渲染引擎(无论是 Blender 的还是 Godot 引擎的)如何处理材质的属性。
从本质上讲,材质就像是一个装满小物品的盒子,它附带一份用户手册(着色器),以便你使用的软件知道如何处理这些小物品。
你现在已经知道了材质和着色器的原始定义,但它们是用来做什么的呢?你在上一章创建的桶形物体上有金属环和木条,这些赋予了它形状。然而,模型中的所有东西看起来相当灰暗。向你的模型添加材质将通过显示你从现实生活中熟悉的颜色和其他属性来增强其形状。
在本章中,你将学习如何通过应用材质让你的模型看起来更真实。为此,我们将涵盖以下主题:
-
介绍材质
-
创建材质
-
分配材质
-
发现着色器
到本章结束时,你将了解如何创建和分配不同的材质,并理解着色器在这个过程中是如何发挥作用的。
技术要求
虽然本章是关于材质的,但你至少需要一个 3D 模型。这可以是你的完成作品,来自第一章,创建低多边形模型。或者,你可以使用本书 GitHub 仓库中Chapter 2文件夹的Start文件夹中的桶形模型:github.com/PacktPublishing/Game-Development-with-Blender-and-Godot。
介绍材质
如我们在引言中提到的,材质被分配给对象。然而,你无法将材质分配给所有对象。当你启动一个新的 Blender 文件时,它包含一个立方体、一个摄像机和一个光源对象。从 Blender 的角度来看,这些对象中只有一个具有实质内容,那就是立方体。让我们更深入地分析一下,了解为什么这很重要。尽管摄像机和光源在现实生活中具有物理属性并且占据空间,但在 Blender 中并非如此。它们是概念性的对象。
相机是一个让你看到世界的工具。所以,你无法看到相机本身的视觉属性。无论相机是涂成红色还是蓝色,都没有关系。同样,光源发出明亮或昏暗的光,有时带有某种颜色,但它不会占据 Blender 场景中的空间。因此,如果没有物质,我们就不能将这些两个物体应用材质。
只要有一种更简单的方法知道哪些物体可以接收材质就好了...
如果你依次选择每个默认对象,你会看到一些图标在屏幕右侧的视图中时隐时现。由图标表示的不同选项集堆叠在属性面板中。此面板将显示所选对象的相应属性。
当你选择立方体时,你会注意到属性面板引入了许多图标,这些图标与相机或光源的图标不同。要么将鼠标悬停在图标上以查看其标题,要么简单地点击图标以快速查看你拥有的选项。在这个过程中,你最终会发现倒数第二个图标,它应该会打开材质面板(如果你需要以后记住它,它看起来像是一个带有棋盘图案的球体图标)。
你还没有创建材质。然而,Blender 从一个默认的立方体开始,它带有默认的材质。让我们学习如何更改其颜色。在选择了立方体之后,按照以下步骤操作:
-
在属性面板中打开材质选项卡。
-
点击基础颜色右侧的彩色矩形。
-
从色轮中选择不同的颜色。
以下屏幕截图将帮助你找到所有这些。一旦你选择了颜色,最初这个变化不会应用到立方体上;你很快就会知道原因:

图 2.1 – 颜色是你可以为材质更改的许多事物之一
在选择颜色时,色轮会帮助你。然而,如果你想更精确地选择颜色,色轮下方的三个按钮(RGB、HSV和Hex)可以提供帮助。在先前的屏幕截图中,使用了Hex模式下的E77EB6值。所有这些颜色模式都像单位一样,但当你在不同模式之间切换时,结果总是相同的颜色。
面板和设置
在 Blender 中工作并不意味着你总是必须直接修改模型的几何形状(顶点、边和面);你经常会发现自己正在寻找设置并在许多面板中更改它们。在这本书的后面部分,当你与相机和光源等对象一起工作时,你会使用适当的面板,以便可以调整这些对象的设置。
让我们来弄清楚你最后做的更改为什么没有在屏幕上反映出来。默认情况下,Blender 将模型显示为实体对象。有时,就像 X 光可以帮助医生了解情况一样,你可能需要以不同的方式查看你的模型。以下是你可以看到物体的四种不同方式:
-
实心:默认选项;你一直都在使用这个选项。它简单地显示你的模型作为一个实体对象。
-
材质预览:你将主要看到你应用到对象上的颜色,但你也会看到你应用的一些其他属性。
-
线框:对象将看起来像金属线被弯曲并焊接成一个定义模型的框架。由于这种模式只渲染边缘和顶点,当你想要可视化多边形和检测超载区域以便轻松优化模型时,它很有用。
-
渲染:这比材质预览更准确,因为它使用 Blender 的渲染引擎创建最准确的表现。它是通过考虑场景中的灯光和阴影来做到这一点的。自然地,它使用更多的 GPU,所以你很可能大多数时候都会使用其他视图选项。
上述列表显示了所有可用于视口着色的选项。默认视图实心在处理材质时速度快但不够准确。现在你已经更改了材质的属性,你必须处于材质预览模式才能看到效果。要切换到它,按Z然后2。或者,在按Z之后,你可以用鼠标选择适当的选项,如下面的截图所示:

图 2.2 – 在径向菜单中展示的不同视口着色选项
现在你对材质有了基本的了解,我们将回到第一章中的桶,创建低多边形模型,并为它创建材质。
创建材质
到目前为止,你一直在编辑默认的 Blender 材质,但创建新的材质足够简单。我们需要至少一个有一定实质性的对象。你可以继续使用你设计的桶,或者打开本书 GitHub 仓库中“第二章”文件夹下的Start文件夹中的文件。如果你使用自己的文件,你很可能仍然有标记为材质的默认材质仍然在材质面板中。使用减号(-)按钮,你可以移除它并从头开始。本书 GitHub 仓库中提到的文件已经为你移除了这个默认材质。
可能会诱使你点击那个位于你刚刚点击的减号(-)按钮正上方的加号(+)按钮。尽管如此,还是点击它吧。你会在材料列表中看到一个空行出现。这两个按钮只是向对象添加或从对象中移除材料槽位,而不是材料本身。一旦你准备好一个槽位,你就可以为该槽位指定一个材料。随着我们继续前进,我们将调查槽位和不同的材料,但让我们按照以下步骤创建我们的第一个材料:
-
选择桶体。
-
在材质面板中点击新建按钮。
-
将基础颜色改为棕色。
-
双击标题后,将你的新材料重命名为
Wood。
以下截图可以帮助你比较你的结果与预期发生的情况。在选择颜色时,你可以点击AD8654,这样你的桶体颜色就与这里显示的颜色相同:

图 2.3 – 桶体的外观更像是木头
现在,让我们创建另一种材料——这次是一个金属材质。但那个新建按钮去哪了?在这种情况下,当新建按钮缺失时,你可以执行以下操作:
-
点击带有加号(+)标志的按钮。
-
点击新建按钮。
-
将你的新材料重命名为
Steel。 -
为它选择一个合适的颜色,例如555E64。
因此,这次,你引入了一个新的槽位并填充了新的材料。看起来桶体对象现在有两个材料,但只有一个在起作用。此外,我们实际上不需要模型桶体部分的钢材质。因此,我们应该将其删除。当钢材质被选中时,按下带有减号(-)标志的按钮,将其从桶体中移除。
尽管你创建了一个新的材料并将其删除,但这并不意味着这一切都是徒劳的。该材料仍然是你的 Blender 文件的一部分,但尚未分配。我们很快就会利用它。这意味着你已经像使用工作台一样使用了材质面板。现在,让我们学习如何将现有材质分配给对象。
分配材料
如果你已经准备好了你的材料,那么你可以轻松地将它们分配给不同的对象。这样可以避免你重复创建相同的材料。在本节中,我们将看到如何这样做。
环对象尚未分配任何材料,但你可以将其分配给钢材质。首先选择环对象,然后展开新建按钮旁边的下拉菜单,如图所示。从列表中选择钢材质。这将把选定的材质分配给对象:

图 2.4 – 现有材料列在这个下拉菜单中
根据其功能命名材料是个好主意,例如木材和钢铁,这样在以后查找时会更方便。你还会看到,材料的颜色以小图标的形式显示在材料名称旁边;这在一定程度上是有帮助的,但如果有很多颜色相似的材料,这就会有限制。
盖子可能使用相同的木材材料,但也许我们可以稍微改变一下。在选择了盖子对象后,执行以下操作:
-
分配木材材料。
-
点击新建材料按钮(看起来像两张纸)。
-
将
Dark Wood重命名。 -
选择一个较深的颜色,例如7E623D。
你刚刚为盖子对象创建了一个原始木材材料的副本。如果你展开材料列表,你会看到所有可用的材料。请随意添加、删除、复制以及/或分配你想要的任何材料以进行练习。
此外,你不仅可以为整个对象分配材料,还可以为该对象的部分分配材料。如果你在编辑模式下选择了一些面,那么你也可以只将这些选定的面应用材料。本质上,材料面板列出了与你的模型关联的材料,无论它是应用于整个模型还是模型的部分。
到目前为止,事物必须看起来更有色彩一些。然而,尽管你付出了所有努力,仅仅通过改变颜色,你只能做到这一步。金属环仍然不够金属感。它们应该看起来更有光泽,所以我们在这里缺少一些东西。我们需要发现新的方法来给基础颜色添加额外的特性。这就是着色器的作用,也是我们接下来要解决的问题。
发现着色器
在本章的开头,着色器被定义为两件事:一段代码和一份用户手册。到目前为止,你是否感觉自己在编写代码?很可能是没有。
然而,在材料用户界面背后,有一个代码层,那就是着色器。例如,你到目前为止一直在使用的默认着色器有数百行代码。以下只是构成该着色器代码的一部分:
metallic = saturate(metallic);
transmission = saturate(transmission);
float diffuse_weight = (1.0 - transmission) * (1.0 -
metallic);
transmission *= (1.0 - metallic);
float specular_weight = (1.0 - transmission);
clearcoat = max(clearcoat, 0.0);
transmission_roughness = 1.0 - (1.0 - roughness) * (1.0 -
transmission_roughness);
specular = max(0.0, specular);
幸运的是,你不需要编写一行代码。更重要的是,Blender 会解释着色器代码,以便它可以提供 UI 元素,如颜色选择器来选择颜色,滑块来定义值范围,以及带有更多高级选项的下拉菜单,这样你可以轻松地使用着色器。
着色器的“用户手册”方面涉及哪些材质属性将暴露给用户。例如,颜色是一个明显的设置,我们应该能够更改。着色器代码将暴露材质的一些属性给外界,这样您就可以轻松地使用材质。这与您使用任何设备的方式非常相似。您通常通过界面通过点击按钮和旋转一些旋钮与设备交互。这些动作的组合会在内部触发某些事件,这些事件不会向您透露,但您会体验到这些操作的结果。
回到原始定义,您通过着色器与材料进行工作。这两者是相辅相成的。此外,正如 Blender 引入了默认材质一样,它也附带了一个默认的着色器分配给这个材质。它被称为Principled BSDF。您可以在材质详细信息的表面部分看到这个名称。如果您在界面中点击Principled BSDF(从现在起,为了简便起见,简称Principled),您将看到一个其他着色器的列表。从该列表中选择不同的着色器将使您的材质与不同的着色器关联。该列表中的其他一些着色器如下:
-
漫反射 BSDF:一个基本的着色器,负责在表面上显示颜色。当对象应该具有简单的颜色时——换句话说,扩散某种颜色——这就是您应该使用的着色器。
-
发射:如果您正在设计一个对象,并希望它像一个光源一样工作,例如荧光灯,您可以使用这个着色器,使其看起来像是在发光。
-
玻璃 BSDF:一个可以模拟玻璃表面的着色器。它包含一个折射率(IOR)设置,这样您就可以决定玻璃的透光性,因为市面上有不同类型的玻璃。
-
光泽 BSDF:这用于添加反射,非常适合模拟金属或镜子。
-
卡通 BSDF:当您需要表面和边缘具有卡通效果时使用。
当您希望您的对象显示不同的特性时,您将希望一些简单的着色器协同工作。例如,在许多科幻电影中,展示了先进的机器等,通常可以看到发光的力场也是透明的。如果您只使用玻璃 BSDF,您将可以看到,但没有发光效果。如果您使用发射,将没有透视可见性。
因此,Principled着色器是常用着色器的最佳组合。它就像一个超级着色器,在同一个屋檐下运用了不同着色器的属性。因此,在这个阶段,最好坚持使用默认着色器。
BSDF
你会注意到一些着色器带有这个缩写。BSDF 是一个技术术语,代表双向散射分布函数。它由 BRDF 和 BTDF 组成,分别负责反射和传输光线。总的来说,这个系统负责光线与你的物体交互的真实性。用通俗易懂的话来说,它计算了材料吸收了多少光线,以及根据强度、角度等因素反射了多少光线。
现在,让我们学习如何修改我们的桶的钢材质。不幸的是,并没有一个你可以打开的设置来给表面一个金属的外观。结果是,并不是所有的金属都是相同的。一些金属表面看起来更反光或更亮,而有些则看起来更粗糙,等等。我们将使用以下属性的混合值来得到我们想要的结果:
-
金属
-
镜面
-
粗糙度
这些词的字典定义可能已经足够好了。但话虽如此,在 Blender 的上下文中,这三个属性协同工作以创建不同的金属表面。因此,你需要平衡每个的强度,有时就像处理食谱一样。当你更改这些值时,为了看到效果,你需要处于渲染模式。你可以通过按Z然后8来切换到它。
着色器值
你为着色器更改的数值没有单位,但更像是一个百分比或强度。0 表示你想要完全没有。1 表示全量。所以,0.5 表示 50%。
让我们分析以下屏幕截图所示的结果。默认的 Blender 材质为金属、镜面和粗糙度分别提供了0、0.5和0.5的值。所以,左上角的球体的值非常接近默认的 Blender 材质。这意味着默认情况下,你的模型将有一些光泽和粗糙度:

图 2.5 – 同样颜色但具有不同金属、镜面和粗糙度值的球体
在右上角,你可以看到只有金属值被增加了。尽管镜面值相同,但我们看到更多的光线被反射。这很合理,因为金属表面反射更多的光线。所以,具有更多金属特性的表面应该反射更多的光线。这正是左下角模型的情况。
最后,右下角的球体展示了当我们有一个完全金属表面并且粗糙度值被放大时的效果。注意球体表面上的光泽是如何分布得更均匀的,因为它是粗糙的。当光线击中一个粗糙的表面时,表面的所有凹凸部分都会以许多不同的方向反射大部分光线。当表面不那么粗糙或更光滑时,光线会直接弹回到你的眼睛——在这个案例中,Blender 的相机。因此,它看起来很闪亮。
顺便说一句,在这些所有情况下,基色仍然是相同的,但最终的外观确实感觉不同。Principled 着色器的用户手册页面包含一些图表,描述了这些着色器设置如何相互作用。它与前面的截图类似,但它包含了更多的情况:docs.blender.org/manual/en/latest/render/shader_nodes/shader/principled.xhtml。
以下截图显示了两种使用相同基色的材料的区别:

图 2.6 —— 如预期的那样,金属环反射了一些光线
左边的桶使用了 Blender 提供的默认金属、镜面和粗糙度值。右边的桶有一个材料,其金属、镜面和粗糙度值分别设置为 1.0、0.5 和 0.2。总的来说,你很可能会需要调整这三个属性才能得到你想要的金属外观。
非金属物体
在非金属情况下,如砖块、液体、草地等,玩转这三个属性也是适用的。让我们比较一下砖块和液体的例子。两者都可以有相同的基色——也就是说,血红色或其他色调。砖块不是一个反射物体,因此它应该有非常低——可能为 0——的金属和镜面值。很可能,它的粗糙度值会很高。另一方面,液体需要更少粗糙,并且具有更高的镜面值。
玩转着色器的属性可能会很有趣,但有时也可能感觉你不知道自己在做什么。为了达到你想要的外观进行实验是没有错的。如果你想对自己的操作更有信心,你可以开始观察你周围的对象。这可能会让你更好地了解选择哪些属性才能得到你想象中的结果。属性名称在这方面很有帮助,但它们很少单独工作,因此即使是专业人士也需要混合搭配。
我们将在介绍 Godot 引擎时再次探讨材料和着色器,但在这里,你已经看到了它们在 Blender 中的工作方式。让我们总结一下到目前为止我们已经学到的内容。
总结
在本章中,你学习了如何使用材质来为对象赋予不同的外观。为了创建材质,你使用了 材质 面板作为工作台,一次性准备了许多材质,然后将这些材质分配给不同的对象。
着色器几乎与材质密不可分,你已瞥见它们附带了多少选项。你也看到了可以为你的材质选择不同的着色器。然而,大多数时候,Blender 的默认着色器 Principled BSDF 就足够了。
使用默认着色器,你创建了一些具有不同品质的材质,例如木材和钢铁。此外,你还发现了通过利用不同强度的金属、光泽和粗糙度属性来创建不同外观的金属表面的方法。
通常与材质和着色器一起讨论的另一个主题是纹理。它被有意省略了,但将在下一章中解释原因。现在,重要的是要知道纹理是可能增强材质视觉影响的图形文件。当你准备好时,翻到下一章,以便更好地了解它们。
进一步阅读
Blender 的官方文档提供了足够详细的解释,说明了不同着色器和它们的属性是如何工作的。以下 URL 列出了许多你可以调查的着色器:Blender 着色器节点文档。
有时候,看到更多示例可能会帮助你更好地进行创作。BlenderKit 是一个有用的 Blender 插件,你可以使用它来访问大量的材质以及更多内容,如模型和场景。请访问 BlenderKit 网站 以阅读安装说明。
由于这本书是关于游戏开发的,所以我们只介绍了 Blender 在帮助我们使用低多边形模型构建游戏时的基础知识。这意味着我们在为游戏创建材质时也限制了细节级别。然而,许多专业人士出于不同的原因使用 Blender,例如创建营销材料、产品可视化、动画等。因此,如果你想深入了解不同工作流程中的材质创建,这里有一些许多优秀的在线课程:
第四章:添加和创建纹理
在典型的 3D 工作流程中,你可能会添加到材质中最常见的属性之一就是纹理。纹理是一个图像文件,它负责模型的纹理外观,使得表面不会只显示单色。尽管你在现实生活中遇到的物体有感知到的颜色,但它们也有由 3D 应用程序中此属性定义的特征外观。例如,一朵花和沙质表面可能都有黄色,但你都知道花朵的花瓣看起来会更光滑,而沙粒则会显得粗糙。
大多数日常物品都有磨损和损坏。看看四周,你会发现大多数表面要么有剥落的油漆,轻微的变形,或者一些划痕。想象一下你在前两章中设计的桶已经使用了一段时间。它自然会在金属环上有一些划痕。你只能通过为你的材质应用颜色和改变粗糙度值来做到这一点。如果你想达到更逼真的效果,你必须为你的模型应用纹理。
一些 3D 专业人士只专注于某些领域并在此领域获得专业知识。纹理化就是这些领域之一,除了建模、照明和动画之外。通常,纹理化专家会使用经典图像编辑应用程序,如 Adobe Photoshop、GIMP 等,来创建纹理。然后,艺术家会将这些纹理带入 Blender,以便它们可以应用于表面。如果你不擅长从头开始创建纹理,你将在本章中学习如何仍然依赖其他艺术家创建的现有纹理。
使用上述工作流程准备和使用纹理通常听起来很静态,因为你需要访问这些纹理的源文件。幸运的是,在 Blender 中有一个动态的方式来创建你自己的纹理,这样你就不必在 Blender 和其他软件之间来回切换。
这不是“一个比另一个更好”的情况,因为每种方法都有其自己的位置和优点。你将了解 Blender 的新部分,以促进这两种方法,这样你可以就使用哪种纹理方法做出明智的决定。为此,我们将涵盖以下主题列表:
-
理解 UV 和纹理坐标
-
使用 UV 编辑器
-
导入和应用纹理
-
以程序方式创建纹理
-
导出你的纹理
到本章结束时,你将学会如何为纹理准备你的模型,应用可用的纹理,并动态地创建自己的纹理。你在本章中获得的经验将帮助你了解为你的项目选择正确的纹理方法。
技术要求
本书 GitHub 仓库(github.com/PacktPublishing/Game-Development-with-Blender-and-Godot)将有一个包含Start和Finish文件夹的Chapter 3文件夹,你可以用来在过程中比较你的工作。这些文件夹还包含其他依赖项,例如必要的纹理文件,以便跟随和完成练习。
虽然你在前面的章节中工作过桶,但我们将只使用标准的 Blender 对象,如立方体和平面,以保持事情简单,这样你可以专注于纹理流程。
理解 UV 和纹理坐标
当你在建模时,你正在改变模型顶点的坐标。因此,你正在使用空间坐标。要给你的模型应用纹理,你需要在一个不同的坐标系中工作,这个坐标系被称为纹理坐标或UVs。让我们看看这两个术语是如何相互关联的。
空间坐标系通常用XYZ这个缩写来描述,因为我们经常使用 X、Y 和 Z 轴来定义 3D 对象的位置。同样,UV也是一个缩写,但它用于纹理流程中;字母 U 和 V 被选中来描述纹理坐标系。所以,UV 并不真正代表紫外线。
将 UV 坐标映射到 XYZ 坐标的过程被称为UV 展开。通过这种方法,你告诉 Blender 如何将图形文件映射到 XYZ 坐标。如果你觉得展开过程不太直观,你可以在心里尝试逆转这个过程。你需要什么样的纹理,如果把它包裹在你的 3D 模型周围,它能够完美地贴合?让我们分析以下图例,其中有一个用棋盘纹理画出的图形文件被应用到标准立方体上:

图 3.1 – 一个 2D 棋盘纹理包裹 3D 对象
在图 3.1中,你看到左边的立方体有一个棋盘纹理。在中间部分,你看到立方体就像礼物包装被剥去一样。最后,立方体在右侧完全展开;其纹理被平铺。纹理文件实际上是所有的棋盘部分,它作为一个 2D 图形文件存在。
我们使用诸如展开和 2D 图形文件之类的词语,是因为我们在一个平面上模拟现实生活中的 3D 对象。实际上,那个立方体会占据空间,有体积,并且会充满它所制成的材料。例如,一个可能是由木头制成的儿童玩具立方体。或者,它可能是一个六面骰子,很可能是用亚克力制成的。如果你切开它,你会看到材料。
要将问题从三维体积问题转换为二维图形问题,你需要一个新的工具。你一直在使用 Blender 的默认界面,该界面方便地设置为编辑 XYZ 坐标。对于编辑 UV,你需要UV 编辑器,你将在下一节中找到它。
使用 UV 编辑器
Blender 自带预设的工作区,这样你可以专注于特定的工作流程。到目前为止,你一直处于布局工作区。你可以将其视为位于应用程序标题下方、紧邻帮助菜单的激活标签。你应该创建一个新文件,并通过点击相应的标签切换到UV 编辑工作区。图 3.2是你处于UV 编辑工作区时看到的。

图 3.2 – UV 编辑是 Blender 中许多默认工作区之一
在UV 编辑工作区中,应用程序将主要分为两个部分:左侧被称为UV 编辑器,显示在平坦表面上排列的一组正方形,右侧显示默认的立方体。你在UV 编辑器中看到的黑色点实际上是3D 视图中立方体的顶点。你可能注意到,如果你在UV 编辑器中计数点,它们并不等于立方体的顶点数。在UV 编辑器中有更多的点,因为其中一些点最终会在UV 编辑器中的正方形折叠到边缘并围绕你的 3D 对象包裹时合并。
到目前为止,Blender 应该已经为你选择了立方体的所有顶点。然而,如果你不小心选中了立方体的一个顶点,你会看到UV 编辑器中的正方形会消失。这是因为我们还没有打开同步模式。在UV 编辑器的左上角,你会看到一个图标看起来像两个对角箭头朝相反方向移动的按钮。如果你按下了这个按钮,你会注意到在任一视图中选择顶点都会同步。
当你添加一个新的立方体时,Blender 默认会展开该立方体。在UV 编辑器中顶点的一般布局类似于一个 T 形,就像你在图 3.1中看到的那样。类似于3D 视图,UV 编辑器中的顶点将形成边和面,但在UV 编辑器中都是二维的。如前所述,我们已经将模型的 3D 特性转换为二维表示,这样我们就可以处理图形文件。
UV 编辑器是你可以看到编辑器中的点如何映射或关联到纹理文件的地方。为了做到这一点,我们需要按照以下方式引入一个纹理文件:
-
打开
第三章文件夹。 -
打开
开始文件夹。 -
将
pips.png拖放到UV 编辑器区域。
如果您在计算机的默认图像查看应用程序中打开那个 PNG 文件,您会注意到它有透明部分。它的 1024x1024 的尺寸并没有完全上色。恰好文件的非透明区域正好位于UV 编辑器中的面上,因此3D 视图中的面。
2 的幂次方
不论是早是晚,您都会注意到大多数纹理文件都采用某些标准尺寸,如 512、1024、2048 等。尽管这些文件不必是正方形,这意味着您实际上可以有 256x512 的尺寸,但仍然值得将任一维度保持在 2 的幂次方。这是由于 GPU 使用的算法,以便它们运行得更有效率。
到目前为止,我们已经利用了 Blender 为立方体提供的默认 UV 布局,并看到了 UV 面如何与我们在UV 编辑器中预览的纹理文件重叠。然而,如果您在3D 视图中启用材质预览,您将看不到应用于立方体的骰子纹理。这是因为我们还没有告诉 Blender 在分配给立方体的材质中使用骰子纹理。让我们在下一节中这样做。
导入和应用纹理
当您将纹理文件拖动到UV 编辑器中时,您实际上已经导入了它,但事实上,立方体的材质还不知道如何使用那个纹理。也就是说,由于UV 编辑器,材质已经拥有了将 3D 顶点映射到 2D 纹理坐标所需的所有信息。它只需要被告知要将哪个纹理应用于立方体。
为了完成这个任务,我们将切换到一个新的工作空间,这样我们就可以将纹理与材质连接起来。此外,我们还将使用不同的方法导入另一个纹理,并将其分配给立方体的材质,以展示您如何使用相同的 UV 信息与不同的纹理文件。
就像您切换到UV 编辑工作空间一样,现在是时候切换到不同的工作空间以方便操作了。第六个工作空间,标记为着色器,就是您要找的。我们将在新工作空间的下半部分进行工作,它看起来像是一个网格;它被称为着色器编辑器。上半部分仍然是那个熟悉的3D 视图,但材质预览已自动开启,这样您可以看到您的更改立即反映出来。因此,着色器工作空间应该看起来与您在图 3.3中看到的大致相同。

图 3.3 – 着色器是为您设置的多个工作空间之一
如您在第二章“建筑材料和着色器”中发现的,Blender 文件自带一个默认材质。我们将修改这个默认材质来了解纹理工作流程。着色器编辑器区域已经包含了两个实体,它们组成了以下材质:
-
原理 BSDF(简称Principled)
-
材质输出
这些被称为节点。左侧的Principled节点包含你在上一章中已经看到的属性。许多这些属性在左侧都有小圆圈。这些被称为插座的圆圈可以连接到其他节点的插座。我们目前没有足够的节点来创建有意义的连接,但很快就会做到。
说到连接性,Principled有一个连接到材质输出节点的输出。如果你按住鼠标在材质输出的表面输入上并拖动连接,你最终会断开这两个节点之间的连接。然后,立方体将看起来是黑色的,因为没有表面信息。尝试通过将BSDF输出拖动到表面输入来重新连接这些节点。默认的灰色颜色将被重新建立。
节点与代码
在上一章中,你被告知着色器是指导 GPU 显示什么的代码行。当你使用Shader 编辑器中的节点时,你实际上是在编写代码,但你是以可视化的方式编码。由于在传统编程中行顺序很重要,因此节点以及进出节点的连接也很重要。然而,可视化编程更容易理解。
当我们在第一章中建模桶时,创建低多边形模型,我们需要在场景中添加 3D 对象。我们通过按Shift + A来实现这一点。我们将做类似的事情。在这种情况下,我们将向Shader 编辑器添加新的节点。Blender 是上下文相关的,这意味着如果鼠标在不同的工作区、区域和界面上方,相同的快捷键会产生类似的结果。如果你在Shader 编辑器上按Shift + A,你会看到一个列表出现并显示与Shader 编辑器相关的实体。
当这个弹出菜单打开时,它正好位于鼠标光标正上方搜索按钮的位置。要添加纹理节点,请执行以下步骤:
-
在添加菜单中点击搜索。
-
使用键盘输入
Image。 -
在过滤结果中选择图像纹理。
-
在其他节点附近点击任意位置。
这将在Shader 编辑器中引入一个图像纹理节点,就像你在以下图中看到的那样:

图 3.4 – Shader 编辑器中的图像纹理节点
当你在UV 编辑器中工作时,已经导入了pips.png文件,因此无需再次导入该文件。我们只需将其调出。通常,图像纹理节点左侧的新建按钮旁边的按钮会弹出一个列表;从该列表中选择pips.png。然后,将图像纹理的颜色输出连接到Principled的基础颜色输入。这将纹理应用到立方体的面上。哇,默认的立方体现在看起来像图 3.5中看到的六面骰子:

图 3.5 – 纹理文件通过材质应用于模型
六面骰子有点数,通常在每面用不同数量的圆圈标记。如果你想要一个看起来不同的六面骰子,用罗马数字表示数字,会怎样?要导入并应用新的纹理,请执行以下步骤:
-
使用Shift+A的帮助创建一个新的图像纹理节点。
-
点击打开按钮。
-
在本章的
Start文件夹中选择roman.png。 -
将此图像纹理节点的颜色连接到原理节点的基础颜色。
由于纹理坐标已经映射在UV 编辑器中,你可以轻松地交换形状相似但设计不同的纹理。
当你处理更复杂的模型时,你需要调整 UVs 的工作量就更多了;只要 UV 坐标与纹理的正确部分对齐,你就没问题。然而,想象一个不同的场景。你会如何建模看起来像是有轻微偏差的重复图案的表面?在下一节中,我们将探讨不同的纹理工作流程。
以程序方式创建纹理
近年来,“程序化”一词被广泛使用,尤其是在视频游戏行业中,用来描述不同的事物。尽管有人可能会说我们迄今为止所做的一切都是遵循某种程序,但在我们的语境中,这个词意味着其他含义。当我们在前一节中导入纹理文件时,它已经为我们设计好了。换句话说,它是一个静态文件。另一方面,“程序化”这个词是一个华丽的词,意味着动态。
在动态或程序化纹理工作流程中,目标是暴露纹理的某些参数,以便可以即时更改纹理,而不是返回图形编辑应用程序。由于一切都是动态的,你不需要导入图形文件,并且可以更改最终纹理的各个方面。例如,如果六面骰子使用的是程序化纹理,那么就像改变点数颜色和/或大小一样。
程序化纹理除了其动态性之外还有另一个好处。静态纹理文件需要你做之前的 UV 工作,以便顶点与纹理的部分对齐。在程序化工作流程中,纹理中的图案可能是无缝的,因此你不必担心 UVs。在我们的语境中,“无缝”意味着图案以完美的方式重复,以包裹模型。
我们将在 Blender 中创建一个程序化熔岩纹理,就像你在图 3.6中看到的那样,这样你可以更改其参数以获得不同的纹理外观。

图 3.6 – 热熔岩流过凝固的地壳
在一个新的 Blender 场景中,删除默认的立方体后,执行以下步骤:
-
添加一个平面。
-
切换到着色工作区。
-
调出默认的材质或创建一个新的。
-
如果需要,重命名材质。
目前没有什么新奇的,但我们很快就会利用以下五个新节点:
-
噪声纹理:Perlin 噪声是黑白值的混合,这些值以渐进的方式混合在一起,因此结果看起来像灰度值的汤。Blender 的噪声纹理与 Perlin 类似,但值不是灰度值;它们带有随机颜色。
-
Bump:它用于模拟高度波动,使表面看起来凹凸不平。
-
颜色渐变:这个节点的另一个名字可能是颜色映射器,但由于它使用渐变,所以“渐变”这个词意味着过渡是平滑的。
-
发射:在正常光照下,热物体有发光效果。这个着色器可以帮助你模拟从烤箱中出来的热钢块或明亮的灯泡。
-
混合着色器:这是一个混合两个着色器以创建组合着色器的着色器。
在我们继续介绍如何混合和匹配前面列出的节点,这些节点看起来像食谱的成分之前,这里有一些解释说明为什么选择了它们。当你想创建自己的程序纹理时,一个类似的过程可能有助于你选择有用的节点,而不是对选择哪些节点进行盲目的猜测。此外,在解释之后,试着想象哪个会连接到哪个。所以,我们开始吧。
噪声纹理字面上是一种带有噪声的纹理;这种噪声纹理中的颜色变化在Bump节点中用于模拟不同的高度。因此,噪声纹理就像是数据,而Bump节点则是其视觉表示,从某种意义上说。接下来是颜色渐变,显示为ColorRamp,它将颜色信息分配给不同的高度值。如果你曾经见过微型景观,它就像是画白色的山顶因为雪,以及根据海拔的不同,低地区域有不同的绿色阴影。
因此,前三个节点负责模拟高度的大部分工作。假设这个熔岩纹理描绘的是最近的形成,所以我们不仅仅是为了显示冷却后的熔岩。我们希望看到蒸汽腾腾、发光的熔岩在变黑和干燥的熔岩之间。因此,我们需要一个发射着色器。最后,由于高度是其自身的事情,我们正在添加发射部分,所以我们需要混合着色器来结合两者。
在处理节点时,你可以拖放节点来为自己安排一个更整洁的布局,以便理解正在发生的事情。无需多言,让我们继续。
-
添加上述五个节点。
-
按以下方式连接:
-
噪声纹理的颜色到Bump的高度
-
噪声纹理的Fac到颜色渐变的Fac
-
Bump节点的Normal到Principled BSDF的Normal
-
颜色渐变的颜色到混合着色器的Fac
-
Principled BSDF的BSDF到混合着色器的第一个输入着色器
-
发射着色器的发射到混合着色器的第二输入着色器
-
混合着色器的着色器输出到材质输出的表面
-
在连接节点时,没有左右方向。有些人将一组节点视为一个单元,并将它们彼此靠近排列。因此,有时,该组最后一个输出节点几乎垂直地连接到另一组节点。话虽如此,从左到右的一般流向符合前面的说明。无论你如何排列你的节点,布局可能类似于你在图 3.7中看到的。

图 3.7 – 火山纹理的节点排列
让我们按照节点列表的原有顺序尽可能多地查看这些节点的值。
噪声纹理
对于噪声纹理,使用了以下值:
-
类型定义了在噪声创建中使用的维度,这涉及到复杂的操作。它在更高级的案例中使用,所以我们保留默认的3D值。
-
缩放属性更像是缩放因子。太低,你更接近于噪声表面。太高,你看到的是更大的噪声景观,就像你在飞机上爬升一样。在这种情况下,我们将缩放设置为3.0。
-
细节属性是显而易见的。尽管较低的值肯定会造成模糊的外观,但超过一定值的高数值并不会对质量有很大提升。它只会增加计算时间。在我们的案例中,选择了8.0的值。
-
粗糙度与你在第二章,“建筑材料和着色器”中看到的概念不同。那一个影响的是表面的反射特性。这个概念是关于边缘的粗糙程度,从某种意义上说。换句话说,噪声值是如何相互混合的,而0.5的值就足够了。
-
扭曲属性创建漩涡和波浪图案。可能对于流动的火山外观,一点是必要的。你可以尝试一下,但超过一定值时,过多的扭曲会使事物看起来过于破碎。所以,0.2就足够了。
凸起
此节点将使用噪声纹理提供的数据,因此它可以以不同的高度值表示不同的颜色值。这就是为什么将高度输入连接到颜色输出,因为整个表面不可能只有一个高度值,所以我们不得不提供一组颜色。
在不勾选反转设置的情况下,使用了以下其他值:
-
强度值决定了颜色值与最终凸起之间的映射效果。它像百分比一样工作,因为值可以在0.0到1.0之间。我们将它保留在1.0。
-
Distance属性是一种乘数。它与Strength属性一起工作。将其中任何一个设置为0将导致表面完全平坦。也许描述这个属性最好的方式是它保留了Noise Texture中设置的所有细节。任何接近1.0的值将显示一个褪色的表面,而更高的值将填充更多细节。因此,3.0的值将产生足够详细的成果。
Emission
这是一个非常简单的节点,它负责使表面看起来发光。我们将在第四章中了解到灯光,调整相机和灯光,但如果你想让你的物体看起来像是在发射或辐射光线,那么你可以使用这个节点。例如,一块热的铁或荧光灯泡;在我们的案例中,熔岩。
由于这是一个非常简单的节点,我们只有以下两个属性:
-
自解释的Color属性用于选择表面将发出哪种颜色。对于热熔岩,你可以在界面上切换到Hex值并选择FF8400。
-
Strength值,在我们的案例中为100.0,定义了发射的强度。这是一个以瓦特为单位的单位,你可以科学地对待它,但选择任意值以获得视觉真实感通常也行得通。
ColorRamp
ColorRamp节点用于通过类似于阈值的梯度将输入值映射到颜色。描述看似简单,但实际上内部有很多内容。所以,让我们来分解它。
大多数时候,你将连接节点的输入和输出插孔到其他节点。然而,有时只使用一种类型的插孔是完全可接受的。例如,在Emission着色器中,你不必使用输入插孔来定义Color和Strength值。相反,你可以手动选择它们的值。因此,节点就像是一个信息源。
然后,有一些节点,将输入插孔连接到另一个节点的输出插孔会更有意义。ColorRamp就是这样的节点之一,它通过考虑传入的值来充当修饰符。Noise Texture的数据将是创建熔岩表面的一个因素(简称 Fac),因此我们连接了两个Fac插孔。
一旦数据被考虑在内,我们需要一个系统来处理它。这是通过ColorRamp节点中的梯度来完成的。梯度这个概念一开始可能听起来有些奇怪。如果你直接将Noise Texture的Color连接到Material Output,你会看到颜色有大小不同的区域。如果你这样做,记得撤销操作,以确保节点再次正确连接。我们需要一种方法将这些平坦但着色的区域转换为高度。
渐变将帮助我们定义哪些区域较高或较低,这样我们可以在稍后为不同的海拔分配适当的颜色。本质上,渐变是一个工具,通过颜色停止点来定义和混合这些区域。默认情况下,有两个颜色停止点,但你可以在渐变上方的加号和减号按钮中添加和删除更多的颜色停止点。这些停止点具有方形,上面有一个小三角形。你可以拖动这些停止点,这将改变我们之前提到的区域过渡。
当你有许多停止点时,有时很难点击和拖动它们,所以使用活动颜色停止点在它们之间进行切换。当你添加一个新的ColorRamp节点时,活动停止点被标记为0,并且它位于标有Pos的标签的左侧,这表示活动停止点的位置。活动停止点和位置字段都显示了必要的 UI 元素,以便你在悬停时更改值;你也可以点击并输入一个值。因此,通过使用活动颜色停止点和Pos,你可以精确标记颜色停止点将要放置的位置,如果你不想拖动它们的话。
最后但同样重要的是,有一个颜色选择器位于Fac插孔上方。你可以使用它来设置活动停止点的颜色。
由于这不是一个简单的节点,我们可以从一些视觉辅助中受益。图 3.8是对ColorRamp节点的放大查看。

图 3.8 – ColorRamp 节点的近距离观察
前面的图应该帮助你看到我们到目前为止所讨论的内容。同样,就像你在 3D 视图中使用鼠标的滚动功能可以放大和缩小一样,你可以在Shader Editor中这样做。这将帮助你更清楚地看到一些属性名称和值。
现在,是时候使用所有这些信息并标记我们的过渡;你将交互所有刚刚展示的元素。为此,执行以下步骤:
-
使用加减按钮来拥有四个颜色停止点。
-
设置
0,然后按照以下步骤操作:-
设置
0.45。 -
在
000000中设置颜色。
-
-
设置
1,然后按照以下步骤操作:-
设置
0.53。 -
在
FFFFFF中设置颜色。
-
-
设置
2,然后按照以下步骤操作:-
设置
0.94。 -
在
FFFFFF中设置颜色。
-
-
设置
3,然后按照以下步骤操作:-
设置
1.00。 -
在
636363中设置颜色。
-
注意,我们只选择灰度值。在真实景观中,较高的区域将是较冷的熔岩,而较低的区域将是炽热的熔岩池。因此,为了表示这个想法,我们选择深色和浅色。通常,某物越白,它就越热。停止点之间的接近程度决定了过渡是平滑还是尖锐。
虽然我们一直在使用ColorRamp节点,但我们的熔岩纹理的颜色将在Principled BSDF和Emission着色器中定义,并在Mix Shader中组合。目前,我们已经利用了Noise Texture的数据,并在梯度及其精心选择的值的帮助下转换了这些数据。我们将在Mix Shader部分再次回顾因子概念,但在那之前,让我们访问我们忠实的朋友Principled BSDF。
Principled BSDF
我们实际上在第二章中看到了这个节点,建筑材料和着色器,但它作为Material Properties界面的一部分显示。当您创建一个新的材质时,它默认使用这个着色器。它在体内结合了许多其他着色器。例如,它有一个发射插座,但由于我们无法一次完成熔岩形成的冷热部分,我们正在使用一个单独的Emission着色器。
我们将保留大多数选项不变,但以下是非默认值,为这次练习所选:
-
在颜色界面的Hex部分中,值为
4A4A4A。 -
0.2。 -
本练习中的
0.2。
您可以参考第二章中的图 2.5,建筑材料和着色器,并在发现着色器部分阅读解释,以刷新理解多个属性如何协同工作并影响最终外观。
Mix Shader
它根据Factor中的值将一个着色器混合到另一个着色器中。对于Factor插座的值,如果您选择0.0,则将完全使用第一个着色器。如果您选择1.0,则表示将使用第二个着色器。
十进制值的范围在 0 到 1 之间,但由于我们无法随意确定使用多少个着色器,因此很难知道选择什么。这就是为什么我们将ColorRamp的Color输出连接为因子,以便Noise Texture的波动会逐渐影响此节点。效果是级联的。换句话说,每个将要被涂上(对于干涸的熔岩)或橙色(对于热熔岩)的像素,都应该基于ColorRamp认为它在Noise Texture中的位置来决定。因此,颜色停止点充当阈值,所有这些都在Mix Shader中考虑在内。
一旦所有节点都已设置并连接,您可以随意调整它们的所有值,特别是ColorRamp。您会注意到,热熔岩部分在岸边似乎更凉爽,而在中间更密集、更明亮。尝试将颜色停止点靠近彼此,看看这些熔岩池中的热点区域如何变化。
使用传统的图像编辑应用程序,如Adobe Photoshop创建这种纹理可能是可能的,但这些应用程序基于图层,并且保持非破坏性并不总是容易。基于节点的处理方式带来的优势是快速迭代。有一点可以肯定的是,你不需要重新导入纹理来查看更改。所有这些都在你的眼前实时发生。
然而,最终,由于你正在开发游戏,你必须导出你的纹理,以便你选择的游戏引擎可以使用它。在以下和最终的章节中,我们将看到如何将我们的熔岩纹理导出到文件系统。
导出你的纹理
在后面的章节中,当我们接近与 Godot 引擎一起工作时,我们将更详细地探讨资产和项目管理。然而,在我们对熔岩材质所做的所有艰苦工作之后,现在是时候学习如何导出纹理了。
在本节中,我们将进行一些有趣但必要的操作来导出我们的纹理。首先,我们将更改 Blender 的渲染引擎。然后,我们将在材质的中间添加一个图像纹理节点,而不将其连接到任何东西。奇怪吧?Blender 有时工作得很神秘。
更改渲染引擎
到目前为止,我们一直在使用默认的Eevee渲染引擎。Eevee是一个实时渲染引擎,可以提供非常快速的结果。大多数游戏引擎都有自己的内部实时渲染引擎,负责计算光线和阴影。因此,Eevee是模拟 Blender 中你将最有可能在导出资产到游戏引擎时体验到的效果的好方法。然而,速度和便利性也带来了一些代价。
Blender 还有一个名为Cycles的引擎。与Eevee相比,Cycles是一个非常精确但速度较慢的渲染引擎。Cycles的精确性归因于它处理高级光照计算,这导致了高质量的结果,例如更好地显示反射和透明表面,显示更精确的阴影,甚至创建体积效果,如雾霾。以下是一个链接,展示了这两个引擎的功能和差异,以及使用案例:cgcookie.com/articles/blender-cycles-vs-eevee-15-limitations-of-real-time-rendering。
在这本书中,我们不会涵盖足够高级的主题,这些主题需要我们在Eevee和Cycles之间做出艰难的决定。因此,Eevee对我们来说已经足够好了。然而,当你与程序纹理一起工作时,至少在我们使用的 Blender 版本中,Eevee无法导出熔岩纹理。我们将不得不切换到Cycles引擎。幸运的是,这只需点击一下按钮即可完成。
在右侧的属性面板中,从顶部开始的第二个标签,看起来像数码单反相机的预览显示,将会打开渲染属性。顶部的下拉列表将显示Eevee;让我们将其更改为Cycles。此外,如果你有一块不错的显卡,你可能还想将第三个下拉列表的设备值更改为GPU 计算,这样你的显卡就可以代替你那老式的 CPU 来处理繁重的工作。
在那长长的属性列表中向下看,你会看到一个标题为烘焙的面板。如果你展开标题,你会看到一个烘焙按钮。我们很快就会点击那个按钮,但我们需要先准备好我们将要烘焙的内容。
烘焙纹理文件
当我们与立方体和骰子纹理一起工作时,我们使用了一个图像纹理节点来绑定文件系统中的一个现有图像。当纹理是程序性的时,我们的情况就不同了,因为这一切都是在内存中实时发生的。我们需要找出一种方法将此信息烘焙到一个文件中。由于没有这样的文件,我们需要假装我们有一个,如下所示:
-
添加一个图像纹理节点。
-
点击新建按钮。
-
在名称部分输入
lava。 -
点击确定按钮。
我们不会将lava连接到材质中。Blender 会做出一个合理的猜测,并将程序化纹理部分烘焙到这张图像中。
现在是时候在渲染属性中点击那个烘焙按钮了。底部的进度条将指示 Blender 正在执行其操作。一旦过程完成,着色工作区的左下角将充满熔岩纹理。那个显示烘焙纹理的小部分被称为图像编辑器。
如果你查看烘焙后的图像,你会注意到一些细节丢失了。在 1.0 中,热熔岩池有温暖和凉爽的区域。
在你的文件系统中的lava.png。现在这个文件可以被导入到一个新的 Blender 文件中,并用于一个pips.png到立方体的纹理。
任务完成。如果你选择了本章中写下的相同值,你应该已经有了图 3.6中看到的程序化熔岩纹理。此外,你还创建了一个静态版本。让我们总结一下本章还完成了什么。
概述
本章一开始简要讨论了纹理是什么以及为什么可能需要它们。为了回顾,如果你对模型表面只有颜色信息就满意,那么一旦建模和材质应用过程完成,你就可以结束了。如果你认为你需要在你模型的表面上展示独特的特性,你需要利用纹理。
为了达到这个目的,你发现了如何创建一个新的坐标系——通过一种称为 UV 展开的方法将空间坐标映射到纹理坐标——可能是必要的。一旦完成 UV 展开,你就可以将不同的纹理应用到你的 3D 模型上,因为 2D 到 3D 的映射已经建立。
虽然使用图像编辑应用程序创建纹理是完全可能的,但你同样知道如何在 Blender 中通过程序创建纹理。这是一个强大的方法,尤其是在处理难以进行 UV 展开的表面时,例如景观。
最后但同样重要的是,你学会了如何更改渲染引擎,以便将你的程序纹理导出到文件系统中。尽管这个文件是静态的,并且无法自动更新(当然,除非你用新的导出覆盖它),但你有一个好处,那就是可以轻松地分享文件。
你已经使用 Blender 的界面和鼠标在场景中移动,旋转视图以更好地查看你的模型、材质等。在下一章中,你将学习如何与相机和灯光对象一起工作,以创建一个可以在最佳光照条件下排列场景中物体的构图。
进一步阅读
要了解每个着色器节点的作用,你可以参考以下链接中的官方文档:docs.blender.org/manual/en/2.93/render/shader_nodes/。
为了进一步练习,想象一下火山纹理的方法还可以用在哪些地方。也许,通过精心策划的值和更多的颜色变化,炽热的岩浆可能是铁锈,而冷却的岩浆可能是油漆?
如果你好奇并想调查其他能够生成程序纹理的软件,你可以尝试使用Adobe Substance Designer。这是一个仅专注于创建纹理的强大程序。并非所有节点都标有相同的标签,但有很多与 Blender 相似的节点。实际上,如果你在那里练习技能并查看他人的创作,你可能会对在 Blender 中创建此类纹理有所启发。
第五章:调整摄像机和灯光
当你开始一个新的场景时,在大纲中会有默认的摄像机和灯光对象。尽管它们是场景的一部分,但当你正在建模一个新对象,围绕它旋转,查看其材质预览时,你仍在使用 Blender 的内部摄像机和照明系统。这种默认行为适合快速工作,但不会产生艺术和准确的结果。
在本章中,你将了解摄像机的作用以及如何使用灯光来达到你想要的效果。前提很简单:没有灯光你什么也看不见,如果没有设备来记录或捕捉,你什么也记录不了。
虽然听起来我们正在介绍两个不同的主题,但本章我们将讨论摄像机和灯光。在这两者之间,我们将优先考虑灯光;你将得到一个解释为什么的原因。
因此,就像在现实生活中一样,摄像机和灯光条件是相互协作的,它们对于获得你想要的最佳镜头至关重要。为此,我们将涵盖以下主题:
-
渲染场景
-
理解灯光类型
-
介绍 MatCap 和环境遮挡
在阅读本章后,你将知道如何选择正确的灯光类型并捕捉场景的镜头。你还将知道为什么你可能想要推迟设置摄像机和灯光。然而,我们将为你提供一种达到一定视觉真实感的方法。
技术要求
在本章中,我们将进入新的领域,因此,你可以更安全地依赖本书 GitHub 仓库中的文件:github.com/PacktPublishing/Game-Development-with-Blender-and-Godot。
当相关时,将提到适当的文件名。这些文件已经为你设置好了,这样你就可以专注于本章的内容。
渲染场景
在计算机领域,“渲染”这个词与词典中的其他含义相似。在 Blender 中的渲染过程将原始场景转换为精细的结果。在更高级的情况下,如果你的场景可能包含物理或粒子对象,这个过程还将负责计算这些动态对象的状态。然而,为了简洁起见,我们只关注摄像机和灯光对象在渲染中扮演的角色。
让我们通过以下步骤创建我们的第一个渲染:
-
开始一个新的 Blender 场景。
-
按 F12。
或者,你可以使用应用顶部附近的渲染菜单。这应该会给你以下输出:

图 4.1 – 使用 Blender 默认摄像机和灯光选项渲染默认立方体的第一个渲染效果
这可能并不令人兴奋,因为当你使用 Blender 工作时,你通常都会看到这样的外观。渲染显示在一个单独的窗口中,该窗口覆盖了你刚刚工作的 Blender 窗口。因此,你可以通过按操作系统的关闭按钮或按Esc返回 Blender 来关闭此窗口。
如果你拍摄更多的渲染并来回切换,你会注意到立方体和其他对象(如相机和灯光)下面的网格不再包含在渲染中。这是预期的。这些被称为辅助工具的对象将为你提供便利,但最终不会与你在一起。它们就像建筑建造过程中的脚手架。虽然它们在工作时很有帮助,但在工作完成后就会被移走。
让我们通过改变一个东西来重复之前的练习。如果场景中没有相机会发生什么?是时候进行实验了:
-
在大纲视图中右键单击相机。
-
删除这个相机对象。
-
按F12。
你预期会看到一个全黑的渲染吗?相反,你得到了一个错误信息,表明场景中没有找到相机。没有相机意味着没有工具来渲染你的场景,所以 Blender 显示了一个错误信息。
让我们通过删除灯光对象进行一个类似的实验。在启动一个新的 Blender 场景后,按照以下步骤操作:
-
在大纲视图中右键单击灯光。
-
删除这个灯光对象。
-
按F12。
让我们推测一下我们期望看到的内容。我们有一个相机来渲染场景,但没有灯光。尽管立方体对象是场景的一部分,但我们不应该能看到它。然而,如果你查看下面的渲染,你会看到一个立方体的轮廓:

图 4.2 – 场景中没有灯光时的意外渲染
大多数软件应用程序都带有默认设置,以便帮助用户。在这种情况下,Blender 自带了一个背景色,不幸的是,这个背景色对之前的渲染结果有影响。如果你将这个设置的色调改为黑色,例如,那么你将得到一个完全黑色的渲染。要实现这一点,请按照以下步骤操作:
-
切换到着色工作区,就像你在第三章中做的那样,添加和创建纹理。
-
在着色器编辑器中通过使用四个视图交汇处的下拉菜单从对象模式切换到世界模式。
-
将
000000更改。
以下截图显示了更改背景色的设置:

图 4.3 – 我们也可以使用着色器编辑器来更改场景的背景色
如果你现在再次进行渲染,你会注意到整个画面都是黑色的。没有直接或间接的光或颜色对结果产生影响。所以,尽管东西看起来相当暗,但这正是我们期望看到的结果。这什么时候会有用?如果你希望没有惊喜,这意味着你宁愿控制每一个光源及其贡献的程度,那么选择黑色作为背景颜色可能是个好主意。
然而,大多数 Blender 用户是艺术家,而不是科学家。因此,他们通常会使用多个光源,并调整这些对象的设置以实现视觉真实感,而不是科学准确性。因此,保留背景颜色可能也是你可能会做的事情。
说到光源及其设置,这正是学习 Blender 使用不同类型光的正确时机。我们将在下一节中点亮事物。
理解光类型
到目前为止,我们已经看到了一个有光源的渲染和一个没有光源的渲染。我们还没有发现这个光源是什么。在本节中,我们将了解不同类型的灯光。到本节结束时,你将对每种类型及其重要性有一个很好的了解。
我们将在Eevee渲染引擎的背景下进行这一发现,因为它很好地模拟了游戏引擎将如何处理你的场景。由于它默认启用,你在这个阶段不需要做出任何更改。因此,你首先需要了解如何使用基本类型的光来照亮场景。这正是我们接下来要做的。
光的类型
让我们来看看可用的不同类型的光:
-
点光源:这是当你开始一个新的 Blender 场景时默认获得的光源类型。有时也称为全向光,简称 omni light,因为它向所有方向发射光线。灯泡是这种光的一个不错的现实生活例子。当然,在现实中,灯泡并不是从底部发射光线,但这是一个很好的近似。
-
太阳光:当你需要恒定强度的光时,使用这种类型。换句话说,光线如此强大,在传播过程中不会失去任何强度。与其他光类型不同,太阳光,就像真正的太阳一样,也只向一个方向发射光线。因此,光线是从无限远的地方发射而来,没有失去其强度。
-
聚光灯:当你需要一个类似手电筒的光源时,你应该使用这种光类型。它会在你指向的方向发射锥形的光束。大多数购物中心和商店都有这种类型的光,通常隐藏在天花板中。
-
面积:如果你想有一个具有大表面(如窗户、电视屏幕或办公室灯光,如传统的荧光灯管)的光源,那么 面积 灯就是你的选择。你还可以定义面积形状。由于与 点光源 相比,它是一个相当大的光源,因此包括阴影在内的结果感觉更柔和。
为了更好地了解每种灯光类型的作用,你将打开一个为你准备好的文件,这样你可以快速切换到不同类型的灯光。按照以下步骤操作:
-
在
Chapter 4文件夹内打开Start文件夹。这可以在本书提到的 技术要求 部分的 GitHub 仓库中找到。 -
打开
Lights.blend文件。 -
按 Z 键,然后按 8 键切换到 渲染 模式。
场景包含一个立方体和一个大平面作为基础,以支撑这个立方体。四种不同的基本灯光类型都处于相同的位置,所有都使用默认设置。只有 聚光灯 在 大纲视图中 被启用,你可以在下面的屏幕截图中看到其效果。通过在 大纲视图 中点击每个灯光类型旁边的眼睛图标,相应地来回切换,你可以看到每种灯光的作用。注意每种灯光通过照亮某个特定区域或改变阴影的呈现方式所创造的总体感觉:

图 4.4 – 一个光源的,特别是聚光灯的属性
现在我们已经看到了每种灯光的作用,让我们来了解一下它们的一些属性。
光的基本属性
样本文件被设置成当你打开它时,属性 面板应该已经切换到适当的 灯光 选项卡;这将显示所有基本灯光共有的五个常见属性:
-
颜色:这是发出的光的色调。如果你在设计壁炉,你可能想选择橙色或红色色调,例如。
-
功率/强度:这定义了你的光源有多强大,以 瓦特 为单位。因此,值越高,光源就越强大。在 日光 的情况下,功率 属性被标记为 强度,但概念仍然是相同的。如果你正在设计一个对准确性至关重要的场景,并且希望你的灯光尽可能真实,那么你很幸运。以下 URL 的 灯光功率 部分列出了某些已知光源的值:
docs.blender.org/manual/en/2.93/render/lights/light_object.xhtml。 -
1.0,这是默认值,不会改变材料的感知颜色。减小它将减少颜色对材料的影响。本质上,这个值决定了光源对材料颜色的影响。 -
镜面反射:这与 漫反射 属性类似,但它影响 镜面反射 质量。
-
体积:这是一个相对高级的话题,当设置材料时涉及更复杂的设置。在这本书中,我们不会涵盖高级材料设置。然而,就像灯光的漫反射和镜面反射属性作为乘数一样,这个属性决定了光在体积中的贡献。
在这五个属性中,你很可能永远不会接触到漫反射、镜面反射和体积。这是因为,大多数情况下,改变材料的漫反射和镜面反射值是有意义的。此外,体积光是一个高级案例,可以通过其他方式处理,类似于通过调整材料的属性来调整它。
更神秘的灯光
如果你是一个好奇的人,并且阅读了有关照明的资料,通常在 3D 应用程序的上下文中,你会听到诸如环境光、全局照明等术语。尽管这些术语在生成渲染时相关且重要,但我们不会在这本书中涵盖它们,有两个原因。首先,基本灯光类型通常就足够了,因为这会给你一个更直接的结果和场景的感觉。其次,高级照明系统依赖于并影响基本灯光,通过调整来发挥作用。因此,作为初学者,理解基本类型会是一个更好的投资。
每种灯光类型的特定属性
虽然你现在已经了解了每种灯光的作用,但我们还没有研究什么类型的设置有助于这些灯光的独特性。现在,让我们看看每种灯光的设置,这些设置赋予了灯光其特有的外观和感觉。
点
半径是一个也用于聚光灯的设置,但我们将在这个部分介绍它,因为点光源没有其他相关内容。我们已经将灯泡作为点光源的类比。实际上,灯泡有不同的尺寸。因此,你可以想象半径值,以米为单位,作为一个确定灯泡大小的机制。
这个值产生的影响在于阴影的计算方式。默认值0.1将产生一个相当锐利的阴影。尝试将此值增加到1.0。你会注意到,会有多个阴影相互重叠,沿着远离光源的方向延伸。
如果你将半径增加到10.0,会发生一些有趣的事情。灯泡足够大,以至于它将包围立方体。它如此之大,以至于它与平面相交。立方体的阴影不再严格沿着远离光源的方向延伸。光源如此之大,以至于它似乎在半径值所对应的球体内部散布着多个微小的点光源。
太阳
在某些 3D 建模软件和游戏引擎中,太阳光通常被标记为方向光。这有一个很好的原因。在现实生活中,太阳离我们很远,但非常强大,以至于所有光线似乎都是平行的。因此,角度属性定义了光线的方向。
那么太阳光的位置如何呢?您可以尝试移动其位置,但由于光线被认为具有恒定的能量,无论它们来自何方,因此场景的整体效果不会改变。因此,角度是此类灯光类型的唯一有意义的因素。
点
点灯具有与点灯相同的半径属性。因此,最初它们是相同的东西,然后点灯在跟随圆锥形状的同时散发光线。
在此灯光类型的属性面板中有一个标记为Spot Shape的折叠部分。该部分包含两个属性:
-
大小:以度为单位测量,此值是圆锥原点的角度。值越高,光线击中表面后面积就越宽或越大。类似地,较低的值将使光线集中在更小的区域内。
-
0.0和1.0,就像百分比一样,用于调整这两个对比区域的融合程度。较低的值将产生更尖锐的过渡。因此,将其设置为0意味着非常尖锐的分离。
区域
为了使此灯光类型更有效,决定其形状设置非常重要。存在四种形状:
-
矩形
-
正方形
-
圆盘
-
椭圆
对于所有这些,您都可以自定义形状的大小。例如,矩形形状将接受两个值,但正方形形状只需要一个维度。如果您在测试场景中调整不同的值,您可能不会看到太大的差异。然而,请放心,它们在分布不同形状的区域灯光的更复杂场景中确实会产生实际差异。
总结
调整灯光设置只是开始。大多数 3D 专业人士致力于某些学科。灯光是这些学科之一,您将在此领域研究诸如全局照明、光晕、体积效果以及许多其他我们在这本书中不会涉及的高级主题。话虽如此,在 Blender 中使用相机和灯光仍然可能对了解您所采取的艺术方向有所帮助。例如,如果您正在设计一辆车,前大灯可能包含一个点灯。如果模型是火炬,则可能适合使用点灯。
现在,你可能认为我们并没有详细讲解关于灯光的内容,但我们对于相机的讲解也更为简略。这是因为本书主要讲述游戏开发。在 第三部分,克拉拉的财富 - 一款冒险游戏 中,我们提到大部分工作将在 Godot 中完成,所以你会看到我们将在 Godot 中设置和微调许多内容。其中一些努力将用于相机和不同的灯光对象。由于我们一直在构建单个模型或为模型构建材质,这些最终都将导入游戏引擎中,因此没有必要在 Blender 中对相机和灯光进行细致的工作。换句话说,在 Godot 中设置相机和灯光是实用的,因为 Blender 中的设置无法迁移。
现在你已经知道为什么通常应该忽略 Blender 的相机和灯光,让我们看看两种有助于你在 Blender 中工作更愉快的方法。
介绍材质捕获和环境遮蔽
由于在 Blender 中对高保真灯光设置进行更多投资已经不再有意义,我们或许应该调查不同的方法来使我们的场景看起来更好。我们接下来要做的事情仍然意味着你所看到的内容不会被导出。然而,这意味着你可以查看那些不再有默认且单调的灰色外观的模型。为什么不呢?与看起来不错的事物一起工作有时会感觉更愉快,并提高生产力。我们将探讨两种有助于你区分模型细节的技术。
材质捕获
材质捕获 代表 材质捕获。我们不会深入讲解 材质捕获 的构建技术细节,但简单来说,它是一种 Blender 内部使用的着色器,用于给模型带来不同的外观。通常,你需要切换到 材质预览 模式来查看你的材质在模型上的外观。
然而,在建模过程中,你通常使用 实体 模式,因为对于 Blender 来说,以这种方式显示你对模型所做的更改性能更佳。因此,当你仍然在 实体 模式下工作时,如果你想获得类似于 材质预览 的更好视觉效果,你可以指示 视口着色 使用 材质捕获。所以,这是两者的最佳结合。
要确保你正在使用 实体 模式,请执行以下操作:
-
按下 Z 键。
-
然后按下 6。
这将切换 视口着色 到 实体 模式。它也在 3D 视口 右上角的第二个图标中以一个圆盘的形式表示。我们将对 视口着色 进行一些修改,以便你的模型可以拥有更明显的细节。如果你点击那些图标右侧的向下箭头,你会展开一个面板。这个面板在下面的屏幕截图中显示:

图 4.5 – 默认视口着色选项
那个面板中的设置允许你在编辑模型时更改模型显示的方式。你已经在上方部分看到了当前设置的预览,作为一个球体。让我们点击光照标题下的第二个按钮MatCap。这应该会改变该面板中预览的外观,以及场景中模型的外观。
我们不会发现颜色部分,但尝试从第一章,创建低多边形模型中的桶体使用随机选项。你会看到桶体的不同部分采用随机颜色。这有助于区分场景中的不同部分。同样,我们将背景设置保留为主题。
让我们调查选项部分,并关注将给我们带来良好结果的部件:
-
启用阴影选项。
-
将其值设置为
0.5。
在实体模式下,你通常不会看到光源的效果,但最后一步将创建阴影效果。这是一个便宜的效果,可以有效地创建深度。
有时,你的模型可能会有远离质量中心的部件。这些外部部件也可能创建出从你的角度看会更深的区域。因此,你会有一些凹槽。为了更清楚地标记这些区域,请执行以下操作:
-
启用凹槽。
-
将其类型值设置为两者。
-
设置
0.5 -
1.0
- 设置
0.751.0
这应该会改变你的模型外观。凹槽选项,将类型设置为两者,将寻找你的模型中处于不同高度水平的部分并突出显示它们。从某种意义上说,如果你的模型布局像景观一样,山脊和山谷将被强调,以便它们更引人注目。我们选择的价值有些任意,所以请根据你的品味或模型的复杂性自由调整它们。
最后但同样重要的是,在MatCap的设置中,如果你愿意,你可以选择不同的材质。毕竟,我们仍然在观察一个灰色立方体,尽管我们已经改善了它的感知。例如,你可以做以下操作:
-
点击视口着色下的MatCap按钮下的球体预览。
-
选择第二行的第三个球体。
如果你的 Blender 版本中选择的界面组织方式不同,我们正在寻找一个看起来像棕色粘土的球体。这将改变你的立方体的外观,变成泥泞的粘土。以下截图显示了到目前为止我们所做的工作:

图 4.6 – 视口着色为你的模型提供多种不同的外观
如果泥泞的颜色太暗,那么第一行第二个球体是一个不错的选择。然而,请记住,这只是为了你在实体模式下与模型工作时的舒适感。这些更改对你在渲染或导出模型到其他软件时的结果没有任何影响。从某种意义上说,这些都是可丢弃的材质,可以使你在 Blender 中的体验更加愉快。
到目前为止,我们一直将实体视图视为材质预览。当你想要获得更多视觉清晰度而不预览模型分配的材质时,这很有用,因为那会进行额外的计算,考虑到灯光。接下来,我们将探讨在渲染模式下如何做类似的事情。
环境光遮蔽
在本节中,我们将发现另一个实用的视觉工具,它可以帮助你获得更高的视觉保真度。这个工具被称为环境光遮蔽(AO),它也是一种在大多数游戏中用来创建更逼真外观的方法。让我们来探讨它是如何工作的以及为什么它能起作用。
首先,让我们弄清楚定义。我们有两个名称:环境光和遮蔽。在 Blender 的上下文中,正如你可能猜到的,环境光是一个用来描述整体光照条件的术语。我们在本章的渲染场景部分接近结尾时将背景颜色切换为黑色来修改环境光。因此,我们已经熟悉了这个概念。
遮蔽意味着阻挡或遮挡某物。在我们的上下文中,这意味着阻挡光线。因此,我们希望有一些光线被阻挡或遮蔽。但具体在哪里呢?
无论是哪里,四处看看。你会发现,由于表面较平坦,一些区域会暴露在来自天花板或窗户的自然光或人工光下。光——更具体地说,组成光的光子——会从这些表面上弹跳。当这些平坦的表面相遇并形成一些更尖锐和更缓和的角度时,它们会迫使光子以之字形散射。结果,光线到达某些地方会更困难,因此你的模型几何形状将遮挡一些光线。
要看到 AO 的效果,请从本章的Start文件夹中打开以下任何文件:
-
Lights.Area.AO.blend -
Lights.Point.AO.blend -
Lights.Spot.AO.blend -
Lights.Sun.AO.blend
此外,记得通过按Z然后按8切换到渲染模式。否则,效果将不可见。你注意到立方体接触平面处的较暗部分了吗?那就是 AO,如下所示:

图 4.7 – 环境光遮蔽在立方体接触平面的地方可见
示例文件已经准备就绪,以便在属性面板的右侧应该已经可以看到环境遮蔽选项。通过开关它,你可以观察其行为。AO 影响边缘,就像有一个额外的阴影体积,在阴影自然发生的地方。这使得它看起来更加逼真。我们将在本书的后面部分单独探讨如何在 Godot 引擎中利用 AO。
此外,在 AO 设置中,如果你选择一个更高的距离值,它将从对象的接触区域采样更大的区域。这可能会帮助你获得更平滑或更锐利的 AO。
我们在本章中涵盖了大量主题。现在,是时候总结一下我们学到了什么。
摘要
我们在本章的开头通过有和无相机及灯光的场景渲染来开始这一章。在这个过程中,我们使用了上一章中介绍的着色器编辑器来改变背景颜色,也称为环境颜色。
然后,我们探讨了不同类型的灯光以及每种类型如何用于模拟现实生活中的案例。我们使用 Eevee 渲染引擎来完成这项工作。如果你切换到 Cycles 渲染引擎,灯光将具有更多和更高级的特性,但你在这章中学到的概念将保持不变。
我们还讨论了当我们在 Godot 中处理事情时,你的渲染关注点将被留到以后。然而,如果我们能够使用更好看的东西,这将是一个更加愉快的体验。为此,你被介绍了两种不同的方法。
第一种方法是MatCap,你可以用它来改变模型的外观,即使没有开启材质预览。第二种方法,环境遮蔽,涉及到感受物体相遇的位置以及它们在现有光照条件下的行为。如果你愿意,可以同时使用这两种方法。
在下一章中,我们将做一些改变。你将学习并准备一个用于动画的模型。为此,你将利用一个称为绑定的过程,并在你的模型内部模拟一个类似骨骼的结构,以便你可以对其进行动画处理。
进一步阅读
尽管本章涵盖了相机和灯光,但这类话题通常在许多出版物中被归类在渲染标题下。这是因为存在不同的渲染引擎,每个引擎对灯光和相机的处理方式都不同。此外,如果你想要进行更高级的渲染,后期处理和色彩管理可能会成为你的关注点。因此,相机和灯光只是渲染过程中的一个小部分。要了解更多信息,Blender 的官方文档页面可能是一个好的起点:docs.blender.org/manual/en/2.93/render/index.xhtml。
此外,这里有一些在线资源可能有助于你更深入地了解:
第六章:设置动画和绑定
在第四章,调整摄像机和灯光中,你看到了为什么你应该忽略 Blender 中的一些概念,特别是摄像机和灯光,因为它们不容易转移到 Godot 上。本章是一种相反的情况。你可能想知道游戏引擎不能为我们移动对象,对吧?毕竟,我们使用游戏引擎来促进诸如显示模型、创建具有视觉丰富效果的环境等事情。期望游戏引擎为我们动画模型是正常的。
虽然在 Godot 中动画简单对象是完全可能的,但对于复杂模型,如人类角色(或任何双足动物,如机器人)或狮子(或任何四足动物,如牛)来说,需要付出很多努力。因此,在 Blender 中做大多数动画更有意义,因为它提供了一个更加流畅的工作流程。我们将详细解释为什么是这样,以便你可以在自己的项目中应用类似的推理过程。
有时候,你可能会遇到一个看起来很完整、很漂亮的模型,但它可能不适合或尚未准备好进行动画。在第一章,创建低多边形模型中,我们讨论了顶点、面和边。我们将回顾一些这些概念,以便为我们的模型准备动画。
然后,当我们认为模型准备就绪时,我们将探讨 Blender 的动画功能。我们将通过发现两件新事物来完成这项工作。首先,我们将利用一种称为绑定的新方法,构建一个在动画模型中普遍使用的绑定。其次,我们将切换到一个专门用于动画的新工作区。在这个过程中,你将了解 Blender 的另一个不同方面。
在你看到绑定是如何进行以及模型如何被动画化之后,我们将探讨在 Blender 中准备和存储更多动画的方法,以便它们可以轻松地在 Godot 中使用。因此,一旦你事先知道后续需要什么,这些知识可能会帮助你根据需要在 Blender 中设置好一切,以免稍后更改变得过于繁琐。
尽管以下章节标题看起来欺骗性地简短,但我们将在本章中有很多内容要介绍:
-
建立动画的位置
-
理解模型的准备就绪状态
-
创建动画
-
为 Godot 准备动画
最后,你将知道 Blender 或 Godot 是处理动画的正确环境,以及如何为动画准备模型,以便你可以进行绑定。
技术要求
本章将包含许多移动部件,从字面意义上和比喻意义上来说。对于大多数刚开始练习 3D 的人来说,动画和绑定都是具有挑战性的话题。虽然我们将一步步进行,以在过程中提供额外的帮助,但你可能希望使用一些中间阶段的文件,而不是一次性完成所有工作。
如往常一样,本书的仓库将在以下链接中提供本章所需的文件:github.com/PacktPublishing/Game-Development-with-Blender-and-Godot。
建立动画的位置
Blender 和 Godot 引擎都具备动画制作功能。因此,你可能想知道哪种软件更适合创建动画。为了回答这个关键问题,我们应该讨论我们要动画化什么。当涉及到动画制作时,尤其是在游戏开发中,我们将探讨以下两个主要概念:
-
全身对象:例如弹跳球、船只或从源头抛出的投射物,这些都是像没有单独移动部件的固体系统一样行动的对象。系统可以作为一个整体移动,而不依赖于其各个部件。
-
连接系统:一些系统依赖于各个部件的运动。这些系统有相互连接的部件,各个部件协同工作以移动它们所属的系统。例如,猫用它们的脚,鸟用它们的翅膀,而人体通过两个与表面接触或与它们所在介质交互的肢体以某个方向移动。
有时,现实生活中的某些工具和设备可以完成类似的工作,并且可以使用其中一个代替另一个以快速解决问题。然而,我们有时会希望为特定的工作选择最佳工具。我们将根据我们刚才提到的概念来讨论 Blender 和 Godot,看看哪个选项可能是一个更好的选择。
在 Godot 引擎中动画制作
Godot 有一个组件,AnimationPlayer,它可以帮助你构建动画。我们将在后续章节中更详细地探讨它,当我们导入模型以创建点对点冒险游戏时。与其他应用程序的动画组件类似,它依赖于设置关键帧来标记动画对象的改变点。例如,要创建弹跳球动画,你会在动画的早期帧中标记球静止在平面上,并在动画的后期帧中标记世界中的更高位置。
使用 Godot 做这件事相当简单。你只需将重要事件标记为关键帧,这个操作被称为键控或插入关键帧。因此,引擎会计算出物体在两个关键帧之间的运动方式。然而,当系统比一个简单的球更复杂,并且有移动部件时,你可能会期望选择这些单独的部件来键控它们。在 Godot 中这样做并不容易,因为工作流程并不是以易于执行此类复杂操作的方式构建的。因此,当系统相对简单时,最好使用 Godot 引擎。
在 Blender 中动画制作
正如刚才提到的,当你正在动画一个由负责创建整体运动的部件组成的对象时,例如通过移动脚和手等个别部分来动画人体,那么在 Blender 中做这种工作将是正确的选择,多亏了称为绑定的方法。稍后,在创建动画部分,我们将解释什么是绑定,并了解如何为我们的模型构建一个绑定。
目前,你应该知道,为动画身体中的各个独立部件移动将需要绑定来加速动画过程。这正是 Blender 发光的地方,因为它提供了工具和自定义界面来帮助你完成这个过程。
除了创建动画的便利性之外,让我们指出另一个为什么 Blender 是动画复杂系统的更好选择的原因。如果你在 Godot 中构建动画,你只能在 Godot 中使用它们。相反,一个 Blender 动画将作为真理的来源,你可以与其他应用程序共享它。
总结
我们再谈一点为什么 Blender 可能是一个更好的选择,无论创建动画的复杂程度如何。如果你想要为你的游戏制作预告片,并且已经费尽心思创建了足够准确的相机和光照条件,类似于你将在游戏中使用的那些,那么你可以渲染你的场景,由许多帧组成,这将利用 Blender 的动画系统。
因此,对于可以移动的简单对象,利用 Godot 引擎的动画系统。对于具有独立移动部件的系统,最好在 Blender 中完成。毕竟,Blender 有专门的工具来促进高级动画的创建。现在,让我们讨论何时你的模型可以动画。
理解模型的准备程度
在第一章,创建低多边形模型中,我们从一个原始对象开始,并改变了它的顶点、面和边。在这个过程中,我们关心模型的外观。虽然听起来可能有些俗气,但外观有时可能会误导。为了正确动画,模型必须遵守某些除了外观之外的传统。换句话说,你必须确保你的模型是否已经准备好。
拓扑和绑定
模型的准备程度可以通过术语拓扑来定义,这听起来有点技术性。用通俗易懂的话来说,它是指模型顶点、边和面的分布和排列,总体上标志着模型对动画的优化程度。
并非所有拓扑都是相同的。有好的和坏的拓扑。让我们看看图 5.1,以更好地理解我们所说的拓扑或分布,特别是作为好坏的例子。

图 5.1 – 具有两种不同顶点分布的相同模型
前面的图显示了具有相同形状的模型,但拓扑结构不同。简单来说,左边的案例适合动画,而右边的案例需要一些工作来整理这些顶点,以形成一个良好的流动。然后,你必须通过均匀分布许多聚集的面来修复一些不规则性。所以,不仅右边的案例令人不快,而且在动画过程中也有害。
让我们简要地谈谈绑定的作用,以了解良好拓扑的重要性。如果你要建模一只人手,你将设计手指、指关节和手腕。模型,或者更准确地说,它的体积将是中空的。换句话说,你只会在给手形状的顶点。然而,在我们的心中,我们知道这只手应该内部有骨头。当你摆弄手指或弯曲手指的指关节和关节时,骨骼的不同部分开始移动,以便与骨骼系统相连的外部结构可以相应地移动。
为了模拟这一点,你利用一种称为绑定的实践,它涉及引入一个骨骼系统和一系列约束,这些约束管理骨骼系统的行为。我们将在本章后面讨论一个绑定示例。现在,我们仍然关注我们的模型是否为绑定做好准备。为了更好地强调拓扑和绑定之间的关系,让我们将注意力转向图 5.2。

图 5.2 – 手模型的不同拓扑
观察中间案例中面的排列更加自然,这显然比左边的案例有所改进。然后,对于中间案例,看看大拇指与手的主要部分相接的地方;该区域需要更多细节,以便当拇指像右边的案例那样伸展时,会有足够的几何形状来适应骨骼的行为。比较第一只手和第三只手,看看哪一只在你看来在手指之间的肉和皮肤上看起来更自然。
当模型在特定点弯曲或拉伸时,它将创建一些皱褶和凸起区域,类似于前图中手指与手相接的地方。如果顶点,因此面,没有平滑的流动,模型在这些薄弱部位将看起来撕裂或压碎。拥有正确的拓扑结构是一个难以掌握的主题,当初学者想要进入动画和绑定时,这会让很多人感到困惑。你可以在进一步阅读部分找到一些链接,帮助你理解良好和不良拓扑之间的区别。
为了满足良好的拓扑结构,由于在动作发生的地方需要正确对齐边和面,我们需要一种机制来移动有问题的边和面,使它们处于正确的位置。为此,我们将发现一种新的方法,或者更确切地说,是一个快捷方式。
抓取
在 第一章 创建低多边形模型 中,你了解到两个在 Blender 粉丝中非常常用的方法。它们是 旋转 (R 作为快捷键) 和 缩放 (S 作为快捷键)。还有一个我们故意在那个练习中省略的常见方法。我们依赖于修改器来移动顶点,所以我们没有使用它;然而,现在是时候使用它了。
如果你能够旋转和缩放物体,那么为什么不能移动物体呢?实际上,你可以,这个新方法将帮助你将顶点、边和面移动到任何你想要的位置。只有一个注意事项。尽管大多数人将这个操作称为 移动,但它的快捷键有点奇怪;它是 G。所以,在移动的上下文中,这个快捷键的一个更简单的思考方式可能是抓取。你抓取一个顶点并把它放在某个地方,从某种意义上说。
在大多数 Blender 教程中,你可能会发现人们将抓取和移动互换使用。它们是同一个意思。所以,在这本书中,当你看到“移动”这个词时,我们指的是抓取操作和 G 快捷键。
让我们通过一系列简单的步骤来练习这个新知识。在你开始一个新的文件后,执行以下步骤:
-
按下 Tab 键进入编辑模式。
-
仅选择默认立方体的一个顶点。
-
按下 G 键并移动鼠标。
当你移动鼠标时,所选的顶点现在正在被拉动。要终止抓取,你可以点击任何地方,这将使所选顶点回到其最后的位置。图 5.3 是我们想要达到的示例。

图 5.3 – 将顶点从原始位置抓取出来并移动到其他地方
你可能已经推测出顶点可以在三个轴上自由移动,这是正确的。如果你想限制移动到某个轴,并且希望精确移动顶点,你也可以做到。在编辑模式下,执行以下步骤:
-
选择另一个顶点。
-
按下 G,然后 X。
-
输入
0.5。
如果你想要选择其他两个轴中的任意一个,也可以。无论如何,你为任何给定轴输入的值定义了移动量。因此,负值仍然会将所选部分移动到你所选择的轴上,只是方向相反。
此外,有时你可能只想将选择移动到除某个方向以外的任何方向。当你开始一个抓取快捷键时,如果你在选取轴之前按下 Shift 键,它将选择移动到剩下的两个轴。所以,Shift+X 将将物体移动到除了 X 轴以外的任何地方。
如果你愿意,可以通过选择边缘或面来进一步练习抓取操作。很快,我们将探索动画的构建块。在这个过程中,你很可能会使用抓取操作。所以,当你准备好了,让我们看看我们如何使事物动起来。
创建动画
正如我们在“在哪里构建动画”部分中提到的,我们在 Blender 中将要做的动画类型涉及系统各个部分独立移动或有时协同移动。我们也说过,我们需要一种称为绑定的方法,所以让我们举一个例子来理解为什么绑定是有用的。
当你说话时,无论你是坐着还是走路,负责说话的肌肉和骨骼通常不会受到或影响你身体的其他部分。然而,当你走路时,你的腿围绕着髋骨旋转,整个系统触发其他自然动作,例如摆动手臂,轻微前后移动肩膀,等等。
在这两种情况下,无论是局部还是系统级的依赖,我们最终都会移动构成模型的一些顶点。由于移动这么多顶点是一项大量工作,我们使用放置在模型内部的结构来告诉必要的顶点移动到何处。创建这种结构的流程称为绑定。从某种意义上说,绑定模仿了现实生活中骨骼和肌肉的作用。
在本节中,我们将进行一个简单的绑定过程,并绑定一个低多边形蛇。通过这个过程,你将为动画准备模型,但首先,我们将了解一些基本组件,如下所示:
-
骨架(Armature):简单来说,骨架是一组骨骼,但更好的定义可能是一个作为控制结构的框架——材料对应纹理,骨架对应骨骼。因此,同一个骨架可以有多个骨骼。此外,如果动画的系统需要的话,绑定过程可能涉及多个骨架。
-
骨骼(Bone):这是绑定系统中最基本的部分。没有骨骼,就没有骨架,因此就没有可以动画化的东西。在现实生活中,当你的骨骼超出其自由区域时,你会感到疼痛,所以你的身体保持完整。可以说,有类似的方法来限制骨骼的自由,以便与其他骨骼协同工作。
我们首先将看看如何绑定一个模型。为此,我们将使用一个骨架和许多骨骼。在将约束添加到一些骨骼之后,绑定过程将完成。因此,最终,我们将使用我们的绑定来动画化蛇。
绑定点(Rigging)
现在理论部分已经讲完,我们可以专注于实际方面,主要是如何设置骨架和骨骼。为了专注于绑定过程,我们将使用一个低多边形蛇模型。Chapter 5的Start文件夹中的Snake.blend文件是一个很好的起点,到本节绑定的结尾,你将看到Snake.Rigged.blend文件中的内容。
除了这两个文件,我们还会提到其他一些补充文件,这些文件展示了中间阶段。和以往一样,你可以在技术要求部分提到的网址找到所有这些文件。
打开Snake.blend文件后,让我们按照以下步骤添加一个骨架:
-
在你的数字键盘上按3以切换到右正交视图。
-
按Shift+A。
-
选择骨架。
你也可以在Snake.First Bone.blend文件中找到前面操作的结果。如果你的键盘没有数字键盘,那么你可以点击3D 视图右上角的工具栏中的X轴,直到你在左上角看到右正交。以下图应该能帮助你看到我们到目前为止所做的工作:

图 5.4 – 警惕蛇!再想想,它似乎身体里没有恶意的骨骼
我们现在场景中有一个新的对象类型:一个骨架。你也可以在大纲中看到它,其标题旁边有两个绿色的棍状人物。目前,我们的骨架中有一个骨骼。所以,在这个阶段,骨骼和骨架几乎意味着同一件事。我们的目标,在绑定中,将是创建和分配蛇网格内部的一组骨骼。所以,让我们添加更多。
虽然我们似乎遇到了问题。我们之前添加的那个骨骼看起来被蛇的尾巴遮挡了。所以,如果我们继续添加更多的骨骼并将它们排列得与蛇的身体对齐,我们就无法看到我们在做什么。幸运的是,解决方案只需几步即可。当骨架仍然被选中时,你可以在属性面板中的骨架设置中展开视图显示,并打开前景选项。这将确保骨架始终可见。
没有数字键盘
数字键盘快捷键很有帮助,它们会让你的生活变得更轻松,尤其是在建模和绑定时,你需要经常从特定角度查看你的工作。以下网站提供了八种模拟数字键盘的方法:essentialpicks.com/using-blender-with-no-numpad/。
网格由顶点、面和边组成。同样,骨骼由三个部分组成:根、身体和尖端。尖端可以是另一个骨骼的根,反之亦然。就像我们可以进入编辑模式来改变网格的内部部分一样,我们也可以对骨架这样做。所以,选择骨架并按Tab。
你应该能够分别点击并选择根和尖部。当你选择关节之间的结构时,由于它们全部相连,它将自动选择根和尖部。图 5.5 仅显示了选中的尖部。

图 5.5 – 在编辑模式下选择了骨骼的尖部
知识的归属
在本节中,我们正在为蛇模型进行绑定,这是一个由艺术家 Quaternius 创作的资产。你可以在quaternius.com上关注他的作品。我们将在后面的章节中也使用他的其他资产。所以,感谢你的慷慨。
现在,我们准备向骨架添加更多骨骼。我们将首先定位初始骨骼,然后添加从尖部延伸出来的新骨骼。在仍然处于 编辑模式 的情况下,执行以下步骤:
-
选择根关节。
-
按下 G 键并移动鼠标,使关节位于蛇胸部的中间位置。
-
点击完成抓取。
-
选择尖关节。
-
按下 G 键并移动鼠标,使关节位于 Y 轴附近但位于尾巴内部。
-
再次点击完成抓取。
一张图可能非常有帮助,因为所有这些移动和定位听起来有点任意。图 5.6 是我们在最后几个步骤中取得的成果的示例。

图 5.6 – 为我们的蛇放置的合适骨骼
由于向上、向下或向右的概念在三维空间中失去了意义,因此有一个简单而有效的方式来表示骨骼的自然流动是很重要的。如果你比较 图 5.5 和 图 5.6,它们分别对应于 Snake.First Bone.Editing.blend 和 Snake.First Bone.Position.blend 文件,你会注意到关节之间的结构正在朝不同的方向延伸。骨骼较宽的部分靠近根部,而骨骼较窄的末端正接近其尖部。例如,想象你的髌骨作为一根骨骼的根部,而你的脚踝作为这根骨骼的尖部。此外,髋骨到髌骨,肘部到腕部,等等。
我们必须向我们的系统中添加几根更多的骨骼。我们将通过拉伸原始骨骼来完成这项工作。在仍然选择骨骼尖部的情况下,执行以下步骤:
-
按 E 键开始拉伸。
-
将鼠标向右和向下移动,使其跟随尾巴的形状。
-
点击完成拉伸。
-
重复 步骤 1 到 3,直到你拥有四根长度大致相同的骨骼。
结果在 图 5.7 中显示,你也可以打开 Snake.Tail Bones.blend 文件来比较你的结果。

图 5.7 – 组成尾巴的四根骨骼
点击的重要性
类似于完成抓取操作,挤出需要点击一下以固化挤出物体的位置。因此,在本章的剩余部分,当你看到单词 挤出 时,你被期望在满意物体的位置时点击并最终完成挤出。如果你提前终止挤出,你可以始终按 G 键并抓取这个新物体将其移动到其他地方,如果你愿意的话,继续挤出。因此,点击以最终完成抓取和挤出,并在需要时经常使用这两种方便的方法。此外,如果你在挤出过程中改变主意,右键单击将取消此操作。
挤出帮助我们一次完成几件事情。我们创建了一个新的骨骼,将其正确放置,使其根与前一骨骼的尖端对齐,将这个新骨骼设置为前一骨骼的父级,并最终将其尖端移动到我们开始下一骨骼的位置。
我们已经完成了一半的蛇骨骼添加工作。因此,现在进行一些整理工作是个好时机。我们将在稍后引用这些骨骼,因此我们现在重命名它们是明智的。如果你在挤出后注意到了新骨骼的名称,你必须已经看到它们被标记为类似于 Bone.00X 的格式,其中 X 是后续骨骼的编号。要重命名你迄今为止添加的所有骨骼,请执行以下步骤:
-
选择原始骨骼。
-
按 F2 并将其重命名为
Tail.1。 -
重复前两个步骤,直到所有骨骼的名称看起来像 Tail.X。
让我们继续添加躯干的骨骼。为此,我们将利用现在已重命名为 Tail.1 的原始骨骼。你在设置模型骨架时所做的某些决定将取决于你打算为骨架做什么。从头部开始并一直延伸到尾巴末端的操作是完全可能的。然而,我们知道这条蛇将有一个倾向点,主要是躯干和尾巴骨骼相交的地方。因此,你需要执行以下步骤:
-
选择 Tail.1 的根。
-
按 E 键以在右侧和顶部方向挤出一个新的骨骼,沿着躯干进行。
-
再重复 步骤 2 两次,以便最终有三个骨骼。
-
选择每根新骨骼并将它们重命名为 Torso.X,其中 X 是从 1 开始的连续数字。
结果就是你在 图 5.8 和 Snake.Torso Bones.blend 文件中看到的结果。

图 5.8 – 新骨骼已经按照躯干顺序添加到头部
我们现在可以规划剩余的骨骼。为了简洁起见,我们只关注两根骨骼:头部和嘴巴骨骼。如果你一直跟着做,Torso.3 的尖端应该仍然被选中。如果没有,请选中它,然后执行以下步骤:
-
按 E 键以在蛇鼻的末端挤出一个新的骨骼。
-
再次选择Torso.3的尖端。
-
按E键拉伸一个新的骨骼到蛇嘴的末端。
最后,完全构建的骨骼,您可以在Snake.Full Skeleton.blend文件中找到,看起来就像你在图 5.9中看到的那样。

图 5.9 – 我们蛇的骨骼已完成
我们已经完成了骨骼的构建。为了完成绑定,我们需要添加两个额外的骨骼,这些骨骼通常被称为控制骨骼。以下是对为什么一个简单的骨骼,尽管是必要的,但仍被认为不够理想的解释。这与以下两个相互冲突的概念有关:
-
正向运动学(FK):当你有一系列骨骼,并且你想移动末端骨骼时,例如,人的手上的大拇指,运动必须在考虑从肩关节开始的所有中间骨骼的位置和方向值的同时进行计算。因此,运动从根部开始,向前进行。
-
逆运动学(IK): 这是一个更有效的方法,在先前的例子中,通过移动大拇指,所有连接的骨骼将依次确定它们的状态,而不是计算整个系统的行为。因此,移动的骨骼决定了后面的骨骼应该如何表现,后面的骨骼也以同样的方式一直到达根部。
在我们的练习中,我们更喜欢逆运动学(IK),因为它使用起来更加方便,并且在行业中得到了广泛接受。如果您想获取更深入的信息,特别是关于正向运动学(FK)和逆运动学(IK)的数学方面,请参考以下两页:
要将逆运动学(IK)引入我们的一些骨骼,我们需要创建控制骨骼,这些骨骼将运动传递到其他骨骼。虽然这些控制骨骼在视觉上看起来像是骨骼的一部分,但它们将与骨骼解耦。目前,所有已经拉伸的骨骼都已经自动设置为父级。因此,当我们从末端骨骼拉伸它们时,我们需要将我们的两个控制骨骼从父级中移除。
看起来这些骨骼中有一个可能是从头部骨骼上来的,而另一个控制骨骼,由于对称性,可能是从Tail.4骨骼上来的。假设您仍然处于右正交视图,为了创建这些骨骼,您需要执行以下步骤:
-
从头部骨骼的尖端向左拉伸一个骨骼。
-
将这个新骨骼重命名为
Head.IK。 -
从Tail.4骨骼的尖端向右拉伸一个骨骼。
-
将这个新骨骼重命名为
Tail.IK。
我们已经创建了两个新的骨骼,但它们仍然附着在骨架上。因此,我们需要将它们分开。ALT+P是一个可以用来清除父级关系的快捷键,但我们将要在别处进行解耦,因为我们还需要关闭另一个设置。所以,让我们同时进行这两步,如下所示:
-
选择Head.IK骨骼。
-
在属性面板中打开骨骼属性选项卡(绿色骨骼图标)。
-
在该选项卡中展开关系部分。
-
通过在名称字段中点击X来清除父级。
-
关闭变形选项。
-
对Tail.IK骨骼重复步骤 3 到 5。
Snake.Full Skeleton.IK.blend文件包含了您迄今为止所做的一切进展,但让我们解释一下最后几个步骤我们所做的工作。我们曾经看到骨架属性,所以我们要求属性面板显示另一个视图来显示骨骼属性。我们断开了我们的控制骨骼与其父级之间的连接。由于没有父级,连接复选框自动关闭。最后,我们关闭了一个至关重要的设置:变形。
如果您还记得拓扑结构是什么以及为什么我们使用绑定系统来动画弯曲和拉伸的系统,那么您就会知道变形是关键。我们希望蛇的骨架变形其所在的网格。然而,我们不会希望控制骨骼也这样做,因为我们将使用这些骨骼来指导整体运动。因此,它们不应该变形任何东西。
话虽如此,它们将负责 IK,这是绑定中最后缺失的一块拼图。为了完成绑定,我们需要添加IK成分,我们将在姿态模式中这样做。
在第一章《创建低多边形模型》中,我们在对象模式和编辑模式之间来回切换。在本章中,我们一直处于编辑模式,以移动骨骼的各个部分并挤出新的部分。骨骼可以处于另一种模式,姿态模式,通过引入约束来定义骨骼之间的关系。将这种新模式视为编辑骨架的行为,从而确定模型的姿态。
假设您已经在编辑模式中,按CTRL+Tab然后按2来切换。或者,如果您在对象模式中,则CTRL+Tab将直接带您进入姿态模式。请记住,如果您已经选择了骨骼或骨架,则此方法有效。或者,左上角的下拉菜单可以帮助您进入正确的模式。我们现在可以按照以下方式添加IK约束:
-
选择Tail.4骨骼。
-
在属性面板中打开骨骼约束属性选项卡(带绑带的蓝色骨骼图标)。
-
在添加骨骼约束下拉菜单中选择逆运动学选项。
-
对头部骨骼重复步骤 3。
我们已经为两根骨骼添加了缺失的IK组件。也许你注意到了,约束不是添加到控制骨骼上,而是添加到它们之前的骨骼上。现在,我们将映射一些IK约束的值,以便使用控制骨骼。要做到这一点,当头部骨骼被选中时,执行以下步骤:
-
在目标字段的IK约束中点击正方形图标。
-
在选项中选择骨架。
-
在骨骼字段的骨骼图标上点击。
-
在选项中选择Head.IK。
这将指定Head.IK作为头部骨骼的控制骨骼。所以,从现在开始,无论何时你与Head.IK交互,它都将控制连接到其他骨骼的头部骨骼。这就是为什么你看到一条从Torso.1和Tail.1骨骼之间的关节到尖端的虚线黄色线。
让我们按照前面的方法将Tail.4和Tail.IK关联起来,这样与Tail.IK的交互就可以控制尾巴骨骼的行为。选择Tail.4然后执行以下步骤:
-
在目标字段的正方形图标上点击后,在选项中选择骨架。
-
在骨骼字段点击骨骼图标后,在选项中选择Tail.IK。
-
将链长度值更改为3。
前面的指令集中的前两步几乎完全相同,只是我们选择了适当的骨骼。最后一步引入了一个新概念,告诉控制骨骼根骨骼在骨骼链中的位置。虚线相应地移动。最终结果就是你在图 5.10中看到的结果。

图 5.10 – 一个完全装备的蛇
我们一直做这些工作,以便骨架成为蛇的一部分。然而,如果你查看大纲,你仍然可以看到这两个对象是分开的。现在是真正将骨架连接到蛇的网格上的时间:
-
切换到对象模式。
-
首先选择蛇网格,然后按住Shift键选择骨架。
-
按CTRL+P调出设置父对象菜单。
-
选择带自动权重。
当你将骨架设置为网格的父对象时,会发生两件事。首先,Outliner中的Snake将移动到骨架项下的子项。其次,Snake将分配一个骨架修改器,这将在这两个对象之间建立连接。
最后,骨架将指定其骨骼到附近的顶点,这样当骨骼移动时,它就会带动相关的顶点。就好像一些靠近特定骨骼的顶点在优先级方面“重量”更重。因此,你不会看到尾巴骨骼将远离顶点那么远。
呼,装置终于完成了。正如你可能已经注意到的,所有这些创建和分离骨骼、添加约束、调整设置等等有时可能变得有些棘手。你会有视觉线索来了解哪个骨骼在做什么以及它们是如何连接的,但场景可能会很快因为小工具而变得杂乱。然而,就像任何其他事情一样,通过练习你会习惯于这样做。关于这一点,你将在进一步阅读部分找到更多高级装置材料的链接。
我们在开始和结束文件夹中提供了Snake.Rigged.blend文件,供你比较结果。你也可以将此文件作为下一节中的起点。由于我们认为装置对于动画是必要的,并且我们的装置已经完成,我们现在可以转向一个新的部分,在那里我们将了解 Blender 的动画工作区。
动画制作
我们即将为我们的蛇进行动画制作。我们已经准备了一个骨架并引入了两个控制骨骼来构建一个装置。在本节中,我们将使用这个设置来创建攻击动画。使用本节中介绍的方法,你可以为你的模型创建不同的动画,并将这些动画与模型存储在同一文件中。
让我们切换到动画工作区,以利用一组更合适的界面。布局将主要变为两个并排的3D 视图面板,以及下面看起来像时间轴的东西。实际上,底部有两个面板,如下所示:
-
曲线图:我们将很快使用关键帧来标记模型随时间移动时的定义点。例如,一只青蛙可以有一个关键帧表示它的休息位置,然后在时间稍后定义另一个关键帧,表示它跳得最高的水平。
-
时间轴:这是曲线图的一个简化版本。它用一个时钟图标表示,并允许你从更高层次看到事物。我们不会过多地使用这个界面,但设置动画的开始和结束关键帧很有用。
除了这两个编辑器之外,还有一个图形编辑器,你可以通过点击任何面板左上角的下拉菜单中的图标来访问。实际上,让我们通过将左边的3D 视图转换为图形编辑器来做这件事。当你完成时,你应该会看到以下类似的内容:

图 5.11 – 我们已经进一步自定义了动画工作区
我们已经拥有了动画蛇所需的一切。我们将从攻击动画开始。为此,我们将把头部向前移动,并将尾巴抬起以描绘一个威胁性的姿态。首先,通过按数字键盘上的 3 键切换到右正交3D 视角,并执行以下步骤:
-
进入姿态模式。
-
选择头部.IK骨骼。
-
按 I 键插入关键帧,并在选项中选择位置。
此操作将在dope sheet的第一帧中添加一个关键帧,并在dope sheet和图形编辑器中填充一些元素。到目前为止,一切顺利。看看动画编辑器中添加了什么,并在两个编辑器中展开Head.IK标题,以了解底层发生了什么。我们正在标记Head.IK骨骼的位置。
对于蛇攻击动画的下一个事件,我们需要将蛇头向前移动并标记(标记)其新位置。为此,我们需要在时间轴中选择一个新的帧,如下所示:
-
将帧值从1更改为10(在时间轴的开始部分稍左)。
-
按G并将头部稍微向左和向上移动。
-
按I插入关键帧并再次选择位置。
这应该在图形编辑器中添加更多元素——更具体地说,是曲线——这是好的,因为您可以使用这些曲线来微调动作的开始和结束方式——更突然或更平滑,这可以用于更戏剧性的效果。我们将其留给您的艺术诠释。目前我们能做的就是完成头部的运动,使其回到原始位置,如下所示:
-
将帧值从10更改为25。
-
按Alt+G将其位置重置为原始值。
-
按I插入关键帧并再次选择位置。
图 5.12显示了我们的进展情况。

图 5.12 – 我们通过控制骨骼对头部骨骼进行了动画处理
最后,我们通过动画Head.IK骨骼来移动躯干骨骼。这就是为什么我们实施了控制骨骼而不是移动单个躯干骨骼。此外,我们没有对Mouth骨骼做任何特殊处理,但这也随着头部移动以保持同步。
让我们对尾巴做类似的事情,如下所示:
-
将帧设置为1。
-
选择Tail.IK骨骼。
-
按I插入关键帧并选择位置。
-
将帧设置为10。
-
按G并将尾部稍微向上和向左移动。
-
按I插入关键帧并再次选择位置。
-
将帧设置为25。
-
按Alt+G重置位置。
-
按I插入关键帧并再次选择位置。
在这个姿势下,尾巴自然看起来很生气,这突出了头部的运动。顺便问一下,你的头在哪里?如果你在** dope sheet中看,头部动画的关键帧已经消失了。Blender 只显示所选对象的关键帧,以保持界面简洁。您可以通过切换仅显示所选按钮来显示所有内容,该按钮看起来像dope sheet标题中的选择图标。图形编辑器**中也有类似的按钮;如果您禁用两个,您应该看到类似于图 5.13中的内容。

图 5.13 – 编辑器中可见头和尾的关键帧
你也可以参考Finish文件夹中的Snake.Animated.blend文件。
我们已经完成了第一个动画。如果你想创建另一个动画,你会在哪里做?看起来我们可以在时间轴上继续添加更多关键帧。然而,我们如何知道哪些关键帧负责特定的动画?
我们可以在 Blender 和 Godot 的上下文中回答这个问题。实际上,一旦我们了解了如何在 Blender 中为同一模型创建独立的动画,我们实际上就已经为动画发送到 Godot 做好了准备。为了做到这一点,我们将在下一节中探索动作编辑器。
为 Godot 准备动画
为不同的动画创建单独的 Blender 文件会非常难以管理。如果我们有一种方法可以在同一个文件中存储多个动画,那就太好了。幸运的是,确实有。我们需要使用一个新的界面,称为动作编辑器来做到这一点。让我们看看我们如何使用它为蛇创建另一个动作。
Dope Sheet面板的左上角有一个下拉菜单。尽管整个面板都可以被认为是Dope Sheet面板,但我们一直使用其默认视图。这与3D 视口的工作方式类似。当我们切换对象模式和编辑模式时,我们仍然在同一个3D 视口面板中工作,但处于其专业视图之一。换句话说,这些下拉菜单自定义了你所在的面板。要将Dope Sheet面板切换到其动作编辑器视图,请执行以下步骤:
-
展开显示Dope Sheet的下拉菜单。
-
在选项中选择动作编辑器。
这将揭示我们第一个动画的标题,“攻击”。现在,你已经将默认名称更改为一个你可以轻松跟踪的名称。此外,当我们将此模型导入 Godot 并想要触发正确的动画序列时,我们将使用此动作名称。让我们按照以下步骤创建更多动作:
-
点击动作标题旁边的第二个图标(堆叠纸张的图标)。
-
将这个新动作的标题更改为
Idle。
这实际上会创建第一个动画的副本。除了标题之外,一切都是相同的,但现在我们可以更改与标题匹配的动画功能。在大多数游戏中,角色的空闲状态通常看起来很平静,但他们会有轻微的上下摆动动作,这表明角色是活着的,但其他方面处于中性状态。我们的空闲动作包括以下步骤:
-
将帧设置为10。
-
选择Head.IK骨骼,并通过按Alt+G重置其位置。
-
按G并将骨骼略微向下移动。
-
按I并选择位置。
-
重复步骤 2 到 5,但将Tail.IK骨骼稍微向上移动。
让我们再做一些事情并测试我们的新动作。将时间轴中的结束值改为25,然后点击播放按钮。这将让您以循环的方式查看动作,以便您能够判断动画中的位置是否足够好。如果您想调整头部和尾部控制骨骼的位置,请记住通过按I来设置它们的值。
我们的蛇正在闲置,上下摆动,可能是在等待攻击目标。通过使用动作标题左侧的下拉菜单,您可以在不同的动作之间切换。
恭喜!您已经正式创建了两个动画。如果有时难以遵循说明,您可以在Snake.blend文件中的Finish文件夹中找到一个完整的示例,以供进一步学习。
我们在本章中做了很多工作。现在是时候总结我们的努力了。
摘要
本章一开始就讨论了哪种软件(Blender 与 Godot)适合动画。我们举例说明了不同的动画案例,并确定 Blender 是动画具有独立运动部件的系统的正确选择。
我们接着讨论了良好几何形状的重要性,也称为拓扑,因为从动画的角度来看,并不是所有看起来好的东西都足够好。一旦系统开始运动,顶点、面和边将像骨架的包装一样行动。如果您知道您将要对模型进行动画处理,您可能需要提前更加小心地创建几何形状。
尽管如此,如果这样的早期选项并不总是可行,为了防止模型某些区域可能出现的撕裂和折痕,我们引入了抓取选项。它可以帮助您通过将它们移动到不同的位置来解决有问题的部分。
一旦顶点的分布处于有利的位置,就可以开始骨架设置。实际上,这是大多数学习任何 3D 建模软件的艺术家面临的最先进的话题之一。有时将骨架想象成控制木偶的一堆绳子是有帮助的。就像一个木偶师一样,你需要知道哪根绳子控制哪个部分。为此,我们引入了 IK,它比更直接的 FK(正向运动学)方法具有优势。
在我们为蛇创建了一个骨架之后,我们发现了动画工作区。由于骨架依赖于通过 IK(逆向运动学)的控制骨骼,我们的动画制作变得轻松自如。在这个过程中,我们学习了如何移动骨架的各个部分并关键帧它们的属性。在我们的简单案例中,这仅限于位置,并且我们保持运动在一个轴上。
最后,我们了解了如何为同一模型存储两个动画,而不是动作。一旦您正确标记了动作,不仅您在将来在 Blender 中找到它们会更容易,而且您在 Godot 章节中也会看到这种做法的好处。
你已经完成了五个章节,从创建模型到为模型添加动画。在这个过程中,你还学习了如何构建和应用材质和纹理。在接下来的章节中,我们将探讨如何从 Blender 导出我们的作品。
进一步阅读
我们提到了拓扑的重要性,了解什么构成好的或坏的拓扑可能具有挑战性。因此,为了查看更多示例并从他人的专业知识中受益,请参考以下链接:
-
blender.stackexchange.com/questions/140963/do-i-have-bad-topology -
www.reddit.com/r/blenderhelp/comments/speyjs/is_this_bad_topology/
一些 3D 实践者只专注于动画。虽然可以在不绑定 Blender 对象的情况下对某些 Blender 对象进行动画处理,例如移动摄像机和灯光以在场景中移动它们,但大多数在线课程通常一起涵盖绑定和动画主题。以下是一份在线课程和材料的列表,供你进一步了解这两个领域:
-
CG Cookie:
cgcookie.com/courses?sort_category=140,179 -
Udemy:
此外,当你浏览更多培训内容时,你可能会遇到一个名为 权重绘制 的主题,这有助于确定绑定如何优先处理附近的顶点。我们为了简洁起见省略了它,但如果你想要更全面地了解,这是一个你很可能想要涵盖的主题。
在接下来的章节中,我们将逐步从 Blender 过渡到 Godot。因此,这一章实际上是 Blender 的最后一章实践操作。如果你想了解更多关于 Blender 能做什么的信息,有一些非常有用的资源,包括书面和视频格式,由 Packt Publishing 提供,如下所示:
-
《Blender 3D 案例教程》 by Oscar Baechler 和 Xury Greer
-
《Blender 3D 建模与动画:在 Blender 中构建 20+ 个 3D 项目》 by Raja Biswas
-
《实现真实感:PBR/Blender 2.8 工作流程》 by Daniel Krafft
第二部分:资产管理
在这个过渡部分,你将学习如何从 Blender 迁移到 Godot。这个工作流程的一个关键部分是了解哪些设置是重要的。通过了解潜在的问题和如何应用解决方案,你可以为必须使用第三方资产的情况做好准备。
在本部分,我们将涵盖以下章节:
-
第六章**,导出 Blender 资产
-
第七章**,将 Blender 资产导入 Godot
-
第八章**,添加声音资产
第七章:导出 Blender 资产
你的 Blender 之旅已经带你到了这个阶段,你想要将 Blender 中的创作部署到 Godot 引擎中。我们将在下一章中介绍如何将这些资产导入 Godot,但首先,我们必须确保我们在 Blender 中的所有内容都符合 Godot 的标准。因此,在导出之前,我们必须解决一些小问题。
首先,我们要确保我们模型的几何形状是正确的。我们已经讨论了多边形;我们将更深入地了解它们,以实现具有更好几何形状的模型。原点在 Blender 和 Godot 中都是一个重要的概念。我们将讨论为什么它们很重要,并学习如何更改原点。
到目前为止,我们还没有讨论过我们模型的维度。然而,比你的模型维度更重要的是,我们将研究一个称为尺度或尺度因子的概念,这在将你的资产发送到 Godot 引擎以及其他游戏引擎时至关重要。准备你的模型的最后部分是一个组织实践:给你的资产命名。
在我们完成准备工作后,我们需要将我们的资产转换为 Godot 能理解的格式。为此,我们将探索glTF并比较这种格式与其他几种格式。一旦 Godot 导入这种文件类型,它将理解如何理解存储在 Blender 文件中的顶点、材质和动画。我们将在下一章中探讨导入。
最后,仅仅因为我们可以从 Blender 文件中传输资产,并不意味着我们应该无所不包。我们将讨论从游戏开发角度来看,Blender 场景中哪些对象是有用的。在这个练习中,我们还将学习如何将我们想要导出的对象的偏好存储在预设中,这样我们就不必每次都记住导出条件。
在本章中,我们将涵盖以下主题:
-
准备导出
-
探索 glTF 和其他导出格式
-
决定导出什么
到本章结束时,你将知道如何为导出准备你的模型,选择合适的导出格式并配置它,以及如何仅导出你想要的材料。
技术要求
这是一章关于理解一些概念而不是实践的内容,所以你将做最少的作业,比如查看某些值,偶尔旋转一些对象。你可能会稍后再次查阅本章,以记住如何导出你的工作样本。因此,先进行初步阅读,然后再回来重读是完全可以的。
在本章的相关部分,将提到Start和Finish文件夹中适当的文件名。包含必要资产的文件已提供在本书的 GitHub 仓库中:github.com/PacktPublishing/Game-Development-with-Blender-and-Godot。
准备导出
计划在未来的版本中使 Blender 和 Godot 引擎之间的转换更加无缝。例如,你将能够直接在 Godot 项目中部署你的 Blender 文件,并直接在 Godot 中访问你的 Blender 场景中的元素。然而,我们还没有达到那个阶段,所以在我们将内容发送到 Godot 之前,我们需要做一些整理工作。
以下列表并不完整,但它涵盖了艺术家在从 Blender 到 Godot 转换时遇到的最常见问题:
-
决定如何处理 n 边形
-
设置原点
-
应用旋转和缩放
-
正确命名事物
现在,让我们讨论这些主题(问题)及其解决方案。我们将从劳动密集型主题开始,并在点击导出按钮之前完成更容易处理的事情。
决定如何处理 n 边形
让我们给n 边形一个正式的定义,并继续讨论它在我们的工作中的相关性。从数学上讲,有n条边的封闭平面是一个 n 边形,但我们为其中一些 n 边形使用了更友好的名称。例如,三角形是 3 边形的另一个名称。此外,对于等于或超过五条边的任何数量的边,我们通常使用希腊前缀来描述它们——这包括五边形、六边形、七边形以及其他。最后,一个问题供你思考:你如何称呼四边形,是正方形还是矩形?
虽然没有阻止你创建由可以组成任何类型 n 边形的面构成的三维对象,但在某些情况下你应该避免这样做。这不是一条硬性规则,但这是你需要记住的事情。那么,这对我们为什么很重要呢?
我们在第一章中简要讨论了图形处理单元(GPU)在创建低多边形模型中的作用。作为一个提醒,GPU 会将多边形分解成最小的 n 边形,即三角形。所以,当你向 GPU 扔出一堆复杂的多边形,如五边形或更复杂的形状时,它会尽其所能将这些复杂形状处理成三角形。这个过程称为三角化。以下图显示了三角化的几个示例:

图 6.1 – 同一多边形的三角化结果可能不同
因此,当你将三角化任务留给 GPU 时,它会假设哪些顶点应该连接。记住,我们不想所有顶点都连接,只希望连接最少的顶点,而不创建任何重叠的边。所以,对于一个五边形,我们可以有五种不同的三角化情况。这对 GPU 来说是一个很大的猜测工作,以确定你更喜欢哪一种。
在 第五章 设置动画和绑定 中,我们讨论了 拓扑 的作用,这主要涉及分布边和面。如果你通过该章节提供的 URL 仔细研究了内容,你一定已经遇到了边流的概念。如果你有一个应该弯曲模型的绑定,你希望边尽可能直地进入弯曲部分。因此,自己进行三角剖分以创建平滑的边流或完全避免任何 n-边形都是有益的。
n-边形通常在你进行环切时出现,但你也可以在编辑模型的其他部分时意外地创建它们,而没注意到。如果你无法避免创建它们,一个快速去除它们的方法是手动连接一些顶点。你会在 Start 文件夹中的 Ngons.blend 文件内找到一个具有五个顶点的对象,因此有五个边共享一个面。那是一个五边形或五角形。让我们看看我们如何修复它:
-
选择顶部的顶点和一个底部的顶点,通过按住 Shift 键。
-
按 J 键触发 连接顶点路径 操作。
这可能看起来没有太大区别,但你通过连接这两个顶点已经增加了一个面。你现在必须有两个面了。让我们做类似的事情,但要注意状态栏右侧显示的面数。在你完成以下操作后,它应该显示 面数:0/3:
-
选择顶部的顶点,然后通过按住 Shift 键选择其他底部的顶点。
-
按 J 键连接这两个顶点。
在你之前的编辑之后,你的五边形将看起来像 图 6.1 中的第三种情况。如果你喜欢,你可以撤销你的步骤并连接另一组顶点。你应该连接哪些顶点取决于你的情况,所以没有硬性规则。
尽管顶点的数量保持不变,但你现在比初始状态多了两个面和两个边。说到初始条件,看一下未保存的 Ngons.blend;你会看到状态栏中的 三角面 仍然显示 3。这是因为 GPU 隐式地三角剖分了五边形。你现在明确地定义了哪些顶点应该连接,因此边和面应该在何处。
现在我们已经讨论了为什么和什么时候固定 n-边形很重要,这里有一个你可能根本不需要担心 n-边形的情况。如果你有一个模型,你确信你不会对其进行动画处理(因此没有需要干净拓扑的绑定),那么你可以不固定你的 n-边形。专业人士坚持固定 n-边形,因为模型很可能会被动画化,所以他们只是以防万一。然而,你现在知道你也有选择。
设置原点
原点是一个所有变换开始的位置。这听起来可能有点技术性,所以有时,把它想象成重心可能更容易理解。然而,这个定义可能有些误导,因为你可以为你的模型改变原点,而在现实生活中,重心通常不会改变。
我们必须打开 Origins.blend 文件在 Start 文件夹中,以深入了解原点。现在,让我们先看看以下截图:

图 6.2 – 这两个桶看起来非常相似,但它们真的相似吗?
Origins.blend 文件将包含两个桶,一个涂成红色,另一个涂成黄色。如果你交替选择红色和黄色桶,你会注意到轮廓内的橙色点在每个桶中都在不同的位置。为了更好地观察正在发生的事情,你可以通过按 3 切换到右侧正交视图,并在选择任意一个桶后观察那个橙色点。那个点就是原点。
按照以下步骤了解原点的作用:
-
选择红色桶。
-
按 R 旋转,然后按 X 限制旋转轴。然后,输入
-45。 -
选择黄色桶。
-
按 R 旋转,然后按 X 限制旋转轴。然后,输入
45。
旋转的值被仔细选择,以便这两个桶向彼此倾斜,这样你就可以比较它们的最终条件。尽管两个桶旋转了相同的角度,但黄色桶似乎更靠近地面。要比较你的结果,你可以参考 Finish 文件夹中的 Origins-1.blend,或者看看以下截图:

图 6.3 – 桶围绕其原点以相同量旋转
你意识到两个桶都是围绕原点旋转的吗?我们可以更进一步,将原点放在桶身体的一块板的底部。
要使桶看起来像是在一个更准确的支点周围倾斜,请按照以下步骤操作:
-
选择黄色桶,然后按 Alt + R 重置旋转。
-
进入 编辑模式 并选择最左边的顶点。或者,按住鼠标中键以获得更好的视图,观察沿着绿色 Y 轴的顶点。
我们还需要完成几个步骤来设置新的原点,但以下截图应该能帮助你找到这个神秘的顶点:

图 6.4 – 这个顶点很快将成为新的原点
在第一章,创建低多边形模型中,我们简要提到了 3D 光标。你可能习惯于使用其他类型的光标,例如你经常在文字处理器或代码编辑器中看到的那些。它们通常在键盘输入时定期闪烁,并将字符放置在那里。
嗯,这是一个 3D 光标,它不会闪烁,但它的作用是相似的。你可以在前面的屏幕截图中看到它位于X和Y轴的交汇处。要将 3D 光标移动到所选顶点并设置新的原点,请执行以下操作:
-
按Shift + S。将出现一个径向菜单,提供许多吸附选项。
-
选择光标到所选或按2。
我们选择的选项将 3D 光标吸附到所选的顶点上。我们还没有完成移动原点的操作,因为我们还没有告诉桶对象新的原点在哪里。为此,我们需要执行以下操作:
-
返回到对象模式。
-
右键单击,然后在设置原点下选择原点到 3D 光标。
这将移动桶的原点至 3D 光标。这就是为什么我们必须将 3D 光标移动到特定的顶点,以便我们可以将其指定为新的原点。以下屏幕截图显示了上下文菜单和找到原点选项的位置:

图 6.5 – 设置原点是常见操作,因此它是上下文菜单的一部分
你可以在Finish文件夹中打开Origins-2.blend来查看应用了之前相同旋转的黄色桶,但这次旋转是围绕不同的原点发生的。
最后,在大多数情况下,设置新的原点需要进入编辑模式选择你将移动原点的位置,然后将 3D 光标临时移到这个点,以便你可以在对象模式中设置原点。当然,你也可以指定一个完全随意的点作为你对象的原点。
在 Godot 中稍后将会使用一个原点,类似于 Blender。如果你在 Blender 中为门设置一个铰链作为原点,那么在 Godot 中绕Y轴旋转该门时,将使用铰链使门旋转,以确保一切看起来都正确计算和调整。
应用旋转和缩放
这无疑是你在导出 Blender 资产之前需要关注的最重要的话题之一。在这本书中已经多次提到,外观可能会欺骗人。应用旋转和缩放属于虚假外观类别。让我们通过在Start文件夹中打开Scale.blend来更好地理解这个问题。
你应该能看到两个立方体,如图所示,它们位于X轴的两侧。此外,变换面板已经展开,以便你可以查看这些立方体的变换,并且你可以使用N快捷键在将来切换它开和关。一个对象的变换由其位置、旋转、缩放和尺寸定义,但我们只对旋转和缩放感兴趣。
这两个立方体看起来确实很相似,除了一个是绿色,另一个是红色,但它们在另一个方面也有所不同。首先选择红色立方体,然后选择绿色立方体。重复多次这个操作,同时注意变换面板中发生了什么变化。
以下截图也显示了你可以找到这个面板的位置:

图 6.6 – 变换面板位于 3D 视图区域的右上角
两个立方体的尺寸都是 4 x 4 x 4 米。它们的位置分别指示它们应该在哪里。到目前为止,一切顺利。然而,缩放和旋转的值告诉我们一个不同的故事。那么,这是怎么发生的呢?简单来说,这个文件的作者做了即使是高级用户有时也会做的事情:他们在对象模式中开始修改红色立方体的属性,而绿色立方体的变化是在编辑模式中接收的。
这样简单的错误相当常见,实际上,它甚至可能不被认为是错误,因为有时你只是想选择一些东西并开始编辑,而不太关心对象处于哪种模式。然而,一旦你完成编辑,你需要将旋转和缩放重置回1,以便游戏引擎能够正常工作。这是人们在将模型部署到任何游戏引擎之前最常见的问题之一,所以这种情况与导出格式无关。因此,如果你想将文件导出为 FBX 格式以便将其导入 Unity,你仍然需要这样做。
幸运的是,修复方法很简单。你可以选择你想要修复变换的对象,然后按Ctrl + A。一个弹出菜单会询问你想要应用哪些属性,这将重置对象所选属性的变换。第五个选项旋转 & 缩放就是我们想要的。当你触发这个选项时,你会看到红色立方体的旋转和缩放值将重置为默认值。
在你将模型导入 Godot 引擎或任何其他游戏引擎之后,当你的模型行为异常,例如某些面缺失或动画出现问题,通常,旋转和缩放是罪魁祸首。所以,在导出之前,请确保它们被归零。
正确命名事物
Phil Karlton,曾在 Netscape 工作,现在是一家解散的公司,他们通过他们的网络浏览器Netscape Navigator为浏览互联网铺平了道路,曾著名地说过以下的话:
“在计算机科学中,只有两件难事:缓存失效和命名事物。”
这句话经常被当作玩笑来传,但就像大多数玩笑一样,其中有一丝真理。如果不是在缓存失效方面,那么在命名事物方面肯定有。看到有意义的名称将使未来的你或同事更容易记住和理解之前所做的工作。
当你从原始对象开始时,Blender 会根据它们的类型来标记它们:立方体、平面、灯光等等。你的模型在某个时候最终会变得更加复杂,它们很可能会有不再看起来像立方体的部分。因此,保留原始名称将在某个时候使你在 Blender 和 Godot 中工作,甚至在其他应用程序中使用你导出的资产时,生活变得更加困难。
所以,给你的对象命名吧!
总结
你可能会比其他一些修复更频繁地做这些修复。忘记应用变换很容易,但这是一个简单的修复。在建模过程中改变原点是让你智能地缩放和旋转事物的一个有用方法。最后,你很可能会将其留在最后的位置,所以回到 Blender 将其设置为永久位置以供游戏应用正确的变换是完全可以的。经常浏览本节中提出的主题列表,随着时间的推移,你将养成习惯。
如果你想要练习到目前为止所提出的概念,我们在“开始”文件夹中准备了一个Fix-Me.blend文件。我们想要设计一个简单而快速的重型对象,所以这个努力让对象保留了默认名称。此外,它的旋转和缩放值看起来有些过早。当你这样做的时候,你还可以修复 n-边形并将原点移动到不同的角落。
在某个时候,你最终会想要将你的文件转移到 Godot 中。为此,当这两个应用程序不共享一个共同的文件格式时,我们通常会使用交换格式。对我们来说,情况就是这样,因为我们不能直接在 Godot 中打开和处理 Blender 文件。因此,我们将发现一个近年来越来越受欢迎的文件格式,即 glTF,这将帮助我们把我们 Blender 中的工作转移到 Godot 引擎中。
探索 glTF 和其他导出格式
不同软件之间的兼容性一直是一个微妙的问题。实际上,在现代社会,大多数物理事物仍然是一个常见问题。例如,许多国家的电源插座和插头形状和尺寸各不相同。在撰写本文时,根据www.worldstandards.eu/electricity/plugs-and-sockets/,全球有 15 种插头类型在使用。在你离家长途旅行之前,你可能想要确保你的设备是兼容的。
似乎没有关于哪种插件最好的共识。同样,当涉及到不同软件之间的数据交换时,有大量的选项可以选择。因此,在接下来的几节中,我们将讨论不同类型的导出格式,以了解为什么我们应该选择 glTF 而不是其他格式,以及为什么 glTF 是更好的选择。然后,我们将详细讨论 glTF。
将 glTF 与其他格式进行比较
在 Blender 提供的众多导出选项中,我们将重点关注 glTF 格式,因为它与 Godot 引擎配合得很好。话虽如此,在我们深入探讨之前,让我们先介绍一些常用的格式,如Collada、FBX和OBJ:
- Collada:这种格式,其文件扩展名为 DAE,最初是为了在 3D 应用程序之间作为数据交换格式而设计的。一开始听起来很有前途,但尽管游戏引擎可以被视为 3D 应用程序,但它并不是——至少就这种格式预期使用的方式而言。Collada 更多地是为了在更经典的 3D 创作程序之间交换信息而设计的,如 Blender、Studio Max、Maya 等,而不是为了游戏引擎。
它基于 XML,因此你可以用文本编辑器打开 Collada 文件。随着时间的推移,这种格式不再受欢迎,因为规范模糊,并且被错误地解释和实现。对于 Godot 的早期版本,特别是在 glTF 出现之前,Collada 曾经是首选的文件类型。现在,我们有 glTF 作为更好的选择。
- FBX:这是 Autodesk 提供的一种专有文件格式。由于没有官方的格式规范可供公众获取,并且 FBX 的许可证不允许开源项目使用 FBX,即使私下获取了规范,也已有尝试逆向工程此格式以编写导出器的行为。这就是 Blender 根据他们的猜测实现了 FBX 导出器的方式。
此外,Godot 工程师们尽力实现了 FBX 导入器。尽管如此,由于规范不公开,所有这些都只是一些猜测。为了防止隐藏的惊喜,以及更平滑地过渡到 Godot,我们不会使用这种格式。
- OBJ:这是由 Wavefront Technologies 创建的一种简单的纯文本数据格式。所以,是的,这也可以用文本编辑器打开。纯文本数据格式提供了编辑的便利性,但由于它们不是压缩文件,解析和导入它们通常很慢。尽管 OBJ 存在不同的问题,它不能存储动画和光源,但它是一个简单且良好的格式,主要用于存储网格信息。
这也意味着它不存储材质和纹理信息。为了实现这一点,你需要创建一个 MTL 文件,与你要创建的 OBJ 文件一起。OBJ 是一种古老且可靠的格式,被认为是行业标准,但它不适合现代游戏引擎。
既然我们已经看到了我们不会使用的格式,让我们关注一下 glTF 为什么是我们更好的选择。我们将通过提供 glTF 的简要历史,然后展示在 Blender 的导出设置中我们必须选择哪些设置来做到这一点。
介绍 glTF
简称图形语言传输格式的 glTF,首次由 Khronos Group 于 2015 年发布,这是一个由许多大型企业成立和授权的成员驱动型非营利性联盟。并非每个成员企业都从事数字内容创作业务,但他们因为 Khronos 维护其他标准,如 OpenGL 和 WebGL,这两种广为人知的图形 API 服务于许多行业,所以他们对联盟有所投资。
在这个阶段讨论文件格式的可靠性可能很重要,特别是如果你计划减少长期维护问题和成本。例如,我们中有多少人还记得早期互联网时代的视频文件格式?仅举几个例子,有 RealMedia、QuickTime、DivX 等等,我们不得不安装编解码器、插件等等,只是为了看几个猫视频。幸运的是,我们观看我们毛茸茸的伴侣的愿望从未改变。
尽管如此,事物最终会汇聚在一起,并为更好的、更易于维护的文件格式让路。因此,来自像 Khronos 这样的标准组织的指导是一个好事情,因为他们确保文件格式得到适当的关注,并随着行业不断变化的需求保持更新。glTF 就是这样一种健康的情况,而且它是开源的,许多公司都愿意支持它,这是一个好兆头。如果你有一天发现你的游戏引擎中有一堆资产,而你又得知你不能再导出这种文件类型,那将是一个糟糕的日子。你会如何处理现有的资产——扔掉它们并将它们转换成新格式?
既然我们已经简要回顾了历史,让我们了解对我们相关的部分。我们将利用 Blender 的 glTF 实现,它支持以下功能:
-
网格
-
材质(原理 BSDF)和无阴影(未照亮)
-
纹理
-
相机
-
准时灯光(点、聚光灯和方向光)
-
扩展
-
自定义属性
-
动画(关键帧、形状关键和蒙皮)
我们不会使用这个功能集的一半。我们在第四章中讨论了为什么我们不会对相机和灯光过于纠结,调整相机和灯光,因为当我们使用 Godot 构建游戏时,我们会设置它们。
关于 Blender 的 glTF 导出器如何处理网格的快速说明:n-边形将被自动三角化。因此,不会留给 GPU 的仁慈。本章的决定如何处理 n-边形部分介绍了如果需要手动三角化的提醒。
让我们通过展示三种不同的 glTF 变体来结束本节。要访问变体列表,您需要在 文件 菜单中展开 导出 菜单项后选择 glTF 2.0 (.glb/.gltf) 选项。在出现的弹出窗口中,您将在右侧看到一个 格式 下拉菜单,它将显示以下截图中的变体:

图 6.7 – 您可以为 glTF 导出使用的三种可能变体
这些格式变体将根据您选择的设置工作相同。我们将在下一节中介绍这些内容,因此首先,让我们了解每个变体做什么:
-
.glb文件扩展名。我们将在本书中使用这种变体,您也可能会在您的流程中使用它,因为它将所有需要的文件存储在一个文件中,并且进行了压缩。这使得与他人分享和在互联网上传输变得容易。 -
.gltf文件扩展名会使文件大小增大,但如果您希望使用文本编辑器进行修改,则非常方便。我们没有实际的理由选择这种变体而不是二进制选项。 -
.gltf文件扩展名,类似于您选择包含数据的.bin文件时得到的扩展名,以及可选的您使用.jpg或.png扩展名使用的所有纹理。因此,它倾向于将事物分开。由于数据存储在.bin文件中,它使.gltf部分保持较小,与 嵌入式 变体不同。尽管如此,我们仍然没有实际的理由偏好这种格式。另外,如果您必须发送您的模型,您还需要记得发送所有单独的部分。
无论选择哪种变体,导入软件都将遵循 Khronos 标准组制定的 glTF 指令来创建您的模型、材质、动画等。因此,选择一种变体可能只有在必要时和更复杂的情况下才是必要的。对于本书中的工作,二进制变体将满足我们的需求。
现在我们知道了哪种变体最适合我们,我们必须反思我们的自身需求,以便在导出器的界面中勾选正确的选项。这就是我们将在下一节中讨论的内容。
决定要导出什么
您场景中的不是所有内容都应该导出。例如,如前所述,我们将在 Godot 引擎内部为游戏世界创建相机和灯光条件。因此,一旦完成,就没有必要在 Blender 场景中保留相机和灯光对象。然而,它们可能对您进行测试渲染以更好地感受场景很有用,而无需不断将模型导出到 Godot。在本节中,我们将确定更好的导出候选对象以及如何使用导出设置来促进这一点。
导出选项被分类,我们将在适当的时候讨论一些选项。我们将通过讨论这些选项如何与场景中的对象相关来做到这一点。请注意,导出窗口是独立的,所以在你选择场景中的对象之前,你不需要关闭它。你可以在这两个窗口之间来回切换。
包含
尽管这个类别的标题很直接,但包含内容的含义可能非常重要。默认情况下,这个类别中的所有选项都没有被选中。因此,这取决于你的工作流程。当你展开这个部分时,你会看到两个组:
-
限制到:这是你选择具体想要作为网格包含的内容的地方。我们将在接下来的段落中更详细地讨论这个问题。
-
数据:任何不是网格的内容都可以被认为是数据。例如,相机和灯光不是具有网格信息的物理对象,而是帮助你渲染场景的辅助工具。我们将保留这里所有内容不勾选。
默认情况下,两个组的所有选项都没有勾选。我们已经说过要保留数据不变,但在限制到部分下你可以选择的四个选项中,最重要的是选中的对象。
如果你没有勾选这个选项,那么 Blender 将会包含场景中的所有内容。这意味着在我们“设置原点”部分练习的结尾,当我们有两个桶时,Blender 会尝试导出这两个桶。这并不是你很可能想要的结果。你可能会想要设计一个桶,并将其导出到 Godot 中。因此,我们首先需要勾选选中的对象导出选项。然后,我们需要进入场景并选择我们想要导出的对象。尽管这样做很方便,但可能会有些不便。
我们一直在设计相对较小的模型,包含几个不同的部分。我们设计的独立部分最多是桶的三个不同部分。在未来,在你的工作中,如果你在 Blender 场景中恰好有十几个或更多的部分,在你点击导出按钮之前,再次选择所有这些部分会变得非常繁琐。如果只有一个选项,它不会导出相机和灯光,但导出我们认为重要的内容,这样我们就可以兼得两者之长了……
这个选项是可见对象。首先,取消选择选中的对象,并保持可见对象选项选中。为了使这个选项对我们有效,我们需要隐藏相机和灯光对象,这样它们就不再被视为导出器的候选对象。你可以通过点击大纲区域中任何你不想导出的对象的眼睛图标来实现这一点。
最后,在决定导出内容时,你将面临各种解决方案。这里没有正确或错误的选择,但你必须选择对你来说最有效的方法。
变换
我们为了完整性会涵盖这个类别。你很少会触及这个类别,因为它只有一个选项,默认是开启的。不过,让我们解释一下原因,并了解+Y Up的含义。
在 Blender 中,三个轴或坐标系,XYZ,已经设置好,所以Z轴定义了一个对象有多高或有多高。在一些其他的应用程序中,例如 Godot 引擎,Y轴被用作上升轴。因此,在 Godot 引擎中,一个对象的Y位置越高,它在游戏世界中的位置就越高。因此,这个 Blender 导出选项将 Blender 的Z轴转换为 Godot 的Y轴。这是一个方便的事情,所以你不需要任意旋转你的模型以匹配正确的方向。
几何
我们将在这个类别下保留大多数选项不变,只讨论对我们来说重要的事项。这些选项如下:
-
应用修改器:我们首次在第一章中发现了修改器,创建低多边形模型。我们使用了一些帮助我们快速建模桶的修改器。你可以堆叠修改器并改变操作顺序的事实很棒。然而,它们是核心对象上的临时添加。所以,除非你在导出设置中开启这个选项,否则基础对象将不会应用任何修改器导出。这将使你的对象在 Godot 中看起来非常不自然和原始。
-
材质:这个选项的默认状态是导出所有材质。这可能对初学者或快速结果来说是个好事。当我们再次讨论材质时,当我们涵盖 Godot 时,如果你决定在 Godot 中创建自己的材质,你可能想选择不导出选项,这样它们就不会包含在生成的文件中。
在更高级的导出场景中,你可能还想启用松散边缘和松散点选项,这样你就可以将松散几何形状作为导出文件的一部分保留。
动画
我们不会更改这个类别中的任何默认选项。我们在第五章的为 Godot 准备动画部分讨论了如何创建多个动画。默认设置将负责转换动画——更具体地说,是动作。
创建预设
如果你发现自己需要在某些场景下打开和关闭某些选项,并且记住正确的组合变得越来越困难或单调,你可以创建一个导出选项预设。导出选项的上半部分有一个下拉菜单,旁边有两个按钮。使用这个区域,你可以创建自己的预设——也许一个用于选定的对象情况,另一个用于可见对象情况。
选择正确的导出选项取决于你的项目所需的不同条件。因此,你必须进行实验,找到最适合你的方法。在某个时候,你将把结果导入 Godot 引擎以可视化 glTF 文件。然而,如果你想要快速了解你的创作,在两个应用程序之间进行大量工作可能是一项繁重的工作。以下是你可以使用来预览 glTF 文件的两种选项:](https://gltf-viewer.donmccurdy.com/)
-
Microsoft 3D 查看器
这项针对我们案例相关的出口选项的调查就此结束。让我们看看到目前为止你发现了哪些其他发现。
摘要
本章主要介绍了如何使你的工作与 Godot 引擎兼容。为此,我们需要探讨几个不同的主题。
首先,我们想要确保我们的模型已经得到了正确的最终修饰。这涉及到去除 n-边形并将这些多边形转换为更易于管理和理想的三角形面。之后,你学习了如何为你的模型设置原点,这在建模阶段也可能很有帮助。使变换永久化是至关重要的,所以如果你的模型,尤其是在动画中,表现得笨拙,这是你需要记住的事情。然后,我们探讨了给事物赋予有意义的名称的想法。当你有更多经验时,你会发现这最终是你越来越需要的。
然后,在 Blender 提供的许多用于导出资产的格式中,我们评估了几种,例如 Collada、FBX 和 OBJ。在这个过程中,我们提出 glTF 已经成为 Blender 和 Godot 之间通信的事实上的格式。最后,我们发现了一些 glTF 导出器的选项,并介绍了一些你可能希望采用的几种可能场景。最后,你学习了如何存储最适合你的导出选项。
现在,我们准备开始将 Blender 资产导入 Godot。这正是我们在下一章将要做的。在实际场景中,你可能会在游戏开发的几乎每个阶段经常执行本章和下一章中展示的操作。在我们继续之前,让我们给你提供一些更多有用的资源。
进一步阅读
Khronos Group 是我们日常使用的许多标准的维护者。这得益于他们令人印象深刻的成员名单,你可以在 www.khronos.org/members/list 上查看。
我们主要使用了他们的 glTF 标准。以下链接提供了更多关于它的技术信息:
由于其巧妙的规格,glTF 交换格式不仅在游戏行业,而且在其他行业也日益受到欢迎。这里展示的是 NASA 著名的旅行者号航天器,其全貌如下:solarsystem.nasa.gov/resources/2340/voyager-3d-model/.
你可能遇到过仍在使用 Collada 格式进行 Godot 项目的网站。也许你已经拥有了一个包含大量 Collada 文件的庞大仓库。如果你想尝试一下,但希望做得更加精细,这里有一个 GitHub 仓库可以帮助你:github.com/godotengine/collada-exporter.
最后但同样重要的是,清理你的模型并保持它们随时可导出将是持续的任务。Blender 的用户手册中有一页介绍了许多工具和方法,可以帮助你在这一过程中:docs.blender.org/manual/en/2.93/modeling/meshes/editing/mesh/cleanup.xhtml.
第八章:将 Blender 资源导入 Godot
你已经走了很长的路。你的模型准备好了。它们的缩放和旋转值已经固定。接下来要做什么?当然是将它们导入 Godot!希望你会觉得导入过程更加直接。这是一个过渡章节,主要涵盖 Godot 主题,而 Blender 的参与度最小。
我们将首先通过点击按钮使用你导入的模型创建游戏对象,这个过程将 glTF 文件转换为游戏对象——更具体地说,在 Godot 术语中是场景。
如果你必须修复你的模型或添加细节,你可以在哪里做这件事?由于你现在在 Godot 中,修复模型可能很有诱惑力,但这是不利的。在本章中,我们将向你展示如何更新你的 Blender 文件,并在 Godot 中反映这些更改。
在第二章 构建材质和着色器中,我们学习了如何在 Blender 中处理材质。我们将在 Godot 的上下文中重新探讨这个主题,以便我们能够理解两种应用程序中材质的工作方式。我们将介绍处理材质在任一应用程序中的优缺点,以便你可以决定哪个最适合你。无论你是单独工作还是团队合作,都有一些决定可能会节省大量时间,或者当你意识到你需要进行基本更改时,可能会感到沮丧。一个合理的材质管道是这些主题之一。
在第五章 设置动画和绑定中,我们在我们的蛇模型中存储了两个动作。我们将导入这个模型来查看 Godot 如何处理存储在 glTF 文件中的动画。本章将仅涵盖如何导入动画;如何使用导入的动画将在本书稍后当我们构建我们的点击冒险游戏时进行介绍。
因此,你将了解到一些至关重要的构建块和实践,这些将在后面的章节和你的游戏项目中为你服务。
在本章中,我们将涵盖以下主题:
-
制作场景!
-
在 Blender 和 Godot 之间切换
-
决定如何处理材质
-
导入动画
到本章结束时,你将能够将你的 glTF 文件转换为可用的 Godot 资源,从项目管道的角度决定如何处理材质,并确保你可以访问模型文件中包含的动画。
技术要求
如前言部分所述,我们假设你已经熟悉 Godot 的基本操作,例如创建和组合场景,向节点添加脚本,使用检查器面板更改游戏对象的条件,等等。
然而,如果你是 Godot 引擎的新手,那么你可能首先想从以下地址的官方学习材料开始:docs.godotengine.org/en/3.4/getting_started/introduction/。
在整本书中,我们将使用 Godot 3.4.4。即使在较小的版本之间,也可能会有新功能或缺失的功能。如果您在阅读本书时使用的是不同版本,您可以切换到本书使用的版本,或者阅读godotengine.org/news上列出的不同版本的相关详细变更日志。
这仍然是一个过渡章节;前一章和下一章也是如此。我们将在第九章“设计关卡”中创建一个新的 Godot 项目,并在后续章节中在该 Godot 项目中工作以制作一个点对点冒险游戏。在此之前,我们可以使用临时的 Godot 项目。这意味着在本章和下一章中,我们根本不会关心我们文件和文件夹的结构。然而,本章中的部分已经按照我们假设您仍在同一个 Godot 项目中工作的方式安排。
如往常一样,本书的 GitHub 仓库github.com/PacktPublishing/Game-Development-with-Blender-and-Godot包含一些与本章节相关的文件。
制作场景!
在 Godot 中构建的典型 2D 游戏中,使用精灵节点是必不可少的。然后您将在 Godot 的检查器面板中将纹理分配给您的精灵节点。3D 版本基本上是相同的,但它涉及使用MeshInstance节点并将其分配给一个网格。所以,纹理对于精灵节点来说就像网格对于网格实例节点一样。虽然创建一个只包含精灵节点并在更大场景中实例化此场景的 Godot 场景是可能的,但这是一种过度设计,因为您可以直接将精灵节点本身附加到大型场景中。
这就是为什么需要将网格实例区别对待并存储在它们自己的场景中,与精灵不同,因为 3D 模型不仅仅是一个纹理的分配,还有很多其他的事情要做。此外,由于 3D 模型有很多可移动的部分,将单个网格分配给网格实例也可能很繁琐,所以让我们做得更好。本节的目标将是从 3D 模型中创建一个场景,并自动化如何将网格分配给网格实例。
Adobe Animate
Godot 的场景概念包含了您如果以前使用过Adobe Flash或现在的Adobe Animate(它使用电影剪辑,类似于 Godot 对场景的处理)可能会熟悉的一些概念。创建嵌套电影剪辑和绑定脚本非常方便,这正是 Godot 项目进行的方式。尽管有这种相似性,但在 Godot 中,有时将 3D 模型视为自己的场景是有意义的,这正是本节将要涵盖的内容。
我们建议您为这一部分开始一个新的 Godot 项目。一旦您这样做,您需要找到本章的Start文件夹中的Sconce.glb文件。此时您有两个选择。首先,您可以使用操作系统的文件系统将此文件复制并粘贴到您的 Godot 项目文件夹中。或者,您可以将 sconce 文件拖到 Godot 的文件系统面板中。当您将 glTF 文件作为项目的一部分时,您将得到以下类似的内容:

图 7.1 – Sconce 模型现在是你的 Godot 项目的一部分
你在Sconce.glb条目中也有一个红色的十字图标作为图标吗?这种情况并不总是发生,但这个图标表示存在配置问题;幸运的是,修复起来很容易。重启 Godot 通常可以解决这个问题。如果这还不行,那么我们就需要按一个按钮来重新导入文件,以便 Godot 为我们配置它。前面的截图也显示了导入面板的焦点。您可以在该面板底部点击重新导入按钮,使文件与 Godot 兼容。
当我们讨论图标问题时,还发生了其他事情。在我们的项目中有两个材质文件:
-
DarkMetal.material -
Fire.material
这些材质包含在从 Blender 导出的 glTF 文件中,因为我们选择保留材质。如果您需要复习这一点,您可以阅读第六章中的决定导出什么部分,导出 Blender 资源。默认情况下,Godot 会将材质放置在模型文件旁边。出于组织原因,您可能希望将模型和材质放在不同的文件夹中。我们将在本章后面的决定如何处理材质部分讨论与此相关的内容。
现在我们已经准备好使用 Sconce 模型创建场景。这项工作将创建显示 Blender 模型在 Godot 中的所有必要绑定。为了实现这一点,您必须执行以下操作:
-
双击文件系统面板中的
Sconce.glb条目。 -
在弹出窗口中点击新建继承按钮。
弹出窗口将在您刚刚点击的按钮旁边显示另一个按钮。还有关于每个按钮功能的说明,但它可能有些令人困惑,所以让我们来解释一下。用通俗易懂的话说,打开任何方式按钮将允许您查看 glTF 文件的内容,但这是只读的。由于您可能想要进行修改,例如附加脚本,您通常会点击新建继承按钮。
如果你打开场景面板,你会看到你的最后努力在空间节点下创建了两個网格实例节点。当你点击Sconce或Flame网格实例节点时,你会在检查器面板中看到它们的网格绑定。我们不必手动创建所有这些结构和绑定;从 glTF 文件创建场景为我们完成了这一切。
当你检查完到目前为止的变化后,你可以将你的文件保存为Sconce.tscn,因为就 Godot 而言,它仍然是一个临时构造。以下截图显示了我们的进度:

图 7.2 – 你只需点击一下按钮就创建了一个场景
你现在可以通过创建更多Sconce.tscn的实例在其他场景中使用它。例如,在大多数 2D 平台游戏(其中包含敌人)中,你必须创建存储敌人角色精灵的场景实例。这很相似。因此,每次你需要 sconce 时,你都可以使用Sconce场景而不是模型文件。在我们这本书的后续部分工作游戏时,我们将创建更多此类场景的实例。
从模型文件创建场景很容易,但改变它有多容易呢?无论是 sconce 还是火焰,都可以稍微调整一下。接下来,我们将探讨如何在场景中更新我们的模型。
在 Blender 和 Godot 之间切换
在 Godot 的后续版本中,特别是从 Godot 4.x 版本开始,你将能够直接将 Blender 文件导入 Godot 并与它们交互。在 Blender 中保存内容将自动更新 Godot 中的情况。我们还没有达到那个阶段。在撰写本文时,我们必须坚持已经尝试并验证过的方法:重新导出我们的资产。让我们看看我们如何轻松地完成这项任务。
在你开发游戏的过程中,你可能会想要更改你的模型。也许你一直在使用你的 3D 艺术家朋友或承包商之前提供的一个原型。现在,他们准备给你一个更精细的作品。所以,让我们通过修改我们一直在使用的 sconce 模型来模拟一个类似的场景。如果你想要跳过 Blender 部分,你可以在Finish文件夹中的Sconce.blend文件中找到完成后的更改。如果你想锻炼一下 Blender 技能,我们建议你在Start文件夹中的Sconce.blend文件中进行两个更改。这些更改如下:
-
将火焰的尖端移动一下,让它看起来不要太尖锐。(提示:进入编辑模式。)
-
将火焰材质替换为明亮的黄色。你可以为它选择一个名字,比如HotFire。(提示:删除旧材质并添加一个新的。)
我们对我们的模型进行了两项重要的更改。首先,我们改变了模型的几何形状,尽管这可能微不足道。其次,我们引入了一种新材料,而不是更改现有材料的颜色。剩下的唯一事情就是重新导出我们的模型,并覆盖 Godot 项目中现有的 Sconce.glb 文件。如果你一直在跟随,项目中的 Sconce.glb 文件看起来似乎没有任何变化。
如果你重新启动 Godot,切换到不同的场景标签,或执行任何其他会刷新视图的操作,那么你就会看到你的更新。否则,你可能仍然保持原来的外观。似乎存在一个普遍的刷新问题。希望像这样的小问题将在 Godot 的未来版本中得到修复。
以下截图显示了您将看到的更新:

图 7.3 – 我们的最新更改使火焰更热、更直
虽然我们成功更新了 Sconce 场景,但我们还向项目中引入了一种新材料。导入过程足够智能,知道即将导入新材料,但它足够谨慎,保留了旧材料,以防将来在项目中可能需要使用它们。
这可能会导致随着时间的推移积累大量未使用的文件。但这还不是你遇到的最糟糕的问题。当你导入越来越多的模型,最终因为文件数量庞大而失去对项目状况的跟踪时,有一个更加隐蔽的问题在等待着你。
在下一节中,我们将展示一个场景,其中直接导入 glTF 文件,就像我们迄今为止所做的那样,可能会引起一些问题。
决定如何处理材料
一个重要的决定等待着你。当你导出 Blender 资产时,在 第六章,导出 Blender 资产,我们简要讨论了导出器 UI 中的导出选项的含义。然而,我们从未真正讨论过保留材料或不保留材料的含义。在本节中,我们将介绍在 Blender 与 Godot 中处理材料的优缺点。
假设你现在已经准备好导入另一个模型。例如,Start 文件夹中的 Vessel.glb 文件是你想要添加到游戏中的东西。如果你查看相关的 Vessel.blend 文件,你会注意到我们使用了一种标记为 DarkMetal 的材料。讽刺的是,也许是无意的,有人决定选择一种浅色,但不管意图如何,名称与我们在 sconce 模型文件中使用的是相同的材料名称。
那么,当我们将此文件导入 Godot 时会发生什么?为了找出答案,请按照以下步骤操作:
-
将
Vessel.glb添加到您的 Godot 项目中。 -
将这个船体模型转换成一个场景。为了方便起见,将其保存为
Vessel.tscn。
以下截图显示了新的场景,以及 FileSystem 面板的状态:

图 7.4 – 东西看起来都还好,但这个容器不应该有一个更浅的颜色吗?
尽管在 Blender 中的标记有误,但我们知道我们想要的容器颜色。它应该是浅色,但在 Godot 中我们看到的情况并非如此。在导入容器模型时,由于项目中已经存在同名材质,Godot 选择不复制资源。这可能很高效,但并不准确。这种事情很容易发生,尤其是如果你正在使用他人的文件。幸运的是,只有新导入的内容看起来不正确。换句话说,新导入的资源并没有覆盖和破坏现有的资源。
那么,我们该如何让容器显示我们想要的颜色呢?我们可以提供一些更组织性的建议。因此,这不仅仅是一个技术解决方案,而是一种工作流程或管道类型的解决方案,这在行业中通常是这样标记的。因此,解决方案在于你如何在项目中处理你的文件,以及你是单独工作还是团队合作。以下是一些建议:
-
通过用途标记 Blender 材质
-
通过颜色标记 Blender 材质
-
将你的模型导入到单独的文件夹中
-
在 Godot 中使用预演区域
这些建议都不是灵丹妙药。你必须尝试并决定它们对你是否有益。此外,不同规模的项目可能会使某些解决方案容易或困难。在你了解每个建议包含的内容之后,决定权在你手中。
通过用途标记 Blender 材质
在 Blender 中通过其色调命名材质,例如 DarkMetal,只能走这么远。我们说的是多暗?迟早我们会发现自己陷入一个形容词的游戏:暗、更暗、最暗,以此类推。当我们想要选择比我们已选择的暗色调更浅的版本时,情况会更糟。
通常,壁灯的底座是铸铁制成的。由于它是一种金属,所以在名称中使用“金属”这个词是有意义的,但它可能会很容易造成混淆。相反,你可以使用物体的名称作为其材质标题。所以,一旦你将其导入到 Godot 中,你将会有 Sconce.material。
通过颜色标记 Blender 材质
如果你想要使用类似颜色的标签,那么你可以以一种明显且独特的方式做到这一点,而不会给 Godot 留下任何以它自己的方式解释的空间。393646.material。
请记住,在创作模型时,你可能会变得忙碌和分心,发现自己正在对模型中的许多事物进行微调,无论是几何形状、材质、动画等等。所以,如果你已经选择了一个十六进制颜色作为名称,并且后来改变了材质的颜色,那么你必须记得更新名称。
将你的模型导入到单独的文件夹中
有些人组织他们的 Godot 项目,以便他们有单独的文件夹来存放更大的概念。这包括材质、模型、场景和脚本。如果你想确保你的材质只属于你正在导入的模型,一个更安全、更简单的方法是在特定文件夹内创建自定义文件夹。例如,如果你的项目根目录下有一个Models文件夹,你不需要将所有 glTF 文件都放入这个文件夹,你可以创建以你正在导入的模型命名的子文件夹。在我们的例子中,你会看到以下结构:
-
Models>Sconce>Sconce.glb -
Models>Vessel>Vessel.glb
然后,每个 glTF 文件的相关材质都将包含在其自己的文件夹中。这乍一看可能似乎适得其反,因为相同的材质文件将在不同的文件夹中重复,尤其是如果材质的名称是彩色编码的话。然而,你至少会知道你导入的正是你最初想要的。
这种方法在某些情况下可能具有优势。也许你正在为你的游戏设计不止一种烛台样式。在这个新样式中,尽管铸铁部分有不同的形状,但它很可能会使用相同的材质。然后,你可以轻松地将文件夹重命名为Sconces来存储多个烛台文件。这样,你是有意同意 Godot 不会创建重复的材质,而是使用第一个导入的模型的材质。
最后但同样重要的是,让我们来谈谈这个技术的注意事项。如果你是通过将文件拖放到文件系统面板来导入文件,你必须小心,因为这个面板是上下文相关的。这意味着你需要选择条目列表中的适当文件夹。否则,被选中的任何条目都将成为接收者。为了确保你知道你的文件发送到了哪里,你可以通过使用操作系统的文件系统来完成所有这些操作。当你切换到 Godot 时,你的文件将被处理,并且根据你系统的速度,你可能会看到一个进度条显示导入的进度。
在 Godot 中使用预演区域
在我们确保模型和材质正确导入的可能解决方案列表中,最后一个是使用预演区域。这意味着,类似于为模型使用唯一文件夹,你可以指定一个文件夹来监控模型的情况。也许这是一个位于Models文件夹内的标记为Staging的文件夹。
使用文件系统面板中的搜索功能,你甚至可以检查其他文件夹中是否有重复的材质。这是一种安全比较材质的方法,因为你可以通过检查器面板观察它们的属性。如果没有明显差异,并且你认为这是安全的,你只需将相关的 glTF 文件移动到其最终位置,同时忽略这个预演区域中的重复材质文件。
这需要一点工作,但在大型团队中这可能是一项必要的实践,以便你可以决定甚至通知艺术家是否存在明显的标签错误。例如,如果有多个类似模型应该使用的相同材质存在拼写错误,你不会得到两个不同的材质。
总结
在所有这些选项中,也许还有你在网上找到的几个选项,你必须决定哪一个最适合你。这是一个常见的情况,你可能会开始一种方式,但随着项目需求的变化而切换到另一种方法。虽然你的选择可能具有技术影响,但它更多的是一个商业决策;因此,在做出决定时权衡利弊。
关于你的材料和模型,你还可以做一件事,但由于本章是关于导入的,所以我们有意将其留到后面。那就是当你决定在 Godot 中创建你的材料并将其手动绑定到模型的网格上时,因为有时你会发现只有网格但没有材质信息的模型。我们将在 第九章 的 设计关卡 部分的 构建缺失的材质 中向你展示如何在 Godot 中创建材质。
现在我们似乎已经完成了关于材质的事情,在下一节中,我们将学习如何导入我们在 第五章 的 设置动画和绑定 中为蛇创建的动画。
导入动画
我们将介绍的关于导入 Blender 资产的最后一件事情是动画。到目前为止,我们已经处理了导入模型的网格和材质。我们甚至讨论了与默认导入流程相关的材质工作流程问题。希望导入动画不会有隐藏的惊喜,但我们如何做呢?你将在本节中找到答案。
你可以从将本章的 Snake.glb 文件移动到你的项目的 Start 文件夹开始。然后,如 制作场景! 部分所示,你可以从这个模型创建并保存一个场景。蛇模型将带来很多它的材质,你的 FileSystem 面板看起来会有些拥挤,但这是我们目前所拥有的:

图 7.5 – 蛇跟随你进入 Godot 引擎
这是个好时机来介绍一些 Godot 使用的 3D 节点。我们将利用蛇场景来完成这项工作,因为它有你在项目中最可能使用的不同节点的良好样本。
根节点是 spatial 类型。Godot 为所有 3D 节点选择的红颜色。如果你一直在使用 Godot 的 Node2D 节点,它们有一个蓝色圆形图标,Spatial 节点就是那个节点的 3D 版本,颜色是红色。而一个 Node2D 节点只有 XY 平面的坐标,一个 Spatial 节点则会在 XYZ 平面有坐标。你通常使用这种类型的节点作为其他节点的根容器。例如,Armature 和 AnimationPlayer 节点是根 Spatial 节点的直接子节点,该节点被标记为 Snake。
节点类型与标签
在蛇场景中,MeshInstance 节点已被重命名为 Snake,这在你有大量网格实例时很有用。Godot 中没有内置的 Snake 节点类型,但说 Snake 节点是可以的,即使它属于 MeshInstance 类型。Inspector 面板会确定类型并只列出相关属性。因此,在这本书的其余部分,我们将使用场景的节点名称或节点类型进行引用。
我们很快将分析 AnimationPlayer 的作用,但让我们先完成对 MeshInstance 和 Skeleton 子节点的查看。
MeshInstance 和 Skeleton
我们在 制作场景 部分的 MeshInstance 和 Sprite 节点之间做了一个类比,指出它们分别负责在 3D 和 2D 空间中持有视觉元素。因此,这就留下了 Skeleton 节点。
在 第五章 设置动画和绑定 中,我们使用了骨骼并将它们绑定起来,以便我们可以对蛇进行动画处理。当 Snake.glb 文件被导入时,骨骼作为一个整体被导入。换句话说,Godot 将你所有的骨骼组合成一个 Skeleton 类型的节点。然而,如果你愿意,你仍然可以访问每个骨骼:
-
选择 Skeleton 节点。
-
在 Inspector 面板中展开 Bones 部分。
-
展开一些条目,特别是 9 和 10。
你能认出这些名字吗?这些是你为 Blender 中的骨骼所取的名字。看看我们构建骨骼需要做多少准备工作。创建所有这些的绑定过程,尽管一开始看起来可能很复杂,但在 Blender 中做起来仍然比在 Godot 中要简单得多。
现在,让我们将注意力转向场景中的最后一个节点类型,以进一步理解为什么在 Blender 中进行动画也是一个更优越和更受欢迎的选择。进入 AnimationPlayer。
AnimationPlayer
Snake.tscn 场景中的最后一个节点是 AnimationPlayer。这个节点的颜色既不是蓝色也不是红色。这意味着你可以在 2D 和 3D 环境中都使用它。如果你已经构建过 2D 游戏,你可能已经熟悉这个节点了。如果是这样,那么你知道你需要在你玩家的时间轴上放置关键帧来标记变化点,就像我们在 Blender 中做的那样。无论你是否熟悉 AnimationPlayer,或者这是你第一次处理它,你都会注意到,像下面的截图所示,创建这么多关键帧是一项大量工作:

图 7.6:你聪明地而不是辛苦地创建了 AnimationPlayer 中的所有这些关键帧
在前面的截图中的每个橙色菱形都是一个关键帧,标志着动画生命周期中的重要转折点。这是我们在 Blender 中创建的 Attack 动作的时间轴。你可以在顶部的下拉菜单中看到它。这是我们选择 Blender 为我们做所有这些的主要原因——我们只关心主要事件,而不是主要事件之间具体发生了什么。Godot 和 Blender 一起填补了细节。此外,在 Blender 中更新你的动画仍然比摆弄那些菱形要好得多。
如你所见,动画和动作会自动导入、识别和组织到 AnimationPlayer 中。尽管这很简单,但 Godot 在动画导入方面目前存在一个 bug。因此,我们需要做一些可能在未来不再必要的事情。我们将在下面讨论这个问题并提出解决方案。然而,要跟进这个问题的讨论和更新,你可以访问 github.com/godotengine/godot/issues/34394。
在 Animation 面板的右侧,有一个看起来像回收符号的图标。在撰写本文时,那个循环按钮,本应无限期地播放动作,只有在编辑场景时才起作用。所以,即使你可以切换循环按钮,当你在游戏中启动时,动作也只会播放一次。希望不久,更新的 Godot 版本将修复这个循环问题。不过,目前提出一个临时解决方案是有意义的。
分离动作
幸运的是,我们刚刚提出的问题有解决方案。我们将指导 Godot 将动作分离到单独的文件中,类似于模型材料在文件系统中保持的方式。
对于保持模型动画的默认行为是将它们存储在其文件内部。在这种情况下,Snake.glb 实体包含了它所有的动画。要提取这些动画,请按照以下步骤操作:
-
在 FileSystem 面板中选择
Snake.glb。 -
打开 Import 面板并向下滚动到 Animation 部分。
-
在存储下拉选项中选择文件 (.anim)。
-
点击重新导入按钮。
以下截图显示了我们已经采取的步骤:

图 7.7 – Snake.glb 文件的导入设置
这将把动作提取到文件系统中。最终,您项目中将多出两个文件:
-
Attack.anim -
Idle.anim
这些是您之前在 Blender 中定义的动作。同样命名的动作也列在 Godot 的AnimationPlayer下拉菜单中。例如,图 7.6显示了选中的Attack动作。我们还需要一个步骤来解决循环问题——那就是将这些我们刚刚分离的动作重新引入到AnimationPlayer中,即使它已经列出了这些动作。为了实现这一点,请按照以下步骤操作:
-
打开场景面板。
-
在场景结构中选择AnimationPlayer节点。
-
在动画面板中点击动画按钮(位于动作下拉菜单左侧的按钮)。
-
从打开文件弹出菜单中选择
Attack.anim。 -
重复步骤 4以加载
Idle.anim。
这将用来自文件系统的动作替换现有的动作。以下截图显示了您可以在哪里找到所有这些名称,因为有很多类似的单词。在这里,动画按钮已经被按下,并显示了可用的命令:

图 7.8 – 动画面板的加载、保存和其他操作菜单
在未来,希望您不需要使用 Godot 的新版本分离和重新导入动作。目前,这将会工作,但我们不会看到效果,直到我们到达这本书的后期章节,在那里我们将触发这些动作。
摘要
由于我们在上一章中处理了 Blender 资产的导出,现在是时候学习如何将这些导入到 Godot 中。这就是本章所涵盖的内容。
首先,我们了解到一旦 glTF 文件成为 Godot 项目的一部分,Godot 会自动处理诸如分离材质等问题。换句话说,由于我们很可能会继续创建更多 3D 资产实例,我们研究了如何从 glTF 文件中创建专用场景。此外,我们还学习了如何在 Blender 中对我们的模型进行修改,并使用这些模型在 Godot 中更新场景。
然后,我们讨论了材质,这是模型工作流程中的一个复杂话题,并讨论了不同的材质标记方法,甚至将模型保存在不同的文件夹中以防止任何材质文件重叠。您决定哪种方法最适合您,因为这类事情可能是团队规模或项目特定的。
最后,我们探讨了动画导入的简便性。从模型创建场景已经处理了所有框架。尽管我们将在后面的章节中学习如何触发动画,特别是循环动画,但我们提出了一个可能发生的问题。我们提供了一个解决方案,并希望你在未来不需要使用它。
这是你关于 Godot 的第一章节,你现在正式开始使用 Godot 引擎。将 3D 资源导入到 Godot 是一个基本操作,我们希望你在 Blender 和 Godot 之间进行游戏开发时能够实现无缝的交互。
在下一章中,我们仍将专注于一个独立主题,添加音效资源,以保持内容简单。到下一章结束时,我们将涵盖设置项目结构的基础知识,这意味着我们可以在之后专注于构建游戏。
进一步阅读
你已经与 Godot 的导入面板进行了交互。该区域有许多设置,如果我们要调查所有可能的组合,可能需要写一个章节。默认设置在大多数情况下都适用,但在右上角有一个预设按钮,列出了最常用的组合。
由于项目的需求以及因此模型导入的要求在事先可能并不明确,我们将发现这些选项包含的任务留给你来完成。话虽如此,这里有一个官方资源,如果你想要获取更多信息,可以参考:docs.godotengine.org/en/3.4/tutorials/assets_pipeline/importing_scenes.xhtml。
同样,你可能想要导入图像而不是 3D 资源。当你正在为游戏构建 UI 元素时,这是必要的。我们在这本书中主要涵盖 3D 工作流程,因此我们不会强调 2D 资源的导入设置。尽管如此,如果你想在处理 UI 主题之前了解相关信息,这里有一个官方网址:docs.godotengine.org/en/3.4/tutorials/assets_pipeline/importing_images.xhtml。
第九章:添加声音资产
声音往往是游戏项目中最被忽视的部分。虽然创建视觉资产可能看起来很难,但很多人仍然会尝试,因为我们能快速可靠地获得反馈,然而,当涉及到制作声音资产时,大多数人甚至不知道从何开始。幸运的是,有一些免费的声音资产可以使用。
本章不会介绍如何制作声音资产,而是介绍如何将它们导入到你的游戏中。我们将关注 Godot 中声音管理的某些技术方面。这涉及到了解引擎支持的不同声音格式。选择合适的声音格式与为动画拓扑整理 3D 模型并无不同。明智地选择,并且最好了解每种格式的优缺点。
接下来,你将学习何时以及如何循环某些声音资产。我们将探讨不同声音类型的导入选项,并提及格式特定的差异。我们还将讨论在哪些情况下循环声音资产是有意义的。
最后,我们将了解在场景中播放声音资产的不同类型的 Godot 节点。这样,你可以为你的项目选择合适的音频播放节点。最后,我们将播放一些示例声音资产,以展示这些不同节点之间的差异。
不言而喻,为了充分利用本章内容,你可能需要在安静的地方练习一些主题,尤其是在本章的后期部分。
在本章中,我们将涵盖以下主题:
-
了解不同的声音格式
-
决定是否循环
-
在 Godot 中播放音频
到本章结束时,你将知道如何导入声音资产,选择正确的文件类型,配置它们的设置,并在你的项目中自动或需要时播放它们。
技术要求
与其他章节不同,我们不会提供一个包含单个资产的Finish文件夹,而是会提供一个包含所有场景和脚本设置的完成后的 Godot 项目。尽管如此,我们仍然希望你们练习,但仅关注本章中提出的话题。因此,我们建议你们从一张白纸开始,从Start文件夹中导入声音文件,并跟随操作。按照惯例,必要的资源可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Game-Development-with-Blender-and-Godot。
了解不同的声音格式
音频文件有不同的格式,就像图形文件可以有不同的格式,包括 JPG、GIF、PNG 等等。行业,有时甚至是消费者,决定了这些格式的命运。让我们在这里将消费者放在正确的背景下。偶尔,文件格式创造者提出的规范并不被使用该格式制作作品的用户所欢迎。然后,作品虽然被创作出来,但由于技术原因,无法被用于分发此类内容的平台所接受。这几乎就像一场拔河比赛,维护文件类型的麻烦或成本超过了其带来的好处和易用性。在这些时候,我们往往会听到关于新格式的消息,这就是为什么存在众多文件格式的原因。
大多数时候,这种技术层对最终用户来说是不可见的,尤其是如果他们只是浏览内容,比如在 Spotify 或 YouTube 上听音乐。然而,既然我们正在制作一款游戏,尽管我们不太关心这些资产的生产,但我们仍然应该对这个主题有所了解,因为我们想为特定场景选择最合适的文件格式。
区分“声音”的含义
这是一个关于我们所说的“声音”的说明。在本章和本书的其余部分,我们将使用“声音”或“音频”这个词来涵盖所有可能的场景,比如与 UI 元素交互时获得的反馈,当玩家角色被游戏内事件通知时,或者环境音乐。
目前这本书所涵盖的 Godot 版本 3.4.4 支持三种不同的音频文件格式。每种格式都有不同的优点和局限性。尽管可以将这些文件相互转换,但我们在介绍它们的正式定义后,你可能决定不这么做。
介绍 WAV
发音为 wave 的 WAV 文件自 90 年代初以来一直存在。它是 Wavefront Audio File Format 的简称,这是一种由 IBM 和微软创建的文件规范。这是音乐和音频专业人士中流行的格式,尽管它是不压缩的,因为它保留了声音记录的质量。多亏了文件存储容量和互联网速度的改进,高文件大小似乎不再是太大的问题。
在局限性方面,从技术角度来看,WAV 文件不能超过 4 GB。然而,这不应该成为问题,因为这个数字相当于近 7 小时的音频。在任何一个视频游戏中,几乎不可能有一个这么大的音频文件。
那么,为什么你应该选择这种格式呢?由于它是一种不压缩的文件类型,负责处理音频文件的 CPU 将更容易播放它。使用这种文件类型的一个可能场景是音效。通常,这些效果是短暂的,比如门的吱嘎声,剑的挥舞声等等。文件大小不会太重要,因为持续时间会很短。
相反,这也不是背景音乐的最佳格式。当然,你不需要解压缩文件就能播放它,但文件大小将显著更大。
总结来说,如果你想快速反应,并且希望音效文件尽可能快地播放,比如效果,那么这是适合你的格式。毕竟,你不想在游戏角色忙于处理下一连串事件的同时,CPU 还在处理效果文件的解压缩。
如果你愿意牺牲几百毫秒来等待解压缩,比如当背景音乐不能立即播放是一个大问题时,那么你可以选择压缩文件类型。这些文件类型有两种不同的风味。
介绍 OGG
我们应该首先澄清这个格式,因为如果你在网上遇到一些资源,名称可能会令人困惑。技术上讲,OGG 是一种容器文件格式,可以容纳音频、视频、文本和元数据等文件类型。其开发者和维护者 Xiph 还负责另一种名为 Free Lossless Audio Codec (FLAC)的音频文件格式。因此,根据 OGG 规范,FLAC 可以是 OGG 文件的一部分。从历史的角度来看,大多数 OGG 文件都包含另一种名为 Vorbis 的不同音频文件格式。因此,你可能会发现一些提供 Vorbis 内容的网站实际上是在遵守 OGG 格式规范。
这里有一个示例来简化所有这些名称以及它们之间的关系。将 OGG 视为一个知道如何处理其内容的 ZIP 文件。一个携带视频和字幕文件的 OGG 文件将触发视频播放器中的必要设置,以便播放器知道在哪里找到字幕,因为它们将嵌入在一个文件中。同样,另一个携带音频和元数据文件的 OGG 文件将指示音频播放器显示专辑和曲目、记录和播放音频。
由于格式不仅仅是一件事,而是一组文件,因此将特定需求与一个文件扩展名关联起来常常令人困惑。例如,.ogg 扩展名在 2007 年之前被用作多媒体容器,因为这是它的原始意图。从那时起,Xiph 建议我们使用 .ogg 扩展名来表示 Vorbis 音频文件。此外,该公司创建了一套新的文件扩展名以简化事物:
-
.oga用于仅音频文件 -
.ogv用于视频 -
.ogx用于复用情况
尽管存在命名难题,但你需要知道的是,OGG 音频格式是压缩的,因此它是一个有损文件格式。在我们的语境中,“有损”意味着我们可以通过要求更少的硬盘空间来获得几乎相同的声音质量。所以,这是一件好事,因为这个文件格式非常适合播放背景音乐。记住,由于 CPU 必须解压缩这种文件类型,因此这不是播放快速音效的首选格式。
说到有损文件格式,我们的下一个候选者是有损文件格式,它在 2000 年代初获得了一些恶名。
介绍 MP3
在 20 世纪 90 年代末,互联网速度和磁盘存储非常昂贵的时候,MP3 在转移音频内容方面填补了一个重要的空白,正好在千禧年之交,大量受众需要它。消费者纷纷涌向网站下载他们最喜欢的乐队的曲目副本。遗憾的是,许多这些网站没有麻烦去获得分发此类内容的合法许可,这导致了版权侵权,在 Napster 的情况下,甚至引发了诉讼。
从技术角度来看,MP3 文件在 WAV 和 OGG 之间,在压缩方面处于中间位置。因此,对于相同音质,OGG 文件的大小会更小。话虽如此,解压缩 MP3 文件比解压缩 OGG 文件要快。因此,这使得 MP3 格式仍然有用,尤其是在 CPU 承受最大压力的地方,比如在移动设备上。
尽管磁盘空间越来越便宜,但从商业角度来看,仍然有优先考虑 WAV 而不是 MP3 的合理性。例如,一些提供免版税音效文件的网站提供 MP3 版本,但将 WAV 版本的音效放在付费墙后面。由于 MP3 文件已经因为其压缩算法而丢失了一些原始数据,因此反复编辑此文件会产生更多有损的结果。所以,如果你想要对其做出修改,能够访问原始 WAV 文件总是更好的。然而,如果你不需要这样做,那么 MP3 版本可能就足够了。
结束语
总结来说,WAV 文件更适合短音效,而较长的音效,尤其是主题曲,用 MP3 文件处理会更好。在撰写本文时,大多数音效库仍然不常用 OGG,尽管它是一个很好的候选者。尽管如此,如果你有很多 WAV 文件并且想要在文件大小上更有效率,那么你可以使用在线转换器将它们转换为 OGG。以下有两个例子:
在音乐文件的情况下,这些文件通常只有几分钟长,如果你的原始文件是 WAV 格式,那么在线上传和处理这些文件可能会花费很长时间,因为文件大小很容易超过 50 MB。此外,一些在线转换器有文件大小限制。为了绕过这些限制,这里有一个链接到网站,比较了一些你可以用于你的努力的离线转换器:www.lifewire.com/free-audio-converter-software-programs-2622863。
无论你选择什么类型的文件,以及它是声音效果还是音乐,在你的游戏开发旅程中,你都会到达一个决定你的声音资产是否应该循环的时刻。在下一节中,我们将讨论为什么开启或关闭循环功能是有用的。
决定是否循环
在字面上,循环是一个连续的运动或结构,如果你随机选择一个点,你可以通过完全穿越它回到那里。在听觉上,这很相似,但我们不是从任何地方开始;我们通常开始播放一个声音文件,但玩家会在它到达末尾时重新启动曲目。
这个定义是经典的,但并不那么有洞察力,所以让我们通过在 Godot 或任何游戏项目中讨论它在各种情境下的应用来做得更好。这样,你可以在项目中做出明智的决定,因为它是特定情境的。我们将通过展示不同的用例来完成这项工作:
-
背景音乐: 这是最常见的案例,在游戏运行时,背景音乐持续播放。作曲家创作这种作品的目的就是一旦连续播放,就不会有突然的结尾。文件末尾的声音会无缝地与开头匹配。当然,如果你注意节奏的起伏,你会知道你在文件中的位置,但只要循环设置开启,一切听起来都会很顺畅,融合在一起,这样你就可以专注于游戏体验。
-
机关枪: 想象一下,在游戏中玩家或敌人角色正在与机关枪互动。虽然可以发射短点射,但由于机关枪的特性,枪可能会连续发射。因此,与其检测声音文件是否到达了结尾并指示玩家重新启动文件,不如如果该文件启用了循环功能,就播放一次文件。这样,机关枪效果将持续播放,直到接收到停止命令。
-
门: 这是一个有点边缘的情况。假设我们在游戏中有一些视觉和其他声音效果,表明我们正在一个风大的户外场景中。也许这扇门状况不佳,铰链生锈,甚至有一个铰链稍微向外倾斜。艺术家可能决定让这扇门动画化以匹配风对门的影响,使其在关闭和打开状态之间摆动。在这种情况下,有一个循环的声音文件是有意义的,其中很可能包含与门动画同步的吱吱声和嘎吱声。
然而,如果一扇门将响应玩家的动作,比如打开或关闭,那么将声音文件设置为循环就没有意义了。这将是一个一次性的事件。
- 用户界面:当您与用户界面交互时听到的声音属于此类。这些通常不会循环,因为它们基于事件,类似于之前用例中的一次性开门动作。然而,让我们提出一个可能认为循环是个好主意的情况。尽管如此,我们仍会出于一个很好的理由将其排除。
想象一下有一个 UI 组件帮助玩家设置一个数值。界面有两个按钮,将增加和减少玩家看到的数值。在任一按钮上放置 UI 音效是可以的,只要玩家继续点击,声音就会播放一次。如果我们想给玩家一个按住按钮的机会呢?毕竟,不断地点击按钮以获得非常高或非常低的数值可能会很快变得乏味。那么,在这种情况下我们应该如何处理循环条件呢?
在这种事件中,人类的感知非常敏感。玩家在游戏过程中通常很忙,所以当 CPU 忙于解压缩音乐文件时,他们不会察觉到延迟。然而,我们通常非常敏感地检测到 UI 按钮保持事件结束时的差异。因此,即使它们可能感觉相似,设计师也会选择单独触发音效,而不是循环播放。
在本节中,我们介绍了不同用例,在这些用例中,循环的使用或未使用是常见的。然而,您还没有看到如何打开和关闭循环功能。我们将通过重新访问我们的老朋友,即导入面板,来展示这一点。
打开和关闭循环
到目前为止,我们已经讨论了循环是什么以及何时打开或关闭循环可能是有意义的,但我们还没有看到如何翻转其状态。在本节中,我们将把每种类型的音文件放入我们的项目中,并研究导入面板中的设置。
我们将使用来自Freesound网站的Loop_Someday_03.wav文件,该文件由用户LittleRobotSoundFactory创建。声音最初是 WAV 格式,但我们已经将其转换为 OGG 和 MP3 版本。您可以在Start文件夹中找到所有版本,并比较它们的文件大小。
一旦您已将文件添加到您的项目中,让我们学习 Godot 如何识别这些文件。因此,打开导入面板,并选择 OGG 或 MP3 版本。然后,选择 WAV 版本。界面差异在以下屏幕截图中显示:

图 8.1 – MP3 和 OGG 版本比 WAV 版本有更少的导入设置
如您所见,默认情况下,MP3 和 OGG 版本都带有循环设置。此外,这些版本似乎没有太多设置。另一方面,WAV 版本的循环默认是关闭的。这是为什么?
如果你记得我们在“了解不同的音频格式”部分介绍的不同音频格式,Godot 自由地循环了压缩版本,因为这些很可能会用于背景音乐。相反,如果我们的示例文件是声音效果,我们很可能会使用没有循环的 WAV 文件,因为它将是一个快速的一次性事件,对 CPU 的要求最小。
其他 WAV 设置
由于我们目前正在使用 导入 界面,让我们也指出,你可以在 强制 部分打开一些选项来减小 WAV 文件的大小。图 8.1 展示了这一点以及一些其他设置,例如修剪和归一化你的文件。前者将在文件的开始和结束处修剪静音部分,这在导出 WAV 文件时有时会自动添加。如果你希望声音效果立即开始而不延迟,这一点尤为重要。
因此,对于任何给定的声音文件,打开或关闭循环功能就像点击一下那么简单,而且你知道如何操作。也许更重要的是决定一个文件是否应该循环。这是你在过程中必须回答的问题。
不论如何,你最终都需要一个 Godot 节点来播放你的声音。在下一节中,我们将了解 Godot 使用的不同音频播放器,并将我们的声音文件附加到适当的播放器上。
在 Godot 中播放音频
由于 Godot 几乎使用节点来处理所有事情,播放声音也不例外。要播放音频文件,你可以将节点附加到你的场景中,并根据是 2D 还是 3D 游戏来配置它们。在本节中,我们将重点关注 Godot 使用的不同音频播放器。
无论你选择什么音频文件类型,你都将能够使用本节中将要介绍的节点来播放它。当然,根据节点类型,你的体验会有所不同,但这取决于你正在制作的游戏类型,你必须做出决定。因此,让我们看看 Godot 使用的音频流节点,以便你可以选择合适的节点。你的三个选择如下:
-
AudioStreamPlayer:这个节点的官方定义有些枯燥;它以非位置方式播放音频。这意味着你不需要关心音频来自哪个方向。对于第一人称射击游戏,知道敌人从哪个方向向你开火是至关重要的。这涉及到位置数据。在这个音频节点中,你没有任何位置信息。然而,这是播放背景音乐的合适候选者。更多关于它的信息可以在
docs.godotengine.org/en/3.4/classes/class_audiostreamplayer.xhtml找到。 -
AudioStreamPlayer2D:正如你所猜到的——这个节点包括位置信息。因此,摄像机从这个节点越远,声音就越小。这个节点对于 2D 平台游戏很有用,例如。所以,一旦游戏对象进入视野,流就会被摄像机拾取。此外,位于摄像机右侧的对象将优先考虑右侧扬声器,反之亦然。更多详细信息请参阅
docs.godotengine.org/en/3.4/classes/class_audiostreamplayer2d.xhtml。 -
AudioStreamPlayer3D:最后但同样重要的是音频流的三维版本。它将三维位置信息传递给听众。因此,这是你在三维设置中会使用的音频流节点。自然地,这种流器采用了更高级的功能,例如衰减,它控制声音在距离上的衰减方式,以及多普勒效应。因此,通过访问
docs.godotengine.org/en/3.4/classes/class_audiostreamplayer3d.xhtml来检查其属性可能是个好主意。
我们可以逐一介绍每种流播放器的每个属性,但我们将这项任务留给你,因为选择正确的流器和配置其设置是一种艺术形式。在本书的后面部分,我们将使用适当的流器构建游戏,并关注该上下文中的重要设置。与此同时,你可以通过访问官方文档中提到的上述 URL 来了解每个流器能做什么。
话虽如此,我们不会立刻离开这一章。让我们播放一些声音来模拟我们之前列举的一些例子。
播放背景音乐
让我们练习一下本章中介绍的一些内容。我们将从播放一个适合作为背景音乐的声音开始。我们将使用在决定是否循环部分导入的loop-someday-03文件的 MP3 版本。要将此声音作为背景音乐播放,请按照以下步骤操作:
-
创建一个新的场景并将其保存为
Background-Music.tscn。 -
在你的场景中添加一个AudioStreamPlayer节点,并在Inspector面板中打开其Autoplay属性。
-
将
loop-someday-03.mp3从FileSystem面板拖放到Inspector面板中的Stream属性。 -
按下 F6。
这将启动你的当前场景并自动播放 MP3 文件。由于文件的循环设置被设置为 true,9 秒长的音乐将无限循环播放。你现在可以将这个场景添加到其他你想有背景音乐的场景中。
按需播放声音效果
为了这个努力,我们将回到决定是否循环部分中的机枪示例。机枪的声音也被设置为循环,但我们不希望当场景启动时自动播放。很可能是你的玩家角色会进入或接近一个敌人用机枪火力猛攻的区域。让我们编写一些代码来模拟这种触发行为:
-
创建一个新的场景并将其保存为
Machine-Gun.tscn。 -
在你的场景中添加一个音频流播放器节点,并将其与以下代码行的脚本相关联:
extends AudioStreamPlayer func _unhandled_key_input(event: InputEventKey) -> void: if event.is_pressed() and event.scancode == KEY_SPACE: play() else: stop() -
将
machine-gun.ogg从文件系统面板拖放到检查器面板中的流属性。 -
按下 F6。
由于我们希望流按需播放,我们将它连接到一个条件,使其为真——即按下空格键。继续按下一次或两次;甚至可以短暂地按住。你会听到机枪声音的开和关,这是由于音频流播放器节点的播放和停止命令。
我们实现的脚本看起来足够好,但也有些问题。你可能已经注意到了。尝试长时间按住空格键,比如 3 或 4 秒,你会听到卡顿的声音。这是因为脚本发出了太多的播放命令。所以,过了一会儿,CPU 将指示播放相同的资产太多次。我们可以通过用以下内容替换脚本的内容来做得更好:
extends AudioStreamPlayer
func _unhandled_key_input(event: InputEventKey) -> void:
if event.is_pressed() and event.scancode == KEY_SPACE:
stream_paused = false
else:
stream_paused = true
在这里,我们用不同类型的命令替换了原来包含播放和停止命令的行。新版本将控制流是否应该暂停。为了让这个脚本工作,我们需要在检查器面板中开启两件事:
-
自动播放
-
流已暂停
这个新的设置将自动播放流,类似于在播放背景音乐部分发生的情况,但随后立即暂停。一开始这似乎有些不合直觉,但让我们分析一下新脚本正在做什么。当按下空格键时,我们恢复流,由于else情况,流将被再次暂停。因此,新脚本不会发送连续的播放和停止命令,从而不会占用 CPU。
我们将通过对本章所展示的内容进行讨论,来总结机枪发射的两种更多风味。
提高游戏体验
你有没有注意到我们为背景音乐和机枪都使用了相同类型的音频流节点?从某种意义上说,我们将机枪视为背景音乐。换句话说,我们并不太关心声音是从哪里传来的。
为了提供更愉悦的游戏体验,你可以在 2D 游戏中使用AudioStreamPlayer2D节点,在 3D 游戏中使用AudioStreamPlayer3D节点。通过调整这些节点上的衰减值,这些值定义了声音随距离传播的方式,当你的角色靠近声音源时,玩家可以听到越来越大的机枪声。这将提升危险感,而且这是一种既经济又好的沉浸式体验方式。
摘要
我们在本章开始时介绍了 Godot 用于播放声音的不同文件类型。了解这些格式之间的差异,当你与作曲家合作时,你可以强调你希望你的声音文件以哪种格式交付。可能性很大,他们可能会询问这个问题,他们甚至可能会以所有三种可能的格式交付。
接下来,我们讨论了几种可能适合循环声音文件的情况。为了便于操作,我们研究了导入面板中提供的选项。然而,是否循环的决定仍然需要你自己来做出。
最后,为了将我们的理论知识付诸实践,我们创建了两个可以播放样本文件的场景。在第一种情况下,我们将声音文件附加到音频流器上,并让它自动播放。在第二种情况下,我们编写了一个非常简单的脚本,允许你开始和停止声音,以模拟敌人角色的行为,因此可能产生的声音效果。
到目前为止,我们一直在探索构建游戏所需的某些成分,例如导入资产——无论是上一章中的模型还是这一章中的声音资产。在下一章中,我们将通过设计关卡来直接构建我们的点击冒险游戏。
进一步阅读
如果你喜欢创作音乐和声音效果,以下是一些你可以开始使用的软件列表:
-
LMMS:
lmms.io -
Waveform Free:
www.tracktion.com/products/waveform-free -
Cakewalk:
www.bandlab.com/products/cakewalk
上述链接仅涵盖音乐制作中工具的使用方面,因此你可能还需要学习其艺术方面,这可以通过多个在线培训平台上的课程来实现,例如 Udemy。
顺便说一句,如果你看到某个声音文件看起来可以免费下载,并不意味着你有权在你的作品中使用这个片段。如果你不想有一天接到律师的电话,你可能需要仔细阅读条款。尽管如此,以下是一些提供付费和免费声音内容的网站:
第三部分:克拉拉的财富——一个冒险游戏
在本书的最后一部分,你将创建一个点击冒险游戏。由于准备所有游戏资源会非常耗时,因此你将获得必要的文件。
在本部分,我们将涵盖以下章节:
-
第九章**,设计关卡
-
第十章**,利用光线和阴影使事物看起来更好
-
第十一章**,创建用户界面
-
第十二章**,通过摄像头和角色控制器与世界交互
-
第十三章**,以声音和动画结束
-
第十四章**,结论
第十章:设计关卡
从本章开始到本书的结尾,你将积极投入到创建一个点击冒险游戏的过程中。我们将向你展示创建游戏的必要步骤,在这个游戏中,你将放置并指挥一个名叫克拉拉的角色。玩家将控制她在洞穴内的行动,起初洞穴是黑暗的,但你将能够给玩家控制权来改变灯光的条件。一旦你弄清楚如何在世界中移动她,你也将在这个洞穴中放置触发点,让世界对克拉拉的行动做出反应,使游戏既有趣又具有挑战性。本书的这一部分将涵盖足够的基本构建模块,让你开始练习构建小型冒险游戏。
通过所有这些努力,你将学会如何利用 Godot 引擎的不同部分,特别是与 3D 工作流程相关的部分。每当需要时,我们会提醒你回顾前面的章节,在那里你可以回顾一些基本原理。这是因为本书的这一部分将大量依赖于我们迄今为止所展示的实践应用。
话虽如此,每个游戏都有一个故事;这是我们的故事:
“不过两周前,克拉拉的叔叔派人叫她。克拉拉正驾船前往叔叔给她的坐标,这时她注意到远处有微光。在她小心翼翼地接近注意到闪光的地方后,她看到这是海中一块岩石形成的悬崖下的洞穴入口。她小心翼翼地操纵船帆,顺利地进入了洞穴。幸运的是,阳光足够她看到码头,她把船锚泊在那里。她兴奋地要去看望她的叔叔。”
虽然有很多事情要做,从调整洞穴环境中的灯光到触发声音和动画,但我们应该首先构建世界。这正是本章的内容。
我们将首先通过从项目文件夹中放置模型来组成一个场景。这种场景结构,玩家在其中体验游戏世界的特定部分,通常被称为关卡,通常表示不同的难度级别或独特的环境。
当我们在安排资源以构建关卡时,我们将探讨在 Godot 中创建和修复材料,因为有时,一些事物在应用程序之间并不能完美地传递。第六章,导出 Blender 资产,和 第七章,将 Blender 资产导入 Godot,介绍了如果需要复习,Godot 和 Blender 之间交换信息的工作原理。
虽然手动布局创建关卡是可以的,但我们总是可以从使用使这类工作更容易的工具中受益。Godot 的GridMap是放置对象在网格结构上的正确工具。为了GridMap能够工作,它需要一个名为MeshLibrary的另一个 Godot 机制。我们将向你展示如何构建一个并使用它作为构建关卡的一种替代方式。
在本章中,我们将涵盖以下主题:
-
创建洞穴
-
构建缺失的材料
-
在网格上放置模型
-
利用 MeshLibrary
最后,我们将通过排列场景/模型、完成缺失的材料,并利用GridMap和MeshLibrary来加快工作流程来构建一个关卡。通过这样做,你将拥有设计关卡所需的正确工具。
技术要求
从本章开始,接下来的章节将继续,你将创建一个点击冒险游戏。由于准备所有游戏资源会花费你太多时间,所以我们提供了它们。我们已经从 Blender 导出了 glTF 文件。如果你需要访问原始文件进行任何修改,或者当提到特定文件时,这些文件可以在本书 GitHub 仓库中的Blender Models.zip文件中找到。
与之前的章节不同,它们通常包含简单的资源文件夹Start和Finish,我们将做一些改变。本章也会有常规的文件夹,但它们将包含 Godot 项目的资源。Start文件夹中的 Godot 项目将包含用于开始构建游戏关卡的基本资源。到本章结束时,你的游戏将达到一个阶段,你可以使用Finish文件夹中的内容来比较你所创建的内容。
此外,从下一章开始,你将只有Finish文件夹,因为你可以使用每个章节中完成的阶段作为下一章的起始条件,依此类推。
我们建议你访问本书的 GitHub 仓库github.com/PacktPublishing/Game-Development-with-Blender-and-Godot,查看我们为你准备的内容,并帮助克拉拉在她的冒险中。
创建洞穴
对于克拉拉冒险的第一级,我们想到了一个小地方,这样你就不至于在构建大型布局时感到不知所措。图 9.1将帮助你可视化我们所构建的内容。这是我们将在 Godot 中尝试复制的 Blender 渲染:

图 9.1 – 我们将为克拉拉构建这个小型关卡
我们的世界将包括一个位于洞穴内部且通往海洋的码头。当克拉拉锚定她的船时,她可以看到洞穴内部。一开始并没有多少光线,但她能看到的最少,码头通向一个铺有石头的码头。她还可以看到到处分布着一些箱子、桶和罐子。尽管墙壁上的壁灯在游戏运行时将开始未点亮,如图 9.1 所示,但你可以看到墙壁上的所有壁灯都是点亮的。这是因为我们想向你展示游戏的后期阶段,以便你能看到我们追求的目标。否则,它将是一个黑暗的轮廓。
在第十章《利用光线和阴影使事物看起来更好》中,我们将探讨如何通过使用适当的灯光类型并启用阴影来创建一个更具戏剧性的级别。我们已经在第四章《调整相机和灯光》的 Blender 环境中讨论了一些内容,但我们也将在 Godot 环境中进行操作。
级别设计、游戏设计和视觉设计之间的比较
如果你是对游戏开发新手,那么你可能会遇到一些令人困惑的名称。单词设计就是这样的一个例子,因为它通常意味着人们看到的东西。然而,实际上,它意味着一种时尚,或者是一种做或构思某事的方法。让我们在正确的上下文中讨论它。
我们本可以设计一个不同的级别,以便在码头尽头的门处提供挑战性的通道。也许光线条件如此之差,以至于克拉拉需要一些帮助才能看到重要的线索。为了在游戏中取得进展,游戏设计规则将定义玩家如何与世界互动。也许玩家只需点击世界中的游戏对象就足够了,而有时,拥有一个库存和制作系统会更好。
最后,视觉设计与前两个设计概念无关。洞穴墙壁仍然是洞穴墙壁,但它们可能看起来超现实主义,你可以感觉到石头是潮湿的,覆盖着苔藓。这会给游戏增添什么,并使其变得有趣?因此,所有这些设计原则同等重要,但又各不相同。
该级别,Level-01.blend,位于本书 GitHub 仓库根目录下的Blender Models.zip文件中。你很可能需要将其打开,以便在 Godot 中构建级别时将其作为参考。
我们将开始构建级别,通过布置它的不同部分。说到这一点,我们必须遵循以下步骤来构建我们的第一个级别:
-
在
Scenes文件夹中创建一个新的场景,并将其保存为Level-01.tscn。 -
将一个Spatial节点作为根节点,并将其重命名为Level-01。
-
在根节点内部创建更多具有以下节点名称的Spatial节点:
-
地板
-
柱子
-
墙壁
-
轨道
-
沉没的墙壁
-
道具
-
岩石
-
壁灯
-
码头
-
我们将使用这些子空间节点来存储级别的不同部分,因为在这个非常小的级别中,我们最终会有很多部分。以下截图显示了我们的最后努力后的节点结构:

图 9.2 – 不同级别的结构被分组在许多空间节点下
在这些空间节点内部,我们将放置级别的相关部分。例如,地板块将放入地板节点。我们可以通过以下方式轻松放置我们的第一个资产:
-
在场景树中高亮显示地板节点。
-
点击顶部的链形图标以在您的高亮节点内实例化另一个场景。或者,您可以按Ctrl + Shift + A。
-
在弹出窗口的搜索部分中输入
Floor_Standard。 -
从匹配部分选择
Floor_Standard.glb,如图下所示。
这将在地板节点内创建Floor_Standard.glb的实例:

图 9.3 – 您需要使用搜索区域来过滤掉不需要的匹配项
你可能已经注意到,尽管我们原本希望继承一个应该具有.tscn文件扩展名的场景,但相反,我们实例化了 glTF 文件。在第七章,“将 Blender 资源导入 Godot”,我们学习了如何从 glTF 文件创建场景。因此,我们可以那样做,创建一个Floor_Standard.tscn场景,然后在该场景内部实例化地板节点。我们采取了捷径。创建场景在你打算添加除了模型结构本身以外的其他元素时很有用。对于地板,我们不需要额外的元素,所以只实例化其 glTF 版本是可以的。
另一方面,当我们创建我们的级别时,直接实例化 glTF 文件将不再适用。例如,在下一章处理灯光和阴影时,从烛台模型创建场景并添加一个灯光对象到同一场景中会更有意义。因此,烛台场景将负责显示 glTF 模型以及包含一个灯光对象,以便它可以后来编程地打开或关闭。如果您只想显示模型,而不需要更多东西,实例化 glTF 文件通常就足够了。
添加第一块后,它将自动选中。如果不是,您可以在 3D 视图中单击地板块或在高亮节点中高亮显示场景树中的节点。一旦选中,您将看到一个位于模型中心的操纵杆,让您可以移动和旋转该部件。如果您的视图已经旋转,您的操纵杆方向可能看起来不同。以下截图显示了我们所期望看到的一个示例:

图 9.4 – 移动和旋转对象的工具
我们试图布置的平面图由更多标准的地面块组成。因此,获取额外块的一个简单方法就是复制现有的块并将它们移开,如下所示:
-
在场景树中选择Floor_Standard节点。
-
通过按Ctrl + D来复制它。
-
通过在工具中拖动蓝色或红色轴来移动新的地板块。
这将在场景中添加一个新的地板块并将其移动。我们故意忽略绿色(Y)轴,因为我们不希望地板在这个时候有任何高度。然而,对于你的游戏,你可以设计具有不同高度区域的水准,并通过楼梯连接它们。
由于我们的平面图看起来像是一个网格,所以地板块能够相互对齐会很好。我们可以通过在XZ平面上移动块并在精确增量上限制它们的移动来实现这一点。为了模拟这一点,删除你最近创建的最新的地板块,然后执行以下操作:
-
复制一个新的Floor_Standard节点。
-
按住Ctrl键,并使用 X 或 Z 工具箭头移动块两个单位。
为什么我们移动了两个单位?因为模型设计成适合一个 2 x 2 米大小的网格。你可以打开相关的 Blender 文件来观察尺寸。我们在 Godot 中不进行测量,但它仍然尊重 Blender 中设置的缩放和单位方面。这就是为什么我们确保模型的缩放设置为1。如果你需要提醒,我们建议你阅读第六章的应用旋转和缩放部分,导出 Blender 资产。
在实施最新的带有对齐功能的移动块指令后,你将得到以下输出:

图 9.5 – 新地板块紧挨着旧的一个
目前要做的只剩下复制足够的地板块,并使用对齐功能移动它们。此外,你还需要在地板节点内实例化和放置两个新的模型:
-
Floor_Standard_Curved_1.glb -
Floor_Standard_Curved_4.glb
这些弯曲的地板块可以适应弯曲的墙壁,这意味着我们可以保持建筑的一致性。通过复制足够的地板块和添加新的弯曲块,并在移动块之后,我们将实现以下输出:

图 9.6 – 添加了两种新类型后,地板已准备就绪
所有地板块现在都位于场景中的地板节点下,这项工作完成了我们构建地板的任务。我们将采用类似的方法在单独的Spatial节点下布置洞穴的其他部分。
建立墙壁
在构建水平面时,下一步的工作是搭建墙面部分。你可以通过在 墙面 节点下实例化墙面部件来完成,这与放置地板部件的方式类似。为了不重复提供非常相似的说明,我们将使用本节来突出你可能会遇到的一些特殊情况。
例如,你最终可能想要放置一些在角落处连接的墙面部件。因此,你需要将其中一个部件绕其 Y 轴旋转 90 度。你可以通过使用工具或通过在 变换 部分的 检查器 面板下输入确切的值来完成这个操作。
另一种情况是,墙面上有一个洞,让许多树枝爬进了码头区域。这是你在 图 9.1 右侧可以看到的细节。我们建议使用 Wall_Hole.glb 来处理该区域的水平面。同样,Curve.glb 应该放置在我们已经建立的弯曲地板部件上方。
虽然从技术上讲门不是墙面,但我们可以说拱门和门可以与其他墙面部件相协调。毕竟,它们在概念上属于同一个结构。因此,对于那个部分,你可以使用以下部件:
-
Wall_ArchRound_Overgrown.glb -
Arch_Round.glb -
Doors_RoundArch.glb
最后,当你把所有的墙面部件摆放好之后,你可以复制它们并将它们在 Y 轴上向上移动两个单位。这样,墙面就会和拱门和门的高度一致。完成这一步后,你的地板应该看起来就像下面截图所示:

图 9.7 – 水平面开始看起来更像我们的参考图片
如你所注意到的,在门附近的弯曲墙面部件旁边地板上有一个缝隙。我们将通过巧妙地放置两株绿色植物来填补这个缝隙。否则,你可能需要为这种边缘情况准备一个地板部件。无论如何,都是可以的,并且来回在 Blender 和 Godot 之间完成缺失的部分也是这个过程的一部分。
由于我们一直在处理墙面,我们可以通过使用额外的墙面部件来模拟水平面与洞穴中的海水相遇的部分。
沉入墙面
看起来这个地方的建筑师费尽心思,用石砖铺就,以防止自然侵蚀地板下面的东西。真聪明!
为了实现建筑师的想法,你可以利用标准墙面部件在地板与水连接的地方创建一个帘幕状的结构。最终,当你将这些部件放置在 场景 树中的 沉入墙面 下时,你会看到下面截图所示的内容:

图 9.8 – 使用相同的墙面部件防止水从下面渗漏
现在将海浪的起伏控制在一定范围内。请注意,我们不想让沉没的墙壁部分完全围绕地面。这是因为你可以始终限制摄像机角度,不显示结构的背面。这是一种保持资产数量低廉的方法。然而,如果你想给玩家充分的自由,让他们可以围绕整个结构旋转,你可能需要改变你的关卡设计以适应这一点。我们将在第十二章中调查摄像机设置,通过摄像机和角色控制器与世界交互。目前,我们仍然需要完成我们的关卡。
放置岩石
由于我们目前关注的是靠近水的地方,让我们在场景中添加一些岩石。在这个级别的 Blender 文件(Level-01.blend)中,你会看到单个的岩石。它们已经被组织起来,以产生岩石群落的错觉。在 Godot 中采用类似的方法,将特定的岩石放置到场景中也是完全可以的,更具体地说是在岩石节点下。
然而,有一个更简单的方法。如果你从 Blender 中导出左右岩石群落作为一个单一物体,这是完全可能的,这就是为什么我们为你准备了两个文件:
-
RocksLeft.glb -
RocksRight.glb
你可以实例化这两个文件,并使用变换工具自由移动实例。这意味着你不需要使用快速移动功能。将岩石的位置调整到你认为最好的地方。
谈到在不使用快速移动功能的情况下移动资产,或许我们可以多练习一下。由于地面看起来很空旷,现在是时候讨论一些补充设计元素,比如道具。
道具分布
道具是一种作为支撑元素的物体。道具也常被称为必要的杂乱,因为它们完成了装饰。否则,当东西看起来太干净时,对眼睛来说就不那么愉快了,我们开始注意到重复的图案或不必要的细节。
相反,我们希望体验场景的人感到舒适。这也是设计师们将重要元素隐藏在明显之处的一个好方法。为此,我们将使用以下道具列表,并将这些资产分布在整个场景中:
-
Barrel.glb -
Backpack.glb -
Bush_Round.glb -
Candles_1.glb和Candles_2.glb -
Cart.glb -
Crate.glb -
DeadTree_3.glb -
Flag_Wall.glb -
Pot1.glb,Pot2.glb,Pot3.glb及其破损版本 -
Statue_Stag.glb
一旦你完成了道具的移动,你的场景将看起来如下:

图 9.9 – 道具已经分布在整个码头周围
当你忙于这件事时,你不妨实例化 Column_Round.glb,再复制两个,并将它们放在 Rail_Corner.glb 和 Rail_Straight.glb 下方,这些可以沿着边缘和靠近鹿雕像放置。你不必对这些对象做到像素级精确,但如果你想更精确,可以使用 Level-01.blend 作为参考。
完成关卡的其他部分
为了完成关卡,我们需要放置壁灯并建造一个码头。这些资产与您在其他地方实例化和移动的资产没有不同。
然而,放置码头部件可能会在定位方面让你有些困惑。你可能发现楼梯部件在尺寸上看起来有些不合适。有时,资产被设计成通用的,而有时,资产会被设计成可以无缝地与其他模型配合或连接。无论如何,由于在 Godot 中可以调整最终位置,我们可以从这些小问题中恢复过来。
为了模拟我们如何处理这个问题,我们将向您提供我们用于两个部件位置的平移值:
-
4,-1,5.5 -
4,-1.5,8.9
由于你无疑是在以对你来说自然的方式移动关卡部件,你的值可能会有所不同。如果你的数字与我们的示例不匹配,请不要担心。我们想指出两个结构之间的相对差异。你也可能有一个数字在一个轴上相同,无论是X还是Z轴。此外,我们根据经验推测,你的楼梯的Y值可能比我们的低 0.5。这将导致一个看起来像是一个整体设计的码头结构。如果你想有一个更高的码头,你可以复制楼梯并相应地移动它。这就是有单独部件的好处。
我们建议您在此处将船模型添加到场景树中的码头节点下,因为它可以被认为是码头区域的一部分。这标志着我们关卡的建设完成。它应该看起来如下:

图 9.10 – 该关卡已在 Godot 中重建
尽管我们声称关卡的建设已经完成,但你可能已经注意到有一些看起来很奇怪的东西。我们有一个没有水的码头区域——门旁边那些丑陋的圆形东西是什么?我们将在下一节中找出如何解决这个问题。
构建缺失的材料
当我们放置道具时,我们通过放置灌木道具来覆盖门附近的缝隙(这可以在图 9.10中看到)。然而,那些灌木有点尴尬。同样,门上的拱门在石头砖块上挂着一些看起来很奇怪的东西。它们应该显示绿色植物和叶子,但我们只有一片平淡的灰色表面。我们将在本节中修复这些问题。
此外,虽然从 Blender 导出单个模型并将其放置在 Godot 场景中是有意义的,但导出水体就没有意义了。即使在 Blender 中,那个对象也是一个应用了模拟水效果的着色器的平面。我们将在 Godot 中重新创建那个效果。
修复叶子
首先,让我们描述一下灰色叶子的问题。其他所有模型似乎都正确显示了它们的材料。尽管有所有的意图和努力,某些事情在应用程序之间永远无法完全传递。叶子就是这样。不过,为了得到更详细的答案,我们需要稍微技术化一些。
你会如何设计一个 3D 叶子?由于叶子边缘有很多细节,如果不使用足够的顶点,很难显示那么多细节。为了保守起见,你可以使用顶点最少的对象,并将透明的叶子纹理应用到这个基本对象上。以下截图显示了这种方法的应用:

图 9.11 – 一个透明的文件被用作矩形形状的纹理
上述截图显示了一个非常简单的着色器。纹理的 alpha 值连接到了着色器的Alpha插槽。此外,在材料的设置下的混合模式被设置为Alpha 剪辑。这意味着纹理的 alpha 部分将被从结果中裁剪出来。我们需要在 Godot 中做类似的事情。
不幸的是,Godot 不会自动理解并开启导入材料的透明度。我们将不得不做一些手动工作来正确显示叶子。幸运的是,这也会让你熟悉检查器面板中的材料和它们的设置。
让我们从寻找灌木的材料开始。模型文件夹以保持不同模型分别放在单独文件夹中的方式组织。因此,在Texture_Leaves.material项目下扩展灌木文件夹。这将使检查器面板中出现该材料的属性。有很多东西可以查看,但我们只需要调整几个地方:
-
扩展旗帜部分。
-
打开透明设置。
-
扩展反照率部分。
-
将
Leaf_Texture.png从纹理文件夹拖放到纹理字段。作为替代,你也可以点击纹理字段并加载必要的文件。
如你所注意到的,材料的纹理缺失,因此灌木无法显示任何内容。其次,通过在标志中开启透明度,我们是在要求 Godot 尊重纹理文件的透明部分。如果你喜欢,可以打开和关闭它来查看差异。最终,我们的场景将如下所示:

图 9.12 – 我们的灌木看起来又健康起来了
你可以为拱形模型做同样的事情,该模型位于Models文件夹内的Architecture文件夹中。这看起来可能像是你在重复自己,你是对的。由于我们正在保留使用相同 Blender 材质的独立模型,并将它们放在相关的文件夹中,因此材质也被复制了。关于这一点,在第七章的决定如何处理材质部分中提供了详细讨论,将 Blender 资产导入 Godot。由于这是一个组织问题,我们把这个决定留给你,但现在你知道如何启用材质的透明度。
我们材质拼图中缺失的另一块是水对象。我们故意省略了该区域的导出。对于大多数游戏开发者来说,编写着色器代码是进入危险水域。然而,这正是我们将要做的。希望你能看到没有什么可怕之处。
创建水
你如何建模水体?答案并不简单,甚至有点哲学。以下是对李小龙关于武术的著名哲学引言的致敬,其中用水作为类比:
“… 要像水一样无形无状。”
你把水倒入一个杯子,它就变成了杯子。
你把水倒入瓶子,它就变成了瓶子。…”
在 Blender 或 Godot 中想象我们应该创建和组织哪些顶点来表示水是很困难的。相反,我们给简单的对象,如平面或立方体,赋予水的特性,如反射、折射、波动和混浊。
因此,对于这项工作,我们通常依赖于着色器而不是 3D 模型。在本节中,我们将编写一个非常简单的水面着色器。最后,你可以使用我们示例中的着色器,或者在网上找到另一个示例。毕竟,那里有很多示例,因为创建一个合适的水面着色器通常取决于你的使用情况,有时一个解决方案并不适合所有人。
让我们从创建一个水对象开始:
-
在Dock节点下放置一个MeshInstance节点,并将其重命名为Water。
-
对于这个新对象,在Inspector面板中将Mesh属性分配给PlaneMesh。
-
在Size中点击此处的
20以设置x和y。 -
20用于Subdivide Width和Subdivide Height。
我们很快就会解释这些数字的含义,但这里是你Inspector面板应该看起来像的:

图 9.13 – 目前看起来相当灰暗的水体
上一张截图显示了Inspector面板中PlaneMesh的属性。我们就水平面尺寸选择了合理的尺寸。使用与移动其他对象时相同的 gizmo,将水对象放置在码头区域和整个场景中合理的位置。一旦我们编写了使这个灰色对象看起来像水的着色器,你可能还想调整它的Y位置。
此外,也许巧合的是,我们选择了20作为细分值。如果你想将平面分成更细小的部分,也可以,但像 20 这样的值将引入足够的顶点。所以,是的,你已经在 Godot 中有效地创建了顶点,而不是在 Blender 中这样做。
现在,我们可以改变这个灰色平面的外观了。为此,我们将为它创建一个材质:
-
右键点击FileSystem中的
res://项,选择新建文件夹。 -
输入
Materials并确认。 -
右键点击FileSystem中的
Materials文件夹,选择新建资源。 -
搜索ShaderMaterial并确认。
-
在即将出现的另存为资源屏幕中将其保存为
Water.tres。
通常,新创建的项目将在FileSystem中的Water.tres中显示,并双击它。你会在检查器面板中看到一个带有白色球体预览的裸骨材质。它需要一个着色器来获得更类似水的视觉效果。以下是创建它的方法:
-
右键点击FileSystem中的
Materials文件夹,选择新建资源。 -
搜索Shader并确认。
-
在即将出现的另存为资源屏幕中将其保存为
cave-water.tres。
在第二章中,建筑材料和着色器,我们讨论了着色器和材料之间的关系,以及它们是如何相辅相成的。这是在 Blender 中完成的,但这个概念是通用的。因此,我们在 Godot 中创建了一个材料和着色器。现在,我们必须将这两个关联起来:
-
将
Water.tres文件的属性拖放到检查器面板中的着色器属性。 -
将
cave-water.tres拖放到检查器面板中的着色器属性。
水材质现在已被分配了一个空着色器。在您完成以下步骤之后,我们将解释着色器代码:
-
双击FileSystem中的
cave-water.tres。 -
在新展开的着色器面板中输入以下代码:
shader_type spatial; uniform sampler2D wave_pattern; uniform vec4 color:hint_color = vec4(0.19, 0.71, 0.82, 0.44); uniform float height_factor:hint_range(0,1.0) = 0.1; void vertex(){ vec4 wave = texture(wave_pattern, UV); float displacement = sin(VERTEX.x * wave.x * TIME) + cos(VERTEX.z * wave.z * TIME); VERTEX.y += displacement * height_factor; } void fragment(){ ALBEDO = color.rgb; ALPHA = color.a; }
我们编写的着色器代码向uniform语句公开了一些选项。这样,您可以修改材质的属性,就像您在修复叶子部分之前能够更改叶子材质的设置一样。那一个是带有许多选项的非常复杂的着色器。我们的着色器非常简单,只有三个参数:
-
用于创建随机性的波浪模式
-
水的颜色(默认为浅蓝色)
-
控制波浪运动的高度因子(默认为
0.1)
其中两个属性具有默认值。我们将在本节稍后向您展示您可以选择的波浪模式,但首先,让我们解释一下所有这些背后的基本思想,因为这可能是有你第一次编写着色器代码的时候。
内置的 Godot 着色器函数
这两个函数,vertex和fragment,是内置的着色器函数。前者控制每个顶点将执行什么操作,而后者负责整体对象的外观。Godot 提供了更多默认函数;我们在进一步阅读部分提供了一个链接,供您探索。
由于fragment函数看起来足够简单,我们将首先介绍它。我们公开的一个属性,color,将在这个函数中使用,这样我们就可以用我们想要的颜色来绘制对象。因此,我们将输入颜色的红色、绿色和蓝色通道应用到着色器的ALBEDO属性。Albedo 是一个科学术语,表示颜色。在某些应用中,它也被称为漫反射或基础颜色,例如在 Blender 中。
自然地,我们希望我们的水对象具有一些半透明特性。为此,我们使用输入颜色的 alpha 通道并将其绑定到着色器的ALPHA属性。这是一个简单但有效的方法来创建透明度。说到这一点,如果您注释掉vertex函数,您仍然应该能够看到透明度,因为每个函数都负责一个主要方面。然而,当它们一起使用时,它们会相互补充。所以,现在是vertex函数的轮到了。
如果水体的身体能够上下移动一点,那将很棒。这就是为什么我们通过细分平面网格引入了更多的顶点。vertex函数将取每个顶点并改变其y值以创建上下运动。函数中的最后一行负责这一点。每个顶点应该改变多少呢?嗯,这取决于您的用例。然而,我们找到了一个似乎合适且足够吸引人的displacement值,以模拟这个洞穴中某种平静的水景。
在计算displacement时,我们使用一个纹理并采样其一些值。这将给顶点的移动方式带来随机性。为此,我们将每个顶点的x和z值与传入纹理的x和z值(wave)相结合。您可以改变这些属性的一些组合,并仍然得到类似的结果。也许更重要的是使用内置的TIME属性,它告诉 GPU 随着每一毫秒的过去而改变结果。从方程中移除TIME,一切都将一次性位移并静止不动。
最后,我们通过在材质设置中可调整的高度因子来调节displacement的强度。这就完成了我们的水着色器。着色器和材质已经连接,但我们还没有告诉水节点它应该使用哪种材质。要这样做,请按照以下步骤操作:
-
在场景树中选择水节点。
-
在检查器面板中展开材质部分。您会看到一个标签为0的槽位。
-
将
Water.tres从文件系统拖动到0槽位。
哇!现在码头应该有一个随时间调节的水对象。移动并缩放你的视口相机靠近下沉的墙壁,以注意到 alpha 效果。这看起来已经很不错了,但我们可以通过应用噪声纹理来进一步改进,这将增加顶点波动的方式的更多变化:
-
在检查器面板中展开材质设置中的着色器参数部分。
-
为波浪模式属性附加一个新噪声纹理。
-
展开这个新纹理,并将其新 OpenSimplexNoise附加到其噪声属性。
这将增加顶点偏移的随机性。当你完成所有代码片段和调整后,你的检查器面板应该看起来如下:

图 9.14 – 注意水在下沉墙壁上的透明度和波浪状
可以调整噪声的值以创建更剧烈的效果,但我们留给你自己去尝试。通过控制高度因子和颜色,你可以模拟更平静或暴风雨的水条件。有了这个,你就创建了一个重要的缺失功能。
关于保持着色器独立
在创建水材质时,你可以在检查器面板的下拉菜单中使用内存着色器为材质设置。大多数 Godot 功能通常是这样开始并保持的,但我们采取了不同的方法,首先创建资源,然后稍后分配它。多亏了这种方法,你可以创建不同的水着色器,并在需要时进行交换。
这样,我们就处理了放置所有必要的元素,甚至完成了缺失的部分,例如修复和/或创建新材料。然而,在创建布局时,你是否感觉像是在重复和移动许多相同的资产,尤其是墙壁和地板部件?我们打赌你确实是这样!所以,让我们介绍一个非常有用的 Godot 工具,如果你是网格布局,你可以轻松地布置东西。
在网格上放置模型
放置诸如蜡烛、罐子、桶等物体,短道具,以及地板和墙壁部件之间的主要区别在于,你可以随意分布前者的物体。它们不需要遵循模式,而地板和墙壁部件必须相互对齐。这种结构也被称为网格。
为了加快速度,我们甚至选择复制一个现有的部件而不是实例化一个新的,因为当你创建一个新的实例时,它将从场景原点开始,你需要将这个新部件移动到你的当前区域附近。你甚至可以一行选择多个瓦片,复制它们,并将这些瓦片紧挨着旧的一批。尽管有所有这些快捷方式,但由于这一切听起来都很公式化,可能需要一个更好的工具。网格图来拯救!
如果你已经使用过 Godot 进行 2D 开发,你可能已经熟悉了TileMap节点。GridMap与它相同,只是它在 3D 中工作。因此,TileMap将允许你在场景中添加精灵,而GridMap将使用网格。对于那些从未使用过TileMap节点的人来说,Godot 中的这两种机制都负责使用一组瓦片或网格。
手动放置的优点
我们提供的GridMap解决方案不仅是为了让你加快创建关卡的速度。由于这些部件是重复的,GPU 将优化这些部件的渲染,你将获得更高的帧率。这对于游戏开发者来说通常是一个非常受欢迎的结果,尤其是当你的关卡变大,场景中使用的对象数量开始变得重要时。
在本节中,我们将介绍GridMap节点的通用设置。尽管这个节点依赖于MeshLibrary来完成其工作,但在这一点上理解个别设置比混合两者更有意义。我们将在利用网格库的优势部分学习如何创建和利用MeshLibrary。
为了保存和比较我们到目前为止所做的工作,我们将放慢一些:
-
将
Level-01.tscn保存为Level-01-Gridmap.tscn。根节点仍然可以是Level-01。 -
添加一个GridMap节点,并将其重命名为FloorGridMap。如果你愿意,你可以拖动这个新节点,使其成为Floor节点之上的第一个子节点。
-
通过按眼睛图标关闭Floor节点。
最后的几个动作将向场景中引入一个GridMap节点。目前它是空的,但当我们了解网格库时,我们会用地板块填充它。你的场景将如下所示:

图 9.15 – 将很快通过 GridMap 引入缺失的地板块
尽管我们缺少网格库,但我们有一个GridMap节点,我们可以查看检查器面板中的属性。我们建议你现在选择FloorGridMap并继续阅读。我们将在这里提供的信息将为你在未来选择网格设置奠定基础。
对于一个单元,全部为2。幸运的是,我们的地板块是 2 x 2 x 2 米。所以,在我们的情况下,我们不需要更改这些值。在你的未来项目中,你可能需要将这些值与你的模型尺寸相匹配。
我们将忽略八分位大小设置,因为它是为更高级的情况而设,在那里你可以进一步增加优化。也许更重要的是,三个用于在任意轴上对齐单元内网格的中心的开/关开关。我们很快就会使用这个功能,但下面的截图应该能帮助你看到我们到目前为止所讨论的内容:

图 9.16 – 每个网格地图都可以有设置来定义它将使用的块尺寸
前面的截图还显示了当你点击视口顶部的网格地图按钮时展开的菜单及其选项。在这些选项中,带有S快捷键的光标旋转 Y可能是你使用最多的一个。我们在创建洞穴部分之前放置的地板块都遵循相同的方向。我们试图用道具覆盖地板以打破单调,但将地板块绕Y轴旋转 180 度将是一个另一种解决方案。
现在理论知识已经建立,让我们继续探讨使用网格地图的实际应用。在下一节中,我们将创建一个网格库,我们将与我们的地板网格地图一起使用,以填补缺失的地板块。
利用 MeshLibrary
当你点击地板网格地图以调查其属性时,Godot 界面略有变化,并通知你应该分配一个网格库,因为没有它,网格地图将无效。在本节中,我们将向您展示创建网格库的过程。我们还将讨论你可能会面临的挑战,不是技术上的,而是工作流程上的。
创建网格库有两种方式。我们将向您展示最常见的方法,因为另一种方法涉及在文件系统中单独保存网格,而我们的项目尚未设置以适应这种情况。无需多言,这就是创建网格库的方法:
-
开始一个新的场景,并将其保存为
Floor-MeshLibrary.tscn在Miscellaneous中。 -
选择一个空间节点作为其根节点。
-
在场景面板下空间节点下实例化Floor_Standard。
-
点击 Godot 顶部菜单中的场景按钮。
-
展开转换为并选择网格库。
-
将你的网格库保存为
Floor-MeshLibrary.tres在Miscellaneous中。
如果你直接将地板块拖放到视口中,它会根据你的鼠标光标位置在场景中的某个地方放置。例如,地板可能看起来很小,因为它离你很远。将位置归零应该将对象放置在世界的中心,并使其更靠近。如果你将块拖放到场景树中,就不会有这个问题。
以下截图显示了 Godot 将你的场景转换为网格库之前的那个状态:

图 9.17 – 我们正在将场景转换为网格库
现在我们已经在库中有一个地板块了,我们可以再添加一个模型。这里的目的是堆放具有相似尺寸的物品。这可能听起来有些令人困惑,但让我们添加弯曲的墙壁。为什么?因为尽管墙壁通常更薄更高,但如果你考虑弯曲墙壁占据的体积,它与地板块并无不同。它的底部尺寸相似。
假设Floor-MeshLibrary.tscn仍然打开,以下是您如何向同一库中引入另一个模型的方法:
-
在文件系统中找到
Curve.glb墙壁块。 -
将它拖放到Spatial上。
-
将你的场景转换为
Miscellaneous。
这个操作将在新引入的块和旧的地面块旁边添加,并更新网格库。因此,创建网格库的一个简单方法是从一个新的场景开始,添加尽可能多的模型,然后将这个充满模型的场景变成一个网格库。
我们还没有关心这些块将去哪里。我们只是选择单独的块作为装饰网格的候选者。现在,让我们将网格库与FloorGridMap关联起来,并开始摆放一些模型。
使用网格地图的网格库
到目前为止,我们一直在准备一个用于FloorGridMap的网格库。这个库里面有两大块。我们将首先使用地板块,然后看看是否使用曲线块也合理。
要使网格地图工作,您需要在检查器面板中填写其网格库属性。让我们先处理这个问题:
-
在场景树中选择FloorGridMap。
-
将
Floor-MeshLibrary.tres从Miscellaneous拖放到检查器面板中的相关字段。
这将显示所有可用的模型作为缩略图在预留的网格地图界面中,如图所示:

图 9.18 – 网格库现在可以被 FloorGridMap 使用
剩下的只是点击其中一个缩略图——例如,Floor_Standard——并将鼠标移到视图中。你应该会在光标下看到所选模型的预览。如果你点击可以看到预览的地方,它就会变成永久。试几次。
这难道不是比你自己摆放所有地板瓷砖要容易得多吗?但是等等——你很可能会注意到有些地方看起来有点奇怪。好像地板块并没有放在它们应该放的位置。它们相互吸附,但似乎并不完全尊重旧的坐标。它们要么升高,穿透墙壁块,要么位于墙壁之外。
这是在你使用网格地图时经常会遇到的事情。解决方案很简单,但请记住,这实际上也不是一个问题。这取决于你为你的模型设置的起点。所以,是的,起点是你可能即使导出模型后也要处理的事情。因此,你可以通过回到 Blender 内部重新导出你的模型来修复你的起点,或者使用检查器面板中提供的选项之一。
目前,让我们尝试切换以下单元格设置:
-
中心 X
-
中心 Y
-
中心 Z
没有固定的公式来确定这些属性是否应该开启或关闭。这取决于在网格库中使用的模型。例如,网格库中的曲线部件其原点位于一个角落,而地板部件从几何学上讲位于中间。由于整个网格地图只有一个单元格设置,你必须有一种标准的方式来处理网格库中的所有模型。因此,这不仅仅是随意堆放一堆模型——这是关于以尊重单元格的方式存储它们,从而形成一个网格结构。
为了可视化我们正在讨论的内容,你可以尝试将网格库中的曲线部件放置到场景中。你会注意到你需要重置中心设置,但这也会将地板部件重置到有争议的位置。因此,这是你必须计划并确保你的对象具有相似的原点以及相似尺寸的事情。
清除单元格
你已经知道点击鼠标左键会将网格库中预览的物品放置到场景中。如果你需要从场景中移除现有的单元格,你可以右键单击它并移动鼠标。如果你恰好有相同的模型处于预览模式,从场景中移除单元格但不在其他地方移动光标可能会给人一种你没有移除任何东西的印象。所以,记得在清除单元格后晃动鼠标。
使用多个网格地图的必要性
要么是因为你的模型尺寸不同,要么是因为原点不一定对齐,你最终会注意到你需要在场景中使用不同的网格地图。由于每个网格地图都可以有独立的单元格设置,完全有可能在这些网格地图之间使用相同的网格库。
在这种情况下,你将方便地创建一个网格库来存储所有类似的项目——例如,所有建筑元素——但只为正确的网格地图使用一些模型。这比创建单独的网格库要省力得多。
总结
使用网格地图是将遵循模式的对象进行分布的一种便捷方式。决定使用它有时是一个自然的过程。大多数人通常从单独放置物品开始构建他们的关卡。这通常是在他们没有使用现成的关卡设计软件时。因此,创建关卡的过程是在你以自然的方式移动物品的同时发生的,类似于移动家具而不是使用平面规划器。
因此,无论是您早期决定还是感到需要切换到它,使用网格图将使您的生活更轻松。话虽如此,当前版本的 Godot 中网格图和网格库充满了错误。例如,将新模型添加到您的网格库场景中,然后将其导出为库,并不总是更新现有库中的新模型。有时,库中的早期项目会被较新的模型所替换。因此,它相当不一致。希望 Godot 的第四版将消除所有这些问题。
我们希望全面介绍创建关卡的不同方法。因此,介绍GridMap节点似乎是必要的,尽管它可能存在缺陷。这样,当社区在未来实现这个工具且无错误时,您就会知道这样一个方便的工具将会可用且有用。
摘要
本章是众多帮助您构建游戏章节中的第一章。为了开始,我们处理了游戏的设计层面。
这个努力包括放置构成克拉拉将体验的环境的许多元素。对于相邻的结构,您学习了如何利用吸附功能,但您也可以在分配道具的情况下无忧地装饰场景。最终,您拥有了一个干净的场景结构,对象被分组在场景树下的相关节点下。
在这个过程中,您注意到一些材质要么配置不当,要么根本缺失。为了修复这些问题,您不得不深入到检查器设置中的材质,以解决透明度问题。此外,您在 Godot 中编写了一个着色器来模拟水体。
考虑到您迄今为止所学的内容以及您可能设计更多具有网格图案的关卡的可能性,我们介绍了 Godot 的GridMap节点。为了能够使用这个方便的工具,您还学习了如何创建MeshLibrary。尽管这种方法有其好处,但目前它是损坏的,但这是您可以在 Godot 的未来版本中使用的。
因此,关卡已经完成到您可以开始添加一些元素的程度,随着您的需要逐步添加。尽管如此,一切看起来有点单调。在下一章中,我们将学习如何通过灯光、阴影和环境效果使关卡看起来更华丽。
进一步阅读
游戏关卡设计并不总是涉及在游戏世界中放置物理元素。有时,它意味着吸引人的音效设计,隐藏与世界观和故事相关的可爱或有意思的背景元素,以及添加玩家可以与之建立联系或简单地讨厌的非玩家角色。设计良好的关卡有一个完整的心理因素层面,以便你可以唤起你希望玩家感受到的情感。如果你想在这个领域提升你的知识,你将不得不检查那些不一定与游戏引擎相关的资源。所以,拓宽你的视野!以下是一些可以帮助你开始的资源:
在本章中,你需要编写一个水面着色器。与着色器一起工作通常被描述为游戏开发者中最不有趣或最令人困惑的经历之一。我们将提供两个链接,以便你熟悉这个主题。前者是官方 Godot 文档,它应该有助于你在项目中产生更直接的结果,而后者应该对长期需求更有用:
第十一章:使用光线和阴影使事物看起来更好
我们有一个简单且看起来干净的水平设计,但它需要一次好的改造。例如,墙上的壁灯和地板上的蜡烛只是静静地放在那里,并没有为场景增添太多兴趣。此外,这个关卡作为一个地下环境确实存在一些问题,因为这是一个洞穴。我们必须找到一种方法来模拟外部光线,因为克拉拉划船进来了。总的来说,我们将让关卡的光线足够亮,以便玩家能够感知到事物。
在本章中,我们将向我们的工作流程中引入光线和阴影,以便我们的场景看起来更具视觉吸引力。我们之前在第四章中介绍了光线,调整相机和光线,但那时是在 Blender 的上下文中进行的。虽然通用概念仍然适用,但这次我们将有机会从游戏开发的角度来做事情,而不是在 Blender 中制作艺术渲染。
在 Godot 中,阴影不是自动可用的。因此,我们将向您展示如何打开阴影并发现一些平衡质量和性能的阴影设置。除了放置光源、启用阴影以及调整它们的品质外,我们还将介绍一个更高级的概念,创建一个WorldEnvironment。这也被称为后期处理,并且是改善你的场景外观和感觉的强大工具。
尽管我们将通过添加我们列出的主题来逐步改进关卡,但为了将这些内容串联起来,我们还将处理一个相对高级的主题,全局照明,这将给场景增添真实感。
在我们创建一个看起来漂亮的关卡之前,还有很多步骤要走。在本章中,我们将涵盖以下主题:
-
添加不同类型的光线
-
启用和调整阴影
-
创建后期处理效果
-
使用全局照明
尽管本章的目的是理解照明系统的工作原理,但我们在旅途中也会介绍一些互补的 Godot 主题。
到本章结束时,你将能够利用光线并启用阴影,以及利用全局照明和后期处理效果来进一步增强关卡的氛围。
技术要求
我们将从我们上次停止的地方继续添加和更改内容。此时你有两个选择——你可以继续使用上一章中的副本工作,或者使用第九章中提到的Finish文件夹,设计关卡,该文件夹可在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Game-Development-with-Blender-and-Godot。
添加不同类型的光线
在 第四章 调整相机和灯光 中,我们讨论了不同类型的灯光如何工作——更重要的是,它们对场景带来的效果。在本章中,我们将重新探讨这个主题,但将在 Godot 的背景下进行。
Blender 使用四种灯光类型:Sun、Point、Spot 和 Area。然而,Godot 只有三种灯光,如下所示:
- DirectionalLight:这是 Blender 中 Sun 灯光的等效物。我们在 Sun 灯光的描述中提到了方向性。这种灯光类型的角度是最重要的,因为它是一个无限远的灯光源,所以所有光线都被认为是相互平行的。因此,在 Godot 中,这个概念是节点名称的一部分,这使得它更容易记住。
我们不会在我们的场景中使用这种类型的灯光,因为这是一个室内环境。尽管如此,仍然可能有人想利用它来提供一个整体的光效果,但这个光源会淹没整个场景。我们需要其他可以调整的东西。因此,我们将专注于其他两种灯光类型。
-
OmniLight:这是 Blender 中的 Point 灯光。灯泡,是的,墙上的壁灯,是这种灯光类型适用的正确对象。提醒一下,omni 意味着在所有方向上。
-
SpotLight:这个很容易理解——它是 Blender 中的 Spot 灯光。它适用于模拟汽车灯光、手电筒和其他具有束状特性的光源。我们将使用这种灯光来模拟从洞穴外渗透进来的外部光线。
那么,Godot 中的 Area 灯光在哪里?它根本不存在。Godot 中有不同机制可以用来模拟 Blender 中 Area 灯光的效果。通常,这种灯光是用来模拟从窗户透进来的光线,可以用发射材料来模拟。
谈到使用不同类型的灯光,让我们先点亮那些蜡烛。
点蜡烛
对于这类练习,OmniLight 类型是正确的选择,但我们应该有多少个呢?如果你仔细观察蜡烛模型,你会发现这个模型由多个蜡烛组成;有的短,有的高。在每个烛芯上方放置一个 OmniLight 是否合理?这是完全可能的,但也是一个艺术上的决定,我们留给您来决定。
在我们的案例中,我们假设从该物体发出的整体光线可以简化为蜡烛烛芯上的一个点。因此,为整个模型放置一个 OmniLight 是完全可行的。现在是时候展示如何做到这一点了:
-
双击
Candles_1.glb文件,它在Candles_1.tscn文件中,位于其原始文件夹 (Models/Candles/) 中。 -
将一个 OmniLight 添加到 场景 树中。
-
调整它的
0.8,例如,使其略微高于烛芯。
这将在你的蜡烛场景中放置一个点光源。目前,使用默认设置,很难看到其影响。如果你靠近灯光对象并调整你的摄像机角度,以便你不再看到地平线和天空,你可以获得更好的视角。也许你可以通过在场景树中打开和关闭OmniLight的可见性来看到灯光的贡献。
我们将保留d6d58e中的大部分设置。这可以在检查器面板的灯光部分找到。结果如下:

图 10.1 – 一个黄色光束直接照在蜡烛上的 OmniLight
让我们花点时间讨论一下,为什么我们给一个由模型构建的场景添加了灯光,而不是直接将其添加到关卡场景中。毕竟,我们已经有几个空间节点来持有类似的项目,比如墙壁、柱子等等。我们可以创建一个名为灯光的空间节点,并将一串OmniLight节点放在那里。
通过将灯光节点引入模型场景而不是主关卡,你可以在其他场景中利用这个蜡烛场景。因此,你不必创建更多的灯光对象并将它们放置在关卡中的所有蜡烛上。当你装饰关卡时,需要蜡烛时,它们将作为一个全服务套餐出现。
克服锯齿边缘
在向场景添加灯光后,你可能注意到一些物体在边缘看起来是锯齿状的,因为细节更加突出。为了消除这一点,你可以打开抗锯齿设置。硬边缘将被平滑,物体将更加无缝地融合,一切看起来都会更加顺眼。要启用它,将Msaa值设置为2x。此设置可以在项目设置的渲染部分的质量子部分下找到。
到目前为止,一切顺利,但灯光会一直亮着吗?目前看来是这样的。让我们看看我们如何通过引入一个可以关闭灯光的机制来完成蜡烛的全服务功能。为此,我们需要添加一个简短的脚本:
-
右键单击根节点(Candle_1)并选择附加脚本。
-
在即将出现的弹出窗口中保持一切不变,但更改路径,使其显示为
/Models/Candles/Candles.gd。 -
你的脚本文件应包含以下代码行:
extends Spatial export(bool) var is_lit = false setget switch func switch(condition): is_lit = condition func _process(_delta: float) -> void: $OmniLight.visible = is_lit
这将为is_lit创建一个切换状态,使灯光再次可见。
要在不运行游戏的情况下测试这一点,你可以在脚本开头添加tool关键字,并在你仍在制作关卡时实时查看你的更改。观察在检查器面板中is_lit中光线的可见性如何变化。
我们还有一个可以从中受益的蜡烛模型,Candles_2.glb。而不是从头开始,我们建议你这样做:
-
在Candles_1场景中,右键单击场景树中的OmniLight节点,并选择复制。
-
将
Candles_2.glb创建为一个场景,并将其保存在原始文件夹中。 -
右键单击此新场景的根节点,并选择粘贴。
-
选择根节点,并在检查器面板中将
Candles.gd附加到脚本属性。
这将最小化你需要执行的步骤数量来添加一个OmniLight,定位它,然后编写几乎相同的脚本来控制它。在这里,我们使用相同的脚本为两个场景,因为场景中的节点引用是相同的。在我们最近的更改之后,Godot 编辑器将如下所示:

图 10.2 – 使用相同脚本实现开关功能的新的蜡烛场景
尽管我们一直在研究一种通过将灯光对象附加到蜡烛模型来添加灯光的智能方法,但我们并没有对关卡本身进行任何更改。我们将在下一节讨论这个问题,并分享一些关于你可以在未来的项目中进行的流程改进。
将蜡烛引入关卡
在第九章,“设计关卡”,我们指导您直接将 glTF 文件实例化到关卡中,这样保持了文件系统的整洁,没有创建冗余的.tscn文件。否则,你将有一个与模型无关的每个模型一个场景文件。仅添加模型到场景的简单工作流程通常就足够了,尤其是在你不知道项目将走向何方的情况下。
另一方面,在某些情况下,例如你有蜡烛和烛台时,你很可能在MeshInstance节点旁边有一个灯光节点,以及一个脚本来控制灯光的行为。在这种情况下,将模型转换为场景并从那里构建是值得的。
关卡的场景树仍然包含原始的蜡烛模型。在第九章,“设计关卡”,我们使用了两种类型的蜡烛,但总共三个模型来装饰关卡。从关卡中移除这些模型以便实例化新的蜡烛场景是完全可行的。不过,你将不得不重新定位这些新项目。因此,我们将遵循不同的路径以保持位置信息:
-
在场景树中选择Candles_1。
-
实例化
Candles_1.tscn,这将产生一个嵌套节点。 -
将嵌套节点从其父节点中拖出,并使其成为其父节点的同级节点。
通过将蜡烛场景嵌套在旧模型实例中,我们正在占用位置。如果你直接将蜡烛场景添加到道具节点中,你将不得不找到模型实例的位置并将其应用到新项目上。
你可以为其他两支蜡烛重复此过程,这将最终使这一层可见蜡烛的数量翻倍。也就是说,我们最初的三个蜡烛模型实例不再必要,因此你可以删除它们。同时,注意当你将蜡烛场景与仅保留模型本身相比时,脚本图标会出现在场景树中。以下截图显示了结果:

图 10.3 – 场景树中的新蜡烛有脚本图标
上一张截图不仅展示了添加到这一层的更先进的蜡烛,还展示了你可以通过检查器面板中的Is Lit属性来打开和关闭这些蜡烛。类似于你对蜡烛所做的那样,你可以通过创建一个由壁灯模型组成的场景来继续练习点光源。在这种情况下,光对象在场景中的位置可能更高,因为模型更高,但概念是相同的。你甚至可以将相同的脚本绑定到这个壁灯场景的根节点。
这确实造成了一些困境。到目前为止,我们一直将所有与蜡烛相关的文件保存在自己的文件夹中,包括脚本。然而,灯光开关脚本非常通用,可以用于任何具有类似结构的场景。尽管也可以将Candles.gd脚本附加到Candles文件夹中的不同文件夹中的场景,但如果你想通用化,可以将脚本文件移动到项目根目录下的单独Scripts文件夹。
这是你将面临众多项目管理难题中的一个,因此如何处理取决于你自己的选择。我们决定尽可能保持内容的通用性。因此,本章的Finish文件夹将让蜡烛和壁灯共享来自Scripts文件夹的光照脚本。
在将壁灯模型与壁灯场景交换后,这一层将更具特色,如下所示:

图 10.4 – 三支蜡烛和四盏壁灯照亮了这一层
我们已经覆盖了基本灯光,但仍然没有那种你可能在内陆洞穴中看到的灯光效果。想法是克拉拉通过一个开口进入这个结构,因此让一些阳光进入这个区域是有意义的。我们将通过使用聚光灯节点来实现这一点。
模仿阳光
在我们的游戏中,故事情节是克拉拉系船的码头区域离入口不远。因此,从外部获取一些阳光是有意义的。实现这种效果的一种简单方法是使用聚光灯节点。让我们也讨论一个替代方案。
使用DirectionalLight节点一开始看起来很有吸引力,但那样会照亮整个场景。我们也希望这个洞穴看起来尽可能黑暗,并且只使用蜡烛和壁灯等人工光源进行照明。为了实现这两个目标,您需要在水平面上放置平面,假装它们是洞穴的天花板,以阻挡大部分光线。因此,由于这种努力感觉是适得其反的,我们将尝试照亮我们需要的,而不是阻挡光线。
因此,使用SpotLight节点似乎是我们目前最好的选择。我们将描述我们用来在水平面上放置灯光的过程,以便突出显示船和码头的一部分。下面是:
-
选择水平面的根节点(Level-01)。
-
添加一个SpotLight节点,并将其在Y方向上定位在船上方大约七个单位的位置。
-
在X和Y方向上将其旋转-70度(提示:在检查器面板下的变换中使用旋转度数)。
-
将其颜色更改为
d6d58e。 -
展开
20。 -
设置
55。
我们将在解释光线放置意图后立即为您提供截图,并对截图本身进行免责声明。由于您 Godot 项目中的默认环境设置会导致场景过于明亮,以至于您无法看到您所做事情的影响,我们临时调整了一些设置,以便更好地突出您正在使用的灯光的贡献。
我们将在探索完灯光和阴影之后,在创建后处理效果部分研究环境效果。目前,我们仍需要向您解释SpotLight节点的设置。即使您遵循了类似的布局,您为地板瓷砖选择的坐标可能差异如此之大,以至于没有简单的方法要求您将灯光放置在特定位置。因此,我们为您提供了一般和精确方向的混合。这是我们到目前为止所得到的:

图 10.5 – 模拟洞穴中太阳的 SpotLight 节点
前面的截图显示了SpotLight节点正好位于船的后面。我们选择了从上到下的视角,以便您可以看到光线从这个物体发出的距离。您在检查器面板中设置的范围和角度属性将配置这个光源,使其足够远和足够宽,以部分照亮入口。因此,如果您的布局需要不同的值,以便您有一个仅足够照亮的区域,如参考图片所示,您可能需要更改旋转和位置值。
如果您喜欢,您可以创建另一个SpotLight节点,并更改其值,就像岩石结构中有一个次要开口,让更多的光线透过来一样。一旦您弄清楚技术部分,剩下的就是您根据个人喜好来推动艺术效果,以达到您满意的结果。
到目前为止,我们一直在分析不同类型的光及其对我们关卡的影响。通常,我们期望有阴影。这些默认情况下是未启用的,所以我们将发现如何启用它们,以及如何在项目上下文中调整一些设置。
启用和调整阴影
在某些情况下,例如在舞台艺术中,工程师们会努力通过从多个角度投射光线来照亮舞台的某些部分,以消除阴影。这是一个极端的例子。通常,阴影是当存在附近光源时自然发生的事情。
尽管这是一种自然现象,但在计算机模拟中,仅仅因为存在光源,模拟阴影并不会自动发生。GPU 需要知道光源来自何方以及其强度如何。因此,它可以从光源照射到的物体底部开始创建一个区域,并通过将其与物体所在的表面混合,逐渐将这个区域向光源相反的方向拉伸。这就是计算机计算和模拟阴影的大致方法。
在 Godot 引擎中,光源负责其自身的阴影。这意味着阴影设置是光对象的一部分,但由于这种努力资源密集,Godot 默认将其关闭。让我们来看一个例子,看看我们如何启用它:
-
双击 FileSystem 中的
Candles_1.tscn项以打开它。 -
在 Inspector 面板中,选择 OmniLight 节点并展开其 Shadow 部分。
-
打开 Enabled 属性。
在这一点上,阴影的颜色无关紧要,但它可能是你可以在项目中调整以获得所需戏剧效果的东西。在此阶段,我们建议你打开 Candles_2 和 Sconce 场景,为它们拥有的 OmniLight 节点启用阴影。当你保存这三个文件并返回到 Level-01 场景时,你应该看到以下类似的内容:

图 10.6 – 有阴影,就有阴影
注意启用阴影如何全面提升体验。柱子、板条箱和其他物体开始栩栩如生。然而,这幅画中还有一个大问题:我们还没有启用用于模拟太阳效果的 OmniLight 节点的阴影。继续将其阴影打开;你将看到码头如这里所示般突出:

图 10.7 – 由于阳光的阴影效果,码头和船只看起来更加逼真
我们正在逐渐提高关卡的画面质量。我们上次的努力引入了阴影。它们很棒,但有时它们也会产生一些缺陷。现在,让我们谈谈你可以在检查器面板中灯光节点的阴影部分找到的一些设置:
- 偏差:你在游戏开发中遇到的一些名字听起来很技术性,并不总是能迅速让你了解它们控制的是什么。这个确实听起来像是那种。简单来说,这个属性控制阴影相对于物体体积的起始位置。一图胜千言,所以请参考以下图表,看看不同的偏差值会导致什么效果:

图 10.8 – 不同的偏差值及其效果
- 联系:当你有一个高的偏差值时,它会在阴影和物体之间(如图所示)产生一个间隙,这个属性将尝试填补这个间隙。
因此,如果你因为启用阴影而出现视觉故障,这可能导致阴影不总是与物体相遇或产生自阴影问题,如图所示,我们建议你探索使用偏差和联系属性组合来调整你的灯光。
这个关卡开始看起来更有生机,多亏了灯光和阴影。然而,一切看起来还是有点太亮。如果我们能降低整体亮度就好了……我们当然可以,这就是我们接下来要探讨的。
创建后期处理效果
由于我们假设克拉拉正在参观一个过去有人类活动、建造了码头并在墙上挂上了壁灯的山洞,因此预期其中一些区域非常黑暗是很正常的。我们一直在放置灯光并打开阴影来提高场景的视觉保真度,但我们正在与环境作斗争;它太亮了。
在本节中,我们将研究一个有趣的 Godot 节点,它将控制环境或世界设置,以便你更好地掌握你的世界看起来如何。这种过程也被称为后期处理,因为它的效果是在直接放置的元素(如灯光、阴影、反射等)处理之后应用的。它附带了很多设置,希望我们在探索之后会变得更清晰。
一切皆节点
如果您来自 Unity,那么 Godot 使用的节点系统可能会让您感到困惑。在 Unity 中,您将脚本附加到游戏对象上以添加或控制系统的行为。节点在 Unity 中类似于脚本,但节点更加实用,因为您还可以将脚本附加到 Godot 节点上。这很方便,因为您可以将节点嵌套并组成更大的节点结构。在 Godot 中,您很可能会找到一个将执行关键任务的节点。我们本章讨论的节点就是这样一种节点。您还可以在docs.godotengine.org/en/3.4/getting_started/introduction/的Godot 的设计哲学部分找到更多关于使用节点的过程。
Godot 有一个巧妙的节点,WorldEnvironment,它负责您场景中的整体氛围。尽管节点的名称很奇特,但将其引入关卡与添加其他节点并无不同:
-
打开
Level-01.tscn场景。 -
从FileSystem添加一个
default_env.tres。 -
双击FileSystem中的
default_env.tres,以将它的属性填充到Inspector面板中。
很可能没有任何变化,但我们已经有效地创建了一个WorldEnvironment节点,并将其与一个环境资源关联起来。当您创建一个新的 Godot 项目时,它附带了一个默认的环境资源。我们不是创建一个新的资源,而是在整个项目文件夹中重新利用了默认环境资源。
这为您打开了不同的可能性。您的游戏可能包含不同的关卡,您希望视觉线索支持特定关卡的特性。在这种情况下,您的项目文件夹可以存储多个环境资源,并在WorldEnvironment节点中相应地使用它们。
虽然根据其名称,WorldEnvironment节点的用途可能听起来不言而喻,但要充分利用它,最好是练习使用其属性。您可以通过查看它所使用的资源属性来完成此操作。属性有很多,我们将发现与我们目标相关的那些。
背景
环境设置的这个部分负责模拟背景。目前,模式设置为Sky,因此背景被描绘成似乎有一个足够远的暗色地面部分延伸到天空。在这种模式下,您可以进一步自定义您想要描绘的天空属性。我们不会涉及这一点,因为我们正在处理一个室内场景。
因此,首先将模式更改为Custom Color。这将默认选择黑色,因此您场景的整个背景将变成漆黑一片。这无疑会突出蜡烛和壁灯。
如果你希望使用 Godot 引擎来捕捉模型的游戏渲染效果,那么你可以将背景设置为清除颜色,这将创建一个透明颜色。在我们的案例中我们没有使用它,因为一个完全黑暗的背景更适合我们的艺术需求,而且水体下面有透明度看起来有点不自然。我们可能需要在其下方再放置一个同样大小的暗色平面来隐藏透明度的影响。
因此,我们将坚持使用自定义背景颜色。这将导致以下输出:

图 10.9 – 洞穴开始看起来更加阴森
在我们继续讨论色调映射之前,先简单讨论一下环境光部分。拱门似乎现在被隐藏了,因为场景中光线不足。因此,为了解决这个问题,你可以选择一个更亮的背景色。然而,这会使整个场景再次变亮,并且一些暗区会变得更亮。有一种更明智的方法可以保持暗区仍然暗,同时让光源的效果更广泛地扩散。我们将在使用全局照明部分稍后探讨如何实现这种两全其美的效果。
色调映射
这是一种你可以用来快速将光线融合到暗区中的解决方案,这将使一切看起来更加均匀。它自带一些属性:
-
模式:默认模式是线性,这就是你一直以来的体验。我们把它留给你自己决定,但我们建议你将其更改为电影或ACES Fitted。它将整个场景的色调重新映射,以至于事物开始看起来更加逼真。
-
曝光:与线性模式相比,其他模式可能会使你的场景看起来非常暗。调整曝光将使场景变亮,同时仍然应用色调映射。
-
白色:数码相机有一个与此类似的设置。你指定一个色调为白色,这样其他颜色就可以根据这个新的基线来计算。较小的值会导致整个场景过曝,因为它会开始考虑更多的颜色作为白色。自然地,较高的值会排除更多的颜色,使场景变暗。
在我们的练习中,我们不会调整曝光和白色值,但这是我们在选择色调映射的ACES Fitted后得到的结果:

图 10.10 – 由于色调映射,一切看起来更加突出
由于我们已经提到了曝光的概念,简单地说一下启用自动曝光。我们不会在我们的工作中使用它,但它是一个有助于减轻你在相机在室内和室外区域之间切换时可能遇到的一些问题的有用选项。
屏幕空间反射(SSR)
当一些物体由于材质设置而具有反射特性时,例如金属、镜面反射和粗糙度,开启这个环境设置将创建更真实的效果。
要欣赏SSR的影响,关卡必须有更多的光源,所以当你开启它时,可能看起来变化不大。雕像的主体有一个反射材质。因此,如果你放大那个区域,你应该能够看到一些反射,在鹿的脚与底座接触的地方。
当附近有更多光源时,反射将会更加明显。当我们处理第十二章,通过摄像机和角色控制器与世界交互,以及 Clara 手持火炬走过雕像时,你可能会注意到这个效果甚至更好。在此之前,我们只需简单地启用这个功能即可。
环境遮挡(SSAO)
这并不是我们第一次遇到这个术语。我们第一次了解到它是在第四章,调整摄像机和灯光,当我们想要强调物体连接处的边缘时。同样,我们也会在 Godot 中开启这个设置,但我们需要调整一些属性:
1.0。
在我们当前的情况下,这似乎就像是一个开/关的开关。然而,由于它的值可以在 0.0 和 1.0 之间任意取值,你可以通过脚本控制这个值,从而将其作为一个有用的刻度。这在你不希望完全关闭遮挡,而是逐渐减少遮挡的情况下是有效的。
- 我们将值设为
0.4,但你可以根据你的喜好设置任何值。
此外,强度属性可以与半径一起使用,以创建更精确的遮挡。此外,借助一组额外的半径和强度,你可以叠加更多细节。
就像游戏开发中的大多数事情一样,调整正确数量的环境遮挡通常是一项艺术性的工作。使用建议的值,结果将如下:

图 10.11 – 环境遮挡开启后的关卡
前面的截图可能并没有充分展示我们所达到的效果。然而,如果你比较前两个截图,你可以看到砖块之间的遮挡,以及板条箱与地面接触的地方。
发光
这个特性在其他应用中通常被称为光晕效果。它用于夸大颜色和光源的效果。虽然它有许多属性,但我们只关注其中几个:
-
0.2的值足以突出壁炉和蜡烛的效果。本质上,虽然暗区将保持相对较暗,但发光区域将会发光。 -
混合模式:为了进一步增强影响,我们建议您将其设置为叠加。由于光源是开放的火焰,这将给场景中的灯光带来非常舒适的温馨效果。
我们不会触及其余的设置。以下截图显示了级别的最终状态:

图 10.12 – 我们的灯光在黑暗中发光
在发光设置中,有一个名为级别的特定部分。你可以展开该区域,并决定光晕和模糊效果会向外扩散多远。当你想要调整包围物体的光晕细节时,这很有用。
调整
在应用不同的环境效果时,一些特性可能会相互竞争。尽管我们为灯光增加了更多的力量,并为模型提供了更清晰的轮廓和阴影,但过了一段时间后,你可能会得到一个看起来有点褪色的场景。你将使用调整功能中的两个属性,这将给你的场景增添不错的触感:
-
1.1或1.2可能就足够了。 -
1.1将与更高的亮度一起使事物看起来更好。
我们可以永远改变这些设置中的许多设置。根据你的品味,你可能更喜欢不同的效果。然而,我们对目前所拥有的效果感到满意。
总结
自从我们开始铺设地板和墙面部件以来,我们级别的外观发生了巨大变化。看起来普通的砖面现在有了特色,由于灯光、阴影,最终是环境设置,场景看起来更加阴森。这在此处可以看到:

图 10.13 – 后处理效果都已就位并协同工作
根据你为游戏想要营造的氛围,你可以想出不同的后处理效果组合。此外,你可以在游戏会话期间编程调整它们的值,以进一步吸引玩家。
当一个太多时
后处理效果很棒。你可能会觉得自己像在糖果店里的孩子。然而,请记住,一些效果会相互增强,而另一些则会相互超越。最终,你可能会发现太多的效果在运行,这会给你的电脑带来负担。当你的 GPU 激烈地试图冷却时,你就能听到它的代价。
尽管我们努力改善我们级别的外观,但仍有改进的空间。虽然我们明显增强了暗部和亮部区域,但场景仍然缺少行业常说的另一个现实生活质量,即全局照明。
使用全局照明
如果你曾经使用过数码相机,你可能已经熟悉我们将在本节中介绍的概念。通过期望和对类似环境的熟悉,我们的大脑会将光线与较暗的区域混合,并填补缺失的部分。另一方面,相机没有关于地方应该如何看起来事先的知识,并且它无法像我们的大脑那样很好地处理暗区。换句话说,人脑近似缺失的部分,并绘制出一个更完整的画面。
到目前为止,渲染引擎的工作方式就像一个相机。如果你现在查看级别,你会看到拱门处于黑暗中。如果我们将光源的强度增加,它将把光线投射得更远。然而,我们仍然会有一些区域比其他区域暗。我们需要某种东西,它能够扩展现有光源的效果,类似于我们的大脑处理光线的方式。
为了达到这个目的,我们将引入全局照明以实现更逼真的外观。通过这种方法,拱门附近的区域将看起来从附近的蜡烛和壁灯那里获得更多的光线。如果你还没有猜到,这里有一个节点来完成这项工作。让我们将其添加到我们的场景中:
-
选择该级别的根节点。
-
添加一个GIProbe节点。
-
调整
12、5和15。 -
打开它的内部设置。
-
将这个探测器放置在你的级别中,使其像一个信封一样包围一切。
GIProbe最初将类似于一个绿色的线框立方体。在你将其放置以使其围绕级别包裹后,Godot 界面将如下所示:

图 10.14 – GIProbe 已经放置,但尚未启用
这个节点将探测其体积内的光源。然后,它将把这个信息插值到较暗的区域,以便光线可以更均匀地分布,正如我们的眼睛所期望的那样。尽管探测器已经准备好了,但在我们触发计算之前,我们需要注意两个重要的事情。
开启光照烘焙
我们已经看到了一些与 3D 模型相关的导入设置。例如,我们看到了材质是自动导入的,因为这是导入面板中的默认设置。此外,通过该面板中的动画部分,我们能够从模型中提取动作到文件系统。所有这些都在第七章,将 Blender 资产导入 Godot中有所涉及。
我们这次将重新访问导入面板以进行不同的需求。我们希望某些模型能够接收到更多的光线。因此,通过开启光照烘焙,一些模型将接收到由GIProbe发送的额外光照信息。正如其名所示,这项技术将一次将场景中的一些光线烘焙到模型的材质中。然后,当光线条件发生变化时,它将根据需要更新。
因此,我们将选择一些看起来可能从光照烘焙中受益的模型列表,因为它们具有大而连续的表面:
-
墙面(墙面孔)
-
曲线
-
地板标准(地板标准弯曲 1和地板标准弯曲 4)
-
圆柱形
较小的物体,如道具,通常不适合进行光照烘焙,但从技术上讲,你可以为导入的任何模型开启此设置。目前,我们将选择墙面模型并为其启用光照烘焙:
-
在文件系统中选择
Wall.glb。 -
打开导入面板并向下滚动以找到光照烘焙选项(提示:这是网格部分的最后一个选项)。
-
在光部分将其值改为启用。
-
点击重新导入按钮。
-
对其他上述模型重复此过程。
一般而言,我们正在为场景中的建筑模型启用光照烘焙。这是方程的一部分。现在我们已经配置了模型以接受光照烘焙,我们必须告诉渲染器这些模型应该烘焙多少光线到材质中。我们将通过调整我们迄今为止使用的光源的能量级别来完成这项工作。
调整间接能量
在拥有适当全局照明的第二大重要事情是调整光源的能量级别。尽管本节的标题表明我们将调整间接能量级别,但讨论直接能量的含义也会很有用。
在 Blender 中,你通过调整光的功率属性来改变光的直接能量级别,这些属性以瓦特为单位测量。这意味着你可以输入现实生活中的灯泡值以获得准确的结果。Godot 中光的能量值不遵循单位系统。因此,这更多的是一个可以根据你的场景和喜好进行调整的艺术值。
当前的能量属性,也称为直接能量,定义了光线的强度,而其间接能量值则用于计算我们在“使用全局照明”部分的起始行中描述的自然效果,其中我们对比了人眼和相机。
当环境足够黑暗时,有一种简单的方法可以在家中观察这种效果。你可以点燃一支蜡烛并观察其附近将会有一个足够照亮的区域。然后,光线将逐渐减弱至远处,但你仍然能够注意到一些远处的物体。它们的细节可能不会非常清晰,但它们最显著的外形将一目了然。使用GIProbe可以模拟这种类型的间接能量效果。
对于这项工作,我们必须调整我们迄今为止使用的某些全向光节点:
-
打开
Sconce.tscn并选择其全向光节点。 -
在光部分将其值改为
2.5。 -
在全向部分将其值改为
8。
这将增加壁灯发出的光照范围,使其能够照射得更远。1.0的能量级别已经被使用,因此我们只调整间接能量,因为我们希望它对全局照明做出贡献。
让我们为蜡烛使用不同的值重复这一努力:
-
打开
Candles_1.tscn和Candles_2.tscn文件,并选择它们的OmniLight节点。 -
将
1.5和3进行更改。
与壁灯相比,蜡烛不应该发出那么多的光。因此,设置较低的值是有意义的。然而,由于不是单独的一支蜡烛,而是一组蜡烛,所以这些值并没有相差太远。这也是你在工作中可能需要平衡的事情:艺术关注与现实主义的平衡。
我们已经为GIProbe执行其任务而设置了各项配置。看起来我们增加了场景的整体光照。我们需要这样做,因为其中一些额外的光照将用于计算更好的光照分布。剩下要做的就是触发GIProbe:
-
在场景树中选择GIProbe。
-
点击 3D 视图上方标题栏中的烘焙 GI 探测按钮。
Godot 引擎将计算你为启用了光照烘焙的模型计算的光线如何从表面反弹。根据光线的强度、范围和间接能量,较暗的区域将接收到更多的光照。这将导致更均匀的分布,并给出符合我们期望的更逼真的外观。图 10.15显示了全局照明对拱门附近区域的影响前后对比:

图 10.15 – 由于光照分布更加均匀,门变得更加显眼
根据你关卡的大小和布局,你可能需要放置多个GIProbe节点。例如,如果你正在设计一个有许多房间和走廊的地牢,将每个房间和走廊视为一个独特的GIProbe节点可能是个更好的主意,因为这样可以达到更准确的光照分布。
此外,当你有一个户外环境与室内环境相连的场景时,为每个区域创建一个GIProbe并相应调整Interior设置是个好主意。使用一个涵盖整个关卡的主要节点会对任何环境都不公平,因此引入尽可能少,有时甚至需要尽可能多的节点。
这样一来,我们提升了我们的关卡外观。让我们总结一下我们为了达到这个效果所采取的步骤。
概述
我们从上一章继承的这个关卡看起来很完整,但缺乏趣味。为了给它增添更多活力,我们在本章引入了一些乐器。
首先,我们介绍了两种灯光节点类型,OmniLight和SpotLight,以模拟蜡烛、壁灯和洞穴中太阳的效果。在完成这项任务的同时,你也看到了为什么为模型创建场景可能比直接在级别中实例化模型更有用,也是必要的。随后,我们添加了一个小脚本,可以帮助你在需要时切换灯光。我们将在本书的后续部分利用这一功能。
虽然灯光是改善视觉效果的一个明显工具,但我们还研究了阴影。它们资源密集,因此你可能只想为那些将对你的场景产生重要影响的灯光打开它们。
为了真正欣赏灯光和阴影的效果,我们应用了一系列环境设置。尽管这极大地帮助了视觉效果,但要将现实感提升到下一个层次,你已经接触到了全局照明。通过仔细选择哪些模型应该接收更多间接光,并调整场景中灯光的设置,你使某些区域更加明亮,从而实现了更精确的表示。
在下一章中,我们将处理不同类型的视觉系统。这是一个玩家可以与之交互的有用机制:用户界面。
进一步阅读
在本章所讨论的所有主题中,全局照明是最技术性的一个。模拟现实生活中的光是一个具有挑战性的任务,而且专业人士仍在积极努力实现这一目标。如果你想尝尝味道,以下是一些应该能给你一个更好了解它所涉及内容的链接:
-
ohiostate.pressbooks.pub/graphicshistory/chapter/19-5-global-illumination/ -
www.scratchapixel.com/lessons/3d-basic-rendering/global-illumination-path-tracing
在更实际一点的情况下,如果你希望了解更多关于本章所涵盖的内容,官方的 Godot 文档可能很有用:
-
docs.godotengine.org/en/3.4/tutorials/3d/lights_and_shadows.xhtml -
docs.godotengine.org/en/3.4/tutorials/3d/environment_and_post_processing.xhtml
第十二章:创建用户界面
让我们从这个章节的开始,先提出一个简单的问题:你第一次玩的多玩家游戏是什么?
如果你正在考虑玩个人电脑或控制台游戏,试着换一种思维方式。想象一群孩子伸出双臂,假装射击并击倒入侵他们社区的坏人。或许前一天晚上电视上播放了一部引人入胜的动作电影。现在,这些孩子正在将他们认为在物理领域内可能的事情变成现实,混合了一点幻想和他们对电影的记忆。有些孩子甚至会假装自己在过程中受伤。最终,阵亡的战友将得到报复,善良将再次战胜邪恶。这里谁在记分——也就是说,谁有多少生命值?
那么服务器、互联网速度以及类似的事情呢?孩子们甚至需要用户界面(UI)来玩游戏吗?不,因为对他们来说,跟踪发生的事情仍然很容易。但是当人们需要关注的事情数量超过某个点时,就会变得难以应对。换句话说,当使用没有 UI 的系统变得不切实际时,就需要一个 UI。
这不仅仅局限于视频游戏。在现实世界中,你使用 ATM 来访问你的银行账户。所需的信息和功能将以清晰、简洁的方式呈现;检查账户、发送电子转账和查看当前利率都很容易,多亏了精心设计的 UI。
在我们的游戏中,尽管克拉拉有所期待,但她叔叔并没有在那里,而是在码头留下了一张便条。我们需要一种方式让玩家获取这些信息。因此,在本章中,我们将介绍 Godot 在它的工具箱中的一些 UI 组件来传达这个信息。我们将从一个简单的按钮节点开始,然后是面板组件。在这个面板中,我们将通过标签组件显示一些文本。
当你在游戏中添加越来越多的用户界面元素时,你也会学习如何将这些元素应用样式,使它们看起来更像是游戏世界的一部分。毕竟,默认的样式看起来都是那种默认的灰色,可能更适合原型设计。
在多次对 Godot 节点进行样式化操作后,可能会感到疲倦,尤其是如果你正在为具有不同文本的相同类型的按钮进行样式化。作为一个解决方案,我们将演示如何利用主题,这是一个强大的工具,将帮助你进行样式化工作。
如同往常,我们将讨论一些与创建 UI 相关的相关话题。考虑到这一点,在本章中,我们将涵盖以下主题:
-
创建一个简单的按钮
-
包裹在面板中
-
在面板中填充更多控件节点
-
利用主题
到本章结束时,你将学会如何利用 UI 节点帮助玩家阅读克拉拉叔叔为她留下的便条。
技术要求
如果您认为您没有足够的艺术天赋来创建 UI,那么请放心,有两个原因。首先,我们将主要关注在 Godot 中利用 UI 组件。因此,图形设计方面不会是我们的关注点。其次,我们在本书 GitHub 仓库的第十一章的“资源”文件夹中为您提供必要的资源。在里面,您会找到两个文件夹:Fonts和UI。只需将这些两个文件夹合并到您的 Godot 项目文件夹中。
这本书的 GitHub 仓库,github.com/PacktPublishing/Game-Development-with-Blender-and-Godot,包含了您需要的所有资源。最后,您可以从上一章继续您的作品,或者使用第十章的“完成”文件夹。
创建一个简单的按钮
UI 是围绕您游戏核心视觉元素以连贯方式排列的组件集合。最基础的 UI 组件可能是Label节点,如果我们想让它类似于在学习一门新编程语言时打印“Hello, world!”。然而,我们将从Button节点开始,因为前者非常简单,我们也可以在这个过程中学习如何样式化Button。
在我们随意抛出一堆 UI 节点之前,我们应该首先提到正确的结构来容纳我们的 UI 节点。我们可以使用CanvasLayer,类似于使用Spatial节点来嵌套其他节点,例如MeshInstance、AnimationPlayer等。
我们已经一直在创建场景主要是为了显示 3D 模型。让我们遵循类似的步骤来创建 UI:
-
创建一个空白场景,并将其保存为
UI.tscn,位于Scenes文件夹中。 -
选择
UI。 -
添加一个
Close。 -
在Inspector面板中为其Text值输入
Close。
目前还没有什么特别之处,但我们现在有一个按钮默认对齐到视口的左上角。当您输入显示的文本时,这个按钮的宽度也扩展了。
控制与 CanvasLayer
我们提到Spatial节点将是 3D 节点的根节点。所以,为了保持熟悉,我们可以使用Control节点来容纳Button节点。请放心,您仍然可以在CanvasLayer内部注入Control节点。我们使用CanvasLayer作为根节点的真正原因是其在Inspector面板中的Layer属性。通过更改此值,您可以更改绘制顺序,这意味着您可以决定哪个CanvasLayer将首先渲染。当您有多个需要以精确顺序堆叠在彼此之上的 UI 结构时,这是一个有用的机制。
我们刚刚添加的按钮看起来很无聊。它并不完全适合我们正在创建的世界。现在,让我们使用自定义图形资源来样式化我们的按钮:
-
在Inspector面板的Theme Overrides部分的Styles子部分中展开。
-
使用正常属性的下拉菜单,选择新样式盒纹理选项。
-
点击样式盒纹理标题,这将填充检查器面板并显示其属性。
-
将
button_normal.png从UI拖到文件系统面板,并将其放入纹理属性中。 -
将所有边距值中的
8展开。 -
按F6启动
UI.tscn场景并尝试与按钮交互。
你已经为简单的按钮进行了很多样式设置,所以让我们分析一下发生了什么。
在步骤 1中,你告诉 Godot 你想覆盖默认主题,该主题为按钮提供了灰色外观。在没有用户交互的情况下,按钮将处于其正常状态;所以,这就是你在步骤 2中打算改变的内容。我们很快就会发现如何更改其他状态。
步骤 3是关于定义这个Lorem ipsum dolor sit amet的属性。注意按钮是如何在不显得拉伸的情况下变宽,同时保持圆角完整。这需要适当的解释。
设置边距不仅涉及容纳文本。精心选择的值将确保纹理可以根据需要放大或缩小,而不会失去其某些特性,如圆角。当资产具有圆角时,如果纹理被拉伸,你最终会得到一个扭曲的外观。保留纹理的核心特性并允许其正确缩放而不失真的做法称为 9 切片缩放。你可以在这里了解更多信息:en.wikipedia.org/wiki/9-slice_scaling。
在步骤 6中启动UI.tscn场景时,按钮必须显示其正常状态,即棕色纹理。如果你将鼠标移到它上面,你会看到按钮将再次显示默认的外观,因为你还没有设置悬停状态。这可以在下面的屏幕截图中看到:

图 11.1 – 按钮仅对其正常状态进行了样式设置
与你为按钮的正常状态分配纹理的方式类似,你也可以为其他状态这样做。让我们为悬停状态做这个操作:
-
在场景树中选择关闭按钮。
-
在主题覆盖下的样式子部分中,将新样式盒纹理分配给悬停状态,并点击这个样式盒纹理来设置其属性。
-
将
button_hover.png从UI文件夹拖出,并将边距设置为8。 -
按F6并将鼠标移到按钮上。
我们也将重复这一过程来设置按下和禁用状态。在我们的游戏中,我们不会使用禁用按钮,但为什么不做彻底呢?此外,在大多数情况下,你可以将按下状态用于焦点状态。不同的结果在下面的屏幕截图中显示:

图 11.2 – 带有自定义纹理的按钮的正常、悬停、按下和禁用状态
在我们介绍更多 UI 节点之前,我们建议你将那个按钮的文本改回关闭,因为我们将使用这个按钮来关闭一个模拟克拉拉叔叔笔记的面板。说到这个,是时候学习笔记中写了什么了。
在面板中包裹
到目前为止,我们已经创建了一个按钮并为其设置了样式。然而,如果它能够发挥作用,那就更好了,尤其是考虑到我们给它赋予了有意义的标签。我们将编写一些代码,以便这个按钮可以在“在面板中添加更多控制节点”部分的末尾关闭一个面板。不过,在我们到达那个点之前,我们需要面板。
随着我们介绍更多的 UI 节点,让我们记住我们为什么在游戏上下文中做这件事。克拉拉的叔叔留下了一封笔记。我们将使用 Godot 中的 UI 节点组合来模拟这封笔记,使其看起来如下:

图 11.3 – 克拉拉的笔记
我们已经处理了按钮,但它目前正坐在一个无处可去的地方。在给出简短声明后,我们将在这个部分用面板节点包裹它。
面板节点是 Godot 中的另一个控制节点,通常用于包含其他组件。还有一个同名节点,面板容器,可能会让初学者感到困惑。面板节点从控制类派生,而面板容器节点从容器类继承。此外,容器类从控制类继承。这类技术细节在执行更高级的工作时可能很重要。我们不会这么做,所以在这个书中,为了我们的目的,两者都可以正常工作。因此,我们将坚持使用面板节点。
到目前为止,我们准备好添加一个面板节点并为其设置样式:
-
在场景树中,在根UI节点下添加一个面板节点。
-
在检查器面板中展开矩形部分。
-
为X输入
600。 -
为Y输入
400。
- 在“主题覆盖”下的“样式”子部分中,将新样式框纹理分配给面板属性。* 将关闭按钮拖动到场景面板中的面板节点上,以便关闭按钮嵌套。
到目前为止,你应该有以下的输出:

图 11.4 – 使用面板节点模拟的纸张纹理
我们越来越接近我们想象中的笔记设计。面板中的按钮仍然对齐到左上角。你可以将它拖动到一个有意义的位子,但如果面板内有文本,可能会更容易决定。这就是我们接下来要处理的。
在面板中添加更多控制节点
叔叔的笔记正在慢慢成形。在本节中,我们将介绍一个标签节点用于文本部分。此外,我们还需要想出如何定位所有这些元素,以便笔记看起来像我们想要的布局。最后,我们将讨论一些可能在某些场景下想要使用的辅助控制节点。
最终,我们仍然会使用最基本的 UI 节点:标签。如果我们一开始就使用它,它将因其默认样式和颜色而显得不起眼。由于我们现在有一个合适的纹理,这个标签节点可以放在上面,所以事情会看起来更有趣。按照以下步骤进行操作:
-
在场景面板中选择面板节点。
-
在检查器面板中添加一个标签节点并将其自动换行属性打开。
-
将其文本设置为以下内容:
我亲爱的克拉拉,
我的一个好朋友急需帮助。我必须立即离开。
检查破旧的购物车旁的背包。里面,你会找到通往楼上的钥匙。请随意。
你的叔叔,伯特
我们最后的努力将导致一个不协调的高文本块。为了解决这个问题,我们可以手动给刚刚插入的标签节点设置一些宽度和高度。当我们这样做的时候,我们也可以改变它的位置,使其看起来居中,并在每个边缘有一些边距。然而,我们可以做得更聪明:我们可以将这个标签包裹在一个会设置边距并自动调整文本大小的边距容器内。
添加边距容器
到目前为止,向场景面板添加新节点对你来说可能是一个常见的任务。然而,有时,例如现在,决定在哪里添加新节点以及将其嵌套在何处可能并不明显。问题是,我们可以在哪里添加边距容器?在面板节点外部还是内部?
边距容器是一个专门的容器,负责引入边距,使其子元素看起来像有填充。如果我们将面板节点包裹在一个边距容器中,由于面板节点包含文本,整个结构,包括按钮,都将有填充。这并不好,因为我们希望文本的边缘和构成面板节点的纹理的边缘之间有一些空间。因此,你需要这样做,只为文本添加填充:
-
在面板节点内添加一个边距容器节点,并将标签嵌套在这个边距容器节点内。
-
在
0和1处设置以下值。 -
在
0处。 -
在
60处。
在前面的操作中,我们提到了很多术语。前两组操作,即我们改变锚点和边距的值,并不特定于边距容器。它们适用于每种类型的控制节点。你还可以将这一事实视为这些属性在检查器面板下的控制标题下列出。
我们选择的锚点和边距值是如此特殊的值,以至于我们可以使用快捷方式达到相同的结果。这需要在 3D 视口标题部分的布局按钮点击后,在展开菜单中选择全矩形选项。这个布局按钮在以下截图中的可见,位于纸张纹理的右上角。
当我们稍后调整关闭按钮的位置时,我们将在该菜单下使用另一个选项。现在,将你的工作与以下截图中的内容进行比较:

图 11.5 – 文本现在有填充,尽管很难阅读
在那个边距容器的属性中,最重要的是调整其在常量子部分中的内容边距值。这给了文本一些空间,并正确地将其定位在纸张纹理上。
虽然文本有点难以阅读。那么,让我们看看我们如何使其可读,甚至更好地使其看起来像图 11.3。
标签节点的样式化
尽管现在边距容器占据的空间与面板节点一样多,并且它为其所持有的文本提供边距,但文本本身几乎难以辨认,因为它很小且为白色,覆盖在浅色表面上。此外,字体选择是错误的,因为它使用了 Godot 引擎提供的默认字体。在本节中,我们将学习如何解决所有这些问题。
首先,我们在场景面板中选择标签节点,以便我们可以在主题覆盖下进行一些更改:
-
在颜色子部分中打开字体颜色选项。颜色可以保持黑色。
-
在字体子部分中,从文件系统中选择
Kefario.otf到字体数据属性。 -
在设置子部分中将
28进行更改。
我们将在稍后讨论发生了什么,但到目前为止我们已经做了以下操作:

图 11.6 – 标签节点现在看起来更像手写文本
文本的默认黑色颜色看起来似乎不错,但如果你愿意,可以选择不同的颜色。当我们引入字体类型时,发生了一个更加剧烈的变化。我们分两步进行。首先,我们选择了一个动态字体类型,它比另一个选项位图字体慢,但它允许你在运行时更改字体的属性。然而,由于它像一个包装器,这不足以渲染字体。因此,你需要分配你想要渲染的字体。这就是我们为什么将字体文件分配给FontData属性的原因。
我们认为你需要注意的一个重要注意事项是字体,因为它们由称为符号的独立元素组成。你可以把它们想象成字母表中的字母。并不是每个字体都支持字母表的完整范围。例如,在我们设计的笔记 UI 中,如果你将文本 you will 替换为其缩写形式 you’ll,撇号将无法渲染,因为它在该字体中不是一个符号。通常,付费字体包含一个更大的符号集。否则,继续寻找具有更完整集合的免费选项。
像素与点
当我们选择 28 作为字体大小时,这个数字是以像素为单位的。在一些图形或文本编辑器中,你经常会发现字体是以点来衡量的。这一点你必须小心,因为如果你直接将这些数字转移到 Godot 中,你的字体渲染效果将会完全不同。所以,注意你的单位。
在现实世界中,克拉拉的叔叔的便条只会包含文本部分。因此,期望在一张实际的纸张上有一个关闭按钮是荒谬的。然而,这是一个游戏,我们已经讨论了 UI 如何将现实与功能混合。为了完成笔记的 UI,是时候定位那个按钮了。
定位关闭按钮
我们使用了一个巧妙的技巧来定位文本,关于它所在的纸张。我们能复制这个技巧用于 关闭 按钮吗?由于按钮不能被视为一个宽结构,我们不能将它放在 MarginContainer 内。然而,我们仍然可以将其相对于 Panel 节点进行定位。
在 添加 MarginContainer 部分,我们使用了一种更长的方法来调整该组件的尺寸。我们还提到我们会使用一个快捷键。这是在 场景 面板中选择 关闭 按钮后如何使用它的方法:
-
展开布局菜单并选择 右下角 选项。
-
按住 Shift 键,然后按键盘上的左箭头键和上箭头键各四次。
这将使 关闭 按钮位于右下角,然后向上拉并向左移动,使其保持在原地。当我们说它将保持在原地时,我们是认真的。例如,选择 Panel 节点,然后尝试使用视口中的手柄来调整其大小。按钮是否仍然很好地藏在那个右下角?很好!至于 Label 节点呢?文本是否流动以填充额外的空间?整洁!
我们开发你在 图 11.3 中看到的内容的努力正在取得成果,如下所示:

图 11.7 – UI 中的所有元素都经过精心定位
如果你想测试你的场景,请按F6。根据您的设置,您可能会注意到关闭按钮可能无法正常工作,因为它在边距容器后面。所以,尝试通过在场景面板中上下拖动节点来调整节点的顺序。当关闭按钮在边距容器之后时,一切应该都能正常工作。
说到功能,我们还没有为关闭按钮设置任何连接。理想情况下,该按钮应该关闭面板的可见性,这样注释看起来就像已经关闭了。让我们接下来这么做。
添加关闭功能
我们有多种方法可以解决这个问题。为了简洁起见,我们将向您展示一种方法,以便您可以看到涉及的内容。您可能需要在未来的项目中以不同的方式应用类似的原则。
例如,到目前为止我们处理UI.tscn场景的方式是将其作为直接子节点的一个大面板节点。你的游戏可能需要更多带有永久可见元素的 UI,更多可打开和关闭的注释,带有展开部分以显示更多细节的库存屏幕,等等。有许多可能性,这就是为什么你可以构建不同类型的架构。在这些不同选项之间总会有权衡,所以我们建议如果你有空闲时间,可以尝试实验不同 UI 结构的优点。
不再拖延,我们建议实现关闭功能的方法是在关闭按钮中添加一个小脚本。选择它并执行以下操作:
-
将脚本附加到
Scripts文件夹中的ButtonClose.gd。 -
让这个脚本文件看起来如下:
extends Button func _ready(): connect("pressed", self, "on_pressed") func on_pressed(): get_parent().visible = false
这种架构假设按钮始终是节点的直接子节点,所以一旦按下,它将使父节点不可见。哎呀!
这种简单结构的优点是方便,按钮不需要知道它所在的节点结构。还有另一种更传统的方法,通过使用节点面板和绑定信号来附加按下行为。两种方法都行。
构建和改进 UI 元素可能很容易变成一个独立的项目。您可能会被诱惑为所有可能的情况创建一个完美的设置,但请记住,过度优化是存在的。稍后,您可能会意识到您一开始根本不需要所有这些准备。我们将在下一个类似的情况中讨论,其中注释可能更长。
总结
现在我们有一个完全功能的 UI 来显示克拉拉的叔叔伯特的注释。如果伯特有更多话要说呢?例如,假设信息在他的名字后面有一行额外的文本,如下所示:
你的叔叔,伯特
P.S. 我想我可能把我的宠物蛇忘在一边了。它可能正在四处游荡,所以小心!
如果你将这段额外文本添加到标签节点的末尾,文本就会不舒服地靠近纸张纹理的顶部和底部。同样,想象一下,如果这个文本块需要更长,这在一些类型游戏中很重要,比如在展示角色扮演游戏中的任务或物品详情时。例如,在显示角色扮演游戏中的任务或物品详情时,这种情况非常常见。
目前,我们可以通过调整文本的字体大小或使边距更窄来适应更多的新文本。然而,在更极端的情况下,使用滚动容器节点可能更好。就像你将标签节点包裹在边距容器中一样,你可以在标签节点周围包裹一个滚动容器,并对一些东西进行调整,以获得可滚动的文本块。
确定合适的嵌套层级和决定 UI 组件的类型及顺序有时需要反复尝试和错误。因此,并没有任何固定的公式。因此,你可能会发现自己正在实践中,看看在你的用例中什么最有效。
话虽如此,将你的努力推广到保持许多 UI 节点的一致外观和感觉可能是有帮助的。我们将接下来处理主题以实现这一点。
利用主题的优势
在你的项目中使用或更具体地说,创建主题在很多方面都是明智的。首先,我们将讨论它们的有用性,展示一些视觉示例,然后创建一个用于练习的示例。让我们从为什么你应该使用主题的原因开始。
首先,使用主题将帮助你避免像迄今为止那样手动应用覆盖。当然,你仍然可以在这里和那里添加一些手动修饰,但如果你想完全改变按钮的艺术方向会怎样?这将触发一系列反应来改变其他组件的外观。所以,你将不得不重新开始手动编辑。此外,最糟糕的情况可能是撤销你的更改,因为你知道,我们都是人类,我们往往更倾向于坚持我们的第一个选择。
其次,你可以在游戏中准备多个主题。尽管按钮仍然只是一个按钮,但你可以给它分配多个主题中的一个。这将使该按钮看起来像属于一组组件。因此,你的 UI 元素将具有一致的风格。
最后,运行时更改主题也是可能的。这意味着,如果您在游戏或您用 Godot 构建的应用程序中想要在特殊场合(如圣诞节)交换主题,这是完全可能的。此外,越来越多的桌面应用程序正在使用 Godot 构建。这些也可以通过主题交换来为用户提供最佳选择。Godot 引擎本身允许您更改主题。您可以通过打开编辑器设置并尝试一些主题来访问现有主题。例如,尝试Solarized (Light)主题。您是否感受到了 Unity 的感觉?
更改主题并不总是关于更改按钮的颜色或字体大小。例如,365psd.com/psd/ui-kit-54589 和 365psd.com/day/3-180 是我们挑选的两个 UI 套件,以展示您的 Godot UI 节点可能看起来有多么不同。图 11.8展示了这两个 UI 套件并排:

图 11.8 – 两个适合作为主题的独立 UI 套件
由于我们已经看到了如何更改三种类型节点的外观和感觉,即按钮、面板和标签,我们将专注于其他类型的控件节点。我们将在创建新主题的上下文中完成此操作。
创建新的主题
由于游戏开发是一个迭代的过程,提前规划每一件事可能并不总是可能的,甚至可能是徒劳的。这就是为什么您首先手动更改 UI 节点是典型的。然而,从一个新主题开始并更改此主题的属性也可能是一个好主意。为什么?因为如果您的单独修改组件的实验取得了成功的结果,您就不必在主题中重复您所做的工作。通过在开始时创建主题,您在前进的过程中构建起来。
此外,创建主题就像在 Godot 中创建任何其他类型的资源一样。我们可以通过遵循几个简单的步骤来完成此操作:
-
在“主题”中的
UI文件夹上右键单击,并更改其名称。 -
在FileSystem中右键单击
Themes并选择新建资源选项。 -
选择
Dark.tres。
这将在您的项目中创建一个主题资源。它还应该在下侧区域启用一个新面板,该面板将显示此新主题的预览。当您对主题进行更改时,可以在该区域预览更新,因为这种方式可能比在场景中添加和删除测试组件更快地监控您的进度。
如果预览区域看起来很小,可以通过点击 Godot 版本号旁边的图标来将其放大。这个图标看起来像两个向上的箭头,上面有一条水平线。按下它,主题预览将占据视口。最后,您的编辑器将看起来如下:

图 11.9 – 主题预览已扩展
顺便说一下,预览区域不是静态的。您可以与这些 UI 组件进行交互。就像一个在 Godot 中运行的 Godot 场景。现在,我们将修改 CheckButton、CheckBox 和 VSlider 组件的主题。我们还将展示 CheckBox 节点的特殊状态,在网页开发中也称为单选按钮。然而,我们的首选候选者是 CheckButton。
设计 CheckButton 的样式
我们将使用来构建新主题的图形资源是 Dark UI Kit,您可以在 365psd.com/psd/dark-ui-kit-psd-54778 找到。我们已经将必要的部分导出到 UI 文件夹中供您使用。
我们创建的主题仍然是默认主题,因此它仍然显示默认组件。我们将选择我们想要更改的一个。这是如何操作的:
-
按下带有 + 图标的按钮。这个按钮位于 主题 预览区域右上角的 管理项目 和 覆盖所有 按钮之间。
-
在即将出现的弹出菜单中选择 CheckButton。通过这样做,您将在主题预览的右侧看到通过选项卡分隔的此组件的相关属性列表。
-
切换到第四个标签页,它看起来像带有山形的宝丽来图标。按下 + 图标为 关闭 和 开启 属性。
-
从
dark-ui-checkbutton-off.png到 关闭 插槽,以及类似地,将dark-ui-checkbutton-on.png拖动到 开启 插槽。 -
在主题预览中与 CheckButton 进行交互。
这将有效地改变 CheckButton 的外观。您的 主题 面板将如下所示:

图 11.10 – 我们已使用自定义资源更改了 CheckButton 组件的外观
CheckButton 是一个具有两个主要状态的简单组件:开启和关闭。我们对其两种状态的禁用版本不感兴趣,仅仅是因为 UI 套件没有为这种排列提供资源。如果您认为您永远不会在这个组件处于禁用状态,那么您也不必创建和分配艺术作品。
这次让我们攻击一个不同的组件。尽管它的名字相似,并且它具有与 CheckButton 相似的状态,但这个节点的一个隐蔽属性使其作为两个不同的组件工作。进入 CheckBox。
更改 CheckBox 并发现单选按钮
这将是一个类似的工作,但我们将利用更多的资源并填写更多的属性。让我们保持势头,并为主题添加一个新项目:
-
再次使用带有 + 图标按钮,从即将出现的项目列表中选择 CheckBox。
-
第四个标签页可能仍然处于活动状态。如果不是,切换到它并执行以下操作:
-
将
dark-ui-checkbox-off.png分配给 未选中 属性。 -
将
dark-ui-checkbox-on.png分配给 选中 属性。 -
将
dark-ui-radio-off.png分配给 radio_unchecked 属性。 -
将
dark-ui-radio-on.png分配给 radio_checked 属性。
-
当你准备你的资产时,选择与资产将被分配到的状态足够接近的文件名。因此,在 文件系统 和 主题 面板之间关联这些文件会感觉很容易。在做出这些更改后,这就是我们得到的结果:

图 11.11 – CheckBox 是我们为暗色主题定制的最新项目
预览区域有 CheckBox 组件供你测试,但没有单选按钮。Godot 中没有 RadioButton 组件。尽管我们添加了它的资产,但我们还不能模拟它。尽管如此,我们可以调整一个 CheckBox 组件,使其表现得像单选按钮。
由于我们需要物理放置一个 UI.tscn 场景:
-
通过在 场景 面板中点击其眼睛图标来关闭 面板 节点的可见性。
-
选择根节点,然后添加一个 HBoxContainer 节点。立即选择这个新节点,以便你可以执行以下操作:
-
向它添加一个 VBoxContainer、VSeparator 和另一个 VBoxContainer。
-
在这两个 VBoxContainer 节点内部添加两个 CheckBox 节点。
-
对于第一个两个
Multiple Choice 1和Multiple Choice 2。 -
对于最后的两个
Single Choice 1和Single Choice 2。
-
我们还没有完成,但下面的截图显示了到目前为止发生的情况:

图 11.12 – 以问卷形式组织的四个复选框
我们离将两个复选框转换为单选按钮又近了一步 - 特别是最后两个,因为我们给了它们一些提到单选的文本。因此,当你在 VboxContainer2 节点中选择 CheckBox2 时,请执行以下操作:
-
在 检查器 面板中将一个新的 ButtonGroup 分配给其 Group 属性。
-
点击那个 Group 槽中的向下箭头以展开下拉菜单并选择 复制。
-
在 VBoxContainer2 中选择 Checkbox 节点,并通过展开其 Group 选项选择 粘贴 选项。这将链接两个复选框,因为它们将共享相同的按钮组。
你应该会注意到两组复选框之间的巨大变化。前两个仍然看起来像复选框,而最后两个旁边有圆形图标,如下面的截图所示:

图 11.13 – 两个复选框已被转换为单选按钮
通过共享相同的按钮组,复选框变成了单选按钮。在这个练习中,创建并分配一个通用的ButtonGroup对象就足够了。然而,如果你想在应用程序的一个区域创建一组单选按钮,而在另一个区域创建另一个控制不同单选按钮集合的集合,你可能需要创建命名的ButtonGroup对象并相应分配它们。
我们不会介绍这种场景,因为我们似乎遗漏了一些我们一直想要的重要东西。我们辛苦工作的复选框和单选按钮都没有反映出我们在主题中定义的艺术方向。让我们看看我们如何利用我们的主题。
附加主题
我们之前提到,使用主题可以帮助你更快地设计组件。这是真的,但我们还没有测试这个说法。因为我们已经为复选框和单选按钮准备了样式,所以剩下的只是将这些主题分配给这些组件:
-
在Scene面板中选择HBoxContainer节点,并在Inspector面板中展开Theme部分。
-
将
Dark.tres从FileSystem拖动到空的Theme槽中。
就这样!我们甚至不需要逐个选择每个组件并逐个分配主题。一个更高层次的结构,如HBoxContainer,就足够分配主题,以便其子组件可以使用相关部分。
你看到这里的真正潜力了吗?将主题分配给根元素在大多数情况下就足够了。话虽如此,由于每个组件都可以分配自己的主题,但不必这样做,你可以有各种各样的排列组合。在最简单的情况下,将主题分配给根节点在大多数场景中就足够了。
到目前为止,我们一直在设计相对简单的 UI 节点,例如CheckButton和CheckBox。也许我们可以处理具有一些移动部分的另一个节点,例如VSlider。
修改垂直滑动组件
当你想要为玩家提供一个简单的方式来调整比率或数量时,例如游戏会话中的可交易物品、音乐音量或游戏设置中的亮度级别时,垂直滑动组件VSlider非常有用。同样,你也可以使用水平版本的HSlider节点,但两者都完成类似的任务。
由于我们只有VSlider的图形资产,所以我们只介绍这种样式的使用。如果你需要,可以将与HSlider兼容的现有资产转换过来。你需要将每个部分旋转 90 度并相应保存。为此,你必须遵循以下步骤:
-
在Scene面板中将VSeparator和VSlider节点添加到HBoxContainer中。
-
为VSlider的Value属性使用
75。 -
在FileSystem中双击
Dark.tres以显示其详细信息。使用带有加号(+)图标的老式按钮将VSlider添加为新类型。 -
在这种新类型的自定义属性中激活第四个标签,并将
dark-ui-vslider-grabber.png分配给 grabber 和 grabber_highlight。 -
切换到第五个标签,它看起来像一条彩虹方块。
-
将
dark-ui-vslider-grabber-area.png添加到 Texture 属性。 -
将 Bottom 属性的
6展开。
- 通过双击
Dark.tres或切换到底部的 Theme 面板来再次打开主题预览。* 为了避免对 grabber_area_highlight 属性重复同样的努力,点击其槽附近的加号(+),然后抓取并放下 grabber_area 属性的样式到 grabber_area_highlight 槽。或者,你可以使用下拉菜单从 grabber_area 复制槽并将其粘贴到 grabber_area_highlight。* 将dark-ui-vslider-slider.png添加到 Texture 属性。* 将 Bottom 和 Top 属性的6展开。* 将所有属性的1设置为1。* 按 F6 并欣赏你的辛勤工作。
在这里我们走了很多步,但只有一两个新事物。首先,我们通过拖放将一种样式重新用于不同的属性。这是一个快捷方法,而不是在槽之间复制和粘贴。当两个槽都靠近时很有用。如果你正在复制槽在不同面板上的元素,那么你仍然必须求助于下拉菜单中的复制和粘贴方法。
其次,我们调整了不同类型的边距,Expand Margin。滑块有两个独立的组成部分构成了其轨道,滚动就发生在这里,因此我们必须调整这个特殊的边距,使其适合外部的蓝色部分。看看下面的截图,你将看到在 VSlider 的轨道内有一个蓝色填充物:

图 11.14 – 经过几步之后,VSlider 组件已经主题化
活动地看到效果比阅读它更容易。所以,当你启动 UI.tscn 场景时,尝试与抓取器交互,看看组件如何根据抓取器的位置用蓝色填充其轨道。
总结
这就完成了我们设置主题的工作。尽管我们只对少数节点进行了样式化,但你可以自由地使用相同的 UI 套件的其他部分,或者从网站上选择另一个来尝试其他 Control 节点。
总的来说,使用主题或单独设计组件涉及两个方面。首先,你可以直接将纹理分配给某些属性,或者通过创建StyleBoxTexture间接分配到适当的槽位。其次,还有一些数值属性你可以调整。我们还没有涵盖这个后者。例如,你可以调整处理文本渲染的组件的行高。这些情况容易理解并测试。因此,我们选择展示更多令人头疼的情况。
希望通过实践我们迄今为止所展示的内容,并自己发现更多,你将能够将美丽的图形设计应用到你的游戏中。
摘要
我们以辩论 UI 是什么开始本章。我们通过简短的哲学和理论解释来完成这项工作。
假设你的游戏需要 UI,我们研究了构建 Clara 叔叔留下的笔记这样的实际用例。这项工作需要我们与多个Control节点一起工作——即按钮、面板和标签节点。
在这个过程中,我们不仅使用了所需的组件,还根据特定的艺术风格对它们进行了设计。
为了避免重复并提升设计的层次,我们展示了如何使用主题可能是一个节省时间的方法。为此,我们展示了如何通过将这些工具包单独导出的图形资产分配给Control节点的属性来利用你可以在网上找到的 UI 工具包。
UI 在某种程度上是玩家与游戏互动的工具。话虽如此,在下一章中,我们将发现一种无需 UI 帮助即可更直接地与游戏世界互动的方法。
进一步阅读
在介绍中,我们讨论了何时需要 UI。然而,有些情况下,最好的界面根本不是界面。有一个应用——抱歉,是一本书——由Golden Krishna所著:《最佳界面无界面:通往卓越技术的简单路径》。他谈论了引入更多步骤和元素作为 UI 伪装的干扰。
我们已经讨论了没有 UI 的游戏的可能性,但现在我们将暂停这个论点。在这个阶段,让你尽可能多地接触信息和示例可能更好。因此,以下是一些技术和实际资源:
本章还向您展示了如何为组件分配字体。市面上有很多免费字体可供选择,但请注意仔细阅读它们的许可协议。虽然它们可能可以下载,但其中一些不能用于商业作品。对于其他任何东西也是如此,尤其是图形资产。
第十三章:通过相机和角色控制器与世界交互
你一直在为游戏世界准备一些零散的东西,尤其是在前两章中。在第十章 使用光影使事物看起来更好中,你向烛台和蜡烛添加了灯光对象。你甚至放置了一个脚本以调整这些对象的点亮状态。然后,在第十一章 创建用户界面中,你通过引入控制节点创建了一个新的场景。这项工作是为了模拟克拉拉叔叔伯特的一张便条。
尽管我们一直在努力使事物更加复杂,但几乎一切感觉都是静态的。在本章中,我们将向您展示一系列实践,这些实践将建立游戏对象和玩家之间的连接。这将使项目看起来更加生动,感觉更像是一款游戏。
我们首先将探讨的是相机节点及其设置。Godot 的视口一直允许你通过一个临时的结构来查看不同的场景,以便你可以使用该软件。这样的临时概念是不够的,因此我们将使用我们的相机系统。
接下来,我们将专注于在世界上的一些游戏对象和玩家之间建立连接。这涉及到在 2D 表面上检测鼠标事件并将这些事件投影到 3D 空间中。可能会有不同的交互,如悬停、点击、按下等,因此我们将探讨检测我们想要执行的动作的方法。例如,我们将点击留在码头上的羊皮纸,以调出我们在上一章中工作的便条。
同样,如果点击发生在我们希望移动克拉拉的区域之一,我们需要一个能够为我们进行路径查找的系统。为此,我们将研究新的 Godot 节点,导航和导航网格实例。
最后,为什么不添加一点动画呢?在我们发现如何在世界中的两个点之间移动游戏对象之后,我们可以指示该对象触发适当的动画循环。在我们的例子中,克拉拉将在她的空闲状态和行走状态之间切换。因此,我们将回顾在第七章 将 Blender 资产导入 Godot的导入动画部分中了解的一些概念。
正如你所见,我们将利用我们已经探讨过的许多主题,但仍有许多新内容等待我们去发现和学习。如果我们能将其列举出来,它将看起来像这样:
-
理解相机系统
-
检测用户输入
-
移动玩家
-
触发动画
在本章结束时,你将对相机设置有更深入的理解,你将能够检测玩家的意图并将它们与游戏中的动作联系起来。多亏了路径查找的简便方法,你将能够将克拉拉移动到你想去的地点,并最终触发适当的动作来模拟她行走。
技术要求
我们将继续在上一章结束的地方继续。这意味着你可以继续工作在你现有的副本上。或者,你也可以从本书 GitHub 仓库中的Chapter 12的Finish文件夹开始:github.com/PacktPublishing/Game-Development-with-Blender-and-Godot。
我们有几个在本章工作中必需的新资产。这些资产位于Finish文件夹旁边的Resources文件夹中。像往常一样,将它们与你的项目文件合并。
理解相机系统
在第四章,调整相机和灯光中,我们简要介绍了 Blender 中相机的概念。我们了解到没有相机就无法渲染场景。尽管我们最终通过引入相机进行了渲染,但我们从未谈论过相机可以拥有的不同设置。这是故意为之,因为我们将在 Blender 中获得的知识不会直接转移到 Godot 上。幸运的是,现在是深入研究相机如何增强游戏体验的最佳时机。
我们不仅将了解如何设置适合我们游戏的相机,还将发现 Godot 库存中不同类型的相机。像往常一样,或者就像你可能在互联网论坛和梗图上听到的笑话一样,Godot 中肯定有一个节点用于此类事物。
是的,有。事实上,正如这里概述的那样,有四个相机节点:
-
相机: 这是作为所有其他相机类型基础的核心类。尽管你可以在场景中拥有多个相机节点,但只能有一个活动相机。而且,类似于 Blender,没有相机在这里就什么也看不到。
-
插值相机: 这是相机节点的增强版本。它增加了三个额外属性,将一个普通的相机节点转变为一个跟踪和跟随目标的机制。如果你正在开发一个肩上视角的游戏,这会非常方便。如果游戏角色是目标,当目标移动时,相机会跟上。
不幸的是,这个节点将在 Godot 4 中删除。幸运的是,通过将一个简短的脚本附加到一个相机节点上,可以轻松地重新创建其功能。换句话说,如果你移除了插值相机节点的花哨部分,你得到的就是相机节点,这就是为什么决定在未来版本中删除它的原因。
-
ClippedCamera:这是另一种特殊的Camera节点,幸运的是,它将被保留在 Godot 4 中,因为它是一个高级相机系统。我们的游戏目前没有使用负责确定哪些对象可以相互穿过或在运动体与碰撞表面接触时弹跳回的PhysicsBody节点。因此,我们不会调查这种类型的相机,但如果你不希望你的相机穿过墙壁,你可能想检查一下这个。它将像遵守物理规则的对象一样表现。
-
ARVRCamera:你可能已经猜到了:这是用于虚拟现实(VR)项目的。由于它依赖于很多以增强现实/虚拟现实(ARVR)开头的其他节点,所以它不是一个作为独立节点使用的工具。构建 VR 项目是一个高级话题,可能需要一本专门介绍这个主题的书。因此,我们也会跳过这个节点。
除了用于 3D 工作流程的相机节点外,还有用于 2D 项目的Camera2D节点。因此,总共有五种类型的相机。
在我们展示的所有这些类型中,最有希望的候选者是InterpolatedCamera节点。为什么?因为InterpolatedCamera节点本质上是一个带有额外功能,如目标和跟踪功能的Camera节点。所以,在你的 Godot 3 项目中,你可以从InterpolatedCamera开始,将其视为一个Camera节点,直到你需要那些额外功能。
由于我们正在继续上一章的工作,整理一些松散的尾巴是有意义的。让我们先从这一点开始,然后我们可以继续介绍相机设置。
整理交互性相关的事宜
在UI.tscn场景中我们最后做的是对 UI 组件进行皮肤化。在这个过程中,我们已经关闭了负责显示克拉拉的叔叔笔记的Panel节点的可见性。然后,我们引入了一系列 UI 节点,所有这些节点都归在一个HBoxContainer节点下。我们也会关闭这个容器,但首先让我们通过按F5来运行项目。你可能会看到类似这样的内容:

图 12.1 – 我们游戏的第一运行
我们做出的 UI 决策在游戏的右上角是可见的。目前我们不需要这些。所以,如果你已经关闭了UI.tscn,请打开它,关闭HBoxContainer节点,再次运行游戏。我们将在检测用户输入部分很快探讨一些 UI 问题。
也许你已经从我们在前几章中使用的截图或者简单地通过查看项目文件中注意到,已经有一个Game.tscn场景被配置为项目的默认场景。这就是为什么当你按下F5时,Godot 没有要求你选择主场景,因为我们已经为你分配了一个。
打开 Game.tscn,让我们看看这个场景是如何构建的。一切可能看起来都很明显,但有一个标记为 Game.tscn 的根节点。另一个节点,Camera,将是本章的主要研究区域。
我们将把剩余的努力主要分为两个不同的领域来理解相机的工作原理。最重要的主题是投影类型,这从根本上改变了整个体验。我们建议你在自己的项目中尽早决定这一点,因为在此选择之后,任何其他调整都可以进行。因此,在我们处理单个相机设置之前,让我们看看有哪些类型的投影。
决定投影类型
如果你上过学习如何绘制建筑的美术课程,这可能是一个你已经熟悉的主题。我们使用的 Godot 版本提供了三种类型的投影。尽管我们将主要关注前两种,但我们会简要定义所有投影类型,如下所示:
-
透视:这是默认的相机投影,其中距离相机越远的物体看起来越小。因此,两个尺寸完全相同的物体,当其中一个物体放置在远离相机的地方时,看起来就像它们的大小不同。这也是人类感知世界的方式,所以如果你不这样,可能需要检查一下。
-
正交:也称为 正交投影,这种投影方式在不改变物体大小的情况下渲染相同尺寸的物体,无论其距离相机有多远。这种投影类型可以为你的游戏带来所需的戏剧性外观。此外,还有一些类型的游戏——角色扮演(Fallout 系列)和 探索、扩张、开发、灭绝(4X)(文明)——更倾向于使用这种投影。
-
视锥体:这是一种相对较新的投影类型,在某些类型的游戏中有其用途——例如,为了获得一些老式游戏曾经使用的 2.5D 外观,其中视觉效果看起来被拉伸。如果你想了解更多信息,
zdoom.org/wiki/Y-shearing有一些关于这个主题的信息。
在大多数情况下,我们列出的前两种投影就足够了。也许通过实验来调查它们之间的差异会更好。因为我们已经看到了 透视 投影类型,所以尝试 正交 投影类型是有意义的,所以请按照以下步骤进行:
-
选择
Game.tscn场景。 -
将其
6改变。 -
按下 F5 运行游戏,并注意其不同的艺术风格。
在我们做出这些更改后,这就是我们得到的结果:

图 12.2 – 从同一位置的正交相机视图
我们选择了12。然而,大多数 PC 显示器都遵循横向布局。这就是为什么保持高度是默认选项,但如果你正在制作移动游戏,你可能想将正确的大小值与选中的保持宽度选项混合使用。
相机特定环境
当我们在检查器面板中查看相机节点的不同属性时,现在可能是复习环境主题的好时机。在第十章的创建后处理效果部分,用光和阴影使事物看起来更好,我们发现了如何创建一个改变关卡外观的环境。如果你想覆盖一些环境设置,你可以通过为相机分配一个单独的环境对象来实现。全局和相机特定环境的效应将会结合。
无论你为哪个平台选择什么值,有一点是明显的。尽管我们没有在世界上移动相机的位置和旋转,但我们得到的效果却完全不同。而我们在图 12.1中看到的后面的洞穴门在透视投影中,正交视图不允许我们看得那么远,如图 12.2所示。当你比较这两个截图时,近处的元素几乎相同,但正交视图模拟了一个比向前看更顶部的视角。
在检查器面板中更改内容并按F5键查看你的更改效果可能会很快感到疲倦。当相机节点仍然被选中时,如果你打开预览复选框,如以下截图所示,你可以在编辑相机属性时加快你的工作流程:

图 12.3 – 预览相机所看到的内容很方便,只需一个复选框即可
这将让你在调整相机设置的同时预览相机所看到的内容。请注意,在预览期间,你无法自由地在场景中移动。实际上,你甚至无法选择对象。所以,当你想回到编辑场景时,记得将其关闭。
根据我们迄今为止所展示的内容,我们应该选择哪种投影类型?我们将选择透视模式。因此,现在,将你的相机节点的投影设置恢复到默认值。由于 Godot 用相关属性装饰了检查器面板,大小属性将被Fov属性取代。
让我们专注于这个新属性以及我们想要应用到相机节点上的其他一些更改,在下一节中。
调整游戏中的相机设置
在本节中,我们将讨论你刚刚接触到的这个新术语Fov,并展示我们应该应用到相机上的其他设置。如果你从开始就在制作自己的关卡设计,那么我们提到的相机位置和旋转将毫无意义。这就是为什么我们会给你一些一般性的指导,以传达练习的精神。同时,希望你看到的截图能帮助你更好地将我们的关卡条件与你的条件对齐。
首先,我们快速定义一下这个新术语。视场角(fov)是以度为单位测量的设备感知世界的角度。实际上,如果你把你的眼睛当作设备,你的眼睛也有一个 fov 值。这是一个高度技术性的领域,所以我们将在进一步阅读部分提供一些链接,让你自己探索。
目前,我们对这个主题的实用应用更感兴趣,因为它与你游戏的横竖屏模式或游戏是为 PC 还是游戏机而设计的相关。Godot 使用的默认值70是一个相当合理的平均值,适用于大多数情况。然而,这个默认值也假设你将以横屏模式运行你的游戏,因为它由保持宽高比属性决定,该属性设置为保持高度。
由于玩家可能拥有不同尺寸和分辨率的显示器,应用程序必须选择高度或宽度作为真实来源(SOT)并相应地应用其他必要的转换,以避免视觉扭曲。有时,这种做法会导致在视觉上方和下方出现黑色条带。这种方法被称为信箱模式,在电影行业中也被用来将具有方形宽高比的电影转换为现代更宽的屏幕(从 4:3 到 16:9 或 16:10 的比率)。
如果你将鼠标悬停在检查器面板中的Fov属性上并阅读工具提示,你会看到你可以根据游戏将使用的宽高比设置多个值。因此,我们将让你选择最适合你条件的最优值。不过,我们提供了以下截图来展示不同保持宽高比和Fov值的排列组合:

图 12.4 – 相机位置相同,不同宽高比约束和 fov 值
差异真是太大了!在不改变相机设置的情况下,不同的排列组合会产生大量不同的结果。让我们通过讨论Fov(视场角)的更高和更低值来结束Fov这个话题,以便你在自己的项目中做出更好的决策。
最后,你应该选择的Fov值将取决于玩家的观看距离,这并不是你事先就能真正了解的事情。然而,你可以遵循一些惯例。例如,控制台游戏使用较低的Fov值,因为它提供了一个类似缩放的视图,以补偿屏幕和玩家之间的距离。通常,控制台游戏玩家会坐在距离屏幕几米远的沙发上,而屏幕通常很大。
另一方面,PC 玩家通常距离显示器不到一米,因此可能最好使用更高的Fov值。这增加了沉浸感,因为玩家会感觉到通过这种稍微缩小的视角,他们能够看到更多的世界。然而,众所周知,过高的Fov值也会造成晕动症。当你的大脑被迫处理过多的世界信息时,你会感到胃部不适,尤其是在第一人称射击(FPS)游戏中。
Fov 计算器
有一个方便的计算器可以帮助找到理想的Fov值:themetalmuncher.github.io/fov-calc/。选择你的屏幕的宽高比和方向,计算器将消除一些猜测。显然,如果你允许玩家在游戏设置中更改屏幕分辨率,你必须通过编程更新游戏使用的Fov值。
为了完成本节内容,我们将保持Fov的值为97,并选择Keep Width作为画面的宽高比,因为这从艺术角度来看效果更好。此外,由于这个级别非常小,因此不需要让摄像机跟随游戏角色。不过,我们仍然可以尝试选择Camera节点的最佳角度和位置,以便看到大部分场景。正如之前提到的,我们的值并不会意味着太多。然而,尝试更改Camera节点的Translation和Rotation Degrees值,以匹配这里所看到的:

图 12.5 – 摄像机的最终位置
这种视角将给我们带来一些东西。首先,它覆盖了最关键的角度。克拉拉只能在这个级别上走到某些地方。此外,并非每个可通行的地方都是重要的。尽管如此,从这个角度看,似乎没有遗漏任何重要的东西。
其次,根据她叔叔的笔记,破碎的手推车后面有一个背包。从这里看很难看到它,因为那个角落的壁灯光线不足以使背包非常明显。所有这些都是故意的,因为我们希望克拉拉手里拿着火把,这样额外的光线就足以让她或玩家注意到一个重要的物品。
最终,我们希望玩家能够看到并交互世界中的对象,特别是背包,因为它持有通往楼上的钥匙。游戏设计师常用的玩家与世界交互的常见工具是鼠标事件,这是我们接下来将要发现的。
检测用户输入
鼠标事件是你在视频游戏中可以检测到的许多用户输入类型之一。其他最常见类型是键盘或游戏控制器事件,这些内容本书不会涉及。然而,检测鼠标动作的原则与其他类型事件的处理方式相似。我们之所以更关注鼠标事件,是因为你还需要处理一个额外的复杂层,这正是本节将要讨论的内容。让我们直接深入探讨。
在传统的桌面应用程序,如文本或视频编辑软件中,界面通常充满了许多按钮、菜单等。你期望此类软件的用户点击这些指定的位置,这是应用程序的制作者预期并为你准备的。那么在 3D 游戏中,你该如何处理这个问题呢?
看看,当你点击屏幕上的任何位置时,你实际上是在点击一个 2D 表面。因此,基于x和y轴定义点击的坐标原本是有意义的。让我们使情况更简单。我们不是点击任何复杂的东西,只是屏幕的中间。通过知道显示器的分辨率,我们可以进行计算,并得出两个轴上都是分辨率一半的坐标。
让我们想象一下,在这个特殊情况下,我们一直在屏幕中央点击,我们看到的是图 12.5中的游戏世界。这个点击在我们的关卡中对应的位置在哪里?更有趣的是,如果你实现了一个可以移动到其他位置的摄像机,甚至因为游戏原因而旋转,你如何将相同的x和y坐标映射到 3D 空间中的不同位置?
这是一个具有挑战性的主题,并不总是容易解决,但让我们看看我们可以使用哪些技术来识别鼠标事件。
知道玩家在哪里交互
行业中有一个常见的检测玩家在 3D 世界中指向位置的技巧。这被称为光线投射,YouTube 上充斥着针对这一特定主题的教程,不仅限于 Godot 引擎,还包括其他游戏引擎。它假设你从屏幕上点击的位置向 3D 世界中的某个位置投射一条光线。由于游戏引擎已经能够通过考虑游戏对象相对于摄像机的位置来渲染游戏,而摄像机恰好是你的屏幕,因此计算已经为你完成,在一定程度上。
尽管这种技术把你引向了正确的方向,但你仍然不知道路径上的哪个对象是你想要选择的。也许射线的一个不幸的类比可能是一个足够强大的子弹,它穿越了它所连接的所有对象。所以,如果射线投射产生了许多结果,你必须消除你不需要的。幸运的是,有一个更直接的方法。
只为我们要的对象分配检测逻辑会方便得多。例如,我们可以在我们的场景中引入一个新的模型——具体来说,是一张羊皮纸——直接放在码头上的木条上。一旦玩家点击这个对象,我们就会触发隐藏在UI.tscn场景中的笔记。通过这项工作,你也将练习一些你在早期章节中使用过的方法。以下是需要采取的步骤:
-
从
Parchment.glb创建一个新的场景,并将其保存为与同一文件夹中的Parchment.tscn。 -
由于有一个默认的环境正在生效,场景将会很暗,很难跟随接下来的步骤。要禁用它,打开
Parchment.tscn。 -
在根节点下添加一个StaticBody节点。
-
在你刚刚引入的最后一个节点下添加一个CollisionShape节点,并在Inspector面板中将一个New BoxShape分配给其Shape字段。
-
通过单击此新形状来扩展它。在Extents部分的X、Y和Z字段中分别输入
0.15、0.14和0.06。这个形状应该包含模型。 -
仍然在它的Translation部分的Z字段下的
0.05。
我们还没有完成羊皮纸场景,但让我们休息一下,解释一下发生了什么。
我们在我们的工作流程中添加了第一个PhysicsBody类型的节点,使用了一个StaticBody节点。还有其他类型,例如KinematicBody、RigidBody,等等,如果你想要提供基于物理的游戏玩法。由于我们将放置在世界的羊皮纸对象不会移动,我们选择了StaticBody。
然后,我们将一个碰撞形状分配给了StaticBody节点。如果你想让引擎检测到你的对象相互碰撞,那么在游戏对象中添加碰撞是必要的。通过这样做,游戏引擎可以确定这些对象的未来轨迹和速度。
游戏引擎可以检测的一种碰撞类型是当玩家使用输入设备与对象交互时。例如,玩家可能将鼠标移到对象上,点击此对象,甚至想要将其拖动到其他地方。在所有这些可能性中,我们只对检测玩家何时点击羊皮纸模型感兴趣。我们将在下一节中学习如何区分我们想要的精确事件。
区分有用的鼠标事件
我们已经构建了所有必要的机制来开始检测碰撞。我们包裹 parchment 模型的基本形状将充当传感器,以了解是否发生碰撞。在众多不同类型的碰撞中,我们主要对监听鼠标事件感兴趣,并且——更具体地说——检测鼠标点击。
我们将把这个对 parchment 的点击视为打开当前隐藏的 UI.tscn 场景的先兆。最终,我们将在 parchment 和 UI.tscn 场景之间建立一个通信线路。首先,让我们看看我们如何捕获碰撞并过滤出正确的类型,以便我们可以在以后触发我们想要的连锁事件。以下是操作步骤:
-
将脚本附加到
Parchment.tscn的根节点,并将其保存为Parchment.gd。 -
选择 StaticBody 节点并打开 Node 面板。
-
双击 CollisionObject 头下的 input_event 项。
-
按下
Parchment.gd脚本,如下所示:extends Spatial signal show_note func _on_StaticBody_input_event(camera, event, position, normal, shape_idx): if event is InputEventMouseButton and event.pressed: emit_signal("show_note")
理论上,我们现在正在跟踪 StaticBody 节点上的输入事件。然而,在实践中,由于生成此事件的碰撞形状正好位于 parchment 上,我们的设置将表现得好像你正在检测 parchment 本身的点击。以下截图显示了我们在编辑器中的进度:

图 12.6 – 我们正在将输入事件附加到 parchment 对象
我们捕获的输入事件足够通用,但我们正在过滤它,以便它仅在鼠标点击条件下有效。然后,我们通过发射 show_note 信号来转换这个点击的意义,但谁在监听这个调用?某个构造体可能能够理解这个信号——更具体地说,是 UI.tscn 场景。让我们接下来按照以下方式连接它们:
-
打开
UI.tscn并将脚本附加到根节点。将其保存为UI.gd并添加以下代码行:export(NodePath) onready var note_trigger = get_node(note_trigger) as Node -
打开
Level-01.tscn并在 Props 组中创建Parchment.tscn的实例。将这个新节点放置在码头木条上,使其相对靠近船只。 -
在 Scene 面板中选择 UI 节点。在 Inspector 面板中,这个节点将有一个 Note Trigger 字段。按 Assign… 并在弹出菜单中选择 Parchment。
-
返回到
UI.gd脚本,并添加以下代码行:func _ready(): note_trigger.connect("show_note", self, "on_show_note") func on_show_note(): $Panel.visible = true
这里发生了很多事情,只有几行基本代码。首先,我们为 show_note 信号准备了一个字段——以便它可以触发 on_show_note 函数。当这个函数作为玩家点击 parchment 的结果运行时,本质上代表伯特笔记的 Panel 节点将变得可见。
当你在第十一章“创建用户界面”中构建 UI 时,如果你没有完美地居中面板,你现在可以通过使用 3D 视图中标题栏的布局按钮来实现。如果你愿意,你可以将面板放置在任何你想要的位置。最终,当你按下F5并运行游戏,在你点击码头上的羊皮纸后,你会看到如下内容:

图 12.7 – 伯特给克拉拉的笔记在玩家点击羊皮纸时打开
请记住,关闭按钮已经连接好了,所以当你按下它时,会关闭笔记。如果你这样做,你可以通过点击羊皮纸再次打开笔记。谁知道一个简单的鼠标点击可以意味着不同的东西呢?在一个上下文中,按下平面表面相当于点击一个 3D 对象,然后触发其他游戏系统。在另一个上下文中,它是按下 UI 元素,如按钮。
灯台和蜡烛
如果玩家能够点击羊皮纸,他们不能点击周围级别的灯台和蜡烛吗?他们可以,但他们现在不会得到任何反应,因为你必须构建一个碰撞结构,就像我们对羊皮纸所做的那样。这是一项你可以作为练习来完成的任务。
我们不打算在我们的游戏中实现库存系统。然而,在那些采用此类功能的游戏中,常常会看到羊皮纸从世界中消失,并在玩家的库存中找到自己的位置。然后,玩家可以稍后点击代表他们库存中笔记的图标,再次调出笔记用户界面。在这种情况下,你的用户界面结构也必须监听来自不同结构的show_note信号,但原理是相似的。
目前没有库存系统并不会真正损害我们的工作流程,因为我们有更紧迫的问题,比如帮助玩家移动。尽管我们有一个有坚实地板的级别,但我们没有能够在上面站立的玩家角色。我们将在下一节中探讨如何引入一个角色并移动它。
移动玩家
你可能听说过,在现实生活中上下文很重要,因为上下文可以使一个普通的词或陈述显得特别糟糕或有趣。这在大多数技术领域都是一致的——更具体地说,当我们试图描述视觉或艺术方面时。有时,使用同义词是可以的,但区分可能至关重要——甚至有时是必要的。例如,在上一个部分的结尾,我们声称我们将移动一个角色。这可能是一种通过书本页面进行心灵感应的荒谬尝试,但如果你想象一个像克拉拉这样的两足生物用腿走路,挥动双臂,我们会错吗?
很可能你已经这样想过,但在这个时候你必须等待,因为我们甚至还没有在关卡上的两个位置之间移动一个对象。从上下文类比的角度来看,并不是每一次移动都必须涉及完整的动画。克拉拉的模型,或者更不用说一个普通的立方体,也可以通过遵循路径来移动。因此,将运动和动画视为两个不同的主题可能更为合适。这就是为什么我们将在本节首先处理运动之后,在触发动画部分稍后介绍动画。
现在你已经知道对象穿越场景和带有动画穿越之间的区别,那么一个重要的问题就是:如何检测移动对象的位置?让我们在关卡设计方面更加具体。我们有一个码头,我们最近在上面放置了一张羊皮纸。基本预期是玩家角色将站在这张羊皮纸旁边。一旦玩家读完笔记,我们希望他们能够到达背包,获取一把钥匙来解锁通往楼上的门。因此,我们需要一个机制来完成以下任务:
-
检测点击
-
寻找一个可能的路径
-
将玩家移动到他们想要的位置
在我们开始处理这些项目之前,我们首先需要两个关键成分:导航和导航网格实例。这两个节点携手合作,指定关卡中的一些区域为可通行区域。毕竟,我们不想让玩家到处走动或穿过物体,这就是我们在关卡周围放置的一些道具的重要性。
为了简洁起见,可以互换使用
尽管我们已经指出了运动和动画之间的主要区别,并声称我们不能互换使用这两个概念,但在我们即将在本节中探讨的两个节点方面,我们却很幸运。你很快就会看到,一个导航节点实际上无法在没有依赖导航网格实例的情况下完成其工作。我们将使用导航作为一个通用概念(除非另有说明)来讨论导航,而在技术上,我们可能正在描述导航网格实例节点的属性。
话虽如此,让我们创建玩家可以通行的区域。
使用导航节点创建可通行区域
我们在第九章 设计关卡中开始设计的关卡有一些不错但也很令人烦恼的特点。从视觉角度来看,道具及其在世界的放置看起来很自然。即使是像破损的手推车和鹿雕像这样体积较大的物体,当人们走在码头和门之间时,也都在视线范围内。有用性和杂乱无章的元素混合在一起。
说到杂乱无章,当我们引入Level-01.tscn并按照以下步骤进行时:
-
在根节点中添加一个Navigation节点。然后,在刚刚引入的最后一个节点下面添加一个NavigationMeshInstance节点。
-
将Floor、Columns、Rails、Props和Dock组拖放到NavigationMeshInstance节点下。
-
选择NavigationMeshInstance节点,并在Inspector面板中将一个新的NavigationMesh分配给其Navmesh字段。
-
点击并展开这个新属性,以便你可以执行以下操作:
-
在其Cell部分的Height字段中输入
0.18。 -
在其Agent部分的Max Climb字段中的
0.2处输入0.4。 -
在其Filter部分下打开Ledge Spans选项。
-
-
按下你的数字键盘上的7键以切换到Top Orthogonal视图。
-
在 3D 视口的顶部按下Bake NavMesh按钮。
如果你的关卡设计与我们不同,请尽量按照我们提供的步骤进行,特别是在你直接将我们的值转移到你的系统上时,这可能不适合。最终,你会看到类似这样的东西:

图 12.8 – 我们已经引入了一个 NavigationMeshInstance 节点并对其进行了配置
注意由Navigation节点引入的浅蓝色叠加。从引擎的角度来看,所有这些都可以通行。不过,有些地方有些尴尬。当你将Dock组拖入Navigation节点时,Water节点也随之而来。因此,它也被考虑为一个候选者。
如果这是一个Dungeons & Dragons游戏,你的玩家可能知道Water Walk咒语,并且能够在水网格上行走。在 Clara 的世界中没有这样的咒语,但如果你允许游戏有这样的机制和风味,这可能是一个需要考虑的因素。因此,我们最好通过以下方式改变其在层次结构中的位置,而不是完全移除水:
-
将Water节点移动到NavigationMeshInstance节点以外的其他地方——例如,在SpotLight节点上方。
-
同样,将Parchment从Props组中拖出。
-
选择NavigationMeshInstance节点并再次按下Bake NavMesh按钮。
在不同的层次结构中,新烘焙的可通行区域应该看起来像这样:

图 12.9 – 由于处于不同的层次结构,水不再可通行
通过确定哪些区域应包含在NavigationMeshInstance节点中,并在Inspector面板中调整值,你可以得到一个更精确的布局。最终,如果你能在玩家到达重要地点之前在他们面前放置一些障碍物,而不是让他们沿着一条完美的直线前进,你将创造更多引人入胜的游戏玩法。
如果你的关卡布局在某些关键区域(如购物车附近的背包)看起来不可穿越,那么就移动一些这些道具并烘焙一个新的地图。当我们介绍移动逻辑时,这将非常重要。
如果你想要更好地感觉哪些区域是可到达的,你可能想要将视图旋转到 Perspective。说到这一点,谁将走过这些区域?在我们深入研究更高级的角色模型(如 Clara)之前,我们应该先介绍最基本的玩家角色。
介绍基本玩家角色
在本章早期,在 了解玩家交互的位置 部分,当我们询问玩家如何与羊皮纸交互时,我们引入了一个 StaticBody 节点,因为该物体不会移动。我们还提到,StaticBody 是除了两个其他常用节点之外,你可以使用的许多 PhysicsBody 选项之一,如以下所述:
-
RigidBody:没有对自己有控制的物体属于这一类别。一开始,“rigid”这个词可能有些令人困惑,因为它传达了物体强度或灵活性的感觉。相反,你可以使用 RigidBody 节点来模拟足球或炮弹的运动。你通常会对具有此节点的物体施加力,这将指导物理引擎如何计算它们的轨迹、碰撞等。
-
KinematicBody:实际上可以控制自己在世界中如何行为的物体属于这一类别。通常,玩家角色使用此节点,但任何创建自己运动的系统(如真正的引擎或火箭)都需要使用此节点。
因此,我们最好的选择是使用 KinematicBody 节点来模拟玩家角色。我们现在将遵循以下步骤来创建一个非常简单的角色:
-
创建一个新的场景,并将其保存为
Player.tscn,位于Scenes文件夹下。 -
以 KinematicBody 节点作为其根节点。然后,在根节点下添加一个 CollisionShape 节点和一个 MeshInstance 节点。
-
为其 Radius 属性选择
0.4。 -
在 Transform 部分的 Rotation Degrees 下的 X 字段中输入
90。
- 为其 Radius 属性选择
0.4。在 Transform 部分的 Rotation Degrees 下的 X 字段中输入90。在Player中选择0.9。
这将创建一个胶囊形状,这是一种模拟玩家角色的快速方法。我们还选择了一个与我们所创建的网格很好地配合的碰撞形状。由于在 Player.tscn 场景中没有什么可看的,所以我们最好向您展示它在世界中的放置位置。在 Level-01.tscn 中创建其实例,并按照以下截图所示进行定位:

图 12.10 – 一个直立的长条形玩家角色
玩家角色,尽管现在看起来像是一个站立的小药丸,但现在它是世界的一部分,可以移动。它只需要被告知去哪里。在我们甚至不知道它应该去哪里之前,我们如何给它指令?为了解决这个谜团,我们不得不准备一个结构来捕捉点击。所有这些最终都会让我们重新审视在检测用户输入部分之前忽略的一个主题:射线投射。毕竟,它将帮助我们了解玩家在世界中的点击位置。
准备射线投射的点击区域
当您确切知道哪些对象应该是交互式的并接收鼠标事件时,我们在区分有用的鼠标事件部分应用的方法仍然有效。它涉及到游戏设计师的预期,因此基本绑定可以在早期完成,就像我们看到的。然而,如果这种情况并不总是可以预见,或者这种方法在更大规模上有多可行?
例如,如果我们给到目前为止使用的每个楼层模型都添加一个静态体节点,我们当然可以检测鼠标点击。然而,有时这已经有点太晚了。现在,我们的关卡中所有的地板块都是模型实例而不是场景实例,因为当时放下模型并完成关卡设计很方便。我们仍然可以尝试从地板模型中创建一个场景,但您仍然需要交换关卡中的所有地板资产。这是一项大量工作。
由于我们已经知道需要一个静态体节点来启动输入响应,我们还可以利用它。我们不必将其附加到每一块地板上,而可以指定一个与所有地板块占据的面积一样大的区域,并检测这个大块上的点击。以下是这样做的方法:
-
在关卡中添加一个静态体节点,并在其中放置一个碰撞形状节点。
-
在检查器面板的形状字段中分配一个新盒子形状。
-
展开这个新属性并调整其
9、1和8,但您可能希望在完成下一步后再调整这些值。 -
将
-1.05的位置调整到其顶部几乎与地板对齐,但略低于羊皮纸。我们将在移动玩家后讨论这个问题。 -
其X和Z值位于其子节点碰撞形状包围地板块和码头上的可通行区域的位置。
如果您切换到俯视图,可能更容易决定测量。以下截图中的蓝色正方形代表我们想要用作点击检测器的区域:

图 12.11 – 静态体节点覆盖所有可通行区域
你可能会想知道我们是否过度了检测区域,因为图 12.11清楚地显示它比可通行区域大得多。简短的解释是,当你点击在可通行区域外的区域时,路径查找算法会将玩家带到附近的地点,但永远不会到达玩家点击的确切位置。例如,如果你在水里点击,那么玩家角色将移动到点击位置尽可能接近,但仍然保持在限制范围内。
当你看到代码时,从技术角度来看可能会更清晰。话虽如此,让我们将一些代码附加到玩家角色上,以便它可以四处移动,如下所示:
-
打开
Player.tscn并选择根节点。 -
将
Scripts文件夹中的Player.gd文件附加到Inspector面板中的Script字段。
让我们解释一下我们刚刚应用代码的最重要部分。你可以参考这个代码块:github.com/PacktPublishing/Game-Development-with-Blender-and-Godot/blob/main/Chapter%2012/Resources/Scripts/Player.gd。前 10 行用于存储我们将要使用的一些启动值和结构。其中三个变量值得详细解释,因为其余的都是不言自明的。让我们更详细地看看它们:
-
camera:玩家场景没有Camera节点,但它需要访问一个相机来进行光线投射。因此,我们作为权宜之计使用了当前使用的相机。 -
space_state:这是我们进入 Godot 的PhysicsServer节点的入口,该节点监控哪些对象相互碰撞或相交。我们将使用这个变量来知道点击是否与地面连接。 -
nav:由于Level-01.tscn场景也包含Navigation节点,我们使用这种机制将Navigation节点注入到Player节点中。这样,Player节点就可以查询Navigation节点以找到可能的路径。
该脚本的其余部分由四个函数组成。尽管如此,其中两个函数承担了主要工作,因为_input和_physics_process方法本质上是将任务卸载到两个其他函数:find_path和move_along。我们本可以忽略这些后置函数,但当你能够将不同的功能分离到它们自己的函数中时,你应该这样做以保持代码整洁。
所有这些都是为了在find_path函数中实现光线投射,这是我们接下来要研究的。
使用导航节点进行路径查找
我们添加到场景中的大型StaticBody节点仍然不足以知道点击发生在地板上的哪个点。仅仅这样只能让我们知道玩家在那个区域点击了某个地方。所以,最终,我们仍然会使用射线投射来找到精确的位置,这样我们就可以开始构建通向该位置的路径。
为了这个目的,Player.gd脚本中的find_path函数将使用以下两种技术:
-
第一是射线投射,以确切知道玩家点击的位置
-
第二个问题是是否存在通向该位置的可能路径
如此,find_path函数中的前三行代码,如所示,就是关于射线投射的内容:
var from = camera.project_ray_origin(event.position)
var to = from + camera.project_ray_normal(event.position) * 100
var result = space_state.intersect_ray(from, to)
首先,我们要求相机系统告诉我们射线将从哪里发出。因此,我们将其存储在from变量中。这恰好是鼠标事件发生的地方。记住,尽管这个事件仍然在我们的监视器的 2D 表面上,但我们仍然没有关于我们在 3D 世界中点击位置的概念。
其次,我们要求相机系统告诉我们,如果我们从世界中的100单位处投射射线,它会去哪里。现在,我们知道要将射线拉伸到哪个位置。然而,这并不能保证这个射线会击中任何东西。因此,我们检查是否有任何东西与射线相交,并将其存储在result变量中。
因此,仅仅三行代码,我们就确定了我们点击屏幕上的位置和世界中的某个位置之间的直线。这次射线投射的结果可能是空的,所以明智的做法是检查是否有物体与我们的射线发生碰撞。只有在这种情况下,我们才能继续寻找路径。
这就是nav变量发挥作用的地方。因为它是对path数组的引用。
分离问题
在像在find_path函数中进行的寻路操作需要射线投射的情况——换句话说,当两个系统彼此紧密相关时——可能不需要将射线投射逻辑分离成单独的函数。我们将在触发动画部分处理更高级的游戏角色时再次回顾这个概念。
总有一天,你会得到一条可通行的路径,尽管这并不自动使玩家角色遵循路径。我们需要更多的代码来实现这一点。
将玩家移动到他们想要的位置
我们已经使用射线投射来检测玩家想要去的位置,并查询了导航节点以找到到达该位置的最近路径。我们现在准备指示玩家节点沿着路径的不同点移动。
Player.gd 脚本中的 move_along 函数接收一个路径并逐个步骤处理它。由于起点和终点之间不太可能有直接的路径,路径将由一系列中点组成,直到玩家到达他们的最后一个停靠点。这就像现实生活中走路,在到达目的地之前你需要进行路线修正。自然地,如果路径为空或所有步骤都已处理,我们提前终止函数。
否则,我们通过检查到下一步的距离是否在某个特定阈值内来在两个停靠点之间移动玩家。说到这个阈值,这可能是一个讨论注意事项的好时机。在编写和测试此代码的过程中,我们有时阈值值应该是 3,有时则是 1。如果你注意到玩家角色行为古怪,你可能想尝试不同的值。这将在 Godot 的后续版本中得到修复,正如官方文档中所述:
当前导航系统存在许多已知问题,并且不会总是返回预期的最优路径。这些问题将在 Godot 4.0 中得到解决。
经过所有这些艰苦的工作,我们现在离玩家角色移动只剩一步之遥,所以让我们继续,如下所示:
-
切换到
Level-01.tscn并选择 Player 节点。 -
使用 检查器 面板,在其 Nav 字段中点击 分配… 按钮,在即将出现的弹出窗口中选择 Navigation 节点。
-
按 F5 并在关卡中的不同位置点击。
当我们测试场景并将角色从码头移开时,看起来是这样的:

图 12.12 – 玩家角色现在可以在世界中移动
你现在必须能够通过在地板上或甚至在水中按下按钮来移动玩家角色。最近的点将被选为目标。此外,当你四处移动时,尝试点击码头上的羊皮纸。如果它放置得恰到好处,位于 StaticBody 节点的下方,那么你将无法触发音符。如果是这种情况,请调整在 为射线投射准备可点击区域 部分为 StaticBody 节点设置的 Y 位置,或者将 Parchment 节点在 Y 方向上向上移动。
只要点击不相互竞争,羊皮纸就会触发音符。如果玩家角色不在附近,音符打开后它将立即移动到羊皮纸附近。如果你此时点击 关闭 按钮,你可能会注意到奇怪的行为。音符将按预期关闭,但玩家角色会突然移动到 关闭 按钮下方。这就像笔记 UI 允许我们的某些点击通过,而路径查找逻辑则捕捉到那个调用。
幸运的是,对于这种行为有一个快速的解决方案。如果你将_input函数替换为_unhandled_input,那么一切都会好起来。如果这两个看起来相似且不清楚,你可能想在手册中找到它们的细微差别:docs.godotengine.org/en/3.4/classes/class_node.xhtml。记住它的用途,以便快速解决许多 UI 问题可能是有价值的。
总结
如果你已经开发了一段时间的视频游戏,你可能已经熟悉了迭代和增量工作流程的概念。例如,到目前为止,拥有不可摧毁的箱子是可以接受的。让我们考察一个你现在想要这些箱子可摧毁的场景。
你不仅必须考虑某些条件发生,例如玩家是否有正确的物品来摧毁这些箱子,而且你还需要准备在摧毁时刻触发的动画。这些既是程序性的也是艺术性的变化,并且在某种程度上可以轻松完成。当你烘焙可通行区域时,Navigation节点认为箱子是固体障碍物。然而,在这种新的动态情况下,你还需要更新NavigationMeshInstance节点以适应新的条件。
如果玩家刚刚摧毁的箱子不再是世界的一部分,而那个特定区域确实应该是可通行的,你必须通过烘焙一个新的地图来更新可通行区域。幸运的是,可以创建多个NavigationMeshInstance资源并将它们保存到磁盘上,以便根据需要交换它们以适应动态情况。
有时候,先进行原型设计更有意义。例如,我们的玩家角色看起来像一个胶囊来测试移动逻辑已经足够好了。让我们的头像看起来更像一个人而不是一个白色药丸会更好。让我们看看我们如何能够实现这一点。
触发动画
在第五章 设置动画和绑定中,我们探讨了在 Blender 中创建动画。然后,在第七章 将 Blender 资源导入 Godot中,我们看到了如何将模型导入 Godot 引擎,并使用AnimationPlayer节点测试模型的不同动作。本节中我们将展示的步骤应该足以将克拉拉引入游戏,但如果你需要提醒如何创建和导入动画,你可能需要查阅这两章。
既然我们已经完成了玩家的移动,那么缺少的就是将克拉拉引入我们的工作流程,并播放适当的动作,比如当她站立时闲置,当她移动时行走。
当我们构建Player.tscn并给这个场景附加脚本时,我们已经创建了一个基本的玩家角色。它很简单,但场景结构是一个很好的起点。按照以下步骤进行:
-
在FileSystem中点击
Clara.glb,然后打开Import面板。 -
在Animation标题下的Storage下拉菜单中选择Files (.anim)。参考第七章的分离动作部分,将 Blender 资产导入 Godot,以记住这一步骤的需要。
-
按Reimport设置克拉拉的依赖项。切换到Scene面板。
-
打开
Player.tscn并删除MeshInstance节点。 -
将
Clara.glb从FileSystem拖到Player节点上。这样,旧的MeshInstance节点将被Clara节点替换。 -
点击根节点并将Translation值归零,因为应用于基本胶囊形状玩家的值现在不再有效。
-
调整
1.2。
这里的主要目标是用克拉拉替换旧的MeshInstance节点,并调整CollisionShape节点,以便正确进行碰撞检测。编辑器现在应该看起来像这样:

图 12.13 – 克拉拉替换了无聊的 MeshInstance 节点
使用这种方法,你可以轻松测试玩家的代码,然后稍后用实际模型替换测试模型。如果你是主要开发者,并且还在等待同事的艺术作品,这可能很有用。
改善角色的外观是一个很好的进步。它看起来更加吸引人。我们将对其运动做同样的处理,因为如果你现在运行游戏,你可能会注意到一些奇怪的行为。克拉拉将会像旧胶囊网格一样移动,因为它缺少两个主要特性,如下所示:
-
面向它当前移动的方向
-
显示出走路的迹象,而不是看起来像一根在表面上滑动的棍子
还有一个问题,但这个问题很小,你可以在不需要太多讨论和解释的情况下修复它。曾经持有简单MeshInstance节点的Player节点,不得不在世界上稍微提高一点。你可以将这个新的Player节点降低到码头的高度,这样克拉拉的脚就能与之连接。如果你不做任何更改,克拉拉看起来就像是在悬浮,然后一旦她的运动逻辑启动,就会以对角线移动。
对于其他两个主要问题,我们不得不比仅仅改变对象位置更深入地挖掘。不过,我们首先需要更新用于Player节点的脚本,所以我们需要做以下事情:
-
在
Player.tscn中选择根节点。 -
将其脚本与
Scripts文件夹中的Clara.gd交换。 -
按F5键,享受看到克拉拉像普通人一样四处走动的场景。
欢呼——她开始走路了!
它怎么会这么快就发生了?我们将在本节的剩余部分致力于发现Player.gd脚本接收了哪些更改以适应我们正在经历并无疑享受的新行为。
理解克拉拉如何四处张望
递增和迭代的工作流程是理解克拉拉如何环顾四周的简短且非技术性的答案,这是我们建议你在任务最初看起来巨大无比时牢记在心的事情。例如,我们最初关注的是基本移动,这在Player.gd脚本中实现。在某个时候,当你知道基本测试系统正在工作时,就是将事情提升到下一个层次的时候了。这就是Clara.gd脚本发生的事情。
现在我们将解释我们将基本滑动动作转变为更复杂的行走动画所采取的步骤。就引入新变量而言,我们使用了一个简单的标志:is_moving。我们跟踪这个标志,以便了解克拉拉是否在移动。这个新变量的使用将在我们做出的其他一些更改的背景下进行讨论。
新术语 - 标志
在编程世界中,标志是一个表示特定条件已满足的变量。它通常用于确定系统的行为,就像一个具有假/真或开/关状态的电开关,因此它们通常被称为布尔标志。然而,标志可能具有不同种类的预定值。
克拉拉的自然行为是朝向鼠标光标的方向看。让我们再次提醒你,尽管光标正在我们的显示器 2D 表面上移动,但我们需要将基本投影到 3D 空间中,以找到正确的方向。我们在Player.gd脚本中的find_path函数中已经做了这件事。既然我们现在想要为确定克拉拉应该朝向哪里进行类似的射线投射,我们就从find_path中提取了那些常见的行,将其放入自己的函数get_destination中。
因此,你在代码中能找到的更常见用途和重复,将它们分离成它们自己的函数会更好。在Player.gd案例中,为了简化,我们故意忽略了这一点。然而,我们现在既有find_path函数,也有turn_to函数依赖于get_destination。
正如find_path依赖于_unhandled_input函数一样,turn_to函数也在使用相同的鼠标event。说到turn_to函数,让我们在这里更仔细地看看它:
func turn_to(event):
if is_moving:
return
var direction:Vector3 = get_destination(event) *
Vector3(1,0,1) + Vector3(0, global_transform.origin.y,
0)
look_at(direction, Vector3.UP)
首先,尽管我们还没有看到moving标志被设置在哪里,如果克拉拉在移动,我们不想让她继续四处张望。因此,我们有一个早期的return语句来终止转向行为。然后,一旦我们通过get_destination函数确定了一个合适的方向,我们就触发 Godot 的内置look_at方法。
逻辑很简单,但确定turn_to中的direction向量的数学可能需要更多的解释。通常,get_destination的值就足够了,但看起来我们似乎是将返回值与另一个向量相乘,然后将其添加到另一个向量中。这是因为get_destination给出的目的地也包含了 3D 空间中的y轴。我们希望克拉拉保持她的姿势不变;换句话说,我们不想让她向上或向下看。这两个向量操作是必要的,这样她就不会以一种尴尬的方式旋转。
你可以通过移除向量操作并仅保留get_destination函数来亲自看到这种奇怪的行为。当你将鼠标光标移到克拉拉的身体附近时,她可能会突然围绕自己的脚旋转,有时甚至翻转过来或侧身。由于 2D 和 3D 之间的投影而产生的复杂性,你将来必须考虑到这一点,这在控制游戏角色时是一个常见的问题。
很好,克拉拉正面对着鼠标光标所在的位置。这还是一个独立的机制,因为她可以在不移动的情况下做到这一点,正如你可能已经通过前面的代码块测试过了。如果她在行走时也能保持看向目的地,那就更好了。这将在move_along函数的增强版本中实现。让我们看看在这个新版本中我们是如何改进的。
向移动功能添加观察行为
当克拉拉静止站立时,看到她四处张望是件好事,但我们还希望她面对她要走的目的地。例如,如果你在墙边的板条箱附近(更像是屏幕的右侧)点击,她应该一直走到她清除了码头,然后转向向右看,然后继续前进。同样,当她在这个新位置时,如果你在远处点击某个地方,比如鹿雕像附近或码头再次,她应该转身并以自然的方式走回来。
这种行为可以很容易地添加到move_along函数中。按照现在的样子,该函数已经决定了克拉拉应该沿着路径走的剩余步数。当她朝着路径上的点走去时,她不妨看看她要去哪里。这就是为什么我们在move_along函数中的move_and_slide之后有一个简单的look_at函数调用。
其他有用的 KinematicBody 函数
我们一直在使用KinematicBody类的内置move_and_slide函数。在同一个类中有一个有用的函数,可能在玩家想要通过跟随斜坡到达更高位置的场景中很有帮助:move_and_slide_with_snap。同样,你可能想要检查玩家是否应该执行下一步。如果是这样,test_move方法可能很有用。
此外,is_moving的状态在以下代码行中确定:
if !path or path_index == path.size():
is_moving = false
$Clara/AnimationPlayer.play("Idle")
return
is_moving = true
注意,与我们在Player.gd中做的那样,if块检查路径上是否还有剩余的步数。正是在这一点上,我们可以设置is_moving标志的状态。因此,与原始版本不同,新的move_along函数的if块确保当克拉拉没有剩余路径可走时,移动逻辑被关闭。
如果玩家点击不同的位置并且确定了一条新路径,那么我们将打开移动标志。只要克拉拉有中间点可以跟随,她就会遵循我们描述的相同步骤——面向正确的方向,走必要的距离,面向下一个方向,走,清洗,重复——直到她不再有任何步骤可走。
除了决定is_moving标志的状态外,在if块中还有关于动画的其他一些事情在进行。让我们在下一部分中关注这一点。
为克拉拉播放正确的动作
我们已经在第七章的分离动作部分中看到动作与动画之间的关系,将 Blender 资产导入 Godot。它们就像原子对分子一样。因此,当我们想要触发一个模型的动画时,我们实际上意味着要触发一个特定的动作。我们将最终利用这个概念,让克拉拉行动起来。
我们已经看到,我们对Player.gd脚本所做的改进如何为克拉拉的行为增添了额外的风味。话虽如此,她也可以从动画部门的微调中受益。这正是move_along函数内部正在发生的事情。
我们已经知道如何确定克拉拉是否应该移动,并且我们通过is_moving标志来跟踪这一点。因此,这是触发她所需动作的正确时机。因此,当她不再应该移动时,我们触发她的is_moving设置为true。
当我们将Clara.glb作为Player.tscn场景的一部分,并变成一个克拉拉节点时,一个AnimationPlayer节点已经包含在内,并且已经设置了克拉拉的所有动作。我们迄今为止编写的代码已经知道这个AnimationPlayer节点在内部结构中的确切位置。如果您导入了一个具有不同Scene树的模型,那么您可能需要修改您的代码以找到到达AnimationPlayer节点的正确路径。
通过书籍的静态页面传达动画是困难的,但当我们把克拉拉移到靠近相机的柱子附近时,这就是它的样子:

图 12.14 – 克拉拉现在可以在关卡中移动
此外,请注意场景中的光线条件是如何影响她在暗淡和明亮的地方行走时的模型的。在下一章中,我们将关闭一些光源。因此,当她或玩家在周围行走时,我们可以使用她持有的火炬来照亮场景。
我们在动画主题中不能不提到一个高级主题:混合动画。我们不会详细讨论它,但对于您想在项目中使用的更高级的动画案例来说,它值得提及。
混合动画或动作
过了一段时间,大多数与计算机相关的事物中使用的名称可能开始看起来像它们之间有某种联系。在前几章中,我们使用了 Blender 来构建资产、纹理、动画等等。我们现在将要讨论的混合与 Blender 本身无关。
我们的点按式冒险游戏目前非常简单。Clara 运行Clara.gd脚本并整合这些其他动作。
在某个时候,当您拥有一个更复杂的系统,其中您触发的动作结束时为另一个动作腾出空间时,您可能会注意到这些动作会突然开始和结束。然后,想象一下音频播放器中的交叉淡入功能如何使歌曲曲目切换时整个体验更加愉快。如果您有一种方法可以平滑地将一个动作的结束过渡到下一个动作的开始,那会怎么样?您可以使用AnimationTree节点轻松实现这一点。
不幸的是,我们的页面数量有限,无法涵盖如此高级的主题。尽管如此,官方文档有一个关于这个主题的非常棒且内容丰富的教程页面。它还附带了许多动画 GIF,这些动画在书籍的静态页面上是无法体验到的。因此,最终,您可能更倾向于通过遵循docs.godotengine.org/en/3.4/tutorials/animation/animation_tree.xhtml上的说明来探索如何混合动画。
在高速动作游戏中,混合动画被广泛用于在不同攻击和奔跑状态之间过渡时,应该看起来更加流畅。在我们当前的情况下,我们没有这种功能并不会损失太多。
我们在点按式冒险游戏中取得了很大的进展。让我们总结一下,数一数我们的胜利。
摘要
本章终于涵盖了我们期待已久的相机主题,即从第四章的调整相机和灯光开始。现在您有多种选择,从简单的相机类型到跟随目标的插值相机类型。如果您想尝试一些高级功能并涉足 VR,您还可以使用ARVRCamera。
由于你现在有了合适的相机,可以展示游戏世界,而不是在编辑器内看到事物,现在是调查如何与世界本身互动的合适时机。为此,我们提出了光线投射作为可能的解决方案,但很快又放弃了它,转而使用碰撞检测,这提供了更大的灵活性和精度。我们使用这种技术来检测特定游戏对象(羊皮纸)上的点击。在这个过程中,你使用信号作为解释玩家点击作为触发器来打开音符的方式。
接下来,你研究了创建一个简单的游戏角色并在关卡中移动它。有时,游戏设计可能缺少关键元素,而有时关卡可能需要更多帮助。一旦你作为一个独立开发者或作为团队对游戏的方向感到满意,你就可以通过引入更复杂的系统来提高难度。
这正是测试Player.gd并在更高级的Clara.gd脚本中改进它之间发生的事情。最终,你能够找到适合 Clara 在世界中移动的位置,并通过使用适当的动画周期来实现这一点。既然你已经掌握了基础知识,现在如果你想使用更多动作和特殊条件来与玩家进行世界交互,那么就需要你自己来增强脚本。
在下一章中,我们将介绍一些额外的工具,这些工具将增加我们一直在构建的交互性,例如播放声音、有条件地触发某些事件以及切换到另一个关卡。
进一步阅读
尽管我们教了你如何从技术上设置相机,但在选择最佳相机设置方面还有一个完整的艺术层面。你可能想查看涵盖诸如构图和叙事等主题的在线课程和书籍。这里提供了一些例子:
-
www.udemy.com/course/composition-and-perspective-for-stunning-visual-art/ -
www.cgmasteracademy.com/courses/93-composition-for-concept-art-and-illustration/
如果Player.gd和Clara.gd文件中的代码看起来非常相似,并且很难逐行比较,那么你可以使用一个在线工具来帮助你查看和突出显示差异:http://www.tareeinternet.com/scripts/comparison-tool/。
我们的游戏不涉及跟随玩家的敌人角色,但它将采用类似的方法。例如,一旦敌人检测到玩家,它也必须进行路径查找以找到玩家的位置并向其移动。许多视频游戏 AI 书籍都涵盖了玩家检测和寻找等主题,例如我们给出的例子。因此,由于大多数 AI 主题通常是通用的,所以不要害怕阅读各种材料。你总是可以在你的 Godot 项目中稍后应用你在其他地方获得的见解。
第十四章:以声音和动画结束
我们已经进入冲刺阶段。我们最初在第九章,设计关卡中开始的努力,通过使关卡在第十章,使用光影使事物看起来更好中看起来更有趣,这使我们能够在第十一章,创建用户界面中实现基本用户界面。我们在第十二章,通过摄像头和角色控制器与世界交互中构建了新的机制,这样我们就可以与我们创建的世界进行交互。因此,克拉拉现在能够按下她叔叔留下的羊皮纸,她也可以四处走动。这一切都很不错,我们可以通过细化一些粗糙的边缘来更进一步。
这里很安静!当她走动时,我们应该触发一个音频文件,以模拟她的脚步声。在此同时,我们还将添加背景音乐和效果,以更好地反映克拉拉所在环境的特性。
你一定注意到了,当克拉拉四处走动时,周围级别的壁炉和蜡烛会照亮她。她能否用她手中持有的火炬做到同样的效果?当然可以!这可能会帮助她看到推车后面的背包。事实上,她将不得不使用火炬来更好地看清楚,因为我们将在本章中关闭所有的光源。
我们将在 Godot 中发现一个新的节点,以了解玩家角色是否进入了一个区域。通过这种方法,游戏设计师通常会触发游戏中的事件,如陷阱、与任务提供者的对话等。我们的事件选择将是克拉拉在靠近壁炉和蜡烛时点亮它们。
最终,她将到达背包那里,她会拿起钥匙。我们在这个游戏中不关心库存系统,但我们将考虑这个钥匙对象作为打开门的必要条件。因此,一旦条件满足,我们需要那扇门为我们打开。然而,这扇门并没有在 Blender 中设置好动画就进入了 Godot。这是我们看看如何在 Godot 内部创建基本动画的机会。
当所有条件都满足时,包括打开门以模拟清晰的楼梯路径,我们将用另一个关卡替换当前关卡。那个特定的时刻将标志着我们小游戏结束的标志,但你可以将它带到你想去的地方。
这将又是一个包含许多不同主题的章节。说到这一点,以下是我们执行到目前为止所提出的计划的标题:
-
播放音乐和音效
-
创建反应点
-
在 Godot 中构建简单的动画
-
加载另一个关卡
到本章结束时,你将完成我们点按式冒险游戏的核心理念。你不仅将构建并使用新的系统,而且还将使这些系统根据世界或角色事件进行条件化。
祝你好运,享受吧!
技术要求
如果你愿意继续在上一章结束的地方继续,那完全没问题。然而,你需要一些额外的资源来完成本章的工作。你可以将这些资源与你的项目文件合并。它们位于本书仓库中的Finish文件夹旁边的Resources文件夹中,该仓库可在github.com/PacktPublishing/Game-Development-with-Blender-and-Godot找到。
播放音乐和音效
音乐和音效有时可以决定人们从电影、戏剧和当然,电子游戏中获得的乐趣。当做得正确时,它们肯定会增加沉浸感。在本节中,我们将从技术角度探讨音乐和音效的使用。在你的空闲时间,我们建议你调查多媒体中声音设计的艺术方面,我们将在进一步阅读部分提到一些资源。
在第八章 添加声音资源中,我们讨论了 Godot 用于在不同维度播放声音的不同节点,如下所示:
-
AudioStreamPlayer3D 用于向玩家传达 3D 位置信息。它最常用于 FPS 游戏中,不仅前后方向很重要,而且来自高处音频流也很重要。
-
AudioStreamPlayer2D 用于那些不需要声音来源方向具有深度信息的游戏。大多数平台游戏是这种类型的良好例子。
-
AudioStreamPlayer 用于背景音乐,因为它可能被认为是一维的。
在这三种类型中,两种似乎是我们目的的正确候选人。我们想要播放背景音乐,所以我们将使用AudioStreamPlayer。然后,当克拉拉四处走动时,使用AudioStreamPlayer3D是有意义的。
后一种情况可能不太明显,我们当然也可以使用常规的AudioStreamPlayer来播放脚步声,但当我们到达那里时再解决它。我们最紧迫的任务是设置环境音乐。
设置背景音乐
在第十二章 通过摄像头和角色控制器与世界交互的理解摄像头系统部分,我们展示了如何使用外部场景结构,如Game.tscn,来保存我们在第九章 设计关卡中构建的水平。像我们这样的包装结构也是放置更多全局规模结构的好地方,例如音频流。然而,在我们继续我们的初步计划之前,我们想讨论一个替代方案。
虽然玩家角色是游戏世界的一部分,但我们决定通过Level-01.tscn场景将其放置在关卡内。如果你将其放置在Game.tscn中,为了保持事物的分离和清洁,你将不得不想出一种方法来连接两个Game.tscn。这并非不可能,但会使事情变得不那么方便。
同样,你应该将播放背景音乐的节点放在哪里?虽然我们可能希望每个关卡都播放其主题音乐,这会引导我们使用Game.tscn。当我们讨论加载另一个关卡部分中的加载不同关卡的话题时,我们希望我们提出的方案会更有意义。
让我们看看如何执行原始计划。打开Game.tscn场景并执行以下步骤:
-
在根节点处添加一个AudioStreamPlayer节点,并将其重命名为BackgroundMusic。
-
将
Native Dream.mp3从FileSystem拖动到新节点的Stream属性。 -
在检查器面板中打开自动播放选项。
-
按F5键并放松。
我们使用的音乐片段大约 2 分钟长,并且将由 Godot 自动循环。因此,当克拉拉或玩家探索关卡时,不会感觉过于重复。
说到在更高层次放置背景音乐结构,你还可以使用另一种方法:单例,也称为AutoLoad。对于绝对初学者来说,这些是在你的项目中可以使用的最高级别的结构。当你启动游戏时,它们总是存在,并且按照你在项目设置的AutoLoad选项卡中定义的顺序加载。通过这种方法,你可以使用一个专门的场景作为音乐的单个来源。你可以在docs.godotengine.org/en/3.4/tutorials/scripting/singletons_autoload.xhtml了解更多信息。
一些玩家关闭游戏音乐是为了专注于音效。在接下来的部分,我们将介绍我们的第一个音效。我们期望克拉拉的走路会触发一个合适的音效,即脚步声。
条件播放声音
让我们看看如何在本文档中条件性地播放声音文件。实际上,实现这个目标并没有什么神奇或特别的方法。这与知道克拉拉是走路还是静止不动相似。在第十二章的通过摄像头和角色控制器与世界交互部分,我们在move_along函数中实现了两行额外的代码,以触发克拉拉显示的正确动作,从动画上展示她当前的状态。
我们可以通过启用声音文件播放功能来利用相同的函数。话虽如此,现在可能是讨论我们的一些实践的好时机。看起来我们过度使用了 move_along 函数的含义。你可能会认为我们目前的努力仍然是一个类似于写作练习中写作草稿的阶段,然后专注于后续的编辑。
有时,在开始大量工作之前,你可能就能推断出良好的架构,可能是因为你之前做过类似的事情。然而,通常情况下,情况可能并非如此,你的发现以及你为了提出一个高效的架构所做的决策可能需要等待以后。一旦你注意到你可以从当前结构中提取出共同的部分,你应该这样做。然而,在决定游戏玩法时,过分关注创建最有效的代码结构和信息流的细节可能不是最好的时间利用方式。
因此,现在,我们将脚步声添加为 move_along 函数内的一个额外元素,直到我们需要一个更高效的方法,如下所示:
-
打开
Player.tscn场景并添加FootSteps。 -
选择
FootSteps.wav并切换到导入面板。然后按照以下步骤操作:-
打开循环和归一化选项。
-
按下重新导入。
-
-
将
Footsteps.wav从文件系统拖动到检查器面板中的流字段。 -
打开自动播放和流暂停属性。
-
在
Clara.gd脚本中,按照以下步骤操作:-
在触发她的行走动作后,输入
$FootSteps.stream_paused = false。 -
在触发她的空闲动作后,输入
$FootSteps.stream_paused = true。
-
我们在这里使用的方法在第八章的按需播放声音效果部分进行了讨论,添加声音资产,当重复触发声音文件时,可能会听起来像声音被卡住。
此外,我们还打开了循环功能并归一化了音量。循环功能是显而易见的,因为我们希望她的脚步声在她行走时不断重复。然而,归一化选项值得多说几句。我们在这个项目中使用的声音文件是从多个来源收集的。这使得所有这些文件具有相似的音量水平变得困难。有些会响亮,有些会安静。我们打开的功能调整了声音文件的音量,使其与其他文件处于相似的水平。
当你现在运行游戏时,你会听到背景音乐,然后四处点击并等待克拉拉走到指定的地点。你听到她的脚步声了吗?很可能是刚刚听到。我们将在通过分贝理解音量部分稍后调整音频音量。
目前,如果在 Godot 中展示一个实用的功能可能更好。可能会有这样的时候,您希望对正在播放的一些声音文件应用特殊效果。Godot 提供了多个音频通道,也称为音频总线,通过它可以决定哪些文件将在特定通道上播放,以便您可以在选定的通道上应用特定的效果。
我们现在假设存在这种情况,并将脚步声在它自己的音频通道上播放。让我们看看如何操作,如下所示:
-
在 Godot 引擎底部部分展开音频面板。在音频面板右上角点击添加总线按钮。
-
将此
SFX重命名。 -
选择FootSteps节点,并在Bus的下拉选项中选择SFX。
在 Godot 中,脚步声现在将在不同的音频通道上播放。显示我们所做更改的界面在图 13.1中。

图 13.1 – 我们正在自己的总线上播放声音效果
通过这种方法,一个专门的音频通道将播放您想要的声音。如您在图 13.1中底部SFX总线在音频面板中看到的那样,音频被发送到主通道。当所有音频源合并并处理完毕后,它会被送到扬声器。此外,通过使用音频总线的添加效果下拉菜单,您可以应用并堆叠通过此通道的效果。
虽然您能听到两段音频,但它们在音量上可能存在竞争。在下一节中,我们将稍微深入探讨音频音量是如何工作的。
通过分贝理解音量
每个行业都有其商业机密和独特的实践,声音工程师也是如此。当他们谈论声音的响度时,他们使用一个称为分贝的单位,标记为dB。如果您习惯了公制系统,这相当于十分之一贝尔,类似于分米是米十分之一。然而,贝尔究竟是什么?
维基百科有一个页面,提供了关于分贝的相当数量的技术信息。因此,我们将向您提供在项目中使用分贝的实际方面和/或潜在问题。
与地震震级测量类似,分贝是一个相对的刻度,每次您将声音级别提高 6 dB,声音的振幅就会加倍。因此,-6 dB 意味着您将振幅减半。就数值而言,0 dB 是数字音频系统将使用的最大振幅。任何高于此值的值,即正值,都会被截断。所以,您可能仍然能在 0 dB 以上听到一些声音,但随着分贝的增加,声音会失真。因此,在挑选值时,您将使用负数范围。
此外,人类听觉有物理限制。声音在-60 dB 到-80 dB 之间不再可闻。因此,最终,你的工作范围是从-60 dB 到 0 dB。如果所有这些都令人困惑,关于分贝可能有一个重要的事实你需要记住。0 dB 表示声音从音频应用程序导出时的正常振幅。如果 0 dB 的基线太安静,你可能需要在源头上进行修复,而不是在 Godot 中选择更高的 dB 值来调整。
话虽如此,我们可以轻松地降低振幅。这正是我们打算对背景音乐所做的那样:
-
打开
Game.tscn并选择BackgroundMusic节点。 -
调整
-12,甚至-18。
由于你现在能够更好地从背景音乐中辨别脚步声,你是否注意到克拉拉的脚步声在她靠近摄像机时变响,在她走向洞穴尽头时变轻?这要归功于AudioStreamPlayer3D节点的 3D 音频处理行为。如果你想更清楚地感知这种效果,可以随时暂时关闭背景音乐,专注于克拉拉脚步声的方向性。
谁在听?
Camera节点内置了一个Listener构造,这使得我们可以确定声音是从哪个方向传来的。在某些情况下,我们可能希望摄像机位于世界的某个角落,而监听器位于另一个角落。因此,创建一个单独的Listener节点不仅可能,而且当你想要模拟麦克风远离摄像机放置的情况时,这将非常有用。
如果你想要更多练习播放声音文件,我们建议你在Audio文件夹中添加一个声音效果到ButtonPress.wav。
看起来世界正在通过播放动画和声音文件对我们的行动做出反应,这很好。在这些努力中,我们主要通过鼠标点击直接参与。在下一节中,我们将发现世界如何在没有玩家直接干预的情况下对玩家角色做出反应。
创建反应点
当玩家点击羊皮纸时,游戏通过用户界面显示羊皮纸上写的内容。当玩家点击世界中的特定位置时,克拉拉通过播放行走动画和脚步声走到那个地方。这些都是玩家端的直接交互,这让我们讨论游戏应该对间接事件做出反应的情况。
尽管没有点亮,克拉拉手持火炬。你已经知道如何在 Godot 中使用Light节点。因此,在Clara节点内放置OmniLight靠近火炬网格很容易。我们的基本预期是,当她走过地板上的蜡烛和墙上的壁灯时,她会用火炬点亮它们。因此,游戏需要知道她靠近某些物体时的情况。
让我们先给克拉拉一个可以携带的火炬,然后我们可以讨论这个火炬如何影响关卡中的其他对象,如下所示:
-
从
Clara.glb创建一个场景,并在Torch002下放置一个OmniLight节点。 -
Y轴上的
0.75可能就足够了。 -
选择
d6d58e用于颜色,并在阴影部分开启启用。
由于OmniLight是火炬网格的子节点,每当AnimationPlayer节点控制火炬时,灯光会随之移动。这也是一个很好的例子,展示了如何使用 Blender 动画并通过 Godot 节点增强它们。
我们有一个专门的Clara.tscn场景,但Player.tscn场景仍然不知道这个新进展。它仍然使用旧的模式引用。因此,你必须删除Player.tscn并实例化Clara.tscn。场景面板看起来不会有太大的不同,但现在它将会有克拉拉手持点燃的火炬。测试你的场景,让克拉拉四处走动,尤其是在门口附近。火炬的光线将与她的行走周期同步。
克拉拉似乎手里拿着正确的工具来点亮那些蜡烛和壁灯。是时候添加触发区域,让世界能够对她的存在做出反应了。这就是接下来要发生的事情。
在世界中放置触发点
我们在第十二章的通过摄像头和角色控制器与世界交互部分的为射线投射准备可点击区域中使用了一个StaticBody节点来检测用户点击,这样我们就可以推断出克拉拉应该移动到哪里。当你知道一个代理,很可能是玩家,会直接触发一个系统时,这很有用。有些情况下,游戏对象会自由地自行行动,并且它们也应该从等待被触发的系统中引发响应。本节将涵盖这种情况。
到现在为止,你可能已经注意到了关于路径查找和玩家目的地的一个奇怪行为。我们设置的StaticBody延伸到地板块与墙块相交的地方。因此,它成功地捕捉到了地板上的点击。然而,如果你点击任何远离或沿着墙壁的地方,路径查找可能会给你一个意外的结果。如果你将StaticBody进一步延伸,类似于它覆盖水的方式,那就没问题了。你可以参考第十二章的图 12.11,通过摄像头和角色控制器与世界交互,来观察StaticBody的放置并调整它以捕捉远处的点击。
一旦确定了目的地,克拉拉将通过靠近道具来向它移动。其中一些物体是触发某些事件的理想选择。为此,我们将使用Area节点,它继承自与StaticBody相同的内部结构。这些节点是相似的,因为它们都源自同一个地方,但提供不同的结果。
尽管我们可以在关卡中为每个触发区域放置和定位一个 Area 节点,就像我们在许多其他节点中所做的那样,但考虑到我们想要为壁灯和蜡烛做这件事,打开我们已有的专门场景似乎更有意义。为此,你将按照以下步骤操作:
-
打开
Candles_1.tscn并在根节点下放置一个 Area 节点。 -
打开 Node 面板,并双击 body_entered(body: Node) 项。
-
按下
LightSwitch.gd脚本。按照以下方式更改它:func _on_Area_body_entered(body): print(body) -
在你刚刚添加的 Area 节点下放置一个 CollisionShape 节点。
-
在 Inspector 面板中为 Shape 属性定义 New BoxShape。
print 语句中的数字可能在你机器上看起来不同,但当你运行游戏时,你将在 Output 面板中看到类似 StaticBody:[StaticBody:2025] 的内容。我们刚刚从添加的 Area 节点得到了一个碰撞结果,但它撞击了什么?它正在检测覆盖所有地板块和一些水面区域的通用区域。
我们需要排除所有不需要的候选者,以便这个触发区域只对我们的玩家活动做出响应。有多种方法可以做到这一点。在介绍一个非常简单的方法之后,我们将详细解释一个更复杂的方法。现在,用以下代码替换你刚刚看到的函数:
func _on_Area_body_entered(body):
if body.name == "Player":
print("Hello, Clara!")
我们所做的更改在 Candles_1.tscn 中,当克拉拉在清除对接区域后向右转时,它将蜡烛组固定在桶上。因此,按 F5 运行游戏,并按照描述将她移到蜡烛附近。你会看到 Output 区域仅在进入那些蜡烛的空间时显示打印消息。图 13.2 将帮助你看到预期的效果。

图 13.2 – 仿佛蜡烛感知到克拉拉靠近并欢迎她
使用这种方法,我们只关心是否知道进入 Player 的 body 的 name。如果是这样,我们可以触发下一系列事件。然而,在我们开始处理我们的初始意图之前,以下是一些关于我们提到的更高级检测方法的说明。
了解更好的碰撞检测方法
Godot 的 PhysicsServer,这是一个负责执行所有应受物理规则(如重力、碰撞、相交等)影响的对象的计算的系统,使用层系统来跟踪对象的位置。这不像你可能在图形编辑应用程序(如 Adobe Photoshop)中看到的视觉层。尽管如此,它很相似,因为如果对象位于不同的层上,那么你可以定义这些层如何相互交互。因此,允许这种功能的结构被称为 Godot 中的 Layer。
此外,如果所有对象始终处于同一层,那么您可能不得不求助于诸如名称检查之类的解决方案。这很简单且有效,但它可能会很容易变得难以控制,因为谁会想为每个游戏对象选择一个独特的名称?毫无疑问,我们之前编写的if块会变得越来越长,以过滤哪个特定的对象进入了区域。为了消除这种情况,Godot 还有一个称为遮罩的结构。
通过一种巧妙的方式创建多个if块,检查什么与什么相撞。从某种意义上说,这种检查将在控制其他不太复杂情况的if检查中为您完成。
以下图示显示了您可以在当前配置的区域节点中找到的层和遮罩选项:

图 13.3 – 使用碰撞层可能是一种另一种检测方法
虽然这种方法有效且有价值,但在我们当前的情况下设置它并通过本书的页面进行解释将是不高效的。相反,我们将利用可用空间来展示其他实际应用。尽管如此,这仍然是一个在您未来的项目中可能必须依赖的重要架构选择。因此,我们建议您通过访问docs.godotengine.org/en/3.4/tutorials/physics/physics_introduction.xhtml中的碰撞层和遮罩部分来了解这一点。
我们更紧迫的问题是,当克拉拉靠近那些蜡烛时我们会做什么。让我们看看她对世界的影响。
点亮蜡烛和壁灯
我们一直在为克拉拉与她周围的世界互动奠定基础。我们最新的努力是通过Candles_1.tscn中的print语句进行近距离检测,但我们正处于一个使其更有趣的好位置。
要真正理解克拉拉对世界的影响,我们应该先从关闭该层的部分灯光开始。切换到Level-01.tscn场景并执行以下步骤:
-
选择所有
Candles_1.tscn和Candles_2.tscn实例。 -
在检查器面板中关闭已点燃属性。
-
对关卡中的所有壁灯重复前两个步骤。
-
按F5运行游戏并移动克拉拉。
大气效果,对吧?当克拉拉走到触发输出面板消息的同一位置时,关卡将看起来如下:

图 13.4 – 克拉拉依赖她手中的火炬
她手中的火炬足以让她看到去往的方向。然而,点亮她旁边的那些蜡烛会更好。我们已经在Candles_1.tscn中完成了艰苦的工作,所以剩下的只是如下内部开启OmniLight:
-
打开
LightSwitch.gd脚本。 -
将
_on_Area_body_entered函数中的print语句替换为is_lit = true。修改后函数将如下所示:func _on_Area_body_entered(body): if body.name == "Player": is_lit = true -
按F5运行游戏,并将克拉拉首先移动到相同区域,然后移动到不同位置。
当克拉拉这次靠近相同的蜡烛时,那些蜡烛将会被点燃。具体效果可能因她站立的位置而异,可能有点难以看到。所以,当她离开那些蜡烛时,你将真正感受到她对世界的印记,如图 13.5所示:

图 13.5 – 克拉拉正在从她刚刚点燃的蜡烛中获得一些帮助
这只是克拉拉互动的一个蜡烛游戏对象。我们还有一个蜡烛场景,Candles_2.tscn,以及一个独立的烛台场景,Sconce.tscn。我们可以轻松地复制到目前为止所做的工作到这些其他场景中,如下所示:
-
首先打开
Candles_1.tscn,然后右键点击区域节点,并在上下文菜单中选择复制。 -
接下来打开
Candles_2.tscn,然后右键点击根节点,并在上下文菜单中选择粘贴。 -
打开节点面板,然后按照以下步骤操作:
-
右键点击列表中的body_entered项,并选择断开所有连接选项。在即将出现的确认屏幕上按下确定按钮。
-
在列表中双击body_entered项。在即将出现的屏幕上按下连接按钮。
-
通常,我们不需要进行第三步。当你在不同场景之间复制粘贴节点时,信号不会传递。因此,我们必须手动移除看似活跃的信号并重新绑定它。幸运的是,这两个蜡烛场景都使用相同的脚本,我们已经有事件处理程序。这就是为什么我们不需要编写编程部分。当你像我们这样在不同场景之间传输节点时,请记住重新连接信号。Godot 4 可能已经修复了这种行为。
因此,运行游戏,让克拉拉走过所有的蜡烛。当她靠近时,蜡烛会依次被点燃,以下是她这样做时的体验:

图 13.6 – 克拉拉走过蜡烛后,所有的蜡烛都亮了
我们建议你将相同的程序应用到Sconce.tscn场景中。这次,虽然要改变2,但你可能想要调整到适合你条件的东西。或者,你可以将整个区域节点稍微向前移动,使其与连接到墙的两个烛台扩展对齐。只要烛台延伸出足够的空间,克拉拉就会触发它。
那么,你还能将这个想法应用到哪里呢?一个简单的例子可能是引入陷阱或敌人对玩家的位置做出反应。在敌人的情况下,他们也可以利用我们在关卡中放置的相同导航节点进行路径查找。此外,在这种情况下,如果敌人跟随玩家一段时间后放弃,这是常见的。如果距离没有缩短,而玩家逃跑得足够快,敌人通常会返回他们指定的巡逻区域,而不是试图追赶玩家。
我们不会在这个游戏中引入这种机制。然而,这可能是你可以追求的更高级游戏功能之一。如果你对敌对玩家行为真的感兴趣,那么我们建议你阅读一些关于游戏开发的人工智能书籍。市面上有很多选择,我们将在进一步阅读部分为你提供一个简要列表。
我们还需要创建两个额外的触发区域。一个是在克拉拉靠近该区域时,位于购物车后面的背包。另一个是她接近通往楼上的门时。让我们从背包开始。
添加背包的触发器
这种努力将与我们在蜡烛和烛台上的做法相似。既然你已经知道通过使用区域节点可以引入交互性,我们将展示一些稍微新颖的内容。
当玩家与世界互动,特别是与游戏对象互动时,他们会感觉到自己对这些物品有控制权。例如,玩家刚刚发现靠近蜡烛会使它们亮起。这不仅是游戏可以有的叙事和故事元素的一部分乐趣。在这个时候,游戏设计师需要交织另一层复杂性。也许,靠近蜡烛只是先决条件,玩家还应该点击蜡烛。
无论游戏设计师期望玩家满足什么条件,向玩家提供反馈是至关重要的。当玩家自己尝试时,他们会得到负面或正面的反馈。这种无害的试错法可以很容易地用来代替教程。提供反馈的一种简单可靠的方法是我们已经看过的。那就是播放声音。
对于背包练习,我们将结合播放音频文件和响应区域效果。一旦克拉拉像对待蜡烛一样靠近背包,背包将播放一个声音文件,通知玩家她已经捡起了钥匙。以下步骤显示了如何操作:
-
使用
Backpack.glb创建一个场景,并将其保存为Backpack.tscn,存放在其原始文件夹中。 -
将
CollectItem.wav放置到流字段中。 -
在X和Z轴上添加
-2。你可能想要选择在场景中合理的值。只要克拉拉有足够的空间到达这个区域,事情应该就会顺利。使用图 13.7作为参考。 -
为根节点创建一个
Backpack.gd脚本,并将其保存在同一文件夹中。激活 Area 节点的 body_entered 信号,这将向脚本添加一个模板函数。然后,按照以下方式修改脚本:extends Spatial signal key_collected func _on_Area_body_entered(body): if body.name == "Player": $AudioStreamPlayer.play() emit_signal("key_collected") -
将
Level-01.tscn与Backpack.tscn的实例进行交换。
我们正在遵循与玩家检测蜡烛时使用的相同原则。这次,我们不是启用灯光,而是播放一个短声音效果。我们选择了 AudioStreamPlayer 节点而不是其 3D 版本,因为我们不希望这个声音效果受到其与摄像机的距离的影响。然而,这是一个完美的机会,你可以交换并尝试两者以查看差异。
声音效果命令之后是自定义信号的发射。简单来说,我们将 body_entered 信号转换成了 key_collected 信号,这将在 在条件满足时播放门动画 部分中用于更高级的场景。
如第三步所述,图 13.7 展示了 Area 节点的相对位置。

图 13.7 – 背包的触发区域偏移,以便克拉拉能够到达
目前,壁炉和蜡烛在点亮时不会播放声音效果。这可能是一个简短而有趣的练习,你可以使用 TorchWhoosh.ogg 文件。默认情况下,文件的 Loop 功能是开启的。所以,记得在 Import 面板中关闭循环后按下 Reimport 按钮。
在制作一些游戏对象交互功能的列表中,拱门是最后一个。我们的工作流程将与之前相似,但还会考虑到在本节中定义的 key_collected 信号。
与门交互
你已经相当自由地使用了 Area 节点一段时间了。所以,你现在应该已经习惯了它。在本节中,你将最后一次使用它来完成交互性的主题。这将是用于门的场景,你也将使用我们最近创建的自定义信号。
由于一些步骤将非常相似,我们将给出更简短的说明,以便专注于独特部分,如下所示:
-
从
Doors_RoundArch.glb创建一个场景,并将其保存在原始文件夹中。 -
将
Scripts文件夹中的Doors_RoundArch.gd脚本附加到根节点。 -
添加两个
LockFiddling和OpenDoor节点。对于这两个节点,分别使用LockFiddling.wav和OpenDoor.wav作为它们的 Stream 属性。 -
在根节点上添加一个具有其依赖项和要求的 Area 节点,例如其碰撞、信号和位置。图 13.8 应该有助于展示我们放置 Area 的位置。
-
将
Level-01.tscn场景中现有的门资产与这个新场景进行交换。同时,将背包资产分配到 Inspector 中的 Backpack 属性。 -
按下 F5 键,让克拉拉直接走到门口。
在你看到编辑器中我们最近更改后的效果后,我们将更仔细地关注这个新场景使用的脚本。

图 13.8 – 这应该为克拉拉在门前的空间足够了
场景布局与您创建的其他示例非常相似,但不是只有一个,而是有两个音频流节点。它们的名称表明了我们试图实现的功能。这一次,克拉拉站在门前可能不足以单独完成,因为我们预计她首先找到了钥匙。
让我们分析一下 Doors_RoundArch.gd 脚本,看看我们是如何工作的。你可以参考这个代码块,链接为 github.com/PacktPublishing/Game-Development-with-Blender-and-Godot/blob/main/Chapter%2013/Resources/Scripts/Doors_RoundArch.gd。
我们有一个标志变量来跟踪是否已经收集了钥匙。只有当 on_key_collected 函数运行时,这个变量的值才变为真。所有这些都依赖于背包变量是否发出适当的事件,这是在 _ready 函数中设置的。这就是为什么你使用 检查器 面板将背包对象绑定到门上,以便这两个对象可以通信。
在 body_entered 函数中,我们检查入侵对象是否是玩家。这就是标志变量发挥作用的地方。如果满足开门的条件,那么我们请求播放开门声音。否则,游戏引擎将播放一个声音文件,表明克拉拉在摆弄锁。
一种解决方案可能并不总是适用
本书展示的解决方案可能并不总是理想的,如果你的级别或游戏结构不同。甚至我们现在正在构建的游戏也可能从一种截然不同且更高效的架构中受益。架构的概念意味着你在场景中布局的游戏对象的层次结构,脚本如何共享公共变量,以及最终你的系统如何相互通信。没有金钥匙的解决方案,而是随着更多编码经验的积累、浏览论坛和参加会议(在这些会议中,经验丰富的开发者分享他们的战斗伤痕)而出现的最佳实践。
我们建议你尝试两种情况,即克拉拉直接走到门前听到禁止声。然后,让她拿起钥匙,钥匙的拾取声已经通知了玩家。最后,她可以再次站在门前听到门吱嘎作响。那扇门确实需要一些润滑!
尽管吱嘎声让我们觉得门打开时有些抗议,但我们还没有看到。到目前为止,我们已经成功地将我们在 播放音乐和音效 和 创建反应点 部分学到的不同学科混合在一起。现在是时候将缺失的动画组件添加到我们的工作流程中。
在 Godot 中构建简单的动画
在 第五章 设置动画和绑定 中,我们讨论了 Blender 和 Godot 引擎在动画需求方面的差异。总的来说,我们声称,如果你要动画比弹跳球和简单旋转对象更复杂的东西,使用 Blender 会更好。为了强调这一点,我们 绑定 并动画了一个蛇模型。同样,我们一直在使用 Blender 制作的类人角色,克拉拉。
然而,有时在游戏引擎中动画一些模型可能是合适的。我们现在讨论的主题是克拉拉站在其前面的拱形门的开门动画。如果你愿意,你仍然可以在 Blender 中打开模型,实现表示门开启的必要步骤,并将你的工作重新导入 Godot。这与其他任何带有动画的导入模型没有区别。
对于这样一个简单的任务来说,这有点过度了。我们仍然会使用 AnimationPlayer,但不是触发导入的动作,而是通过在时间轴上手动放置关键帧来创建自己的动画,以匹配我们打开门时播放的吱嘎声。
创建门动画
在你开始处理任何类型的手动动画之前,我们建议你仔细查看模型使用的 MeshInstance 节点。在我们的例子中,我们很幸运,只有两个。然而,这也可能是一个问题。
模型的网格显示了用于抓取和拉动以打开这样沉重的大门的金属环。遗憾的是,它们是同一个 MeshInstance 节点的一部分。这意味着它们不能单独动画。为了能够做到这一点,你需要在 Blender 中将这些部分分开并重新导出模型。然后,你将拥有更多可以工作的 MeshInstance 节点。记住,尽管任何一种选择都是可行的,但都伴随着权衡。更多的独立对象通常意味着自由,但如果没有必要,它们也会使 场景 面板变得杂乱。
目前我们不太关心门上的环。我们的目标是学习 Godot 的动画基础知识,这从打开 Doors_RoundArch.tscn 场景开始。之后,你将执行以下步骤:
-
在根节点下放置一个 AnimationPlayer 节点。这将自动在底部弹出 Animation 面板。如果没有,请按底部菜单中的 Animation 按钮。
-
在面板顶部区域按下动画按钮以显示上下文菜单,并在选项中选择新建。提醒一下,你在第五章中,设置动画和绑定部分使用了该上下文菜单中的加载选项。
-
输入
Open并按下确定按钮以确认。 -
通过在面板右侧时钟和循环图标之间的区域输入来设置动画长度为
2.3。
在最后一步中有很多类似名称的按钮或选项。因此,图 13.9将帮助你看到你最新努力后的编辑器外观。

图 13.9 – 开放动画的脚手架已完成
动画轨迹为空,但基础工作已完成。我们需要告诉AnimationPlayer对象的一个特定属性随时间如何变化。为此,你应该这样做:
-
在场景面板中选择Doors_RoundArch_L节点。
-
在检查器面板中展开变换部分。按下旋转度数属性的键图标。将出现一个确认弹出窗口。
-
按下创建按钮以接受提出的更改。
-
点击并拖动鼠标到
2.3的时间轴上的数字上。或者,你也可以在时间轴上方的区域输入它来移动时间标记。 -
改变
-60并再次按下键图标。这次不会出现确认弹出窗口。
如果你像移动时间标记那样来回刮擦时间轴,你现在将看到门绕其铰链旋转。说到这一点,这已经在第六章的设置原点部分中讨论过了,导出 Blender 资产。
此外,你也可以自由使用前进和后退播放按钮来测试打开动作。我们很快将程序化触发它,但我们应该首先处理门的另一部分,如下所示:
-
在场景面板中选择Doors_RoundArch_R节点。
-
在动画面板中将时间标记重置为
0。 -
按照前面的指令集的步骤 2-5进行,只有一个不同之处。这次标记
60,因为方向相反。
在两组更改之后,编辑器将类似于你在图 13.10中看到的样子:

图 13.10 – 门模型的两个部分已经设置了关键帧,因此进行了动画
这将在发生变化的点添加必要的键帧到时间轴上。由于我们希望门一次性打开,没有任何减速或卡住的效果,所以我们没有引入除我们使用的以外的更多键帧。如果你喜欢更复杂的情况,你可以将时间标记沿轨迹移动到你想要引入更多键帧的位置。
你刚刚创建的 打开 动画应该在条件下运行。我们已经讨论过,并在一定程度上实现了必要的条件。然而,我们并没有真的在门脚本中放置动画部分。让我们立即这样做。
在条件下播放门动画
在 与门互动 部分之前,我们已经将脚本附加到了门场景。这个脚本包含了检查玩家是否满足打开此门条件的所有必要规则。从那时起,我们还做了一大堆其他事情。所以,让我们总结一下到目前为止我们已经做了什么。
弧形门场景有一个 区域 节点,它会响应玩家的存在。门无论哪种情况都会提供听觉效果,但如果克拉拉已经拿到了钥匙,我们期望门会发出嘎吱声打开。恰如其名,我们应该触发 打开 动画。这个变化很简单,你需要做如下操作:
-
打开
Doors_RoundArch.gd脚本。 -
将
print(“Open Sesame!”)替换为$AnimationPlayer.play(“Open”)。 -
按 F5 运行游戏。让克拉拉先去拿钥匙,然后站在门前。
哇!通往楼上的一大障碍已经被消除。
虽然无法通过静态图像传达声音和视觉效果,但无论如何,以下是你辛勤工作的成果 图 13.11:

图 13.11 – 克拉拉只在从背包中收集到钥匙后才打开门
如果你将克拉拉移开并靠近门,动画和声音会反复触发。提出执行事件的必要条件很重要。然而,有时阻止它再次发生可能同样重要。你可能已经注意到了蜡烛的类似,也许令人烦恼的重复行为。某些效果应该只触发一次。
在这一章中我们还有许多事情要做。这就是为什么我们将为你提供一个快速指南来消除这种重复行为。通过嵌套或组合 if 块,你不仅可以确保条件当时已经满足,而且之前也已经满足。为此,你可能需要利用简单的布尔变量。如果解决方案没有出现在你的脑海中,你总是可以查看 GitHub 仓库中的成品。
到目前为止,克拉拉还需要做什么?嗯,她现在正站在那里等待上楼。在这个上下文中,上楼意味着加载另一个关卡,我们将在后面的 加载另一个关卡 部分发现。目前,我们还不确切知道我们何时应该加载下一级。让我们看看我们如何确定这一点。
等待门动画触发事件
当我们开始打开门时,加载下一级是很诱人的。话虽如此,你已经努力跟踪克拉拉的行为,作为开始门打开动画的先决条件。如果你立即切换到新级别,动画将毫无意义。
相反,我们应该等待打开动画完成。只有在那时,改变事情才更有意义。有两种常见但同样尴尬的方法来做这件事。我们将讨论这两种方法,这样在你我们放弃它们以寻找更好的替代方案之前,你就能了解它们,如下所示:
-
yield:在触发yield行(如加载新级别)之后,你可以添加yield($AnimationPlayer, “animation_finished”),这将使你等待动画完成。从某种意义上说,这就像是在等待。除非程序释放,否则不会发生任何事情。这个概念在 Godot 4 中将改变,以支持await命令,这是一个比在代码执行期间阻塞事物更宽容的架构选择。 -
yield表示你仍然让事情继续运行,这引入了2.3秒,因为这是我们打开动画的长度。然后,一旦时间到了,这个节点将触发一个超时信号,你可以为它编写一个监听器。
在我们的情况下,这种方法的使用是在你开始打开动画时立即开始计时器。由于计时器的等待时间将与你正在执行的动作同步,所以看起来就像在动作完成后立即加载新级别。
我们不会使用这两种方法,因为当你已经可以用你熟悉的工具集完成某事时,为什么还要让你的生活变得更复杂呢?我们不会改变方向,而是看看AnimationPlayer如何仍然能帮助我们,如下所示:
-
在
Doors_RoundArch.gd脚本中添加以下函数:func load_level(): print("What level?") -
选择AnimationPlayer节点,并按添加轨迹按钮展开上下文菜单。
-
在选项中选择调用方法轨迹。你将看到一个可供选择的节点列表。因此,在即将出现的屏幕上选择根节点,Doors_RoundArch。
-
将时间轴标记移动到
2.3秒。在动画轨道中,右键单击蓝色时间轴标记与函数的Doors_RoundArch条目相交的地方。为了更好地理解,请参考图 13.12以查看我们所说的位置。 -
在即将出现的列表中搜索并选择load_level。按F5运行游戏,并遵循之前必要的步骤来打开门。
一切都将保持不变,除了当门动画播放完毕后,load_level函数也会运行。由于显示门动画没有意义,我们宁愿显示编辑器的状态,如第四步所述:

图 13.12 – 当时间轴到达我们设置的关键帧时,将触发 load_level 函数
打开 动作的最后一帧是我们调用负责加载下一级的功能的地方。目前,它只打印一条语句。我们将在 加载另一个关卡 部分稍后探讨如何交换我们的当前关卡与一个新关卡。
当我们仍在构建简单的动画时,我们可以处理那些看起来有点静态的光源。
让灯光闪烁
我们在 第十章 中为介绍 灯光 节点到我们的游戏所做的 工作,即 使用灯光和阴影使事物看起来更好,并没有包括动画。尽管如此,自从那时起,我们一直在逐步改进其他一切。
因此,我们很乐意按照以下方式为我们的光源添加一些活力:
-
打开
Sconce.tscn并将一个 AnimationPlayer 节点添加到根节点。 -
引入一个新的动作。将其命名为
Flicker。 -
将长度设置为
2秒。同时,打开 动画循环 和 加载时自动播放。 -
按下 添加轨道 按钮,并选择 属性轨道。从弹出的列表中选择 OmniLight。这将显示另一个列表以供选择。
-
选择
0.0、0.4、1.3和1.9秒来打开上下文菜单并选择 插入关键帧。 -
选择每个关键帧,并在 检查器 面板的 值 属性中分别输入
8、6、7和5。 -
按下 F5 键,让克拉拉点亮壁炉架。它们应该开始闪烁。
在我们讨论更精细和高级的版本之前,以下是我们 动画 面板中的内容:

图 13.13 – 在壁炉架中的 OmniLight 上已定义闪烁动作
当你点亮第一个壁炉架时,现在看起来必须更加自然。然后,也许在第二个或第三个之后,舒适的闪烁效果看起来会令人不安地重复,不是吗?如果不同壁炉架之间有延迟,它们就不会同时触发 闪烁 动作。
实现这一点相对容易,但我们建议您首先复制 Sconce.tscn 并将其粘贴到 Candles_01.tscn 和 Candles_02.tscn 场景中。当我们将动画用于每个地方时,更容易注意到随机性的效果。
当所有光源都点亮时,整个关卡将看起来像是在脉动。让我们看看我们如何打破这种一致性,并按照以下方式在我们的内容中引入一些随机性:
-
在 AnimationPlayer 中关闭所有三个场景的 加载时自动播放。
-
打开
LightSwitch.gd脚本,并按如下方式修改_process函数:func _process(_delta: float) -> void: $OmniLight.visible = is_lit if is_lit: yield(get_tree().create_timer(randf()*2.0), "timeout") $AnimationPlayer.play("Flicker")
我们所有的光源都共享这个脚本。因此,这些更改将适用于所有实例。虽然我们并不赞成使用yield命令,但在这种情况下这样做相对无害。最后三行告诉引擎动态创建Timer,并随机在 0 到 2 秒之间选择Wait Time。当这个计时器响起时,Flicker动作开始播放。
尽管您复制并粘贴了相同的AnimationPlayer节点,该节点强制光源与具有完全相同值的长度和关键帧共享,但由于我们的最新更改,每个光源的Flicker动作都开始于一个延迟,这将产生足够的视觉差异。
此外,如果您想更加精致,可以添加另一个如light_energy这样的轨道,以改变光源的亮度。
总结
慢慢但稳定地,通过在这里和那里引入小的变化,无论是将它们放置在世界上形成非重复模式,还是通过动画一些游戏对象的关键特征,您将拥有一个更加完整和逼真的游戏体验。
有时候完成这个任务的方法会完全不同。例如,我们用来模拟水面效果的着色器并不使用像AnimationPlayer这样的节点,但我们仍然实现了运动效果。尽管如此,当水面在运动时,让那艘船看起来如此静止是令人误解的。在本节中获得的知识基础上,我们建议您将船模型变成一个场景,并为其添加类似船只的摆动动画。
虽然您应该对自己知道如何动画游戏对象的基本属性感到自信,但您遗漏了一个重要的点:克拉拉本应上楼。让我们帮助她完成这个任务。
加载另一个级别
在我们开始动画“让那里有闪烁的灯光”部分中的光源之前,我们已经准备好将克拉拉带到楼上。为此,我们使用了load_level函数的一个巧妙特性,它在输出面板上打印了一条语句,作为真实事物的替代。在本节中,我们将探讨如何交换现有的级别与另一个级别。
让我们提醒您,我们当前的水平,Level-01.tscn,被实例化在Game.tscn场景中,该场景包含一个change_scene节点,可以更改当前场景为另一个场景。然而,这可能是危险的,因为它将替换整个结构。在我们的案例中,这不仅仅是Level-01.tscn,而是Game.tscn中的所有内容,因为那是主场景。
我们提供的解决方案是一个比Level-01.tscn本身操作级别更高的过程。理想情况下,你的场景应该通知更高权威机构它们想要引入的整体系统的变化。实际上,这完全可以是通过Game.tscn场景来完成的,不仅可以通过它来加载新关卡,还可以处理游戏中的其他事情,比如保持日志文件,联系数据库存储重要更改,甚至联系第三方服务来展示广告。
现在我们已经确定了Game.tscn承担加载新关卡任务的重要性,那么我们该如何让它知道何时执行呢?你之前已经使用过信号来促进不同游戏对象之间的相互了解。这涉及到通过将脚本变量暴露给检查器面板,在另一个对象内部放置一个对象的引用。尽管我们仍然可以尝试这种方法,但还有更好的方式。
使用事件总线
当我们将变量暴露给检查器面板,以便脚本能够识别其他游戏对象并能够连接到它们的信号时,我们在某种程度上将事物耦合在一起。当对象和信号的数量增加时,这种方法将难以维护。有一种替代方案,称为事件总线,这可能有助于不断增长的依赖列表。
我们将在进一步阅读部分更详细地回顾这个概念,因为这个概念是可供你使用的更大选项集的一部分。目前,我们将满足于它的实际应用。以下是它所包含的内容:
-
在
Scripts文件夹中创建一个EventBus.gd脚本。向其中添加以下行:signal change_level(level) -
打开项目设置并切换到AutoLoad选项卡。
-
使用带有文件夹图标的按钮来查找
EventBus.gd脚本。 -
按下添加按钮,将此脚本添加到下面的列表中。
图 13.14显示了编辑器将看起来是什么样子。

图 13.14 – 我们的第一个单例已设置并准备好使用
我们刚刚将一个脚本添加到AutoLoad列表中。单例也是行业内对这个概念使用的另一个常见名称。这意味着只能有一个脚本实例。除了传统描述之外,在 Godot 特定的上下文中,一旦你将其引入AutoLoad选项卡,就始终只有一个副本;它也会为你加载并可供项目中的所有构造使用。
那么,谁将利用这个新脚本,因为它似乎没有连接到任何东西?毕竟,它只是存在那里,但由于AutoLoad使其始终可用,我们可以在门动画完成后使用它。
让我们从等待门动画触发事件部分重新评估我们的工作。当我们运行并等待Doors_RoundArch.tscn场景和load_level函数时。该函数体中目前有一行占位符代码,形式为打印一条简短的语句:什么级别?
那是我们最初打算加载下一级的地方。然而,根据我们在加载另一个级别部分的讨论,我们现在希望将此委托给Game.tscn场景。为此,我们创建了一个EventBus.gd脚本,将我们的请求传达给相关接收者。因此,你必须进行以下更改:
-
打开
Doors_RoundArch.tscn场景。 -
按照以下方式更新
load_level函数:func load_level(): EventBus.emit_signal("change_level", "Level-02.tscn")
在我们早期的努力中,游戏对象直接使用emit_signal命令。例如,背包正在发出一个key_collected信号。在这里,我们概括了这个想法。我们不再关心知道哪个对象在发出。我们使用一个高级结构,如EventBus,来为我们做这件事。图 13.15显示了我们所提出的新的架构图。

图 13.15 – 由于 EventBus,我们不再需要耦合结构
在背包的例子中,发出的信号被门直接捕获,以便游戏可以决定玩家是否完成了必要条件。因此,类似于现实生活中的通信方式,事件有两个主要部分:一个发射器和接收器。我们对发射情况进行了更新。让我们看看在接收器端我们可以改进什么。
监听 EventBus 信号
回到门和背包对象之间的关系,背包没有意识到门的存在,但门有一个我们在检查器字段中设置的域来引用背包。因此,当背包触发事件时,门已经在某种方式上监视着背包。
我们现在正试图避免这种类型的架构。我们不是直接使用对象来触发事件,而是告诉EventBus为我们做这件事。然而,在我们新的例子中,谁是门呢?换句话说,谁在监听我们的事件以及如何监听?简短的答案是Game.tscn场景。
让我们先实现一些代码。有时,它起到展示而非讲述的作用。然后,我们将解释其背后的原因。以下步骤显示了你在打开Game.tscn后应该做什么:
-
创建一个新的
Level。 -
将Level-01节点拖入这个新的Level节点。
-
创建一个新的脚本作为
Game.gd,并将其附加到根节点。你可以将它保存与场景文件一起。然后,你输入以下代码:extends Node func _ready(): EventBus.connect("change_level", self, "change_level") func change_level(level:String): var new_level = load("res://Scenes/" + level).instance() $Level.remove_child($Level.get_child(0)) $Level.add_child(new_level)
你看到那个_ready函数了吗?我们在这里使用了EventBus架构。这就是美妙的部分。这样,Game.tscn和Doors_RoundArch.tscn都不需要知道彼此的任何事情。它们通过EventBus共享和处理它们的责任。
在某个地方,在某个时刻,一个结构可能会触发一个change_level信号。我们只关心这个,在我们表达了对它的兴趣之后,我们也为自己准备好了如何处理它,以防事件得以实现。如果是那样的话,我们就在change_level函数内部处理它。
命名约定
有些人为了将函数视为信号的扩展,会保持它们的信号和事件处理程序(函数)名称相同。不过,Godot 的信号绑定会添加一个_on_前缀。将你自己的事件处理程序名称与信号名称保持一致可能有助于你将它们与 Godot 的绑定区分开来。然而,你也可以在你的绑定中遵循 Godot 的命名约定。
现在我们来分析change_level事件处理程序中正在发生的事情。当我们从拱门场景中触发信号时,EventBus以字符串的形式传递了一个参数:Level-02.tscn。change_level函数的第一行查找并加载这个字符串在项目的Scenes文件夹中。在找到匹配项并创建其实例后,我们想要存储这个新场景,因为我们仍然需要与当前场景做一些工作。在我们添加新场景之前,我们应该将其销毁。
由于我们对$Level.remove_child($Level.get_child(0))做了一些更改。只有在之后,我们才添加新的关卡。
你只剩下一件事要做。按下F5,让 Clara 完成触发门开启所需的所有步骤。一旦门打开,游戏就会带你上楼到一个新的关卡。你应该会看到图 13.16 展示的内容。

图 13.16 – 欢迎来到我们的新关卡
恭喜!你已经引导 Clara 在黑暗中找到收集钥匙的方法,这把钥匙解锁了这个新关卡的门。她可以从这里继续她的冒险。那里有一个箱子吗?不过,就在它前面有一个陷阱门,所以要注意。使用我们向你展示的工具,你可以继续创建新的条件和障碍,让玩家去克服。这取决于你的想象力。
我们现在将本章的剩余部分用于讨论你根据我们的指南所做的选择,以及你也可以做的一些不同的事情。
讨论我们可以做出的某些选择
我们在这本书中的目标是教会你构建一个简单的点击冒险游戏所需的 Godot 引擎的必要部分。这是一个简单的声明,但它包含了两个不同的努力。一方面,我们应该尽可能多地教你关于游戏引擎的知识,而不要让它看起来像是在阅读文档。
另一方面,我们计划构建的游戏必须足够先进,但也要简单到足以通过阅读尽可能少的内容就能轻松跟踪其进展。事实上,一本书的页数是有限的。因此,我们在游戏制作过程中所做的某些选择受到了这些因素的制约。
你在自己的项目中也可能面临类似但不同的限制和难题。一个早期的计划,即使是糟糕的,通常也比没有计划要好。即便如此,一些情况可能真的很难提前准备,比如让游戏玩法有趣或实现良好的用户体验。
例如,关卡切换在技术上已经完成。然而,变化发生得太突然,玩家可能想要一个短暂的休息来整理思绪,回味他们在关卡中的旅程。你可以通过延长动画长度并将load_level函数推后到更晚的帧来实现这一点。这可能会看起来在门动画和下一级加载之间有一个健康的暂停。
更好的是,在切换发生之前让屏幕淡出可能是个好主意。实际上,这可能甚至从技术角度来看也是有益的。我们的第二级非常小,因此很容易从磁盘加载。然而,在更雄心勃勃的项目中,你的关卡可能充满了等待加载的游戏对象。
此外,如果你的游戏加载了之前的会话,你将不得不将游戏对象的状 态重置为其最后已知的值。在切换关卡或加载之前的游戏会话之间有一个通用的加载屏幕可能是一个更好的架构。通过遵循这一实践,你很可能会发现自己越来越多地从直接实现的系统中抽象出更多系统。
因此,这可能是我们能提供的最有价值的建议:如果你感到困惑或不确定如何处理某个主题,首先关注特殊情况及其实现,然后如果可能且必要,尝试将其推广。
摘要
这又是一个包含了许多移动部件的章节,它结合了游戏引擎的许多不同方面。让我们分析一下你的活动,这些活动有助于为从上一章继承下来的许多事物添加最后的修饰。
首先,你处理了背景音乐和音效。你已经在第八章中看到了声音的使用,添加音效资源,它涵盖了简单场景。在本章中,你学习了如何在适当的环境中使用音效资源。
接下来,你重新审视了你在第十二章中看到的主题,通过摄像机和角色控制器与世界交互——玩家检测。这次,你使用了区域节点作为触发区域,因为不会有直接的玩家交互,例如鼠标点击和动作。相反,当克拉拉处于正确的区域时,她会触发预定的事件。
当一个区域节点被积极使用时,你也能够在游戏对象之间传递信息,这些对象本质上是在分离和遥远的系统中。例如,当玩家到达背包时,满足打开门的条件。背包通过使用自定义信号让门知道发生了什么。
你用一个声音效果来象征钥匙的拾取。也许,一段简短的动画会用来显示一个 3D 钥匙向上移动并淡出的效果。有时,一个图标会出现在你的显示器底部,并在某些游戏中被称为快速栏的位置找到其位置。这两种方法都很好,但我们不想做其中任何一种。
由于本章旨在教授在 Godot 中创建动画,我们想展示一些足够复杂的案例,例如闪烁的光源或打开拱形门的两个部分,而不仅仅是将钥匙在游戏世界中向上移动。我们相信我们的努力具有更多的教学价值,你可以将其转移到其他简单的用例中。
在完成简单的动画后,尤其是门的开启动作后,是时候让克拉拉上楼了。为了实现这一点,你考虑了用新关卡替换当前关卡。虽然你可以通过让游戏对象之间传递信息来实现这一点,但你被介绍了一种更通用的方法,即通过EventBus架构来完成。
即使还有一章,这也是你应该给自己鼓掌的时刻。你已经构建了一个完全功能性的、尽管规模很小,的点对点冒险游戏。下一章将向你展示如何导出你的游戏。我们还将讨论你在游戏开发旅程中可以考虑的其他选项。
进一步阅读
正如承诺的那样,我们想和你分享一些关于声音管理艺术方面的内容。有时,一段音乐会有很高的节奏。这意味着它的每分钟节拍数(BPM)会更高。根据你正在制作的游戏或关卡,你可能想要选择或创建具有最合适的 BPM 值的音乐,以传达最佳的情感。
也有情况下,游戏玩法会要求在快节奏和慢节奏之间混合。这在角色扮演或动作游戏中很常见,玩家希望在陷入困境时感到紧张。例如,如果您的健壮、持枪的玩家角色在背景中播放经典或轻松音乐时躲在掩护物后面,这将绝对破坏沉浸感。同样,当两个动作区域之间看起来都很平静时,如果游戏播放快节奏音乐,您将无谓地让玩家感到紧张和困惑。
幸运的是,Udemy 上有许多关于这个主题的课程。在这里列出课程列表会对我们未能提及的其他课程不公平,因为列表很长。我们建议您使用游戏音乐关键词在他们的网站上查找。
在声音管理主题的最后,是使用补充技术的使用。如果您的游戏无法使用预先安排的声音资产,以下两种技术中的任何一种都将帮助您为不断变化的情况创建即时解决方案:
-
FMOD
-
Wwise
我们也在本章中简要提到了人工智能。这是一个广泛的话题,但以下是一些相关的书籍列表:
-
游戏 AI 入门,作者:伊恩·米林顿
-
游戏 AI 行为数学,作者:戴夫·马克
-
史蒂夫·拉宾的游戏 AI 专业 360系列:
-
游戏 AI 专业 360:角色行为指南
-
游戏 AI 专业 360:移动和寻路指南
-
游戏 AI 专业 360:架构指南
-
游戏 AI 专业 360:战术和策略指南
-
本章中我们提出的EventBus解决方案在许多编程圈子中经常被使用。有时它被称为带有邮局的EventBus。当您订阅的杂志即将出版最新一期时,出版商会通知邮局,您将收到您的订阅。
自计算机科学和特别是软件编程的诞生以来,开发者们已经注意到了表现出特定行为或性质的问题。这些常见问题的解决方案被称为设计模式。有很多资源处理这个话题,在经典软件的框架下。然而,近年来游戏开发者也得到了一些关注。无论特定领域如何,以下是一些例子:
-
《Head First 设计模式》:构建可扩展和可维护的面向对象软件,作者:埃里克·弗里曼
-
通过游戏编程学习设计模式,作者:菲利普-亨利·戈塞林
第十五章:结论
恭喜!
你已经创建了一个使用 3D 资产、集成对玩家输入做出反应的摄像头和角色控制器、触发视觉和声音效果以提供反馈、跟踪玩家进度并加载新关卡的点对点冒险游戏。
本章将涵盖一个通常在你到达终点时才会涉及的话题。我们将向您展示如何导出您的游戏,以便您可以将它与世界分享。话虽如此,我们还将讨论为什么你可能需要比仅仅等待结束更频繁地导出的原因。
之后,我们将完成引擎的技术部分。因此,我们将提出一些建议,更像是你在开发周期中可以遵循的指南,以便在开始项目之前或项目进行中提高效率。
最后,你将了解一些你可以使用 Godot 引擎的游戏类型。每个游戏引擎通常都是围绕至少一个强大和一些核心需求构建的。话虽如此,大多数值得信赖的引擎也支持最预期的功能。你将看到你在本书中学到的某些知识如何在新领域得到扩展。
这将是一个相对简短且肯定不那么技术性的章节。尽管如此,我们仍有一些以下主题需要处理:
-
导出你的游戏
-
提供不同的游戏体验
-
探索不同的游戏类型
到本章结束时,你将学会如何导出你的作品,评估你可以提供给玩家的不同选项,最终找到你可以考虑使用 Godot 引擎的游戏类型列表。
技术要求
本章将不会有任何新的资源。如果您愿意,您可以继续上一章的工作,或者浏览我们在这个书的存储库中保留的内容:github.com/PacktPublishing/Game-Development-with-Blender-and-Godot。
导出你的游戏
那么,你有一个游戏。接下来怎么办?你可以像之前一直做的那样在编辑器中继续运行游戏。然而,在某个时候,你很可能会想向你的朋友和家人展示它,甚至将其部署到公共场合供所有人查看。本节将教你如何导出你的游戏,以便你可以与世界分享你的创作。
尽管我们只涵盖如何在 Windows 上操作,但 Godot 引擎也能够将你的游戏导出到以下平台:
-
Android
-
iOS
-
HTML
-
Linux
-
macOS
-
通用 Windows 平台(UWP)
虽然导出通常是一个简单的过程,但检查文档总是明智的,因为平台收到的更新有时会改变你必须采取的步骤。您可以在以下位置找到最全面的指令列表:docs.godotengine.org/en/3.4/tutorials/export/.
那么,关于游戏机呢?
由于版权问题,控制台不在上述列表中,因为它们处于一个相对模糊的领域。作为开发者,你需要与控制台制作人保持联系并签署协议,以获得访问他们的工具和套件。本质上,尽管这仍然有一些技术方面,但在法律部门也有一些变动。
在我们开始处理 Windows 特定的导出设置之前,我们需要在我们的项目中添加或更改一些内容。
准备项目以进行导出
默认情况下,Godot 不会以全屏模式启动你的游戏,尽管这是大多数游戏所使用的。虽然最终我们的游戏将覆盖整个屏幕,但讨论一下当你打开项目设置时将看到的几个其他选项是值得的。更具体地说,当你访问显示组下的窗口部分时,你会看到两个功能,如下所示:
-
可调整大小:此选项使你的游戏屏幕可调整大小,就像你能够调整任何非全屏模式的应用程序一样。这个选项默认是开启的,所以请将其关闭。
-
无边框:当你的游戏不以全屏模式运行时,它将需要由你的操作系统定义的边框。开启此选项将移除这些边框和窗口的标题栏。顺便说一句,如今大多数现代桌面应用程序(如 Slack、Discord 等)都使用这个功能。
我们建议你开启全屏选项以及我们刚才提到的其他两个选项关闭。之后,这就是我们的项目设置屏幕看起来像的:

图 14.1 – 在导出游戏之前我们使用的项目设置
到目前为止,我们只专注于构建游戏本身,而没有担心开场、游戏设置或字幕屏幕。这些可以像任何其他 Godot 场景一样构建。然后,一旦你弄清楚这些场景之间的流程,你就可以使用change_scene函数切换到玩家请求的场景。或者,你也可以将这些屏幕作为隐藏场景放在Game.tscn文件中,并在需要时打开它们的可见性。
由于我们的游戏现在将以全屏模式运行,你将无法通过使用操作系统的按钮来终止它。在 Windows 中,按下Alt + F4键组合将退出窗口。我们需要提供一个更好的方式让玩家退出游戏。
创建关闭游戏机制
电影在电影院屏幕上以制作公司的标志和演员名单结束。除非你真的想看这些字幕,否则你会认为这是你起身离开电影院的提示。无论哪种方式,或者如果你在任何时候想要提前结束电影体验,你都有权离开场地。
如果你在电脑上的视频播放器中观看电影,点击一下按钮就会发生类似的情况。当我们以全屏模式运行我们的小游戏时,由于周围没有可以点击的按钮,这就需要你以不同的形式向玩家展示这一点。
这通常是通过在键盘上按下Esc来揭示一个屏幕——有时会遮挡游戏屏幕,有时作为叠加层——以便玩家可以选择进入游戏设置或加载不同的游戏会话,显然是退出游戏。
在本节中,我们将仅实现Esc按键部分,并将其视为玩家想要退出的意愿。为此,我们建议你打开Game.gd脚本,并向其中添加以下代码行:
func _input(event):
if event.is_action_pressed("ui_cancel"):
get_tree().quit()
你可能期望在那个if块中看到Esc。它在那里,但作为一个标识符。如果你转到项目设置并打开输入映射标签,你会看到一个映射到易于理解名称的快捷键列表。以下截图显示了输入映射的一部分:

图 14.2 – 输入映射标签是项目设置的一部分
如果你正在构建允许玩家使用多个输入设备的游戏,那么配置输入映射将非常有帮助。例如,你可以设置,如果玩家希望使用键盘执行相同的行为,那么游戏控制器或操纵杆的按钮按下具有相同的意义。这是一种将不同的输入统一在易于在代码中跟踪的单一名称下进行整合的好方法。
我们已经处理了屏幕尺寸和允许玩家退出游戏的问题,因此我们应该为导出我们的游戏做好准备。
配置 Windows 导出设置
与其他游戏引擎相比,Godot 的下载大小非常小。其中一个原因是它没有预装导出包。平台要求有时会变化,Godot 的具体功能必须符合它们的指南,因此,在导出过程中下载和更新导出包是有意义的。
由于我们从未导出过游戏,我们的设置中没有导出包。要获取一个,请点击顶部菜单中的编辑器按钮以访问管理导出模板设置。当你打开它时,你会看到一个界面,你可以下载并安装适用于你使用的版本的正确包。以下截图显示了当前导出模板的状态:

图 14.3 – 这个屏幕将帮助我们下载导出模板
你应该点击下载和安装按钮并等待。一旦完成,你就可以在那个界面中点击关闭按钮。接下来在我们的导出工作中,我们需要处理的是导出设置,所以请按照以下步骤操作:
-
点击顶部菜单中的项目按钮,并在选项中选择导出。
-
按下添加按钮,并在选项中选择Windows 桌面。
-
在项目文件外部填写
Build文件夹,因此我们将其定义为../Build/Clara.exe。 -
在导出界面的底部部分按下导出项目按钮。
-
关闭底部附近的导出带调试选项。确认您的文件路径并按保存。
在我们继续解释之前,这里有一些步骤的视觉表示,这些步骤是您在导出时必须执行的:

图 14.4 – Windows 的一些导出设置
假设您的 Windows 运行在 64 位机器上,这些步骤会将您的游戏导出到您定义的文件夹。当您运行可执行文件时,您应该会像在 Godot 中开发时一样玩游戏。按下Esc将终止程序并带您回到操作系统。
您可能已经注意到除了Clara.exe之外还有一个带有 PCK 扩展名的额外文件。如果您想将这两个文件放在一起,您可以在导出设置中打开嵌入 PCK选项,但保持它们分开可能也是一个好主意。Godot 将您的游戏资源保存在一个单独的包文件中,并在您运行可执行文件时使用它。
为什么或何时这会有用?如果您想通过更多内容来增强您的游戏,您可以创建内容包,并指示游戏可执行文件将其拉入。您的下一个 DLC 可能就在转角处,这是实现这一目标的实用机制。
不仅您有一个完成的游戏,您还可以将其发布!确实令人兴奋。虽然我们主要提供了技术说明,但我们认为分享一些关于您可以提供给玩家的不同游戏体验的词也很宝贵。
提供不同的游戏体验
有时候,使用原型资产或另一位艺术家的创作是完全可以接受的,这样您可以专注于乐趣。我们这样说是因为我们始终建议您确保您使用的资产的许可。话虽如此,我们想要讨论的主题是您一旦获得这些资产后如何处理它们。
Models文件夹包含我们在整本书中没有使用的一些额外资产。当您在第九章中构建第一个关卡时,设计关卡,我们提到您可以使用一些其他资产。也许您确实使用了,并根据您自己的条件遵循了后续章节中的说明,特别是输入检测、路径查找等。
在某个时候,就像现在这本书的最后几页一样,您可能会发现自己不知道还能为游戏添加什么。
具有迭代创作过程
有些人发现,将视觉资产直接放在他们面前会更有力量。当他们观察不同物体的大小和形状关系时,创造力开始涌现。然后,还有其他人觉得这很麻烦,阻碍了他们制定合适的计划。如果他们弄清楚需要做什么,他们就可以开始修改资产或寻找新的资产。最后,这两种方法的结合可能更有效。
最后——特别是,如果你想将你的作品商业化——你必须将玩家放在你的工作流程的中心。快速迭代,随后是早期和频繁的测试,可能正是你所需要的。你的某些选择与玩家对游戏的期望相结合可能会产生很多压力,所以要有这个意识。我们将通过使用第二级的资产和布局来给你一个例子。
目前在该级别上有两个书架:一个直立的书架和一个倒下的书架。这是一个相对便宜且有效的叙事方法。为什么有一个书架在地板上?也许曾经发生过一场灾难,但我们不知道。它会被移开吗?如果你作为开发者想要它,或者测试表明这是一个强烈的要求,那么你需要在 Blender 或 Godot 中花更多时间来制定书架的动画计划。Clara 很可能需要另一个动作来展示她抬起并移动书架。如果她不应该这么做,因为她不太可能举起如此重的物体,那么你可能需要一个工具或一个同伴来帮助她。
一点简单的更改或请求,你就会被一系列任务淹没。不幸的是,这些更改并不全是视觉上的。你还得考虑编程部分,比如你需要保持书架的状态仍然在地板上,或者将其移开。
最终,作为创作者,你必须问自己这个努力可能将引领你走向何方。如果你可以将这个想法与 Clara 访问另一个级别或游戏中使用的秘密相结合——换句话说,将其与已经作为机制存在的东西混合在一起——你可以用最少的步骤来复制它;这可能值得。
因此,这始终是一个权衡。尽管你应该尊重乐趣和玩家的请求,但你应该谨慎处理,并考虑什么最适合你。
当我们即将完成我们的书籍时,让我们讨论一下你还可以用 Godot 做些什么。
探索不同的流派
尽管 Godot 引擎因其创建高质量的 2D 游戏而闻名,而其他知名引擎更受青睐用于构建 3D 游戏,但你已经看到 Godot 实际上在构建 3D 游戏方面相当有实力。当 Godot 4 发布时,这将变得更好。
到那时,你还能用 Godot 做什么?坦白说,你可以用它构建任何类型的游戏。最近也有一种趋势是使用 Godot 引擎构建桌面应用程序。然而,我们将考虑这些情况为特殊情况,并专注于一些更常见的、使用 3D 功能的游戏类型,如下所示:
-
模拟和策略游戏:当你使用射线投射来检测用户输入时,这是为了让 Clara 能够通过路径查找移动到特定的位置。在模拟或策略游戏中,无论是在网格上还是在自由移动的结构中,你的选定单位或单位可以以类似的方式移动到指定的目的地。你甚至可以在上面结合回合制功能,以跟踪哪一方的单位已经移动。
-
赛车游戏:Godot 已经有一个VehicleBody节点来模拟汽车的行为。这不是很好吗!通过在MeshInstance节点内部适当地放置一个Camera节点,并结合VehicleBody节点的机制,你就可以构建下一个令人惊叹的赛车游戏。启动你的引擎,Godot 引擎,轰鸣吧!
-
第一人称射击游戏:这是一个可以用 Godot 引擎构建的经典例子。在这种类型的游戏中,你会大量使用射线投射来检测子弹是否与对象连接。如果它们连接了,可能在你面前的是一个很好的技术和创意问题的混合。子弹是否应该以相同的方式穿透或摧毁每个对象?
-
角色扮演游戏:这与第一人称射击游戏类似,因此可以制作。在这个类型中,你通常有一个较长的叙事要向玩家展示。此外,你还需要跟踪玩家在故事中的位置以及他们是否满足了一些条件以揭示故事的下一部分或谜题的结果。我们在这本书中没有发现这一点,但检查
Resource作为一个有用的 Godot 机制来促进内容丰富的游戏可能是个明智的选择。 -
多玩家/合作模式:这本身不是一个独立的类型,因为任何类型都可以制作成多玩家或合作模式。然而,有些游戏如果没有网络连接,体验将不会相同,所以我们不得不单独提及这一点。Godot 有可以用来连接第三方服务或使同一网络中的两台计算机相互连接的网络组件。
这些是一些肯定可以用 Godot 制作的游戏类型。你还可以包括一些其他类型,如益智游戏或体育游戏,或者任何使用 3D 资源的子类型。
摘要
随着我们在本章结束本书,你的游戏项目也即将完成。因此,我们首先向您展示了导出游戏所需的必要步骤。尽管在游戏构建完成后处理这一阶段似乎顺理成章,正如在迭代创作过程部分提到的,经常导出游戏并与他人分享以获得频繁的反馈可能是个明智的选择。
本章的其余部分致力于讨论你在游戏开发努力中可以采取的不同方法,最佳实践,一般指南,最后是了解你可以针对的不同流派。
你在游戏开发之旅中已经走了很长的路。它始于前五章的 Blender,然后通过几个过渡章节继续,直到你完全转向使用 Godot 引擎来构建游戏。希望你现在对这两个应用程序的工作原理有了更好的看法。此外,如果你有一些先前的经验,我们希望这本书在某些方面提高了你的信心水平。
在我们离开你的时候,我们祝愿你在未来的努力中一切顺利,愿你的代码第一次就能编译成功!
进一步阅读
你可能已经注意到导出的游戏使用了 Godot 的图标。拥有自己的自定义图标会更好。这涉及到几个部分,但这是可能的。说明列在docs.godotengine.org/en/3.4/tutorials/export/changing_application_icon_for_windows.xhtml。
如果你想要为了反馈目的部署你的游戏,而不是通过电子邮件或聊天应用程序发送文件,你可以使用以下平台:
后者 URL 在我们的情况下特别有用,因为该平台还托管 Godot 游戏马拉松。对于 PC 游戏,Steam 是一个大型的市场,但上述地方可能比在 Steam 上注册并通过申请流程更快。


浙公网安备 33010602011771号