Unity-和-PlayMaker-游戏设计实践指南-全-
Unity 和 PlayMaker 游戏设计实践指南(全)
原文:
zh.annas-archive.org/md5/5352a9be1195cbd193cd96ea96752db6译者:飞龙
前言
从历史上看,游戏设计是一个广泛的概念。它仍然经常与创建游戏规则、建立系统、用粗线条定义游戏将是什么以及创建特定的游戏玩法场景或关卡联系在一起。有时,我们会走得更远,赋予游戏设计师定义游戏宇宙和美学将如何运作的权力。这个定义可能看起来很宽泛,但在一个领域仍然非常有限,那就是技术。
随着游戏行业向成熟发展,许多小型工作室开始涌现,通常是 1 到 3 个人的团队,他们将成功归因于现在游戏开发者可用的新一代工具。虽然拥有数百名开发者的大型游戏公司大多数能够站稳脚跟,但在这个新的环境中,焦点已经转向了多才多艺和自给自足。
游戏设计师总是希望对自己的游戏有更多的控制权,而历史上,他们第一次没有理由不伸手去争取。了解你的游戏内部是如何工作的意味着拥有更多的控制和创作自由。这也意味着如果有的话,与团队成员更有效的沟通。它赋予你独自制作游戏的能力,无论是仅仅是一个原型、一个游戏节项目,还是完整的商业发行。
Unity 3D 是一个改变了我们永远思考游戏开发方式的游戏制作工具,使它变得更加便宜和民主。它允许任何愿意投入时间的人几乎不花任何代价就能制作出惊人的游戏世界和体验。它一直是推动这一惊人变革的主要力量之一,数百万新开发者加入游戏行业,仅仅因为他们现在可以这样做。行业每天都在变得更加开放,这丰富了来自不同背景的人们带来的想法池。
本书是 Hutong Games 关于使用 Unity 和 Playmaker 进行实用游戏设计的入门指南。后者让任何人都能制作游戏,而无需编写任何编程代码,同时不会放弃 Unity 所能提供的任何功能。在游戏的技术方面工作,了解编程基础知识仍然很有价值,但深入其中不再是强制性的,这意味着制作游戏现在比以往任何时候都更容易。
即使你知道如何编码,学习如何在 Unity 中使用 Playmaker 也可能帮助你更清晰地看到你游戏机制和技术之间的协同作用,使你的项目开发更多地关于寻找合适的设计,而不是围绕计算机代码进行工作。
对于实用游戏设计的美好未来,我们毫无疑问,这本书为你提供了成为这个未来一部分的机会。
本书涵盖内容
第一章, Unity 和 Playmaker 入门,提供了有关安装 Unity、购买和安装 Playmaker、创建和设置项目以及调整界面布局的信息。
第二章, Unity 和 Playmaker 的用户界面,介绍了 Unity 和 Playmaker 的基本界面元素:菜单、面板和视图。
第三章, 组件和状态机,详细介绍了在 Unity 中(带 Playmaker 和不带 Playmaker)的项目结构,并介绍了基于组件的游戏开发方法。
第四章, 创建您的第一个游戏,提供了有关应用和扩展您在前面章节中学到的 Unity 和 Playmaker 技能以创建真实游戏机制的信息。
第五章, 脚本和自定义动作,介绍了使用 JavaScript 和 C# 进行 Unity 脚本编程,并制作自定义 Playmaker 动作。
第六章, 网络和多人游戏,详细介绍了如何使用名为 Photon Cloud 的插件将多人游戏添加到您的游戏中。
第七章, 使用外部 API,提供了关于使用外部 API 的信息,使您的游戏可在 Kongregate 上在线运行,并集成其在线排行榜。
您需要为本书准备的内容
您需要 Windows XP SP2 或更高版本、Windows 7 SP1 或 Mac OS X "Snow Leopard" 10.6 或更高版本。您的显卡必须支持 DirectX 9 并具有 2.0 着色器模型。
Playmaker 是一个付费插件,因此您需要在开始使用本书之前购买它,或者在第一章, Unity 和 Playmaker 入门中解释的资产商店中获取它。
虽然前四章不需要,但您必须有一个互联网连接,才能集成多人游戏和外部 API。
本书面向对象
这本书适合所有希望对游戏的技术方面有更多控制的初学者和有经验的游戏设计师。它也适合那些希望快速全面了解 Unity 和 Playmaker 的人,这些工具可以从零开始学习。
您需要足够熟悉您的操作系统及其标准软件,以便在使用 Unity 时同样舒适。虽然了解编程的基础(如函数、变量和值)对于遵循本书的所有步骤(特别是第五章, 脚本和自定义动作)很重要,但本书提供了链接到免费在线资源,这些资源将帮助您快速有效地学习这些概念。
习惯用法
在这本书中,您将找到许多不同风格的文章,以区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。
文本中的代码单词将如下所示:“您已经在您的硬盘上有了unitypackage文件。”
新术语和重要词汇将以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,将以这样的形式出现:“只需点击蓝色的下载按钮。”
注意
警告或重要注意事项将以这样的框显示。
小贴士
小技巧和窍门将以这样的形式出现。
读者反馈
我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正从中获得最大收益的标题非常重要。
要向我们发送一般反馈,请简单地将电子邮件发送到 <feedback@packtpub.com>,并在邮件主题中提及书名。如果您在某个领域有专业知识,并且对撰写或参与一本书感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您已经是 Packt 书籍的骄傲拥有者,我们有多个方面可以帮助您从您的购买中获得最大收益。
下载示例代码
您可以从www.packtpub.com您购买的所有 Packt 书籍的账户中下载示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
错误清单
尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然会发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误清单,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击错误提交表单链接,并输入您的错误详细信息来报告它们。一旦您的错误清单得到验证,您的提交将被接受,错误清单将被上传到我们的网站,或添加到该标题的错误清单部分。任何现有的错误清单都可以通过从www.packtpub.com/support中选择您的标题来查看。
盗版
互联网上对版权材料的盗版是一个持续存在的问题,所有媒体都存在。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以追究补救措施。
如果您发现了疑似盗版材料,请通过 <copyright@packtpub.com> 联系我们,并提供链接。
我们感谢您在保护我们作者方面的帮助,以及我们为您提供有价值内容的能力。
问题和建议
如果您在本书的任何方面遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决。
第一章. 使用 Unity 和 Playmaker 入门
在本章中,我们将介绍将项目准备就绪以便与本书一起使用的过程。我们将涵盖以下主题:
- 
下载、安装和设置 Unity 
- 
使用 Asset Store 购买和安装 Playmaker 
- 
创建新项目并更改项目设置 
如果您熟悉下载和安装 Unity 及其插件的过程,您可以跳转到本章的设置您的项目部分。它显示了 Unity 和本书中将要使用的项目设置布局。
下载和安装 Unity
Unity 适用于 Mac OS X 和 Windows PC,这两个平台的安装过程非常相似。本书将使用 Unity 的 Mac 版本,但当我提供 Windows 版本的不同方向和快捷键时,我会提供给您。幸运的是,实际上它们非常相似,如果您选择这样做,您应该不会在两个平台之间遇到任何问题。
首先,让我们前往unity3d.com/unity/download/下载本书写作时可用的最新版本的 Unity(本书写作时的版本是 4.3)。只需单击蓝色下载按钮,文件就应该开始下载到您的硬盘上。相关的文件将是为 Mac 用户准备的 DMG 文件或为 Windows 用户准备的安装程序 EXE 文件。将其保存在您硬盘上的任何位置。
如果您使用的是 Mac,双击 DMG 文件将显示 PKG 文件,这是安装程序。双击Unity.pkg将产生与在 Windows 中双击Unity.exe基本相同的结果——它将提示安装向导。除了您想要安装引擎的位置之外,您没有其他 Unity 特定的选项。
小贴士
您可以在同一台计算机上安装多个 Unity 版本。为此,您只需更改 Unity 将要安装的文件夹的名称,并将新的安装放入不同的文件夹中。如果您想同时安装不同的版本,无论是由于许可证限制还是因为您想要访问某些特定版本的特性,您可能需要这样做。请记住,如果您将项目更新到 Unity 的新版本,您不能回退到旧版本。
以下截图显示了 Unity 的安装窗口。在这个向导阶段,您可以指定 Unity 将要安装的目录或更改一些安装参数。在大多数情况下,您可能希望使用默认参数安装 Unity。如果是这种情况,只需单击安装并跳过下一步。
如果你想更改安装位置,点击更改安装位置...。自定义按钮允许你包含或排除特定的包,例如主 Unity 安装、示例项目和 Unity Web Player。对于这次安装,请保留所有复选框选中。我们将在本书的后面讨论包。

点击安装将在你的硬盘上安装 Unity。安装完成后,你可以转到安装它的文件夹(默认情况下,Mac 上的Applications/Unity或 Windows 上的C:\\Program Files\Unity)并启动它。
如果你第一次启动 Unity,会弹出一个窗口,要求你选择你的许可证类型并输入你的 Unity 账户登录名和密码。如果你还没有账户,请创建一个并输入详细信息。稍后,你将需要它来访问论坛和 Unity 问答——当你寻找 Unity 相关问题的解决方案时,这两者都非常有帮助。在这本书中,我们将使用 Unity 的免费版本,因为这里的所有示例都不需要任何专业许可证功能。所以选择激活 Unity 的免费版本并点击确定。

第一次打开 Unity 时,可能会加载一个默认项目。安装时包含的这些测试项目(以及通过 Unity 网站提供的项目)在你想要了解如何做某些特定事情(例如,迷你地图或水物理)但不知道从何开始时将非常有帮助。
然而,现在我们想要一个新空项目。一旦 Unity 加载,转到屏幕左上角主菜单中的文件菜单并选择新建项目…。

这将打开项目向导窗口,你可以选择你的未来项目要存放的位置以及如何命名。在 Unity 中,项目是一个文件夹,所以你给项目文件夹起的名字将自动分配给项目。对于本书中的示例,你现在不需要任何包,所以保留所有复选框为空,除非你已经下载了 Playmaker,在这种情况下请选中 Playmaker。准备好后,点击创建项目。
小贴士
如果你总是想从项目向导开始(如果你计划拥有多个项目,这强烈推荐),转到Unity | 首选项并勾选始终显示项目向导,然后关闭首选项窗口。对于 Windows 用户,它是文件 | 首选项。
由于我们没有加载任何重型资产包,项目将快速加载,你应该再次看到主 Unity 界面,但这次一切都将为空。

购买并导入 Playmaker
在我们更改项目布局之前,有一个必须导入的组件缺失,那就是 Hutong Games 的 Playmaker(www.hutonggames.com/)——我们将在这本书的大部分练习中使用的(除了 Unity 本身之外的)主要工具。要获取 Playmaker,我们将使用资产商店。后者是一个对任何 Unity 开发者都很有价值的在线市场,它允许我们通过导入其他用户制作的有用插件和资产包到我们的项目中来节省大量时间。通过资产商店提供的资产中,一些是免费的,但大多数,如 Playmaker,是由用户制作的并需要付费。您也可以通过资产商店提供您的工具和资产。把它看作是针对开发者的 iTunes Store 或 Amazon 的类似物。
虽然您可以通过网页(unity3d.com/asset-store/)访问和搜索资产商店,但它以最实用的形式直接集成到引擎中。要从 Unity 直接访问资产商店,请转到主菜单中的窗口子菜单,并从列表中选择资产商店。这将打开 Unity 中的资产商店窗口(您需要连接到互联网才能查看商店内容)。
在资产商店窗口的右上角,有一个搜索框。在框中输入Playmaker并按键盘上的return (Enter)键。当搜索结果显示时,点击搜索结果左上角的第一项,简单地称为Playmaker,如图所示:

资产商店窗口现在将显示 Playmaker 的描述、用户评论、一些截图、其价格以及购买按钮。点击此按钮,输入您的信用卡或 PayPal 详细信息,并像在其他任何在线商店或服务中一样完成购买。之后 Unity 将提供导入包。点击导入按钮,当 Playmaker 下载完成后,在导入窗口中,保留所有复选框选中,再次点击导入。这将把 Playmaker 文件添加到您的新 Unity 项目中。
小贴士
安装 Playmaker(或任何其他插件)的另一种方法是资产 | 导入包 | 自定义包...,前提是您已经在硬盘上有了包含unitypackage文件的文件。
Playmaker 还会自动导入它所依赖的一些其他流行的 Unity 插件:用于移动和动画的 iTween 以及用于网络和多人游戏的 Photon。我们将在第六章 网络和多人游戏中稍后讨论 Photon。

设置您的项目
关于 Unity,您必须知道的一个重要事情是:您可以通过多种方式自定义它,甚至可以通过您自己的自定义窗口和面板来扩展其界面。我们目前不会讨论这个问题,因为用户界面将在第二章Unity 和 Playmaker 的用户界面中更详细地讨论,但您应该知道,当您安装新的扩展,如 Playmaker 时,如果用户界面发生变化,这没有什么可惊讶的。您还可以拖动不同的标签,看看哪种布局最适合您的屏幕尺寸/配置和项目。现在我们将设置界面,以便简化下一章几章的解释。一旦您熟悉了界面,您就可以随意自定义它。
如果您没有拖动任何东西,您应该看到默认的 Unity 布局。如果您已经拖动过,请转到主菜单中的窗口 | 布局,然后点击默认以重置到默认窗口布局。您将看到一个位于左侧的层次结构标签,中间的场景和游戏,右侧的检查器,以及底部的项目和控制台。我们将在第二章Unity 和 Playmaker 的用户界面中更多地讨论这些标签及其功能。目前,为了节省屏幕空间,我们将拖动一些标签并改变它们的显示方式。
以下截图显示了 Unity 中的默认界面布局。

让我们从屏幕底部的项目标签开始。如果它没有被突出显示,请点击项目这个词。目前,项目和控制台标签都固定在屏幕底部,因此您可以在它们之间切换以选择一个或另一个。不幸的是,这种布局并不理想,因为在大多数情况下,您将需要同时直接访问项目和控制台标签。将项目标签拖动到场景和游戏标签的右侧,直到它紧挨着检查器标签对齐。它将变得又高又窄。
不幸的是,现在项目面板中由于那些大图标,空间非常有限。幸运的是,Unity 允许我们通过点击项目面板右上角的小选项图标 ( ) 并从列表中选择一列布局来轻松解决这个问题。
) 并从列表中选择一列布局来轻松解决这个问题。
现在您的项目面板将需要更少的空间,您将能够看到您项目的所有文件结构,而不会因为图标和子面板挡道而感到沮丧。
小贴士
这里需要注意的是,一些开发者确实更喜欢两列布局,因为它有收藏夹部分和类似文件管理器的子文件夹表示;然而,你很可能希望尽可能多地获得空闲工作空间,尤其是在使用像 Playmaker 这样的插件时,这些插件会添加你需要持续访问的额外标签。只有在你有一个相对较大的屏幕时,你才应该考虑两列布局。
现在项目面板已经设置并放置好了,让我们通过将控制台面板移动到场景和游戏面板下,为层次结构面板腾出更多空间。为此,就像移动项目标签一样,点击并拖动控制台标签,并将其移动到场景和游戏面板的底部,直到它直接位于它们下方。

以下截图显示了如果你正确地遵循了本章中的所有说明,你的编辑器窗口应该看起来是什么样子。

最后,你的工作空间已经全部设置好了!现在是我们更改项目设置的时候了。在开始工作之前这样做是个好主意,以便将其处理完毕,让我们开始吧。我们不会深入探讨为什么某些项目设置以某种方式设置,其中大多数都是相当自解释的。
首先,从主菜单中选择编辑 | 项目设置 | 玩家。应该在右侧的检查器面板中显示一个新菜单。你可以在菜单顶部附近的公司名称字段中输入你的名字或公司的名字,默认情况下,产品名称(在其下方)应该与你在创建项目时指定的项目文件夹名称相同。更改项目名称不会重命名你的项目文件夹。实际上,你在玩家设置中更改的每一项都只会影响输出的游戏或应用程序。我们将为本书的示例构建 Unity WebPlayer 项目,所以点击带有小地球图标的面板( )。
)。
目前我们只关注这里的两件事:屏幕分辨率和 WebPlayer 模板。确保默认屏幕宽度和高度分别设置为960和600,并且活动模板是无上下文菜单。这里还有更多选项,但我们现在将保留它们的默认值。
接下来,我们将更改构建设置以匹配我们的目标平台。转到文件 | 构建设置…,从可用平台列表中选择WebPlayer(它应该是列表中最上面的项目)。然后点击切换平台按钮(如以下截图所示)。这将允许您直接使用输出分辨率测试游戏。一旦 Unity 完成文件重新导入,您就可以关闭构建设置窗口。

摘要
在本章中,你学习了如何下载和安装 Unity,从 Asset Store 购买和安装插件,为你的项目做好准备,优化你的工作空间,以及选择输出平台。在下一章中,我们将更详细地探讨 Unity 的界面。特别是,我们将检查你在本章中遇到的各种面板和视图(例如,检查器,层次结构,游戏,场景),并开始使用它们。
第二章 Unity 和 Playmaker 的用户界面
在上一章中,我们探讨了下载和安装 Unity、Playmaker 以及为使用本书设置项目的过程。你已经学会了如何在 Unity 窗口内移动面板以组织你的工作流程,以及如何更改一些项目设置。
在本章中,我们将更详细地查看你迄今为止遇到的所有 Unity 面板和界面元素,以及一些你尚未见过的元素,包括 Playmaker 的 动作 面板和 有限状态机 (FSM) 视图。你还将学习如何创建简单的游戏对象并修改它们的属性,如位置、位置和缩放。
界面概述和主菜单
Unity 的界面是模块化的:这意味着你可以拖动其元素,将它们附加到编辑器窗口的不同部分,甚至完全分离并将它们放在另一个屏幕上以方便使用。当你打开 Unity 时,你看到的一切都是编辑器:面板、视图、控件等。编辑器 是 Unity 在 Unity 内部的自称。让我们看看主要界面元素。
屏幕顶部有主菜单。你的菜单应该包括 文件、编辑、资产、游戏对象、组件、Playmaker、窗口 和 帮助 子菜单。它们将帮助你创建游戏对象(例如,原语、灯光和相机)、附加组件,并打开新的面板和视图。子菜单可以解释如下:
| 子菜单 | 描述 | 
|---|---|
| 文件 | 此子菜单包含有关构建、保存和打开项目和场景的所有命令。这里有三个你将非常常用的命令,因此记住它们的快捷键是个好主意:command + S (Ctrl + S 在 Windows 中) 保存当前场景,command + N (Ctrl + N 在 Windows 中) 创建新场景,以及 command + Shift + B (Ctrl + Shift + B 在 Windows 中) 打开 构建设置 窗口。 | 
| 编辑 | 此子菜单允许你对文件和游戏对象执行各种操作,例如复制(command + C 或 Windows 中的 Ctrl + C)、粘贴(command + V 或 Windows 中的 Ctrl + V)、复制(command + D 或 Windows 中的 Ctrl + D)和删除(command +Delete 或 Windows 中的直接 Delete)。在此之下还有撤销(command + Z 或 Windows 中的 Ctrl + Z)和重做(command + Shift + Z 或 Windows 中的 Ctrl + Shift + Z)命令。这些都是跨操作系统的标准命令,因此你应该假设它们在 Finder(Windows 资源管理器)或 TextEdit(Windows 记事本)中的工作方式相同。 | 
| Assets | 此子菜单允许您创建、导入和导出文件,在游戏开发术语中称为资产。这些可以是脚本、动画、着色器、材质等等。您项目中的资产列表可以在Project面板中查看。您将在本章后面使用此子菜单中可用的命令。 | 
| GameObject | 此子菜单允许您创建新的游戏对象。与Assets子菜单链接到Project面板的方式类似,GameObject子菜单链接到Hierarchy面板。当我们在 Unity 中讨论面板和视图时,您将了解更多关于不同类型游戏对象的信息。现在只需记住,您可以通过按command + Shift + N(在 Windows 上为Ctrl + Shift + N)来创建一个新的空游戏对象。这是在 Unity 中工作时您将经常要做的事情。 | 
| Component | 此子菜单允许您向您的游戏对象添加新的组件。这非常重要,因为空的游戏对象什么也不做,并且是不可见的。通过向其添加组件,您可以改变其外观和行为。我们将在下一章中更详细地讨论组件。 | 
| PlayMaker | 此子菜单不是默认的 Unity 子菜单;它是通过从 Asset Store 导入的 Playmaker 插件添加到主菜单的。这个专门针对 Playmaker 的子菜单允许您访问 Playmaker 组件和面板,例如 FSM 视图及其面板,允许您为您的对象选择新的动作和转换。我们将在本章节和下一章节中稍后讨论 Playmaker。 | 
| Window | 此子菜单允许您打开新的面板和视图,例如Asset Store、Hierarchy和Scene。如果您不小心关闭了某个面板或视图,此子菜单非常有用,可以帮助您找到或重新打开它。它不允许您打开相同界面元素的多个实例,因此如果已打开现有面板,您将始终找到它。 | 
| Help | 此子菜单允许您访问有用的 Unity 参考资源,例如 Unity 手册、参考材料和脚本参考。这些资源在网上有副本,但始终使用与您的安装一致的副本是一个好主意:这样您可以确保您使用的文档与您的 Unity 版本一致,并且本地文档副本始终运行得更快。Help子菜单还提供 Unity 社区网站的快捷方式,例如论坛、答案和反馈,并允许您报告错误和检查更新。 | 
现在,在主菜单下方是工具栏,包括(从左到右)Scene视图的主要控制、游戏执行控制和层过滤器及布局选择下拉框。 |

在工具栏下方,你会看到面板和视图标签。我们将在本章稍后更详细地介绍它们,但就目前而言,你需要知道的是,视图(如名称所示)以某种方式让你看到你的游戏场景,而面板则提供有关游戏对象、场景或整个项目的额外信息和工具。
小贴士
请记住,视图和面板之间的区别仅仅是一个有用的约定,绝对不限制标签页的功能。这尤其适用于由各种插件或你自己添加的自定义标签页。在这本书中,这种区别是为了方便。
下面的图显示了如果你从第一章以来没有移动任何标签页,你的编辑器窗口应该看起来像什么。我们将在本章中参考这个布局,所以如果你的布局不同,我建议你将其更改为与图片匹配。你总是可以在以后回到它。

在 Unity 编辑器窗口的底部,有一条长长的空灰色线条。这是状态栏:它显示了控制台面板中最后显示的内容,点击它将显示控制台,突出显示其中的最后一条日志消息。状态栏、主菜单和工具栏都是 Unity 中的永久界面元素,与标签页不同,不能关闭、分离或调整大小,除非操作编辑器窗口本身。
层次结构面板
在 Unity 中,面板是一个提供有关游戏对象、场景或整个项目信息的标签页,或者为你提供额外的控制。面板可以随意添加、关闭、分离和连接。你可以根据需要拖动它们以优化你的工作流程。如果你遵循了第一章中的说明,“使用 Unity 和 Playmaker 入门”,你的当前布局应该与本章前一部分的最后一张截图相匹配。
首先,让我们看看层次结构面板(你应该在编辑器窗口的左侧将其连接上)。
你看到写着层次结构这个词的地方是标签页的标题。你可以通过标题拖动任何标签页,无论是视图还是面板。编辑器中可以存在任意数量的面板,位于任意位置,所以有时你可能会遇到多个标签页附着在屏幕的同一区域(如下面的图所示):

让我们尝试重现这种情况。点击层次结构面板右上角的选项按钮( )。然后从下拉列表中悬停在添加标签选项上,最后点击层次结构。现在你应该有两个层次结构面板附着在屏幕的同一侧。它们的标题应该相邻:一个处于活动状态,另一个处于非活动状态。活动面板是颜色较浅的那个。
)。然后从下拉列表中悬停在添加标签选项上,最后点击层次结构。现在你应该有两个层次结构面板附着在屏幕的同一侧。它们的标题应该相邻:一个处于活动状态,另一个处于非活动状态。活动面板是颜色较浅的那个。
下图显示了允许你向屏幕的某个区域添加新标签的菜单;在这种情况下,它位于右侧,与层次结构面板相同的地点。使用它来添加另一个层次结构面板。

点击非活动面板的标题以查看其内容。此时你应该看到两个面板看起来完全相同:两个面板中唯一的内容是主摄像机。这是因为两个面板都显示了相同场景的内容。你可以有任意多的层次结构面板,但它们都会显示完全相同的内容。我们不需要两个,所以让我们关闭一个:在层次结构标签页的标题上右键单击,并从出现的上下文菜单中选择关闭标签。请注意,这与你在面板右上角点击选项按钮时出现的菜单相同。这只是做同样事情的一种方式。
我刚刚提到了一个叫做场景和场景内容的东西——但这究竟是什么意思?Unity 中的项目由场景组成;如果你喜欢,可以将其视为关卡文件,除了实际的游戏关卡之外,场景还可以包含如开场动画、加载界面、游戏菜单或任何内容。这只是将你的游戏划分为可管理的块的一种方便方式,而层次结构面板揭示了当前打开场景中的所有内容。在任何给定时刻,你只能打开一个场景,这就是为什么无论你打开多少层次结构面板,它们都会显示完全相同的内容。我们将在下一章中更详细地讨论场景。
另一件你应该注意的事情是面板顶部左角的创建按钮,它位于标题下方。点击它,你会看到与从主菜单导航到游戏对象 | 其他时相同的菜单。这个菜单允许你在列表中点击它们的名称时创建添加到当前场景的游戏对象。
在 Unity 中,游戏对象有点像是一个外壳,一个可以包含任何内容的容器:一个立方体、一盏灯、一个摄像机或一个角色。现在就创建一个立方体、一个四边形和一个方向性光源。我们将在接下来的章节中使用这些来创建一个游戏。
现在我们有了所有这些游戏对象,我们想在场景中以某种方式定位它们。为此,我们将使用检查器面板,以及其他一些工具。
检查器面板
检查器面板的主要目的是对游戏对象执行各种操作,包括更改它们的属性,例如外观、行为、位置、大小和旋转。在层次结构面板中通过单击一次选择立方体游戏对象。此时,检查器面板的外观应改变,以包含以下截图显示的多个项目:

你首先应该注意到的是面板右上角靠近选项按钮的小锁图标 ( )。与层次结构面板不同,检查器显示的是对象的属性而不是场景,由于你可以在当前活动场景中同时拥有任意数量的游戏对象,因此你可以为不同的检查器分配不同的对象以始终关注特定的对象。通过右键单击检查器标题并选择添加标签 | 检查器来打开一个新的检查器标签。两个检查器都将聚焦于你在层次结构中选择的立方体。点击其中一个检查器中的锁图标。这将使此检查器始终显示立方体的属性,因此,如果你在层次结构中选择四边形,锁定的
)。与层次结构面板不同,检查器显示的是对象的属性而不是场景,由于你可以在当前活动场景中同时拥有任意数量的游戏对象,因此你可以为不同的检查器分配不同的对象以始终关注特定的对象。通过右键单击检查器标题并选择添加标签 | 检查器来打开一个新的检查器标签。两个检查器都将聚焦于你在层次结构中选择的立方体。点击其中一个检查器中的锁图标。这将使此检查器始终显示立方体的属性,因此,如果你在层次结构中选择四边形,锁定的  检查器将继续显示立方体的属性,而另一个将显示四边形。你可以通过点击你打开的两个检查器面板的标题来验证这一点。
 检查器将继续显示立方体的属性,而另一个将显示四边形。你可以通过点击你打开的两个检查器面板的标题来验证这一点。
小贴士
如果你想在不需要点击标题的情况下同时暴露两个对象的属性,你可以通过点击并拖动其标题将其中的一个检查器面板分离到任何你想要放置该面板的位置。如果你想要从一个对象复制组件或属性到另一个对象,或者如果你想始终显示一个重要的对象,这可能会很有帮助。
确保你对拥有多个检查器以及它们与所选游戏对象相对应的概念感到舒适。此外,请注意,检查器面板可以同时暴露多个游戏对象的属性。你可以在层次结构面板中通过按住command (Ctrl 在 Windows 中)来选择多个游戏对象。你也可以按住Shift并选择一系列游戏对象。这些操作与你在 Finder(或 Windows 资源管理器)中执行的操作类似。尝试多次这样做,直到你能够在层次结构面板中舒适地执行这些操作,并始终注意未锁定检查器面板的内容如何根据你选择的游戏对象而变化。一旦你觉得你已经试验得足够多了,可以通过右键单击其标题并从上下文菜单中选择关闭标签来关闭锁定的检查器。
在检查器面板的标题下方是所选游戏对象的名字。你可以随意更改它。现在,让我们将我们的立方体重命名为Wall。确保你在层次结构面板中选中了立方体,并且其属性显示在检查器面板中。点击当前显示为立方体的文本字段,删除所有内容,输入Wall,然后按回车(Enter)。你会注意到层次结构面板中的名字也发生了变化。
在名称字段下方是一个长条部分,其中包含各种组件,由黑色水平线分隔。默认的立方体(现在称为Wall)具有变换、网格过滤器、盒子碰撞器和网格渲染器组件。我们将在下一章中详细检查这些组件。现在你应该专注于变换组件(如下图中所示,包括检查器面板的标题),这是 Unity 中每个游戏对象都拥有的唯一组件。

变换定义了游戏对象在 3D 空间中的位置、旋转和缩放。这些都是它的属性。通过点击X、Y和Z字段,删除其中的所有内容,输入你想要的值,然后在键盘上按回车(Enter)来设置Wall的位置为(0,0.5,0)。你刚刚改变了Wall游戏对象的变换组件的位置属性。下一章将更详细地检查属性和组件。现在只需将场景中游戏对象的位置和旋转设置为以下值:
| 名称 | 位置 | 旋转 | 
|---|---|---|
| 方向光 | 0, 0, 0 | 50, -30, 0 | 
| 主相机 | 0, 10, 0 | 90, 0, 0 | 
| 四元数 | 0, 0, 0 | 90, 0, 0 | 
| Wall | 0, 0.5, 0 | 0, 0, 0 | 
目前,我们将所有对象的缩放属性保留为其默认值(1,1,1)。
现在你已经修改了场景,你可能想保存它。按command + S(在 Windows 中为Ctrl + S)。应该会出现一个对话框窗口,询问你想要将场景保存到何处。默认情况下,它应该建议在Assets文件夹中保存。将场景命名为Scene1并点击保存。以下截图显示了场景文件应该保存的路径。请注意,你不能在Assets文件夹之外保存场景。如果你尝试这样做,将会弹出一个错误消息警告你。

项目面板
看一下项目面板:你应该看到四个文件夹和一个名为Scene1的文件。项目面板是你的项目文件浏览器,类似于 Mac OSX Finder 或 Windows 资源管理器,但它只显示位于项目Assets文件夹中的文件和文件夹。
与层次结构和检查员不同,项目(见以下截图)只要您打开了相同的 Unity 项目,就始终显示相同的内容。所有场景文件、脚本和艺术资产都会显示出来,并且可以从这里进行操作。使用搜索框右侧的图标,您可以通过标签或类型对项目面板中显示的内容进行筛选。

通常来说,保持项目文件结构井然有序是一个好习惯,因为它有助于提高生产力和节省查找文件的时间。为了组织您的资产,您可以将事物放入具有清晰名称的文件夹中,例如Artwork、Scripts或Music。首先,让我们尝试整理一下项目面板中已有的内容。
点击面板左上角的创建按钮;这将显示一个菜单,允许您创建不同类型的资产。这是您在主菜单中转到资产 | 创建时得到的相同菜单。
小贴士
访问资产菜单的第三种方式是在项目面板文件列表下方的空白区域右键单击,然后在出现的上下文菜单中点击创建。
创建一个名为Scenes的文件夹。如果您在创建文件夹时忘记输入名称,可以按return(或在 Windows 中使用F2)。一旦您给它起了合适的名字,点击并拖动Scene1文件,将其放入Scenes文件夹中。从现在起,我们将把所有场景保存到该文件夹中,而不是Assets根目录。这样,当我们想要打开特定场景时,它们将更容易找到。
视图
现在项目面板已经井然有序,并且您的对象已经在场景中定位,是时候看看视图选项卡了:游戏和场景。前者显示所有摄像机的输出,而后者是 Unity 中的主要工作区。这是进行关卡设计的地方。当您在工具栏中点击播放按钮( )或使用快捷键command + P(或在 Windows 中为Ctrl + P)时,游戏视图会变得交互式,前提是在 Playmaker 或其他组件中定义了某种类型的输入。您还可以通过按command + Shift + P(在 Windows 中为Ctrl + Shift + P)来暂停游戏。这就是您稍后测试游戏的方式。目前还没有交互,但我们很快就会达到那个阶段。
)或使用快捷键command + P(或在 Windows 中为Ctrl + P)时,游戏视图会变得交互式,前提是在 Playmaker 或其他组件中定义了某种类型的输入。您还可以通过按command + Shift + P(在 Windows 中为Ctrl + Shift + P)来暂停游戏。这就是您稍后测试游戏的方式。目前还没有交互,但我们很快就会达到那个阶段。
如果当前没有激活,请点击场景视图的标题来激活它。在层次面板中,选择墙并按F。这将使场景视图聚焦于墙。你应该能看到你创建的立方体,其下方有一个四边形和方向光工具(看起来像一个小太阳)。工具通常是 3D 空间中的一个 2D 元素,允许你选择和/或操作对象,即使它们在场景中未以其他方式表示。工具也可以是非交互式的,例如一条简单的线或一个线框立方体。这些通常用于揭示某些不可见事物的位置或方向。

选择一个对象并按F是导航 3D 空间的一种方法。另一种方法是使用鼠标。你可以在场景视图中直接通过点击它们的网格(3D 模型)或工具来选择不同的对象。要环顾四周,请按住鼠标右键并在场景视图中移动鼠标。使用滚轮进行缩放。使用中间点击来拖动视图。你还可以通过按住Alt并点击左鼠标按钮并移动鼠标来围绕一个点旋转。
点击方向光工具并按W或工具栏中的 。你应该会看到三个箭头出现:红色、绿色和蓝色。这些箭头分别对应于 3D 空间中的 X、Y 和 Z 轴。点击并拖动其中一个箭头将移动对象在空间中的位置。将方向光移动到任何你想要的位置:其位置并不重要。然而,重要的是它的旋转。
。你应该会看到三个箭头出现:红色、绿色和蓝色。这些箭头分别对应于 3D 空间中的 X、Y 和 Z 轴。点击并拖动其中一个箭头将移动对象在空间中的位置。将方向光移动到任何你想要的位置:其位置并不重要。然而,重要的是它的旋转。
在工具栏中按E或 。一个带有彩色和白色圆圈的球体将出现在对象周围。每个彩色圆圈负责对象围绕一个轴的旋转。再次强调:红色对应 X 轴,绿色对应 Y 轴,蓝色对应 Z 轴。当你选择你的光源时,你也应该看到从它发出的黄色光线。这是一个工具,显示了你的方向光在场景中照明的方向。将方向光旋转,使墙和四边形在游戏视图中看起来被很好地照亮,几乎呈白色。不要忘记,你可以在任何时刻在游戏视图和场景视图之间切换,以查看游戏中的预览。
。一个带有彩色和白色圆圈的球体将出现在对象周围。每个彩色圆圈负责对象围绕一个轴的旋转。再次强调:红色对应 X 轴,绿色对应 Y 轴,蓝色对应 Z 轴。当你选择你的光源时,你也应该看到从它发出的黄色光线。这是一个工具,显示了你的方向光在场景中照明的方向。将方向光旋转,使墙和四边形在游戏视图中看起来被很好地照亮,几乎呈白色。不要忘记,你可以在任何时刻在游戏视图和场景视图之间切换,以查看游戏中的预览。
你还可以通过点击工具栏中的全局/局部切换按钮在旋转、位置和缩放工具的显示方式之间切换。枢轴/中心切换按钮确定其出现的位置:在对象的枢轴点或其几何中心。
一旦你对灯光满意,选择 Quad 对象并按 R 或  在工具栏中。这是缩放操作模式。如果你按 F 来聚焦于 Quad,你会看到一个类似运动操作器的 Gizmo,但每条线的末端是彩色的小方块而不是箭头。和之前一样,颜色对应于轴:红色代表 X 轴,绿色代表 Y 轴,蓝色代表 Z 轴。中间还有一个白色的方块,这是统一缩放操作器,允许你同时改变三个轴的缩放。
 在工具栏中。这是缩放操作模式。如果你按 F 来聚焦于 Quad,你会看到一个类似运动操作器的 Gizmo,但每条线的末端是彩色的小方块而不是箭头。和之前一样,颜色对应于轴:红色代表 X 轴,绿色代表 Y 轴,蓝色代表 Z 轴。中间还有一个白色的方块,这是统一缩放操作器,允许你同时改变三个轴的缩放。
使用这个 Gizmo,将 Quad 在 X 和 Y 轴上的缩放调整为大约 100。它的 Z 轴缩放应保持不变。当你选择一个对象时,你可以在 Inspector 面板的 Transform 组件中检查位置、旋转和缩放当前值。
通过按 command + S (在 Windows 中为 Ctrl + S)保存你的场景。
Playmaker 界面
是时候最终看看 Playmaker 了。在主菜单中,转到 PlayMaker | PlayMaker 编辑器。应该会出现一个带有不同 Playmaker 选项和 playMaker 面板的欢迎窗口。关闭欢迎窗口,然后将 playMaker 面板附加到与你的 Console 面板相同的区域,如以下截图所示(如果你自 第一章 以来没有更改任何内容,这应该在 Scene 和 Game 视图下方)。

左侧较暗的区域是 FSM 视图。
小贴士
请记住,FSM 视图在官方 Playmaker 文档中被称为 Playmaker 编辑器。在这本书中,我们将使用一个更清晰的名字,FSM 视图,来区分 playMaker 面板的不同区域。
这里你可以编辑你的有限状态机并创建状态节点以及它们之间的转换,我们将在下一章中更详细地讨论。现在,请按照 FSM 视图右下角的说明操作:
- 
在 Hierarchy 面板或 Scene 视图中通过点击来选择 Wall 游戏对象。 
- 
在 FSM 视图的任何位置右键单击,并从出现的上下文菜单中选择 Add FSM 以将有限状态机添加到 Wall 游戏对象。当你这样做时,注意 Hierarchy 面板中 Wall 旁边出现的红色图标。这个图标表示该对象具有 Playmaker FSM 组件。还要注意在 Inspector 面板中出现的 Playmaker FSM 组件。 
- 
现在,你可以操作 FSM 来为 Wall 分配各种行为。仔细阅读 playMaker 面板中显示的所有提示。 
小贴士
要更好地查看任何选项卡,包括 playMaker 面板,请将鼠标光标悬停在选项卡上并按 Space 键。然后再次按空格键以最小化。你可以在 Unity 编辑器的任何视图或面板中这样做。
在playMaker面板的右侧,有一些标签页,用于显示和修改不同类型的信息,包括整个 FSM 以及其状态、事件和变量。点击每个标签页,检查灰色矩形中的提示,解释每个标签页的内容。完成提示后,你可以按面板底部的提示切换按钮来禁用它们。你也可以通过按F1来实现这一点。在这样做之前,点击旁边的首选项按钮并阅读那里的提示。我们将在下一章中检查对 Playmaker 首选项的必要更改。
在 FSM 视图中,你应该看到与工具栏中的暂停/播放类似的控件。实际上,这些是相同的按钮,它们的存在只是为了在你使用 Playmaker 时,使控制游戏更加方便。
第二个最重要的与 Playmaker 相关的面板是动作浏览器。你可以通过从主菜单中选择Playmaker | 编辑器窗口 | 动作浏览器来打开它。以下截图显示了动作面板的一部分:

通过其标题将其拖动到编辑器的右侧,直到它与检查器的同一区域对齐。此面板显示了你在 Playmaker 库中的动作类别。点击其中一个类别,例如,相机,将显示相应的动作。面板顶部附近还有一个搜索栏,允许你更快地访问所需的动作。当你选择一个动作,例如,相机 | 屏幕到世界点,其参数的预览将出现在面板底部。它显示了所选动作在playMaker面板的状态选项卡中的外观。如果你有一个启用了 Playmaker 的对象,并在 FSM 视图中选择了状态,你可以通过点击动作面板右下角的添加动作到状态按钮,向其添加动作。
你可以按播放按钮(或使用快捷键command + P或Ctrl + P Windows)来查看是否存在错误,并且一切是否正常工作。完成后,再次按播放并保存场景。
摘要
在本章中,你检查了 Unity 和 Playmaker 的一些主要界面元素,向场景中添加了一些对象,并操作了这些对象。你还查看了游戏对象和组件,并学习了组件的属性。这些主题将在下一章中更详细地探讨。本章中创建的场景为创建游戏奠定了基础,你迄今为止创建并操作的所有对象都将在最终游戏中使用。
第三章:组件和状态机
在上一章中,我们学习了 Unity 的界面,以及事物在编辑器中的组织和显示方式。你向一个游戏对象添加了一个有限状态机(FSM),并简要地查看与 Playmaker 相关的界面元素。你还使用诸如层次结构和检查器面板以及场景视图等界面元素来操作游戏对象和组件。在本章中,我们将涵盖以下内容:
- 
Unity 依赖的基于组件的游戏开发方法 
- 
游戏对象、组件及其属性的更详细内容 
- 
使用组件定义外观和行为时游戏对象的可互换性 
- 
有限状态机、动作和转换 
- 
使用 Playmaker 制作简单的游戏机制 
游戏对象、组件和属性
Unity 使用的是一种流行的游戏开发通用方法,称为基于组件的架构。这种方法在软件开发中被广泛使用,以使事物更具可重用性和易于管理。
让我们谈谈在 Unity 中事物是如何组织的。其中一些已经在之前的章节中提到过,但我会简要重复一遍,这样你可以从更广泛的角度看到游戏对象、组件及其属性。
首先,你有一个项目,本质上是一个文件夹,其中包含你游戏的所有文件和信息。其中一些文件被称为场景(可以将其视为关卡)。场景包含你添加到其中的多个游戏对象。场景的内容由你决定,你可以拥有尽可能多的场景。你也可以让游戏在不同的场景之间切换,从而激活不同的游戏对象集合。
在较小的尺度上,你有游戏对象和组件。一个游戏对象本身只是一个无形的容器,它什么也不做。如果不添加适当的组件,它就无法在场景中显示,接收玩家的输入,或者移动并与其他对象交互。使用组件,你可以轻松地组装功能强大的游戏对象,同时重用几个小型部件,每个部件负责一个简单的任务或行为——渲染游戏对象、处理输入、承受伤害、播放音效等——使你的游戏开发和管理变得更加简单。Unity 高度依赖这种方法,所以你掌握得越好,你将越快地精通它。
在 Unity 中,每个游戏对象默认都附加了一个名为变换(Transform)的组件。它允许你定义游戏对象的位置、旋转和缩放。通常,你可以随意在任意游戏对象中附加、分离和销毁组件,但你不能移除变换(Transform)。
每个组件都有一些您可以访问和更改的属性:这些可以是整数或浮点数、文本字符串、纹理、脚本、游戏对象或其他组件的引用。它们用于更改特定组件的行为,影响其外观或交互。您在第二章中已经遇到的某些属性,包括Unity 和 Playmaker 的用户界面中的位置、旋转和缩放属性,变换组件。还有其他一些您已经看到,包括 FSM,我们将在本章后面讨论。
以下截图显示了附加了变换、网格过滤器、盒子碰撞器、网格渲染器和脚本组件的墙游戏对象。显示了变换的属性。为了显示或隐藏组件的属性,您需要左键单击其名称或其图标左侧的小箭头。

Unity 有一些预定义的游戏对象,它们已经附加了组件,例如摄像机、灯光和原语。您可以通过从主菜单中选择GameObject | 创建来访问它们。或者,您可以通过按command + Shift + N (Ctrl + Shift + N 在 Windows 上)创建空的游戏对象,并使用组件子菜单将组件附加到它们。
以下图显示了我们所讨论的项目结构。请注意,一个项目中可以有任意数量的场景,一个场景中可以有任意数量的游戏对象,一个游戏对象可以有任意数量的组件附加到它,最后,一个组件中可以有任意数量的属性。

目前您需要了解的关于组件的最后一件事是,您可以通过在检查器面板中右键单击组件的名称,并从以下截图所示的上下文菜单中选择复制组件来复制它们。您还可以将组件的属性重置为其默认值,删除组件,或根据您的方便将其上下移动。

复制的组件可以通过在检查器面板中右键单击任何组件的名称,并从相同上下文菜单中选择粘贴组件为新组件或粘贴组件值来粘贴。这两个命令之间的区别在于,前者将添加一个与您复制的组件完全相同的新的组件,而后者将简单地传输所有属性的值。一旦您复制了组件,这些命令就会变为活动状态。
使用预制体
为了创建具有相同组件和属性的多个游戏对象实例,或者从不同的场景访问相同的游戏对象,你可以将其保存为Assets文件夹中的一个文件。在 Unity 中,这些文件被称为预制件,它们就像游戏对象模板。要创建一个空预制件,在项目面板的空白区域右键单击,然后从上下文菜单中选择创建 | 预制件。应在项目面板中看到一个名为New Prefab的新文件。将此预制件命名为Wall。现在创建一个名为Prefabs的空文件夹,并将Wall预制件放入此文件夹中。
目前预制件是空的。
- 
在层次结构面板中,选择你在第二章中创建的Wall游戏对象,Unity 和 Playmaker 的用户界面。点击并拖动它到项目面板中的 Wall预制件。
- 
Wall游戏对象的名字在层次结构中会变成蓝色,当你选择 Wall预制件时,你将在检查器面板中看到原始Wall游戏对象拥有的所有组件。
现在,如果你将Wall预制件拖放到场景中,将创建一个名为Wall的新对象,其属性与预制件中定义的属性完全相同。
此外,如果你使用检查器面板更改预制件中的一些组件和/或属性,这些更改将自动应用于场景中该预制件的全部实例。如果你有多个相同类型的对象(即墙壁、怪物、树木等),这将为你节省大量时间,因为你不必在层次结构中逐个选择它们并多次进行相同的更改。
你也可以通过选择层次结构中的一个实例,进行更改,并在检查器面板顶部附近点击应用按钮来修改你的预制件(以及由此扩展的场景中所有实例)。你还可以通过点击还原按钮撤销所有更改。这将将当前选定的实例重置为其预制件设置的方式。
这之所以有效,是因为实际上实例的所有组件和属性都链接到预制件的组件和属性。只有当你更改实例中的它们时,它们才会解除链接,这意味着对该属性在预制件上的进一步更新不会应用于此实例。这些解除链接的属性可以在检查器面板中通过其名称变为粗体来区分。如果你想重新链接一个特定的解除链接属性,你可以右键单击它并选择将值还原为预制件。
除了Apply和Revert按钮外,所有预制件实例还有一个Select按钮。此按钮允许你选择并突出显示在Project面板中对应于当前选中实例的预制件,这样你可以更容易地访问它。此外,正如我们之前看到的,预制件实例可以通过Hierarchy面板中其蓝色的名称轻松识别。创建预制件的另一种方法甚至更简单:你可以直接将Hierarchy面板中的游戏对象拖放到Project面板中。在Assets文件夹中会创建一个适当命名的文件。
关于预制件,还有一件非常重要的事情需要了解,那就是它们可以在网络上进行复制和共享,因为它们不过是 Unity 识别的包含组件和属性信息的文件。现在,按照以下步骤修改Wall预制件以更好地满足我们的需求:
- 
在Project面板中创建一个新的文件夹,并将其命名为 Materials。它将包含包含游戏对象纹理、着色器和颜色的信息文件。
- 
通过点击Project面板左上角的Create按钮,并从下拉列表中选择Material来创建一个新的材质文件。 
- 
将新材质命名为 WallMaterial并将其放入Materials文件夹中。
- 
在Project面板中选择WallMaterial,然后在Inspector中点击文本旁边带有Main Color字样的矩形。在出现的Color窗口中,选择黑色并关闭Color窗口。以下截图显示了Material修改界面以及Color窗口: ![使用预制件]() 
- 
现在你已经设置了这种新材料,选择 Wall预制件,然后将WallMaterial拖放到预制件Mesh Renderer组件的Materials属性的Element 0元素中。它应该替换Default Diffuse。以下截图显示了分配新材料后Mesh Renderer组件应有的样子。![使用预制件]() 
- 
现在,所有 Wall预制件的实例都将变为黑色。
- 
此外,将Transform组件的 X 缩放属性设置为 15。这将使所有新墙变得更长。
- 
选择Main Camera游戏对象,并使用Inspector面板将其Camera组件的Projection属性设置为Orthographic。这将使摄像机的视图变得扁平;现在游戏视图将呈现二维。 
- 
将Size属性设置为 5。你可以稍后调整外观,但就目前而言,这将帮助我们专注于游戏玩法。
小贴士
在设计过程的初期,限制自己使用简单的原始形状、彩色编码的无纹理材质和正交投影相机通常是一个好主意,因为这会让你专注于游戏玩法。这样,如果你的游戏很有趣,你就会知道,如果它不好,你也不会被花哨的视觉效果所分散注意力。
制作四个墙壁并将它们在场景中排列,以便你在Game视图中看到以下截图。如果不是这样,你可以回到第二章的表格,Unity 和 Playmaker 的用户界面,看看你的Main Camera游戏对象是否定位和旋转正确。其位置应设置为(0, 10, 0),旋转为(90, 0, 0)。别忘了,你可以通过在Transform组件中更改旋转角度将对象旋转到精确值,例如90度和180度。同时确保所有墙壁的 Y 位置保持等于0.5。

有限状态机、状态和动作
我们已经讨论了游戏对象和组件。现在,让我们看看 Playmaker 中的 FSM、状态和动作。
Playmaker 中的 FSM 是一个由状态和它们之间的转换组成的图,附加到一个游戏对象上。它允许使用图的不同状态和触发转换到其他状态的事件进行可视化编程。
如果你选择了Wall预制体并打开playMaker面板,你会看到它的有限状态机(FSM)有两个状态:默认的Start状态,一切从这里开始,另一个默认称为State 1的状态,通过一个箭头将前者连接到后者。箭头代表一个转换。你不能移除 Start 状态或与之相连的状态,因为如果你能这样做,你就不需要将 FSM 附加到对象上了。然而,你可以创建新的状态并为它们定义转换。
小贴士
你可以几乎以与Scene视图相同的方式导航 FSM 视图。使用中间鼠标按钮拖动视图。使用左鼠标按钮移动状态。这不会改变状态机的逻辑,但让你以对你有意义的方式组织一切,同时如果你整个状态机太大而无法一次性显示,还可以查看你此刻最感兴趣的部分。
FSM 中的状态默认为空,不做任何事情,就像一个没有任何组件附加的空游戏对象。为了使状态执行某些操作,你需要将其附加到动作。
正如我们在第二章中看到的,Unity 和 Playmaker 的用户界面,你可以通过选择对象,在playMaker面板的 FSM 视图中右键单击,然后从上下文菜单中选择Add FSM来向对象添加一个 FSM。你可以通过在Inspector中右键单击playMaker FSM(Script)组件的标题并从上下文菜单中选择Remove Component来从 Playmaker 控制中移除对象。这将移除 FSM 并擦除你对它所做的所有更改,包括添加的状态和转换。如果一个对象附加了 FSM,那么在Hierarchy面板中它的名称旁边会有一个红色的 Playmaker 象形图标 ( )。
)。
作为本书的一个示例,我们将制作一个经典的空气桌球游戏版本。为了开始,我们需要添加一个冰球和一个球杆。在本章中,你将根据鼠标位置移动球杆,并像在现实生活中那样推动冰球。
目前在你的场景中有四面墙,一个背景四边形,一个摄像机和一个方向性光源(在第二章中创建,Unity 和 Playmaker 的用户界面)。现在是我们使事物变得交互的时候了。让我们从球杆开始。
- 
在主菜单中选择GameObject | Create Other | Cylinder来创建一个新的圆柱形原形。 
- 
将这个游戏对象重命名为 Mallet,并为它创建一个深绿色(在Color窗口中 RGB 颜色设置为10,155,10)的材料,命名为MalletMaterial,然后将其分配给它,就像之前为Wall预制体所做的那样。
- 
将Mallet的缩放设置为( 1.35,1.35,1.35),并将其位置设置为(-6.5,1.45,0)。
- 
现在我们将使球杆移动。首先,我们需要向它添加一个名为Character Controller的组件。这个组件负责角色物理。选择Mallet,然后在Inspector面板底部附近点击Add Component按钮。在搜索栏中输入 Character Controller,然后在列表中双击Character Controller项(如图所示)。当 Unity 询问你是否要替换现有的CapsuleCollider组件时,点击Replace。![有限状态机、状态和动作]() 
- 
在Character Controller组件中,将Skin Width属性设置为 0(它将被设置为可能的最小值,即0.0001)。我们这样做是为了确保我们的球杆碰撞看起来精确。
- 
通过选择球杆,在playMaker面板的 FSM 视图中右键单击,并从上下文菜单中选择Add FSM来向球杆添加一个 FSM。 
- 
选择状态 1,然后在playMaker面板右侧的状态选项卡中,从顶部第一个文本框输入 Move。这负责当前选中状态的名称。当您输入新名称时,您应该会看到 FSM 视图中状态的变化。
- 
保持移动状态选中,打开动作面板(它应附加在检查器面板相同的区域;或者,点击状态选项卡右下角的动作浏览器按钮)并找到输入类别下的鼠标拾取动作。点击它,然后点击面板右下角的将动作添加到状态按钮。您应该会注意到鼠标拾取动作出现在playMaker面板的状态选项卡中。 此操作在您悬停在对象上时获取 3D 空间中的光标位置。在底层,它会从鼠标位置在相机的近裁剪平面(当您选择主相机时,您可以在场景视图中将其视为白色工具箱矩形之一)绘制一条不可见的射线(此操作称为射线投射)。如果射线路径上有物体,则会检测到射线碰撞,Unity 会找出碰撞的确切位置。在我们的例子中,我们将使用背景四边形来获取鼠标光标的位置,然后使球槌跟随它。 
- 
为了选择正确的射线碰撞位置,我们需要确保没有其他物体阻挡射线。为此,我们将告诉射线仅与背景四边形交互。选择四边形游戏对象,并将其重命名为 Background以提高可读性。然后找到检查器面板右上角的层下拉菜单,点击默认情况下显示为默认的下拉按钮,并在其中按下添加层…。此时,检查器的外观应发生变化,以显示如图所示的标签和层列表:![有限状态机、状态和动作]() 此菜单称为标签管理器。点击用户层 8的右侧,并在出现的编辑字段中输入 Background,然后按键盘上的Return键。再次选择背景对象,并通过从您之前用于访问标签管理器的下拉层列表中选择适当的元素,将其层设置为背景。
- 
再次选择球槌。在移动状态下的鼠标拾取操作中,将层掩码参数设置为 1。这决定了您将设置多少层与射线交互。元素 0应出现在下方。在其右侧的下拉列表中,选择您之前创建的背景层。
- 
从现在开始,鼠标拾取动作中的射线投射将忽略不在背景层的所有对象。现在我们需要将射线击中位置存储在一个变量中。转到playMaker面板的变量选项卡,在底部的新变量字段中输入 mousePos。点击添加按钮。将变量类型设置为Vector3。一个Vector 3变量包含三个数字:X、Y和Z。
- 
返回到状态选项卡,并将鼠标拾取动作的存储点属性设置为mousePos。这将保存射线击中的位置在您刚刚创建的Vector3类型变量中。最后,确保在鼠标拾取动作的底部勾选每帧属性复选框,以确保鼠标位置持续更新,而不是仅在游戏开始时更新一次。 
- 
将位于动作面板角色类别下的控制器简单移动动作添加到您的移动状态。它应该出现在鼠标拾取动作下方。如果它出现在上方,你可以通过点击并拖动其标题将其向下移动。 需要注意的是,状态中动作的顺序很重要:前面提到的动作将在后面提到的动作之前执行,所以如果你想使用在鼠标拾取动作中设置的变量,你必须确保鼠标拾取在将要使用它的任何动作之上(在我们的例子中是控制器简单移动)。 
- 
将控制器简单移动动作的移动向量属性设置为mousePos。将其他属性保留为默认值。 
游戏对象之间的交互
现在球槌可以移动了,我们将让它与球体交互。当球槌接触到球体时,我们将对其施加一个相反方向的力。
你可以点击工具栏中的播放按钮,看看球槌如何根据鼠标位置移动。你会注意到它会与墙壁发生碰撞,平滑地跟随鼠标光标,并根据你移动鼠标的速度和距离改变其移动速度。
- 
创建另一个名为Puck的圆柱体,并将其放置在( -3,0.85,0)的位置。将其比例设置为(1,0.7,1)。
- 
向其添加一个Rigidbody组件(组件 | 物理 | Rigidbody)。将质量属性设置为 0.1,取消勾选使用重力,打开约束部分,并勾选冻结位置 Y和冻结旋转 X、Y和Z。
- 
创建一个新的深灰色( 65,60,60)材质(资产 | 创建 | 材质),命名为PuckMaterial,并将其分配给圆柱体的Mesh Renderer。
- 
现在球体已经设置好了,我们将让球槌推动它。转到 FSM 中球槌的移动状态,并添加一个名为碰撞事件(在物理类别下)的动作。将碰撞属性设置为控制器碰撞器击中。 ![游戏对象之间的交互]() 
- 
通过选择Puck游戏对象并在检查器的标签下拉菜单中选择添加标签...来打开TagManager。以与为背景层添加标签相同的方式添加一个名为Puck的新标签。标签位于TagManager的顶部附近。如果您修改大小变量,可以拥有任意数量的标签。将Puck游戏对象的标签设置为Puck。 
- 
返回到球槌的 FSM 中的移动状态。将碰撞标签属性设置为Puck的碰撞事件动作。 
- 
在playMaker面板中打开事件选项卡,在选项卡底部的添加事件字段中输入 Push,然后按键盘上的Enter键。
- 
再次打开状态选项卡,并将Collision Event动作的发送事件属性设置为Push。 
- 
现在我们必须告诉 FSM 事件调用后会发生什么。通过在 FSM 视图中任何位置右键单击并从上下文菜单中选择添加状态来在球槌的 FSM 中创建一个新状态。将新状态命名为 Push Puck。在 FSM 视图中右键单击移动状态并从上下文菜单中选择添加转换 | Push。在移动状态下方应出现一个新的标签Push。单击它并将出现的线条拖动到Push Puck状态。这条线是当在移动状态中调用Push事件时发生的转换。以相同的方式向Push Puck状态添加一个FINISHED事件。您不需要创建它,因为它是一个默认的 Playmaker 事件。
- 
从FINISHED事件到移动状态创建一个转换,以确保当球被推时,球槌将保持在玩家的控制之下。以下图显示了您的 FSM 应该看起来像什么: ![游戏对象之间的交互]() 
- 
将以下动作逐个添加到球槌的 FSM 中的Push Puck状态:Get Controller Hit Info,Get Position,Vector3 Subtract,Vector3 Normalize,Vector3 Multiply,和Add Force。请注意动作的顺序很重要,因为列表上方的动作将先执行。以下截图显示了动作的正确顺序。 ![游戏对象之间的交互]() 
- 
使用playMaker面板的变量选项卡创建以下变量:hitPos(Vector3),pushDir(Vector3),pushMag(Float)。选择pushMag并将其浮点值设置为 20。此值将决定球槌推球的力度。
- 
返回到状态选项卡,将Get Controller Hit Info动作的接触点属性设置为hitPos。 
- 
将Puck游戏对象从层次结构拖动到Get Position动作的游戏对象槽中,然后将向量设置为pushDir。 
- 
在Vector3 Subtract动作中,将Vector3 变量设置为 pushDir。点击Subtract Vector旁边的选项图标(![游戏对象之间的交互]() )。这将允许你从列表中选择一个变量,而不是使用数值。将Subtract Vector设置为 )。这将允许你从列表中选择一个变量,而不是使用数值。将Subtract Vector设置为hitPos。
- 
在Vector3 Normalize动作中,将Vector3 变量设置为pushDir。 
- 
在Vector3 Multiply动作中,将Vector3 变量设置为pushDir,并将乘以设置为pushMag。如果需要,点击小选项图标以显示 FSM 变量。 
- 
最后,在添加力中,将游戏对象属性设置为指定游戏对象,并将Puck游戏对象拖动到下方出现的槽位中。将向量设置为pushDir。点击X、Y和Z旁边的选项图标,并保持它们在无状态,以确保它们不会被重置为 0,而是简单地没有分配。如果需要,点击小选项图标以显示无选项。然后设置空间为世界。
如果你点击播放并使球棒与它碰撞,你的球现在应该变得可交互。我意识到最后一点同时有很多动作,所以我们将在下一章中讨论这些动作中确切发生了什么。这是一个我们在讨论游戏机制时将要探讨的一些复杂逻辑的例子。现在,将其视为一个熟悉动作和变量以及它们在playMaker面板中添加和分配方式的练习。
作为另一个练习,保存你的场景,创建几个带有 FSM 的新游戏对象,并尝试实验不同的动作和变量,看看它们是如何添加和分配的。如果你的动作没有做什么,不要担心。尝试熟悉 Playmaker 界面,并记住如何添加、移动和删除动作,创建新的变量和事件,并将它们分配。
一旦你觉得对这些动作很熟悉,你可以删除你用于练习的对象,或者简单地通过在项目面板中双击Scene1文件来重新加载场景而不保存它。
摘要
在本章中,我们讨论了 Unity 中的项目结构:场景、游戏对象、组件和属性。我们还更详细地了解了 Playmaker 界面:动作、事件、变量和转换。你通过实现一个用鼠标移动的球棒和一个当球棒接触时被推动的球,迈出了创建冰球游戏的第一步——一个真正的游戏机制。在下一章中,我们将添加更多的游戏机制,更详细地解释已经实现的游戏机制,并尝试使游戏更加有趣和美观。
第四章。创建你的第一个游戏
在 第三章,组件和状态机 中,你为空气曲棍球游戏制作了第一个游戏机制,我们将在此书的剩余部分继续改进它。你制作了一个冰球和一个由鼠标指针控制的球槌,球槌可以推动冰球。推动机制相当复杂,需要一些深入的分析,这是我们本章要讨论的第一件事。本章将涵盖以下主题:
- 
使用矢量几何和物理 
- 
胜/负条件 
- 
创建人工智能 
- 
Playmaker 调试 
最后,我将给你一些练习,你将能够使用你在本章结束时获得的技术和知识来完成它们,以及一些如果你有任何问题如何寻找答案的建议。
使用矢量几何和物理
如果你像我一样,你可能会觉得这个部分的标题令人畏惧。然而,在你决定跳过它或开始寻找关于 Unity 的新书之前,我向你保证,它不会让你接触到任何数学公式。相反,它将使用 Playmaker 动作,并解释每个动作的作用,这将反过来引导我们得出关于其背后的科学结论。
几乎没有哪种 3D 视频游戏不需要某种矢量几何和物理,而且自然地,这两者都是解释你的新空气曲棍球游戏中球槌-冰球交互工作原理的关键。
首先,让我们在 playMaker 面板的 FSM 视图中点击 Push Puck 状态,然后在右侧打开 Variables 选项卡。列表中有三个 Vector3 类型的变量和一个 Float 类型的变量。以下变量对我们来说很重要,因为它们用于计算推力和方向:
- 
hitPos 是一个 Vector3 变量,我们用它存储球槌和冰球碰撞点的 X、Y 和 Z 坐标 
- 
pushDir 是一个 Vector3 变量,我们用它存储当冰球与球槌碰撞时将要被推的 X、Y 和 Z 方向 
- 
pushMag 是一个当前等于 20 的 Float 变量,对应于推力的幅度 
要理解视频游戏中的事物是如何工作的,你必须熟悉使用 Vector3 变量,因为你将要做的很多事情都将发生在 3D 空间中。每个 Transform 组件有三个 Vector3 属性:Position、Rotation 和 Scale,每个属性都有其自己的 X、Y 和 Z 值。
3D 空间中的每个点都有可以写成三个 Float 变量形式的坐标:X 轴上的位置、Y 轴上的位置和 Z 轴上的位置。一个 Vector3 变量可以同时存储这三个位置值。
你可以对 Vector3 执行各种操作,你可以将它添加到另一个向量中,你可以将它乘以一个浮点数(或一个整数),你可以将它的 X、Y 和 Z 存储在单独的 Float 变量中,或者将单独的浮点值写入其 X、Y 和 Z。这些操作中的大多数都在 Actions 面板的 Vector3 类别下。
我们使用的第一个动作被称为 Get Controller Hit Info,它获取游戏对象发生的最后碰撞的位置。如果你查看触发 Push 事件的 Move 状态中的 Collision Event 动作,你会看到这只有在槌子与冰球碰撞时才会发生,所以我们不需要担心在 Push Puck 状态中过滤掉假碰撞。我们唯一需要做的就是将接触点保存到一个变量中,这正是所发生的事情。

接下来,我们简单地获取槌子的当前位置并将其存储在 pushDir 变量中,暂时使用 GetPosition 动作。这个变量被称为 pushDir,因为它将包含槌子将被推的方向。我们通过访问槌子的 Transform 组件的 Position 属性来获取这个位置。

如果这些都是位置,那么如何得到一个方向?方向是什么,你如何在 Vector3 中写下来?答案是相当简单的。方向是空间中一点相对于另一点的相对位置,这另一种说法就是它是 位置 A 和 位置 B 之间的差异。以下图显示了两个点及其相对方向。

因此,为了找到点 B 相对于点 A 的方向,你必须从点 A 的位置中减去点 B 的位置。然后你需要对结果进行归一化,这意味着将其写成一个具有其 X、Y 和 Z 属性为介于 0 和 1 之间(即没有大小)的 Vector3 变量。
例如,如果减法运算的原始结果是 (-1, 27, 350),则方向归一化向量为 (-0.00284867, 0.076914, 0.997034),而且最好的部分是,你不需要知道它是如何发生的,因为有一个 Playmaker 动作会自动为你完成。以下截图显示了用于从槌子中心到击中点的两个动作:

让我们回顾一下我们到目前为止所涵盖的内容:
- 
我们需要槌子的位置,因为这是我们打算从那里推动冰球的。 
- 
我们需要球拍和球之间的撞击点,以便在球拍击打球时找到球相对于球拍在空间中的位置 
- 
这两个值都保存为Vector3变量,每个变量都包含一个X、Y和Z位置 
- 
我们可以通过从球拍的位置减去撞击位置并归一化结果向量来找出球相对于球拍的方向,即将其X、Y和Z属性以 0到1之间的数字写下
现在我们知道了如何找到球相对于球拍的方向,我们希望将球推向这个方向,所以这正是我们要做的。然而,我们不能直接将方向向量作为球的速率,因为方向向量没有大小,这意味着球的速率将远远小于我们想要的。
为了控制推动球的力量,我们必须将方向乘以一个大小,这个大小存储在pushMag变量中。该变量决定了球一旦遇到球拍就会以多快的速度向相反方向移动。
小贴士
如果你将pushMag赋值为负值,每次球与球拍碰撞时,球都会被推向球拍,这反过来意味着它会粘附在球拍上。这也可以是一种有用的游戏机制,尽管在这个案例中不是。
以下截图显示了Vector3 Multiply动作,该动作将pushDir方向向量乘以pushMag大小,以及Add Force动作,该动作在撞击点对球施加力。

赢/输条件
虽然有些游戏不需要赢/输条件,但冰球台游戏绝对不是其中之一。在经典冰球台中,桌子两侧各有两个目标槽位。如果球进入玩家 1 的目标,那么玩家 2 得一分,反之亦然。游戏在一方玩家得到 7 分时结束。
有短期赢的条件(得一分)和长期赢的条件(在对手之前得七分)。我们只会实现短期赢的条件,尽管我们强烈鼓励你在完成这一章后尝试实现长期条件。
在我们开始实现赢/输条件之前,让我们确保球不能卡在角落里,因为这种行为将阻止赢/输条件被触发。制作四个新的墙壁,将它们在 Y 轴上旋转 45 度,并将它们放置在角落,以消除下一个图中显示的直角。这应该会解决问题。
现在已经处理好了这个问题,让我们制作目标槽位。将球的大小Scale属性更改为(1, 0.7, 1),并将其放置在(-3, 0.3, 0)。
- 
在左侧再建一堵墙,在右侧再建一堵墙。墙的放置应如图所示: ![胜负条件]() 
- 
制作两个新的立方体(GoalLeft和GoalRight),并将它们放置在下一图所示的左侧和右侧的间隙中。将左侧的立方体设置为绿色( 0,255,0),将右侧的立方体设置为红色(255,0,0),为它们创建新的材质。它们都应该在 Y 轴上的2.5处。这些是为了阻挡球槌,而不是球。
- 
再制作两个立方体。将它们命名为 GoalTriggerLeft和GoalTriggerRight。将它们放置在游戏场地的两侧,正好在桌子后面。这些对象将作为触发器来检测哪个球得分。检查这些游戏对象的Box Collider组件的是触发器属性。这个碰撞组件的属性确保对象不会进行任何物理碰撞(与球或墙壁相反),而是作为触发器,检测刚体何时进入、停留在其中以及何时退出。这些对象是实际触发胜负条件的球门。
- 
在这一点上,你的场景视图应该看起来像以下截图: ![胜负条件]() 
- 
选择GoalTriggerLeft游戏对象,并向其添加一个 FSM。创建两个状态,将State1重命名为 得分,将State2重命名为加载级别。
- 
右键单击得分状态,从上下文菜单中选择添加过渡 | 系统事件 | 触发进入。 
- 
从触发进入到加载级别创建一个过渡。当某个物体进入触发器时(由于场景中物体的放置方式,这应该是球),将调用此事件,并执行过渡。 
- 
选择加载级别状态,并向其添加一个加载级别动作。它应该位于级别类别下。 
- 
在加载级别动作的级别名称属性中,输入你的游戏场景名称。如果你没有做任何更改,它应该被称为 Scene1。为了确保这一点,查看Unity 编辑器窗口的顶部。它总是显示为[SceneName].unity - [ProjectName] - [Platform],其中括号中的内容对我来说是Scene1、UPMTutorial和Web Player,但可能因人而异。
- 
对于GoalTriggerRight也做同样的事情。目前,保持 FSM 完全相同。 
现在,我们可以通过导入 3D 模型来为我们的场景添加一些完成效果:
- 
确保你已经从 Packt Publishing 网站下载了项目存档。如果你还没有,现在就做,并解压存档。 
- 
在项目面板下文件列表的空白区域右键单击,并从上下文菜单中选择导入新资产...。应该会弹出一个文件浏览器。 
- 
在 UnityPlaymakerTutorial目录中定位WallVisual.fbx文件,并点击导入。文件浏览器将关闭,并在项目面板中显示WallVisual文件。创建一个Models文件夹,并将其拖入其中。
- 
选择 WallVisual文件,并查看检查器。在这个检查器中有三个选项卡:模型、绑定和动画。这些选项卡都负责更改模型的导入设置。
- 
由于我们导入的模型不是动画模型,因此只有模型选项卡对我们感兴趣。将缩放因子设置为 1.05,并取消选中导入材质。将其他属性保留在默认值。缩放因子在导入时改变模型的缩放,这意味着变换缩放属性将等于(1,1,1),而实际在 3D 空间中的大小可能会改变。这是必要的,因为 3D 建模软件(如 Maya 或 3DS Max)中的工作比例可能与 Unity 中的不同。为了使模型看起来更大,增加缩放因子。请参阅以下截图以确认您的导入设置是否正确:![胜负条件]() 
- 
将WallVisual拖入层次结构两次。将第一个实例放置在( 0,0.5,-3.5),第二个实例放置在(0,0.5,3.5)。将第一个实例的旋转设置为(0,0,0),第二个实例的设置为(0,180,0)。小贴士预制件和导入模型的区别在于,您不能将您对实例所做的更改应用到其中一个实例上。您也不能从模型中添加或删除任何组件。您可以从模型的实例中创建一个预制件,以便能够这样做。 
- 
将两个WallVisual游戏对象的材质设置为之前创建的用于墙壁的WallMaterial。选择所有Wall对象,并禁用它们的网格渲染器组件。以下图示显示了如果您一切操作正确,您应该在游戏视图中看到的内容: ![胜负条件]() 
现在,当您按下播放时,您应该能够在两个球门得分。这样做将重新加载当前加载的水平。我们还确保了冰球不会卡在桌角的角落里,并导入了一个覆盖我们立方墙的模型,使游戏看起来更像真正的空气曲棍球桌。这意味着游戏最终可以玩,尽管坦白说,目前还不够有趣。
创建人工智能
在游戏中加入人工智能(AI),即使是一个非常简单的 AI,也肯定会使其更具挑战性和趣味性,所以让我们立即着手进行。
这就是我们的 AI 将如何工作:它将不断尝试移动到冰球右侧的点,并在碰撞时将其推向左侧。我们将保持与玩家球拍相同的碰撞逻辑,以保持游戏的公平性。为了确保 AI 在直接向左推时不会卡在墙上,我们将根据球拍当前的位置调整方向。这样,AI 看起来就像是在瞄准玩家的目标槽位。
按照以下步骤实现 AI:
- 
通过在层次结构中选择Mallet游戏对象并按command + D(在 Windows 上为Ctrl + D)来复制Mallet游戏对象。 
- 
将副本移动到桌子的右侧,并放在红色球门旁边。 
- 
将左侧的原始球拍命名为 MalletLeft,右侧的副本命名为MalletRight。
- 
为MalletRight制作一种新的红色材料并将其分配给它。 
- 
使用检查器中可访问的TagManager菜单,将两个球拍放置在名为玩家的新层上。 
- 
在主菜单中,转到编辑 | 项目设置 | 物理。你将在检查器面板中看到带有层名称的复选框矩阵,这些名称水平垂直地写在每一行和每一列旁边。 
- 
找到玩家水平方向和玩家垂直方向之间的交点,并取消选中该框。确保其余的框保持选中状态。这个矩阵决定了哪些层可以相互交互以及与自身交互,所以如果你意外取消选中了其他内容,例如默认/玩家或默认/默认,冰球可能不再与目标触发器碰撞,球拍可能停止与墙壁碰撞。 
- 
选择MalletRight游戏对象。在检查器中,移除Character Controller组件并添加胶囊碰撞器。添加一个Rigidbody组件。在Rigidbody组件上设置冻结旋转为 X、Y 和 Z。如果不这样做,当你按下播放时,红色球拍会掉落。Rigidbody是负责物理交互的组件。 
- 
在playMaker面板的 FSM 视图中,选择移动状态。我们将在这里设置 AI。 
- 
删除所有动作,除了碰撞事件。你可以通过Shift-点击它们的标题来同时选择多个动作。打开动作面板,将以下动作添加到状态中,同时注意动作的顺序,并将碰撞事件移动到最底部:获取属性,获取属性,获取属性,浮点乘法,浮点加法,浮点加法,设置 Vector3 XYZ,和向目标移动。以下截图显示了你应该在MalletRight的 FSM 的移动状态中具有的动作的确切顺序。 ![创建人工智能]() 
- 
在事件选项卡中,添加一个新事件,命名为 Return。
- 
打开 变量 选项卡,添加/删除变量,直到你拥有以下截图所示的变量: ![创建人工智能]() 
- 
这些是之前留下的用于碰撞计算的变量,以及一些用于我们之前讨论的简单 AI 行为的新变量。 
- 
返回到 状态 选项卡,并打开第一个 获取属性 动作。将 Puck 游戏对象拖放到 目标对象 中,并将 属性 设置为 transform | position | x,并将 存储浮点数 设置为 puckX。检查 每一帧。实际上,每次你在这个状态下看到这个复选框时,都要检查 每一帧。 
- 
对第二个 获取属性 选项卡执行相同的操作,但选择 z 而不是 x,并将 puckZ 而不是 puckX。这些动作用于检索对我们重要的两个轴上的冰球位置。 
- 
打开第三个 获取属性 动作,并将 MalletRight 拖动到其 目标对象 属性中;将其 Z 位置存储在偏移变量中。 
- 
在 浮点数乘法 动作中,将 浮点变量 设置为 offset,并将 乘以 设置为 0.2。这是我们定义 AI 应该多努力瞄准玩家球门的地方。如果它太努力,它会错过。如果它不够努力,它也会错过。
- 
在第一个 浮点数添加 动作中,将 浮点变量 设置为 puckX,并将 添加 属性设置为 offset。你需要点击右侧的选项按钮来完成此操作。这是实际应用偏移的地方。 
- 
在第二个 浮点数添加 动作中,将 浮点变量 设置为 puckZ,并将 添加 设置为 0.9。这个值以后可以按需更改。它定义了实际冰球中心与 AI 将要移动到的点的距离。AI 稍微向右瞄准非常重要,否则它永远无法击中玩家的球门。
- 
在 设置 Vector3 XYZ 中,将 Vector3 变量 设置为 targetPos,X 设置为 puckX,并将 Z 设置为 puckZ。确保 Y 设置为 None。这是我们定义 AI 在当前帧中将要尝试移动的实际位置的地方。 
- 
Move Towards 是将移动应用到 AI 的动作。将 目标位置 设置为 targetPos,最大速度 设置为 5,完成距离 设置为0.15,以及 完成事件 设置为 Return。
- 
最后,打开 碰撞事件,并将其 碰撞 属性更改为 碰撞后停留;保留其他一切不变。 
- 
现在,你可能在 Move Towards 动作中遇到错误。那是因为没有从 Return 事件过渡。在 FSM 视图中右键单击 Move 状态,并添加从 Return 事件到 Move 的过渡。是的,这是一个自我循环的状态。以下图显示了你现在应该在 MalletRight 的 FSM 视图中看到的布局。 ![创建人工智能]() 
如果你现在点击播放按钮,AI 应该完全功能正常,实际上相当强大。现在,有一些改进它的方法将在本章的练习部分讨论,但应该足以让你在 Playmaker 中享受游戏调试的乐趣。
现在你已经有了玩家控制器、胜负条件和功能性的 AI,游戏开始变得相当庞大。如果你想要向其中添加任何内容或者出现问题,你将很难实时了解你的动作中到底发生了什么。这就是调试的作用:它是一个诊断工具,让你能够获取关于你的游戏或其特定系统的额外信息,你可以在之后使用这些信息来修复某些问题。
让我们来看一个例子。选择MalletLeft并在playMaker面板的 FSM 视图中打开其移动状态。如果你查看状态标签页的底部,你会看到那里有两个复选框:调试和隐藏未使用。第一个复选框显示你所有属性在所有时间点的值,而第二个复选框隐藏未使用的属性。检查这两个复选框,并保持MalletLeft被选中,然后按下播放按钮。
当你移动球槌时,观察在所有动作属性下变化的数字。当你实现一个新的游戏玩法特性时,你将想要知道数值变化在什么范围内,以及它们是否真的发生了变化。
你还可以使用控制台面板来调试某些事情。取消选中调试和隐藏未使用,然后打开相同 FSM 的推球状态。在它的顶部添加一个调试日志动作(在调试类别下)。将日志级别设置为警告并在文本框中写下击中!。
小贴士
你应该知道,存在三种类型的调试日志:信息、警告和错误,每种都有特定的功能。警告是为了吸引你的注意力到某个问题,信息是提供关于某物信息的简单消息,而错误则让你知道存在问题。这些只是惯例,但你将看到一些遵循它们的 Unity 内部信息、警告和错误。
现在打开控制台面板。我们之前简要地讨论过它,但现在让我们详细看看其中的各种按钮(从左到右)。
- 
清除相当直观;它清除日志,移除所有消息,除了未修复的编译错误。 
- 
折叠是一个切换按钮,它使得相似的调试消息出现在同一行或分别显示。 
- 
播放时清除是一个切换按钮,当你按下播放时,它会强制控制台清除日志。 
- 
错误暂停会在出现错误时自动暂停游戏,以便你能更好地查看它。 
最后,右侧的三个切换按钮是控制台的过滤器,让你能够关注不同类型的调试消息:信息、警告和错误。
如果你现在按播放并使用球槌击打冰球,Console中应该会显示一个带有黄色三角形的消息,例如MalletLeft : FSM : Push Puck : DebugLog : Hit! 你可能还会看到几个这样的消息。停止游戏,看看当你开关Collapse和切换警告过滤器时会发生什么。
如果你现在按播放并使用球槌击打冰球,Console中应该会显示一个带有黄色三角形的消息,例如MalletLeft : FSM : Push Puck : DebugLog : Hit! 你可能还会看到几个这样的消息。停止游戏,看看当你开关Collapse和切换警告过滤器时会发生什么。你可能会看到几个,如下面的截图所示:

你也可以用同样的方式调试变量值。打开playMaker,移除Debug Log动作,并在Get Controller Hit Info之后添加一个Debug Vector3动作,将Log Level设置为Error并将Vector3 Variable设置为hitPos。然后打开Console并再次测试游戏,用球槌击打冰球。现在你应该会在Console中看到红色错误消息。如果你启用了Error Pause,游戏将暂停,Console面板将展开以显示错误。
你可以通过使用称为断点的东西来实现类似的效果。从MalletLeft的 FSM 的Push Puck状态中移除Debug Vector3动作,然后在 FSM 视图中右键单击Push Puck状态,从上下文菜单中选择Toggle Breakpoint。在 FSM 视图中状态名称旁边将出现一条红色线。
现在,如果你按播放并用球槌触摸冰球,游戏将像使用Error Debug日志和Console中的Error Pause切换时一样停止,但这次暂停将由 Playmaker 触发。在 FSM 视图中将出现一个带有断点状态名称的红色圆圈。以下截图显示了它应该看起来像什么:

注意,从Push事件到Push Puck状态的转换箭头变成了黄色。这意味着断点是在这个特定的转换之后触发的,而不是其他原因。
当游戏因为断点而暂停时,你可以取消暂停游戏,但如果这个或另一个断点被触发,游戏将再次暂停。你可以通过右键单击带有断点的状态并从上下文菜单中选择Toggle Breakpoint来移除断点。
另一个有用的调试工具是逐步执行。当游戏暂停时(包括由于断点或错误暂停而暂停),你可以按下工具栏或playMaker面板底部的下一步按钮。这将执行游戏的下一帧。正如你所知,一些动作在每个帧都会执行,所以,通过在playMaker面板底部的状态选项卡中的调试复选框,你可以看到任何特定帧中每个动作的每个参数的确切值。你可以按需按下下一步按钮来观察变化。当你完成调试后,只需取消暂停或停止游戏。
我们将要讨论的最后一个调试工具是 Playmaker 的FSM 日志面板,它让你可以看到所有在 Playmaker 控制下的对象发生的事情。你可以通过按下playMaker面板底部靠近播放/暂停/下一步按钮的调试按钮来打开它,然后从下拉菜单中选择打开日志窗口。以下截图显示了FSM 日志面板的实际操作:

保留这个日志在手边可能是个好主意,这样你就可以将其作为第二个面板附加到屏幕的层次结构或项目区域。
练习
我有一些练习想建议你在进入下一章之前尝试做,这些章节将涵盖更高级的主题。你已经了解了 Unity 和 Playmaker 的基础知识——足够让你改进你的游戏或者从头开始制作一个全新的游戏。做这些练习将提高你的技能,并帮助你巩固在这本书中接触到的新的信息。练习按难度排序,从最容易到最难:
- 
记录游戏中所有重要事件(包括冰球击中、进球击中和球槌碰撞墙壁)的调试日志。 
- 
击打声音:你的 Wall预制体有一个 FSM。使用它来播放一些当球槌和/或冰球击中墙壁时的声音。Freesound.org是一个免费音效的好资源。如果你更喜欢复古声音,试试bfxr.net。
- 
多级设置:尝试制作多个级别,中间有障碍物/额外的墙壁。你可以在任何地方复制它们,然后,当这一轮结束后,进入下一级。 
- 
到现在为止,你可能已经遇到了一个情况,即冰球从桌子上掉下来,你必须使用播放按钮重新开始游戏。这对其他玩家来说也很烦人!你可以尝试改变桌子设置,检测冰球位置,或者以其他方式解决这个问题。 小贴士当你在做这项工作时,想想目标触发器是如何工作的,或者发挥创意,提出你自己的解决方案。 
- 
你知道如何操作场景视图并为对象创建新材料,你也知道如何更改摄像机的尺寸和投影。如果你能想出一个美丽的配色方案和/或摄像机投影/方向,你的游戏会变得更加漂亮。 
- 
到现在为止,你可能已经开始怀疑鼠标控制是否是这款游戏的最佳解决方案。这是一个完全合理的问题,你应该自己做出这个决定。除非你尝试不同的方法,否则你永远不会确定,所以请查看动作面板中的输入类别。 
- 
如果你成功实现了键盘控制,为什么不更进一步,制作一个关卡,在这个关卡中,你可以在同一台电脑上与朋友或家人一起玩,而不是与 AI 对手竞争? 
- 
仔细观察控制 AI 的状态和动作。尝试修改那里的某些属性,甚至替换动作本身,以使 AI 更加智能和逼真。你可以尝试让 AI 根据冰球的位置改变其行为,通过回到自己的目标位置来保护它。你也可以尝试找出一种方法,阻止 AI 在玩家推动 AI 试图推动的冰球时被推回。 小贴士为了做到这一点,你需要在冰球上添加一个 Playmaker 变量,然后使用状态机类别下的Get FSM动作之一。 
- 
向游戏中引入一个新的游戏机制:如何关于七局游戏的回合而不是一局?或者加力?或者限制球棒在桌子的那一侧?你可以选择,如果你喜欢,你可以做所有这些。 
在你接受这些挑战之前,你应该了解一些有用的 Unity 和 Playmaker 在线资源,这些资源可以帮助你找到一些,如果不是所有问题的答案。你可以确信,当你开始自己实现某些功能时,问题不可避免地会出现。
- 
Unity 问答: answers.unity3d.com/
- 
Unity 维基: wiki.unity3d.com/
- 
Unity 论坛: forum.unity3d.com/
- 
Unity 文档: unity3d.com/learn/documentation
- 
Playmaker 手册: hutonggames.fogbugz.com/
- 
Playmaker 论坛: hutonggames.com/playmakerforum/
在 Unity 问答和 Unity 论坛上,你不应该犹豫提问;Unity 社区非常活跃,你的问题可能在几分钟内就得到了解决。只是确保你解释得很好。但在提问之前,我强烈建议你首先使用搜索选项,因为有很大可能性,你可能遇到的大多数问题都已经得到了解决。
摘要
在本章中,你学习了使用 Unity 和 Playmaker 进行游戏开发的基础知识。你现在知道如何创建对象,使它们相互交互,使它们对输入做出响应,甚至根据你的算法自行移动。除此之外,你现在还了解了你将在大多数游戏中使用的矢量几何学基础知识。
下一章将介绍编程。你将学习如何编写自己的 Unity 组件,并将其转换为 Playmaker 动作。我们还将演示 Playmaker 本质上是一种使用与常规脚本非常相似的逻辑的视觉编程。
第五章。脚本和自定义动作
在前面的章节中,你学习了如何使用 Playmaker 的内置动作制作游戏。不幸的是,它们的性能有限,迟早你会发现自己需要 Playmaker 无法直接解决的问题。在这种情况下,你可以尝试在互联网上寻找现成的解决方案,但为了确保你的每一个问题都有答案,你肯定需要学习如何编写脚本。我们将讨论以下主题:
- 
在 Unity JavaScript(有时也称为 UnityScript)和 C# 中进行编程 
- 
常见的 Unity 类、变量和函数 
- 
编写脚本并将其用作组件 
- 
将脚本转换为 Playmaker 动作 
编写 Unity 脚本
解释编程的一般工作原理超出了本书的范围,因此我将假设你已经知道变量、函数、类和运算符是什么。如果你不知道这些,使用 Code Academy(www.codecademy.com/tracks/javascript)或 Unity 自身的入门级教程(unity3d.com/learn/tutorials/modules/beginner/scripting)来掌握基础知识不会花费你太多时间。
在本节中,我们将从 JavaScript(JS)开始,因为它使用起来更简单,并且不需要对面向对象编程有超出我们在第三章(组件和状态机)中讨论的基于组件的开发方法的理解。此外,你最终将编写更少的代码。
JS 和 C# 都使用相同的 Unity 类和函数,语法上的差异并不很大。然而,对于更复杂的事情,通常使用 C# 是一个好主意(例如,目前无法在 JavaScript 中编写 Playmaker 动作)。我们将从一个 JS 脚本开始,然后将其翻译成 C#,并解释差异。你可以稍后选择对你来说更好的语言。
我们将创建一个脚本,用于替换两个球拍的状态机中的 Push Puck 状态的所有 Playmaker 动作。当你看到你需要执行五个或更多动作的链时,通常将它们组合成一个自定义动作会更简单,尤其是如果你计划在多个对象上使用这个组合动作。
让我们从创建一个新的脚本开始。首先,使用 项目 面板创建一个名为 Scripts 的新文件夹。然后,右键单击新创建的文件夹,导航到 创建 | Javascript。将文件命名为 PushPuck。双击文件。标准的 Unity 编程环境,MonoDevelop,应该会打开。选择该脚本中的所有内容,并将其替换为以下代码:
// Automatically added, compiler =directive 
// that makes JavaScript more explicit
#pragma strict
// Global variables available from Inspector
var pushMag : float = 20f;
var collisionTag : String = String.Empty;
// Function that will detect the collision with 
// controller and apply force in point of the collision
function OnControllerColliderHit (hit : ControllerColliderHit) 
{
  if (hit.gameObject.tag == collisionTag)
  {
    // Get the position of the object we collided with
    var hitObjectPos : Vector3 = hit.transform.position;
    // Get the position of the collision
    var hitPointPos : Vector3 = hit.point;
    // Calculate the direction of the force, 
    // multiply it by magnitude
    var pushForce : Vector3 = Vector3.Normalize(hitObjectPos - hitPointPos) * pushMag;
    // Finally, apply force in position
    hit.rigidbody.AddForceAtPosition(pushForce, hitPointPos);
    // Print a message in Console saying that 
    //the collision did happen and force was indeed applied
    Debug.Log("Detected hit with " + collisionTag + ", applying force of " + pushForce + " in " + hitPointPos + ".");
  }
}
@script RequireComponent(CharacterController)
小贴士
你可以从你购买的所有 Packt Publishing 书籍的账户中下载示例代码文件。www.packtpub.com。如果你在其他地方购买了这本书,你可以访问 www.packtpub.com/support 并注册以直接将文件通过电子邮件发送给你。
在 MonoDevelop 中按 command + S (Ctrl + S 在 Windows 上) 保存脚本更改。
让我们逐行检查这段代码。第一行是 #pragma strict。这是一个预处理器指令。如果你有一些之前的编程经验,你可能之前见过类似的东西。它不参与脚本逻辑;它所做的就是通过在编译器中强制更严格的错误处理来使 JavaScript 更加明确。对你来说,这意味着你必须显式地定义变量的类型,这在标准 JavaScript 中通常不会这样做。
小贴士
对于 JS 来说,另一个常见的预处理器指令是 #pragma downcast,它允许你忽略不关心的不明确向下转换警告。
之后有两行变量声明:pushMag 和 collisionTag 被声明为 float 和 String。一旦你将此脚本附加到对象上,这些变量将在检查器中显示,因为它们都是公共的并且已序列化。由于在 Unity 中脚本被视为组件,一旦将脚本附加到游戏对象,变量就会作为参数出现在检查器中。
如果你想要声明一个不在检查器中显示的变量,但希望其他脚本仍然可以访问它,你应该在它前面加上 [System.NonSerialized]。如果你想隐藏它并关闭所有访问,只需在 var 关键字前面加上 private 即可。最后,如果你想将私有变量暴露在检查器中,你应该在其声明前加上 [System.SerializableAttribute]。
接下来是函数声明:function OnControllerColliderHit (hit : ControllerColliderHit)。OnControllerColliderHit 是负责检测碰撞的标准 Unity 函数之一。其他此类函数包括 OnCollisionEnter、OnCollisionStay、OnCollisionExit、OnTriggerEnter、OnTriggerStay 和 OnTriggerExit。
如果脚本附加的对象具有角色控制器组件并且该角色控制器与碰撞器发生碰撞,则会自动调用 OnControllerColliderHit 函数。类型为 ControllerColliderHit 的 hit 变量被分配,并可以在函数内部使用。通过键入 hit 和一个点,可以访问有关碰撞的所有类型的信息。
例如,在函数内部有一个 if 条件:if (hit.gameObject.tag == collisionTag)。我们使用点操作符访问我们的 Character Controller 所碰撞的 gameObject,然后再次使用它来访问 gameObject 中的该标签。然后我们将该标签与在检查器中分配的 collisionTag 字符串变量进行比较。如果标签与指定的字符串匹配,则执行花括号内的代码。
在这个脚本中,我们重现了 mallets' FSMs 的推杆状态中精确的动作序列。你可以参考它。首先,我们获取滑块的位置并将其存储在名为 hitObjectPos 的 Vector3 变量中。然后我们获取击中的点并将其存储在另一个名为 hitPointPos 的 Vector3 变量中。然后我们计算一次推力,而不是像在 Playmaker 中使用的那样分三步。最后,将力应用到滑块的刚体上。
之后,有一行包含 Debug.Log 的代码,它会将关于每次击中的信息打印到控制台。你可以通过在其前面输入 // 来注释掉这一行。现在先不要注释它,以确保脚本能够正常工作。
脚本的最后一行是 @script RequireComponent (CharacterController)。它在那里是为了确保附加到该脚本的游戏对象上有一个 Character Controller 组件。如果你将此脚本附加到一个没有 Character Controller 的游戏对象上,它将自动附加。如果你尝试在不先删除 PushPuck 的情况下删除 Character Controller,Unity 将显示警告对话框窗口,并阻止你这样做。
现在是时候看看我们新创建的脚本是否能够正常工作了。回到 Unity,打开控制台面板。如果没有出现红色错误,这意味着脚本已正确编译并准备好使用。如果脚本中存在某种错误,双击控制台中的错误,它将在 MonoDevelop 中打开脚本并直接跳转到错误发生的行。
如果一切正常,选择 MalletLeft,打开 playMaker 面板,并在 Move 状态中通过取消选中其名称旁边的框来禁用 Collision Event 动作。如果你现在开始游戏,与滑块碰撞将不会推动它。现在是我们使用全新的 PushPuck 脚本的时候了。在选中 MalletLeft 的同时,从 项目 面板拖放 PushPuck 文件到 检查器 面板。它将作为组件附加到上。按照以下截图设置其参数:

打开控制台面板并启动游戏。注意每次你用球槌击打冰球时,消息如何在控制台中显示。如果你停止游戏并双击一个Debug.Logs,MonoDevelop 将打开并指向PushPuck脚本的Debug.Log行。现在你知道脚本工作正常后,你可以取消注释该行以防止它向控制台发送垃圾信息。
此外,现在你也可以将其应用于 AI 对手。记得首先禁用碰撞事件动作。
标准 Unity 类概述
虽然我不会将整个 Unity 脚本参考复制到本书的这一章节,但我希望列出一些你将非常频繁使用的几个重要类和函数。
最明显且最常用的标准函数是Awake、Start、OnEnable、OnDisable、Update和FixedUpdate:
- 
Awake是当场景加载时首先被调用的函数。Awake函数在每个场景加载中只能发生一次。通常将所有初始化代码放入Awake函数是一个好主意。
- 
Start在Awake之后发生,也只运行一次。有时将一些代码放入Awake,其他代码放入Start是一个好主意,以确保一个在另一个之后执行。当你在一个脚本中有Awake函数,而在另一个脚本中又有另一个Awake函数时,你不能确定哪个会先执行。如果顺序很重要,将其中一段代码放入Start函数。小贴士确保正确的脚本执行顺序的另一种方法是,你可以通过从主菜单导航到编辑 | 项目设置 | 脚本执行顺序来决定哪个脚本先执行或后执行,然后在检查器中按下加号按钮,选择你想要定义执行顺序的脚本,然后在界面中上下拖动它。你会注意到当你拖动脚本时,右边的数字会改变:这个数字是脚本的执行顺序。你可以通过从列表中移除脚本为其赋予默认执行顺序。你可以通过在列表中按其名称旁边的减号按钮来实现。 
- 
OnEnable函数与Start函数非常相似,除了它会在附加脚本的物体被激活以及组件本身被启用时被调用。
- 
OnDisable是OnEnable的反义词。当对象或组件被禁用时被调用。
- 
Update每帧被调用。大多数游戏逻辑通常在这里发生。
- 
FixedUpdate在物理更新时被调用,这通常比Update频繁得多。所有不能依赖于帧率的代码(例如,例如,移动)都应该放在FixedUpdate函数中。
除了功能之外,还有许多类及其方法和变量,在 Unity 编程时你绝对应该了解。很难强调某一点,你真的应该浏览一下 Scripting Reference 中列出的所有主要类(docs.unity3d.com/Documentation/ScriptReference/)。类真的很多,几乎没有人知道它们全部的功能,但我们将首先查看几个你应该关注的类:Mathf、Vector3、Color、Input、GameObject、Transform、Renderer、Material、Collision、Collider,当然还有 Object、Behaviour、Component 和 MonoBehaviour。
最后四个类尤为重要,因为它们包含了你在 Unity 中编写脚本时每次都会使用的内容,包括 Update、Destroy、GetComponent 和 Start 等函数,以及 enabled、name、layer 和 tag 等变量。仔细阅读这些类及其函数和变量的描述,并查看文档提供的与它们相关的示例。
创建 Playmaker 动作
是时候将我们的脚本翻译成 C# 了。以创建 JS 脚本相同的方式创建一个新的 C# 脚本。命名为 PushPuckAction。打开脚本,找到以 public class 关键字开始的行。确保类的名称与脚本名称相同,然后按 command + S (Ctrl + S 在 Windows 上) 保存你的更改(如果你做了任何更改)。
如你所见,C# 脚本的默认模板与 JS 的模板看起来不同。这是因为它向你展示了更多内容。例如,在 JS 中,默认认为脚本内部的所有内容实际上都在一个同名的类中,但你却看不到类的声明。Unity 中的组件类必须继承自 MonoBehaviour,这在 C# 中是可见的,而 JS 则将其隐藏起来。然后你有两条带有 using 关键字的行。所有 JS 脚本都使用这些命名空间,但 JS 也将其隐藏起来。下面的脚本是我们之前拥有的同一个脚本,但这次翻译成了 C#。用这个模板替换它。
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (CharacterController))]
public class PushPuckAction : MonoBehaviour 
{
  // Global variables available from Inspector
  public float pushMag = 20f;
  public string collisionTag = string.Empty;
  // Function that will detect the collision with 
  // controller and apply force in point of the collision
  void OnControllerColliderHit (ControllerColliderHit hit) 
  {
    if (hit.gameObject.tag == collisionTag)
    {
      // Get the position of the object we collided with
      Vector3 hitObjectPos = hit.transform.position;
      // Get the position of the collision
      Vector3 hitPointPos = hit.point;
      // Calculate the direction of the force, 
      // multiply it by magnitude
      Vector3 pushForce = Vector3.Normalize(hitObjectPos - hitPointPos) * pushMag;
      // Finally, apply force in position
      hit.rigidbody.AddForceAtPosition(pushForce, hitPointPos);
      // Print a message in Console saying that 
      //the collision did happen and force was indeed applied
      Debug.Log("Detected hit with " + collisionTag + ", applying force of " + pushForce + " in " + hitPointPos + ".");
    }
  }
}
按 command + S (Ctrl + S 在 Windows 上),让我们看看除了已经提到的内容之外还发生了什么变化。C# 中的组件要求语法与 JS 不同。除此之外,RequireComponent 属性必须放在类声明之前。
#pragma strict 指令已经不再使用。C# 本身是显式的,需要你指定所有内容的数据类型,因此它不再需要。
小贴士
在 C# 中,以下内容非常有用:#region [Name]/#endregion,这是一种将代码划分为可以折叠的区域的好方法。例如,编写 #region Variables 将创建一个名为 Variables 的区域。然后你将能够按下 MonoDevelop 窗口左侧的减号来折叠代码区域,其结束必须用 #endregion 标记。
不使用 function 关键字。相反,函数声明由返回类型 precedes。你同样可以在 JS 中指定返回类型,但这需要在括号后的 : 运算符后完成,例如,function Update() : void。
最后,所有变量声明都由变量类型 precedes,而不是 var 关键字。由于我们脚本的相对简单性,我们无法在这个示例中看到更多的语法差异,但这些是主要的。
现在,如果你在 Mallets 上将你的 JS 脚本替换为 C# 脚本,它们将表现得和之前完全一样。尝试这样做,然后从 mallets 中移除 Push Puck Action 组件。是时候修改脚本,将 PushPuckAction.cs 转换为实际的 Playmaker 动作。用以下代码替换 PushPuckAction.cs 的内容:
using UnityEngine;
using System.Collections;
namespace HutongGames.PlayMaker.Actions
{
  [ActionCategory(ActionCategory.Character)]
  [Tooltip("Detect collision with CharacterController, then push the other object into the opposite direction.")]
  public class PushPuckAction : FsmStateAction 
  {
    [Tooltip("Push magnitude")]
    public FsmFloat pushMag;
    [Tooltip("Object with this tag will be pushed")]
    public FsmString collisionTag;
    public override void Reset ()
    {
      pushMag = 20f;
      collisionTag = string.Empty;
    }
    public override void DoControllerColliderHit(ControllerColliderHit hit)
    {
      if (hit.gameObject.tag == collisionTag.Value)
      {
        FsmVector3 hitObjectPos = hit.transform.position;
        FsmVector3 hitPointPos = hit.point;
        FsmVector3 pushForce = (hitObjectPos.Value - hitPointPos.Value).normalized * pushMag.Value;
        hit.rigidbody.AddForceAtPosition(pushForce.Value, hitPointPos.Value);
        Debug.Log("Detected hit with " + collisionTag.Value + ", applying force of " + pushForce.Value + " in " + hitPointPos.Value + ".");
      }
    }
  }
}
按 command + S (Ctrl + S 在 Windows 上) 保存脚本。正如你所见,这次有更多东西发生了变化,尽管你仍然可以看到相同的结构。让我们逐行检查代码。
using 指令相同,但差异从它们之后开始。namespace HutongGames.PlayMaker.Actions 这一行对于所有 Playmaker 动作是必需的。没有它,Playmaker 将不知道你正在编写的脚本实际上是一个动作。
[ActionCategory(ActionCategory.Character)] 行将你的新动作放入一个类别。在这种情况下,我们将其放入 Character 类别,因为动作是关于与 Character Controller 发生碰撞的事物。它也可以放入 Physics 类别。要将它移动到那里,该行必须是 [ActionCategory(ActionCategory.Physics)]。
之后是 [Tooltip("...")],这相当直观。当你从 Actions 面板中选择动作时,它会显示一个简短的描述。
PushPuckAction 类现在从 FsmStateAction 继承,而不是 MonoBehavior。你仍然可以访问所有标准 Unity 类,但现在还添加了 Playmaker 特定的类。
然后还有另一个 Tooltip,这次是为一个变量而不是整个动作。当你在 playMaker 面板的 State 选项卡或 Actions 面板中将鼠标指针悬停在变量上时,此提示文本将显示。
注意,pushMag 变量的类型已从 float 更改为 FsmFloat,同样,字符串变量 collisionTag 也已从 string 更改为 FsmString。这些都是 Playmaker 的变量类型。可以对它们执行与之前相同的操作,但为了访问它们的值,您现在必须使用点操作符和单词 Value,例如,pushMag.Value 将返回一个浮点数,即 Playmaker 变量的值。
小贴士
如果您不想能够在 playMaker 面板的 State 选项卡中直接分配值,并强制从现有变量中选择,您可以在变量声明之前的行中写入 [UIHint(UIHint.Variable)],就像我们为工具提示所做的那样。
添加了一个 Reset 函数。当向状态添加新动作或您在动作标题上右键单击并按 Reset 时,就会发生这种情况。在其中,我们重新初始化变量。
然后是 DoControllerColliderHit 函数。它的名称已从标准的 Unity OnControllerColliderHit 更改。在函数内部,一切保持大致相同,唯一的区别是 Vector3 变量的类型已更改为 FsmVector3,因此要访问它们的值,使用 .Value。此外,我们使用了 .nomalized 而不是 Vector3.Normalize,它确实做了完全相同的事情。
小贴士
虽然很明显,为了找到示例和标准 Unity 类的 API,必须去 Unity 脚本参考,但对于 Playmaker 特定的事情可能就不那么明确了。找到示例的最简单方法是打开位于项目 PlayMaker/Actions 路径下现有 Playmaker 动作的脚本文件。例如,如果您不确定如何检测鼠标输入,并且想通过 Playmaker 自定义动作来实现,您可以打开 PlayMaker/Actions/MousePick.cs 并查看 Playmaker 的创建者是如何解决这个问题。
注意,Reset 和 DoControllerColliderHit 的类型前都有 override 关键字。这意味着我们正在用我们自己的函数替换 Playmaker 中定义的基函数。一般来说,您需要使用此关键字覆盖所有 Playmaker 标准函数。
现在我们已经编写了一个自定义的 Playmaker 动作,我们可以尝试使用它。从 MalletLeft 和 MalletRight 中移除 Push Puck 和/或 Push Puck Action 组件。在它们的 Move 状态中,移除 Collision Event 动作。然后在 Actions 面板中找到新创建的 Push Puck Action 并将其添加到状态中。设置属性如以下截图所示。最后,从 FSM 中删除 Push Puck 状态以及 Push 事件和转换。要删除转换,只需在 FSM 视图中右键单击事件并按 Delete Transition 即可。

摘要
在本章中,你学习了在 Unity 中进行脚本编写,包括 JavaScript 和 C#,以及如何创建自定义的 Playmaker 动作。你应该尝试重复本章中描述的过程,使用另一组动作进行实践。为自己设定一个游戏目标,例如,你可以选择第四章末尾提供的练习之一,创建你的第一个游戏,编写一个 JS 或 C# 脚本来实现目标;然后将其转换为 Playmaker 动作。完成这些后,你对脚本编写会感到更加得心应手,因为这一切都关乎实践,而阅读和理论无法替代使用文本编辑器和搜索引擎的实际操作经验。
下一章将继续讨论高级主题,例如网络和外部 API,我们将在第七章与外部 API 一起工作中进行更多的脚本编写。
第六章:网络和多玩家
在前面的章节中,你学习了如何使用 Unity 的界面,操作对象,以及向它们添加组件和行为。你使用 Playmaker 动作和 Unity 脚本创建了游戏玩法。我们还探讨了从 C#脚本中创建自定义动作。你使用所有这些工具制作了一个完全可玩的双打冰球游戏,并使用人工智能对手。
在本章中,我们将讨论网络。你将使用Photon Unity Networking(PUN)为游戏创建多玩家模式,这是一个有用的插件,与 Playmaker 一起提供,让你几乎可以毫不费力地制作多玩家游戏。我们还将讨论游戏网络理论,并讨论 Unity 原生网络作为 Photon 的替代方案。
本章中,你将涵盖:
- 
理解网络和多玩家 
- 
设置 Photon Unity Networking 
- 
制作多玩家游戏 
理解网络和多玩家
解释像 TCP/IP 和其他低级网络概念超出了本书的范围,我们将尽量使一切尽可能接近实际应用。另一方面,如果你至少熟悉一些理论,那么构建多玩家游戏会容易得多。
你需要知道的第一件事是服务器和客户端是什么。简单来说,服务器是一台响应来自其他计算机网络请求的计算机,或者更精确地说,是一个响应来自其他系统网络请求的系统,因为你在同一台计算机上可能有多个服务器和多个客户端。这意味着客户端通过服务器相互通信。通常,玩家系统是多玩家游戏中的客户端,而服务器位于远程访问的计算机上。有时,玩家可以托管游戏,在这种情况下,玩家要么充当服务器,要么简单地告诉服务器为游戏保留其资源。
你可能之前听说过网络架构。在游戏中,最流行的架构可能是客户端-服务器和对等网络。前者意味着所有客户端都订阅一个单一的服务器。服务器托管关于游戏的大部分重要信息,并在玩家之间分配。后者是关于对等方(玩家)直接连接到彼此,因此所有客户端都相互连接,网络负载均匀分布。
客户端-服务器的好处是它允许创建一个更稳定的系统,确保作弊要么不可能,要么非常困难,同时也使得开发者更容易监控一切,并在游戏中即时做出更改。然而,这种方法通常成本较高,如果你是一个正在学习制作第一个多玩家游戏的独立开发者,那么自己实现起来相对较难。
点对点不需要拥有一个强大的服务器同时托管多个游戏会话,允许玩家相互连接,在他们之间分配网络负载。点对点的缺点是它通常不太稳定,而且相对难以监控。此外,如果你想跟踪游戏会话、执行匹配和让玩家在互联网上而不是仅在本地网络中玩游戏,你仍然需要一个服务器。
小贴士
没有服务器很难连接到互联网,这是因为有一种叫做原生地址转换(NAT)的东西。不深入细节的话,应该注意的是,这是网络路由器做的事情,我们大多数人现在都有路由器。一个常用的过程叫做NAT 穿透,用于连接一台计算机到另一台计算机,这个过程需要一个服务器作为两台计算机之间第一次连接的中介。
在 Unity 中,设置本地局域网(LAN)、对等连接或客户端-服务器连接相对简单,无需使用任何外部插件,只需让一位玩家作为游戏主机即可。LAN 连接意味着所有玩家都连接到同一个本地计算机网络。不幸的是,无论你做什么,你都需要一个服务器来确保玩家可以始终通过互联网相互连接,这正是我们想要做的。Photon 采用了客户端-服务器架构,并将其封装在一个极其易于使用的界面中,使得任何人都可以在没有先前经验的情况下创建多人游戏。而且,它的价格也非常实惠。
一些 Photon 服务允许玩家自己托管服务器,但这需要一些时间来设置。在这本书中,我们将使用 Photon Cloud。正如其名所示,所有的游戏会话都在云端进行,即在远程 Photon 服务器上。你所需做的就是通过它们同步你的游戏数据,并确保玩家可以找到彼此。你不需要设置服务器,也不必担心涉及点对点多人游戏的问题。
同步工作的方式是这样的,有一个叫做网络视图(在 Photon 中称为Photon 视图)的东西,这是一个组件,它使得一个或多个游戏对象的属性在网络中同步。当某个属性在一个客户端发生变化时,它会在所有客户端中发生变化,服务器会跟踪所有变化并向客户端发送命令。例如,球槌的位置可以是一个这样的属性。这样,当玩家 1 移动他们的球槌时,玩家 2 会看到他们移动它,反之亦然。几乎任何其他属性都是如此。
小贴士
正如我们在本章的示例中将要展示的,同步位置对于玩家对象(如冰球桌中的球槌或第一人称射击游戏中的角色)是可以接受的,但对于具有物理行为的对象(如冰球),在远程计算机上可能会出现严重的网络延迟。目前,除了使用 Unity 原生网络之外,还没有简单的解决方案来解决这个问题。
有一些对象可以属于场景(如墙壁和背景),因此在整个客户端中都是完全相同且不可更改的,然后还有属于不同客户端的对象,例如球槌。通常,您希望尽可能少地在网络上同步数据,以避免高响应时间。场景对象不需要同步,因为它们不会改变。此外,这样每个玩家的球槌只响应那个玩家的输入,这在游戏玩法上是非常合理的。
现在,您已经准备好开始设置 Photon 了。所有这些理论可能听起来很复杂,但实际上它归结为使用一个名为 Photon View 的特殊 Photon 组件在网络中同步变量。
设置 Photon Unity Networking
Photon Unity Networking 是一个免费的 Unity 插件,可选的付费订阅允许您将构建多人游戏的大部分繁重工作外包出去。免费版本具有完整的功能,但受同时在线玩家数量的限制。这不是问题,因为您只需要能够连接到足够多的玩家,以便您的游戏可以玩(例如,冰球桌游戏需要两个玩家)。
- 
首先,我们需要设置 PUN。在主菜单中,导航到PlayMaker | 插件 | Photon Networking | 设置 Photon Networking。这应该会打开Photon 设置向导窗口。点击橙色设置按钮。 
- 
如果这是您第一次使用 Photon,您将需要一个账户,因此请在相应的字段中输入您的电子邮件,然后点击发送按钮。 
- 
之后,您可以通过点击通过电子邮件收到的链接来完成注册过程。登录到您的 cloud.exitgames.com/账户,点击账户页面上的新建应用按钮,输入游戏名称和描述,然后点击创建。您应该会被重定向回您的账户页面。在详情部分,复制AppID下的代码,然后返回 Unity。
- 
在Photon 设置向导中,点击设置按钮,将您的 AppID 粘贴到您的 AppId文本字段中,并通过按下一个云区域按钮选择您的区域。请确保您选择地理位置上离您更近的区域,因为这会影响您游戏多人连接的速度。 
- 
一旦您粘贴了 AppID 并选择了区域,点击下面的保存按钮。应该会弹出一个窗口,表示您的设置已保存。点击确定。以下截图显示了您完成设置后设置窗口应有的样子: ![设置 Photon Unity Networking]() 
- 
确保绿色标签Photon 服务器已正确设置出现在窗口的顶部附近。按下其下方的主菜单按钮。它应该带您回到Photon 设置向导的第一个屏幕。 
- 
复制您的游戏主场景并命名为 Multiplayer,通过在项目面板中双击它来加载它,然后返回到Photon 设置向导窗口并点击将 Photon 系统添加到场景。按钮应该消失,您应该看到第二个绿色标签,上面写着场景已正确设置。
- 
到此为止,Photon Cloud 已设置完成,您可以自由地关闭向导窗口。保存多人游戏场景,确保PlayMaker Photon 代理游戏对象已被添加到层次结构中。 
您可以随时修改设置。如果您选择这样做,您还可以更改您的 AppID。
现在我们已经将 Photon 添加到项目中并创建了一个专门用于多人游戏的场景,是时候在网络中同步对象并设置匹配了。
制作多人游戏
在我们的游戏中,有一些游戏对象需要同步,包括目标、冰球和球槌。我们还需要对场景进行一些调整并设置匹配。
我们将使用 Photon Playmaker 示例作为我们游戏的模板,使用那里的场景并对其进行修改以适应我们的目的。如果您想快速设置多人游戏,这通常是一个好主意,因为这些示例具有众多且复杂的有限状态机(FSM),设置起来会花费相当多的时间,而您需要的许多东西已经实现。
您可以在此处下载演示场景:www.hutonggames.com/samples.php。只需点击链接下载 PlayMaker Photon 演示版(需要 Unity 3.5+ Playmaker 1.6.1+)。然后您需要在您的计算机上找到下载的 unitypackage 文件并双击它。这将弹出导入窗口。点击窗口右下角的导入按钮。
小贴士
示例下载页面上的警告建议不要将示例导入到现有项目中。我们可以忽略这个警告,因为我们没有任何文件与示例中的文件同名。作为一般规则,在将新包导入新空项目之前,您应该始终确保这一点。否则,您可能会发现自己丢失了项目中的重要资产。
保存多人游戏场景,然后让我们将演示场景添加到我们的项目中。它们应该在Photon Unity Networking/PlayMaker/Demo/Separated Scenes Demo下。通过双击其文件打开demo_lobby场景。
我们需要做的第一件事是将新场景添加到我们的项目中。通过按Shift + command + B(在 Windows 上为Shift + Ctrl + B)打开构建设置,然后点击以下截图所示的添加当前按钮。这将把当前打开的场景添加到项目中。为demo_lobby和demo_room都这样做。然后关闭构建设置。

这两个场景将负责游戏的两种状态:玩家可以找到和创建服务器的匹配状态,以及游戏本身进行的匹配状态。你可以通过同时打开两个游戏实例来测试其工作方式:一个在 Unity 编辑器中,一个在你的网络浏览器中。
- 
要创建浏览器构建,从主菜单中选择文件 | 构建与运行,并在文件浏览器出现时将构建文件保存在你电脑上的任何位置。你可能想要创建一个特殊的构建文件夹,以确保你总能找到你的构建。这应该会自动在浏览器中打开游戏。 小贴士请注意,你的平台必须设置为 Web Player,如第一章中所述,Unity 和 Playmaker 入门。如果不是,打开构建设置,从左侧列表中选择Web Player,然后点击切换平台按钮。然后关闭构建设置窗口。 
- 
在创建房间旁边指定你的昵称和房间名称,然后点击房间名称文本字段右侧的GO按钮。 
- 
应该会加载一个新的场景,在那里你可以控制一个可以在平面上四处走动的建筑工人。 
- 
在你的网络浏览器中保持游戏打开,回到 Unity 编辑器;确保demo_scene已打开,然后点击播放。 
- 
点击现有房间旁边的GO按钮。这将加入一个随机存在的房间。由于我们只创建了一个房间,现在两个游戏实例都将连接到同一个游戏会话。这样,你可以通过查看两个玩家的视角来测试网络功能。 
现在你已经看到了示例项目中多人游戏的工作方式,是时候修改其场景以配合我们的游戏了。我们将保持匹配功能不变(实际上,几乎可以以这种方式保持任何多人游戏)。唯一需要更改的是可以加入单个房间的最大玩家数量。你可能已经注意到示例项目允许 100 名玩家。我们需要将其更改为2。
- 
在demo_lobby场景中,选择菜单游戏对象。 
- 
从playMaker FSM 视图顶部的菜单中,从左侧第二个下拉列表打开创建房间FSM。 ![制作多人游戏]() 
- 
在创建房间状态的Photon Network Create Room动作中,将最大玩家数属性设置为 2。现在最多有两个玩家可以加入一个房间。
- 
保存场景。 
现在休息室已经设置好了,是时候设置游戏本身通过网络工作了。
- 
将demo_room场景复制一份作为备份,以便在出现问题时可以返回。打开原始的demo_room场景。你应该有一个未命名的游戏对象,它有四个子对象,以及Chat、Game和PlayMaker Photon Proxy游戏对象。 
- 
删除除Game游戏对象之外的所有内容。这里是大多数初始多人逻辑(例如实例化玩家)发生的地方。 
- 
保存场景并返回到Multiplayer场景。 
- 
通过按command + A (Ctrl + A在 Windows 中)或点击列表中的第一个项目然后Shift-点击最后一个项目来选择场景中Hierarchy中的所有对象。 
- 
通过按command + C (Ctrl + C在 Windows 中)复制选定的对象,然后再次打开demo_room场景并按command + V (Ctrl + V在 Windows 中)粘贴它们。 
- 
在Prefab/Resources下创建名为 Goal、Mallet和Puck的空预制件。如果你还没有创建这些文件夹,请先创建它们。
- 
将GoalLeft游戏对象拖入 Goal预制件,将MalletLeft游戏对象拖入Mallet预制件,将Puck游戏对象拖入Puck预制件。
- 
从场景中删除以下对象:MalletLeft、MalletRight和Puck。当玩家加入房间时,它们将从你刚刚创建的预制件中实例化。 
- 
通过禁用它们的Mesh Renderer组件使GoalLeft和GoalRight不可见。这些对象也将从预制件中实例化,但不是立即实例化,因此我们需要将它们保留在场景中以确保没有球槌可以离开桌子。你也可以将它们都重命名为 Blocker,因为它们需要用来阻挡球槌。
- 
选择Game游戏对象,并在playMaker面板中打开其Game ManagerFSM。 
- 
在变量选项卡中,确保你有三个GameObject变量:goalRef、player prefab和puck prefab,以及一个Int变量player count。将 Mallet预制件拖入player prefab的GameObject槽中,将Puck拖入puck prefab的GameObject槽中。
- 
在事件选项卡中,创建一个名为 Two Players的新事件。
- 
在instantiate player状态的Photon Network Instantiate动作中,确保旋转设置为( 0,0,0)。
- 
在相同的状态下添加另一个Photon Network Instantiate动作。通过从项目面板拖动 Goal预制件将其游戏对象属性设置为Goal。将位置属性设置为(-7.98,2,0),将旋转属性设置为(0,90,0)。将存储对象设置为goalRef。
- 
创建一个名为 How many players?的新状态,并向它添加Photon Network Get Room Properties和整数比较动作。确保前者在列表中位于后者之前。如果出现一个错误,说需要一个 PlayMakerPhotonGameObjectProxy 组件,点击它,相关的组件将被自动添加。
- 
将Photon Network Get Room Properties动作的玩家数量属性设置为玩家数量变量。 
- 
在整数比较动作中,将整数 1属性设置为玩家数量变量,将整数 2属性设置为 2。将等于设置为两位玩家。检查每帧框。如果你看到一个关于事件的错误,现在忽略它;稍后会修复。
- 
创建一个名为 Create Puck的新状态。
- 
在实例化玩家状态中添加一个完成事件。从它拖动一个过渡到有多少玩家? 
- 
向How many players?添加一个两位玩家事件,并从它拖动一个过渡到创建冰球。 
- 
在创建冰球状态中,添加两个动作:光子网络实例化和设置位置。确保设置位置是列表中的最后一个。如果出现一个错误,说需要一个 PlayMakerPhotonGameObjectProxy 组件,点击它,相关的组件将被自动添加。 
- 
将Photon Network Instantiate动作的游戏对象属性设置为 Puck预制件变量。将位置设置为(0,0.3,0)和旋转设置为(0,0,0)。
- 
在设置位置动作中,将游戏对象设置为指定游戏对象,并从下拉列表中选择goalRef变量。确保向量是无,X是 7.98,而Y和Z是无。
- 
保存你的场景。 
这个状态机负责生成应该对不同玩家独特的对象:当玩家连接到房间时,玩家的球槌和目标被生成,而冰球在第二个玩家连接时生成,以确保玩家 1 不会在房间里独自获胜。
现在我们将设置我们创建的每个预制件的个别参数和同步,从Goal开始。
- 
在项目面板中选择 Goal预制件,并使用检查器面板添加三个组件:PlayMaker FSM(脚本)、Photon View和Play Maker Photon Game Object Proxy。你可以通过在检查器中点击添加组件按钮并输入它们的名称来找到这些组件。
- 
将 Goal预制件的Transform组件拖动到Photon View组件的观察属性中。
- 
使用playMaker面板的FSM选项卡将状态机命名为 ColorSync。
- 
将起始状态重命名为 是我的吗?并向它添加两个事件:是和否。
- 
创建两个新状态:绿色和红色。然后从是到绿色和从否到红色创建过渡。 
- 
在是我的吗?状态下,添加一个Photon View Get Is Mine动作。将是事件设置为YES,将不是事件设置为NO。 
- 
在绿色状态下,添加一个设置材质动作。将材质设置为为绿色目标创建的材质。 
- 
对红色状态做同样的操作,但选择红色材质。 
这个状态机确保游戏开始时目标会改变颜色。请确认场景中仍然有PlayMaker Photon Proxy游戏对象。如果你没有从多人游戏场景中复制它。在这个时候,你可能想要构建并启动两个游戏实例来测试一切是否运行良好。
现在我们将改变 Mallet 的颜色以匹配目标颜色。我们还将确保只有 Mallet 的所有者才能移动它。
- 
在项目面板中找到 Photon Unity Networking/PlayMaker/Demo/Resources下的Fsm Photon player预制件,并选择它。
- 
通过右键单击 Fsm Photon player组件的标题并从下拉菜单中选择复制组件,然后将组件复制到Mallet预制件中。然后右键单击Mallet预制件中的一个组件标题,并从下拉菜单中选择粘贴组件为新的。
- 
对 Fsm Photon player预制件的三个Play Maker FSM组件做同样的操作:名为GameObject 命名、位置同步和variable synch repository的组件。
- 
现在,Mallet 应该有四个 FSM。将名为FSM的一个重命名为 Movement,以确保我们记得它做什么。
- 
将名为Play Maker FSM的variable synch repository拖放到Photon View的观察槽中。将观察选项属性设置为可靠数据压缩。 
- 
在playMaker面板中,打开Movement FSM,并向其中添加一个名为isMine的 Bool变量。
- 
按照以下截图设置 FSM,确保移动和推杆状态保持不变。如果你没有推杆状态(你可能有)确认移动状态已附加推杆动作。为了设置一个状态为起始状态,右键单击它并从上下文菜单中选择设置为起始状态。 
- 
选择是我的吗?状态,并向其中添加Photon View Get Is Mine动作。设置其参数如以下截图所示,然后从是事件添加到移动状态的转换: ![制作多人游戏]() 
- 
切换到GameObject 命名 FSM。 
- 
在将"我"添加到游戏对象名称状态下,添加一个设置材质动作,并将其材质属性设置为为其中一个 Mallet 创建的绿色材质。不用担心它不再是绿色的,只需确保每个玩家的目标材质与他的Mallet材质匹配。 
- 
在添加玩家到游戏对象名称状态中,同时添加一个设置材质动作。这次将材质属性设置为与红色目标材质相匹配的材质。 
- 
切换到位置同步状态机,并选择使用 lerp 设置玩家位置状态。 
- 
在设置位置动作中,将X设置为None,Y设置为 0.85,将Z设置为None。
现在,球槌应该可以正常使用了,它们的网络位置同步,并在启动时设置了颜色。保存你的场景,并通过构建游戏并启动两个实例同时连接到相同的比赛来确认球槌的位置已同步。
剩下的就是通过网络同步 Puck 的位置。
- 
选择Puck预制体,并向其添加Photon View和Play Maker Photon Game Object Proxy组件。 
- 
从 Fsm Photon player预制体中将位置同步和变量同步仓库状态机复制到它上面。
- 
将Play Maker FSM的变量同步仓库组件拖放到Photon View的观察槽中,并将其观察者选项设置为可靠数据压缩。 
- 
切换到位置同步状态机并选择使用 lerp 设置玩家位置状态。 
- 
在设置位置动作中,将X设置为None,Y设置为 0.3,将Z设置为None。
- 
保存你的场景。 
你需要做的最后一件事是确保当球击中球门时,其中一个玩家离开房间而不是仅仅重新加载关卡。要做到这一点,选择GoalTriggerLeft游戏对象,并在其状态机的LoadLevel状态中,将加载关卡动作替换为Photon Network Leave Room。对GoalTriggerRight也做同样的操作。
现在你的多人游戏应该已经设置好了。在测试一切之前,确认你已经正确地从Multiplayer场景复制了所有文件。确保在demo_lobby和demo_room场景中都有一个名为PlayMakerPhotonProxy的游戏对象。如果其中一个场景中缺少它,可以通过从主菜单进入PlayMaker | Addons | Photon Networking | Components | Add Photon proxy to scene来添加它,然后保存场景。
你可以通过构建游戏并在浏览器中打开它来测试它,同时启动编辑器中的游戏。确保你始终从demo_lobby场景开始,否则匹配将不会工作。
你会看到球槌和 Puck 的位置通过网络同步。也就是说,它们在编辑器和网页浏览器中同时移动。你还会注意到远程对象的移动有些生硬和不精确(尤其是在主机的客户端上的 Puck)。这是由于不可避免的网络延迟和物理数据计算频率高于 Photon 包发送频率所导致的。目前解决这个问题的唯一真正方法是切换到 Unity 原生网络,但这需要你有自己的服务器,如果你想在互联网上运行游戏的话。
对象运动平滑处理的地方是Puck和Mallet预制体的Position synch状态机。如果您查看这些状态机的get player position和Set player position with lerp states,您会看到,如果对象最初是在本地机器上创建的,其位置会在每一帧保存到一个变量中并通过网络同步。如果对象不属于玩家的客户端,则读取该变量,然后对对象的本地副本的位置进行插值。
小贴士
您可以尝试更改Set player position with lerp状态中Vector3 Lerp 2动作的Amount属性。这个变量决定了插值的精度。如果您想了解更多关于 PUN 内部工作方式的信息,请确保查阅其在线文档,该文档详细解释了演示场景中的每个状态机:hutonggames.fogbugz.com/default.asp?W927
如您所见,光子网络设置起来相当简单,但并不是非常适合需要物理模拟的游戏。这主要是因为云服务器的限制以及与 Unity 引擎的集成有限。大多数不重度依赖物理的简单游戏可以从光子网络中受益。在其他情况下,应使用 Unity 原生网络。更多关于它的信息可以在 Unity 文档的“网络参考指南”部分找到:docs.unity3d.com/Documentation/Components/NetworkReferenceGuide.html
Playmaker 也能够与原生 Unity 网络协同工作。您可以在动作面板的网络部分查看可用的 Playmaker 动作列表,并将它们与参考材料中描述的动作进行比较。
摘要
在本章中,您学习了游戏网络的基础知识,设置了 Photon Unity Networking 插件以与 Playmaker 协同工作,并将 PUN 多人游戏功能添加到您的游戏中。我们还探讨了 Photon 的优缺点,并指出它可能不适合像冰球这样的物理游戏,因为网络延迟的原因。
下一章将展示如何将您的游戏放到网上,并添加 Kongregate API 以保存游戏分数到在线排行榜。
第七章。与外部 API 协同工作
在前面的章节中,你创建了一个多人制冰球游戏。扩展它的一个方法是为它添加更多关卡和机制,但你已经知道如何做到这一点。另一种方法是将它集成到不同的外部服务中,例如分析工具、在线得分平台和排行榜。
在本章中,我们将讨论应用程序编程接口(APIs)。我们将涵盖以下主题:
- 
API——它是什么以及它用于什么 
- 
现有的有用外部 API 
- 
Unity 通常与外部 API 通信的方式 
- 
将游戏与现有的一个 API(Kongregate)集成 
我们将查看一些代码片段,你将使用这些代码片段将你的游戏与 Kongregate 集成,在线测试游戏,并在 Kongregate 的服务器上保存玩家获胜的次数。
关于外部应用程序编程接口
简而言之,在 Unity 的上下文中,外部 API 是一个可以从 Unity 脚本访问的外部代码库,它为你的游戏提供一些额外的功能。一些 API 允许你访问 WebPlayer 游戏所在的页面的 JavaScript 代码,而其他 API 则提供将游戏数据传输到远程服务器并获得信息的能力。
在上一章的冰球游戏中,你已经使用了一个 API:Photon Unity Networking。大多数对远程服务器的调用都隐藏在其源代码的深处,但它仍然是一个 API。
你可能会遇到的其他 API 包括在线游戏平台,如 Kongregate 和 Facebook;分析工具,如 Google Analytics 和 Game Analytics;以及在线数据存储平台,如 Scoreoid 和 Steamworks。
除了不同的功能外,APIs 使用不同的方式连接到外部代码库,并在之后以不同的方式与之通信,这可能看起来像是一项艰巨的任务,但实际上很少是这样。通常,API 的官方网站上会有一个全面的指南,而且即使没有,Unity 社区也经常伸出援手,整理出自己的指南、模板文件和代码片段,这些都可以在 Unity 问答或论坛中轻松获取。
我们将把我们的游戏与 Kongregate 集成,以展示这是如何工作的以及你需要使用什么样的代码。Kongregate 被选为一个相当直接、非常常见且完全免费的 API。
将你的游戏上传到 Kongregate
在开始使用 Kongregate API 之前,你必须确认你实际上可以将你的游戏上传到网站。为此,如果你还没有,你需要一个 Kongregate 账户。
- 
访问 kongregate.com,然后在页面顶部附近找到并点击注册链接,如图所示:![将你的游戏上传到 Kongregate]() 
- 
应该会出现一个注册表单,提供你使用 Facebook 连接或手动输入账户信息的选择。这样做并点击注册按钮。 
- 
现在,如果你转到 Kongregate 主页并悬停在网站登录块下面的GAMES按钮上,应该会出现一个子菜单,分为三个部分:特色、类别和开发者。对我们来说,最后一个是感兴趣的。在那里找到上传游戏按钮并点击它。 
- 
打开 Unity 并创建一个 Web 构建,就像你在第六章中测试多人游戏时做的那样,网络和多人游戏;记住你保存的位置。 
- 
在 Kongregate 网站上,你现在应该有游戏信息菜单。输入游戏名称、类别和描述,然后点击继续。 
- 
下一步要求你选择游戏文件并将它们上传到 Kongregate 的服务器。点击游戏文件旁边的选择文件按钮,导航到你保存 Unity Web 构建的文件夹,选择具有 unity3d扩展名的文件,然后点击打开。
- 
在下面的两个文本框中,输入游戏的分辨率(宽度 960 和高度 600)。 
- 
上传一个图标作为图标。你可以测试,但不能发布没有图标的游戏。 
- 
你还可以上传图片作为截图。这不是必需的,你可以稍后进行。 
- 
如果你计划不上传游戏到其他任何地方,请勾选此游戏仅限 Kongregate。这将确保你从玩你游戏的人那里获得更多的广告利润。 
- 
阅读许可协议并勾选其下的四个复选框。 
- 
统计 API部分是我们将要使用 Kongregate API 的部分。点击添加统计。 
- 
将统计名称设置为 Wins并选择添加类型单选按钮。这是统计类型,它决定了统计将如何表现。在我们的例子中,我们将简单地累加玩家的胜利次数。如果你的游戏有一个得分系统,你可以为它创建另一个最大值类型的统计。
- 
勾选显示在排行榜上以确保该统计信息显示在游戏的公共页面上。 
- 
点击保存,然后在表单底部点击上传。 
- 
短暂等待后,游戏应该出现在你的屏幕上,完全可玩。你可以测试多人模式,以确保自你上传到服务器以来没有发生变化。 
如果一切正常,我们应该将我们的Wins参数与游戏本身集成。为此,我们需要编写几个脚本。
编写 Kongregate API 代码
我们需要制作两个脚本,以便将我们的Wins得分参数保存并显示在排行榜上。第一个脚本将设置 Kongregate,确保游戏确实在 Kongregate 页面上,并通知游戏 API 连接状态。
第二个脚本将涉及根据球击中的目标来增加获胜玩家的分数。
不再拖延,以下是对话脚本KongregateAPI的代码:
using UnityEngine;
using System.Collections;
// You have to add System in order to access the Convert class
using System;
public class KongregateAPI : MonoBehaviour 
{
  // We are going to check this variable to confirm that
  // Kongregate connection is established
  public static bool isKongregate = false;
  // Player ID
  public static int userId;
  // Player account name. This can be used for greeting the player,
  // for example
  public static string userName;
  // Game ID
  public static string gameAuthToken;
  void Start()
  {
    // Establishing connection with Kongregate
    // Make sure that the game object this script is attached to
    // is in the first scene that gets loaded,
    // and that the name of the object it is attached to is KongregateAPI
    Application.ExternalEval(
    "if(typeof(kongregateUnitySupport) != 'undefined'){" + " kongregateUnitySupport.initAPI('" + gameObject.name +"','OnKongregateAPILoaded');" + "};" );
  }
  // This method gets called if the game is on Kongregate
  void OnKongregateAPILoaded(string userInfoString)
  {
    Debug.Log("Kongregate connection established.");
    isKongregate = true;
    // Kongregate returns a string of chars that we divide and save into variables
    string[] parms = userInfoString.Split("|"[0]);
    userId = Convert.ToInt32(parms[0]);
    userName = parms[1];
    gameAuthToken = parms[2];
  }
}
这段代码相当直接,不需要在脚本中已有的注释之外做太多解释。只需注意两点:Application.ExternalEval是使你的 Unity 游戏与所在页面的 JavaScript 通信的关键。Unity 以文本字符串的形式发送消息到页面,该消息被 Kongregate 接收并解释为代码。这个字符串的内容使用了 Kongregate API,其完整版本可以在此查阅:www.kongregate.com/developer_center/docs/en/using-the-api-with-unity3d。ExternalEval是访问外部 API 的一个非常常见的方法。
重要的是,你的脚本附加到的游戏对象(以及脚本本身)应被称为KongregateAPI。在demo_lobby场景中创建一个空的游戏对象,并将脚本附加到它上,然后保存场景。
页面上的 JavaScript 代码执行后,Kongregate 会向 Unity 发送一个回调消息。这个消息始终以OnKongregateAPILoaded(string userInfoString)方法的形式出现。这也是 API 的一部分。然后我们使用|符号将其给出的字符串分开,并将部分内容保存到变量中。
小贴士
将 Kongregate API 脚本转换为 Playmaker 动作没有太大意义,除非你不想在游戏中使用除 Playmaker 之外的其他组件,在这种情况下,我将由你来决定是否这样做:过程与我们用于第五章中的类似,即脚本和自定义动作。
除非你改变了获胜条件,否则游戏目前无法区分玩家 1 和玩家 2 的胜利,无论发生什么情况,游戏都会简单地重新开始。然而,由于我们有一个多人模式并且想要保存每个玩家的胜利次数,这不再适用于我们。我们需要将目标触发器制作成预制体,并按照我们之前制作目标和球棒的方式生成它们;然后,当球击中其中一个时,检测它是否属于我们,如果不是,就将胜利信息发送到 Kongregate。
首先,让我们准备一个 Playmaker 动作,将胜利信息发送到 Kongregate。
using UnityEngine;
using System.Collections;
namespace HutongGames.PlayMaker.Actions
{
  [ActionCategory(ActionCategory.Level)]
  [Tooltip("Increment the Wins variable on Kongregate.")]
  public class KongregateSendAction : FsmStateAction 
  {
    public override void OnEnter()
    {
      if (KongregateAPI.isKongregate)
      Application.ExternalCall("kongregate.stats.submit", "Wins", 1);
    }
  }
}
这里使用的是Application.ExternalCall。它调用页面中的外部函数,而不是ExternalEval,后者评估一个可能包含或可能不包含函数调用的代码片段。
小贴士
ExternalCall和ExternalEval都只在 Unity Webplayer 中工作。
按照以下步骤来增加 Kongregate 上的胜利统计:
- 
打开demo_room场景。 
- 
创建一个名为 GoalTrigger的预制件,然后从Hierarchy中将GoalTriggerLeft游戏对象拖到它上面。
- 
从场景中删除GoalTriggerLeft和GoalTriggerRight游戏对象;我们将在启动时生成它们。 
- 
选择Game游戏对象,并在其Game Manager FSM 中添加一个名为goalTriggerRef的GameObject变量。 
- 
导航到这个有限状态机(FSM)的实例化玩家状态。向此状态添加一个新的Photon Network Instantiate动作。将游戏对象属性设置为GoalTrigger,位置设置为 (-9,0.42,0),旋转设置为(0,90,0),并将存储对象设置为goalTriggerRef。
- 
打开Create Puck状态,并向其中添加一个Set Position动作。在这个动作中,将游戏对象属性设置为指定游戏对象,然后将其设置为goalTriggerRef变量。将向量、Y和Z设置为无,将X设置为 9。
- 
现在我们已经设置了目标触发器的实例化,我们需要将我们的分数发送到 Kongregate。在项目面板中选择 GoalTrigger预制件,并向其添加一个Photon View组件,然后将它的Transform组件拖到Photon View的观察槽中。
- 
将PlayMaker Photon GameObject Proxy组件添加到预制件中。 
- 
在playMaker面板中,使 FSM 看起来如图所示,添加所有缺失的状态、事件和转换。 ![编写 Kongregate API 代码]() 
- 
向is mine?状态添加一个Photon View Get Is Mine动作。将是我的事件设置为YES,将不是我的事件设置为NO,前提是在事件选项卡中之前已经创建了YES和NO事件。如果没有,请现在就创建它们。 
- 
将Kongregate Send Action添加到KongregateSend状态。这是我们之前创建的动作。 
- 
保存场景。 
现在,如果你构建游戏,上传到 Kongregate,玩游戏并获胜,将添加分数。你应该在你的游戏页面右侧看到一个高分标签。
如果没有这样做,请不要担心,有时提交第一个分数可能需要一些时间。如果你觉得有问题,可以在游戏页面打开互联网浏览器的 JavaScript 控制台,查看 Kongregate 与你游戏交换的命令。
一旦你确定游戏运行良好,并且胜利统计信息被正确提交,你可以尝试添加更多统计信息,或者只需通过点击页面顶部的适当链接来发布游戏。然后你可以通过发送公共链接给你的朋友或者自己打开两次并加入同一个服务器来测试它。
摘要
在本章中,你学习了 Unity 中的外部 API 是什么以及有哪些类型的 API,然后将其添加到你的游戏中。你将游戏上传到 Kongregate,并在 Kongregate 的服务器上保存了你的多人空中曲棍球游戏的统计信息。

 
                    
                     
                    
                 
                    
                







 )。这将允许你从列表中选择一个变量,而不是使用数值。将Subtract Vector设置为
)。这将允许你从列表中选择一个变量,而不是使用数值。将Subtract Vector设置为











 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号