安卓-Unity-游戏开发学习手册-全-
安卓 Unity 游戏开发学习手册(全)
原文:Learn Unity for Android Game Development
协议:CC BY-NC-SA 4.0
一、为什么这是一个为 Android 开发游戏的激动人心的时刻
这是独立游戏开发商的黄金时代。
以前也有过这样的时候。那是在家用电脑的早期——ZX 频谱和 Amstrad 的时代。由于技术的限制,那时的游戏非常简单。不管你的开发团队或预算有多大,你用那个硬件能做的也就那么多了!这使得每个人都处于一个公平的竞争环境中,这意味着一个敏锐的程序员可以在他们的地下室舒适地获得像 Arkanoid 这样的名副其实的成功(见图 1-1 )。
图 1-1。
Arkanoid, from a time when all games were indie
然后技术在发展,游戏产业在成长,我们看到了 AAA 级游戏的出现。游戏成了比好莱坞电影更大的赚钱机器,硬件也突飞猛进。一个开发者不可能与光环或侠盗猎车手这样的公司竞争。不仅像这样的游戏中的每个模型都需要从头开始设计,每一行对话都需要记录,而且简单地编写决定事物爆炸方式的物理量对于一个单独的开发者来说是不可能完成的任务。
然后命运介入了。iOS 和 Android 等移动设备和操作系统的增长引入了新的“低规格”设备。与此同时,更好的分销渠道,如谷歌 Play 商店、iTunes 应用商店,甚至 Steam,都帮助小开发者发现了他们的创作。
慢慢地,越来越多的独立开发者开始发布游戏并获得好评,随着时间的推移,这最终转化为商业上的成功。像《洞穴探险》这样的早期作品!显示了伟大的游戏性和创造性的想法可以胜过 AAA 级的生产价值。后来,像 Limbo 或 Fotonica 这样的艺术尝试表明,风格化的视觉效果可以像超真实的图形一样引人注目。很快,像《超级肉仔》和《愤怒的小鸟》这样的游戏开始在销量上与顶级工作室竞争。事实上,《我的世界》,一个世界著名的独立游戏和家喻户晓的名字,现在实际上是有史以来第二畅销的游戏(仅次于俄罗斯方块)。《无人天空》是 Steam 上有史以来最畅销的游戏之一——它也来自一家独立工作室。
这一运动近年来势头越来越大。随着许多游戏玩家逐渐对过度刺激的《使命召唤》(Call of Duty)等普通的大预算续作变得漠不关心,独立游戏为自己打造了一个利基市场,并因能够提供更具创意和大胆的体验而赢得了声誉。有时候,这些经历甚至挑战了传统的游戏“游戏”的概念著名的例子是最近越来越受欢迎的“行走的模拟人生”。
输入 Unity
虽然许多因素促成了独立游戏的快速增长,但 Unity(图 1-2 )等工具也对这一运动做出了巨大贡献。Unity 是一个游戏引擎,它让开发变得特别容易,让初学者可以开始创建自己的程序。
图 1-2。
The Unity logo
游戏引擎是软件的主干,它提供了游戏运行所需的许多基本元素。这些元素包括显示(渲染)3D 和 2D 图形的能力,处理基本物理(称为物理引擎),检测游戏对象之间的“碰撞”(碰撞引擎),甚至提供基本的人工智能脚本或其他现成的素材。
像 Unity 这样的工具允许一个开发团队专注于使他们的游戏独特的元素,然后简单地将它们插入到工作环境中,而不是从头开始创建游戏。Unity 将这一点与便捷的界面和跨平台功能相结合,从而节省了数以千计的时间,否则从零开始构建一个完全实现的游戏并将其移植到 Android、iOS 和 Windows 是必要的(图 1-3 )。
图 1-3。
Unity saves developers a huge amount of time
在这一点上,你可能想知道使用像 Unity 这样的工具在某种程度上是否是“欺骗”。如果 Unity 为你的物理学提供了底层代码,那么你真的“制作”了这个游戏吗?如果它提供了一个用户友好的图形界面来拖放预先制作的游戏资源,它与“游戏制作者”有什么不同呢?和《超级马里奥制造》差那么远吗?
毫无疑问:Unity 是一个全面的工具,被成千上万大大小小的开发者所使用。许多你在 Android(和其他地方)上最喜欢的游戏很可能是用 Unity 开发的,包括:
- 神庙逃亡
- 坏猪
- Lara Croft:走吧
- 双人舞:秋天
- 逃跑计划
- 银河战士系列在线版
- 锈
- 超级英雄
- 幸运的故事
- 纪念碑谷
- 库法达
- 奥里和盲林
- 布洛费
- 倾斜画笔
简而言之,Unity 是一款专业级软件,已经被用于创建一些最大的独立游戏,甚至一些最大的 3a 游戏。使用现成的素材远不是不专业的标志;事实上,它是编码中最重要和最受鼓励的策略之一。任何优秀的程序员都遵循的一个关键原则是:不要重新发明轮子。换句话说,不要花大量的时间在物理引擎、人工智能甚至 3D 模型上,因为这些工作已经存在并可供使用。简单地这样做不是对时间的好利用,随着游戏变得越来越复杂和雄心勃勃,共享资源和代码不仅是明智的,而且是必要的。专业的开发者知道这一点,业余的也应该效仿。
当 Unity 是一个被证实的量时——当你知道它能够产生大量的热门歌曲时——你为什么要忽视它而使自己的生活更加困难呢?
共享素材
分享的风气是现代发展的重要组成部分,只需要快速的谷歌搜索就能意识到这一点。Unity 消除了许多复杂编码的需要,否则你必须自己处理,如果你想做一个非常基本的 2D 平台,那么你几乎不用写任何代码。当你不得不写代码时,如果你在论坛或其他地方提出这个问题,你通常会找到愿意帮助你的人。当然,这本书应该为你提供你需要的所有基本代码,以及理解如何创建更多的代码。
有时,您会需要 Unity 本身没有的代码,并且您不想从头开始创建这些代码——例如,特定的视觉效果或高级控制方案。幸运的是,这就是共享再次派上用场的地方。Unity 实际上在软件本身中构建了一个解决方案——不需要搜索网页或下载文件并将其导入到您的项目中。Unity 素材商店(图 1-4 )是一个资源,你可以在这里下载脚本、模型、精灵、纹理、特效等等,所有这些都是由社区或 Unity Technologies 本身提供的。下载这些组件会自动将其集成到您的项目中。更好的是,许多素材是免费的;其他的花费相对较少。通过充分利用这一特性,您几乎只需编写一行代码就可以创建任何您能想到的东西。
图 1-4。
The Unity Assets Store, where you can find all the scripts, sprites, effects, and more
这种开源的分享精神是最近帮助独立开发如此大规模起飞的另一件事。通过众包和借用游戏元素,开发者可以在原本需要的很短时间内构建出具有专业水准的巨大世界。
最好的部分?Unity 本身对业余爱好者和小型独立开发者也是免费的。你可以下载它,立即开始,无限制地发布到 Steam 或谷歌 Play 商店,直到你的游戏开始每年超过 10 万美元,这时你需要支付许可证费用(这仍然是非常合理的)。如果你每年的投资超过 20 万美元,你也需要付费,如果你打算将你的应用想法带到 Kickstarter,你需要记住这一点。有些功能也只对付费账户开放,但是大多数首次开发者不需要担心这个(我会在第二章详细介绍)。
此时你应该感到兴奋!通过使用 Unity,你可以用最近一些最大的开发者使用的完全相同的工具来构建一个游戏。构建一些基本的东西几乎不需要任何编码,当你确实需要独特的元素时,你通常可以在线获得它们。考虑到你将能够开发的游戏的质量,这里的学习曲线是非常宽松的——而且它是完全免费的(只要你坚持在免费许可证的限制之内)。
如果你一直梦想成为一名游戏开发者,但觉得遥不可及,那就再想想吧。从来没有这么容易过,有了 Unity 和这本书在手,没有什么能阻止你。
为什么移动设备非常适合独立项目
Unity 的另一个优点是它是跨平台的。你可以在你的 Windows PC 上制作一款游戏,然后在 Android、iOS、Xbox、Playstation、Unity(图 1-5 )等平台上销售。其中一些要求您申请开发人员许可证,购买开发工具包,或者面临其他限制。然而,理论上,跨平台的可能性是无限的。正如你将在本书后面看到的,Unity 甚至支持虚拟现实平台,如三星 Gear VR、Oculus Rift、HTC Vive 和谷歌 Daydream。当你按照这些页面中的说明进行操作时,如果你愿意,你可以选择将你的游戏移植到所有这些平台上。
图 1-5。
Indie titles are highly popular on Steam
但这本书的重点将是 Android。这是因为 Android 是所有这些平台中最开源和最大的。它的进入门槛也最低,你会发现它通常是最容易成功的平台。
再回头看看用 Unity 制作的大型游戏列表,你会发现很多都是手机游戏。其中一个原因是 Unity 和移动平台都吸引了独立开发者。有两个原因说明移动设备是印度的理想之选:
- 手机让你通过一个简单的分销渠道接触到大量的受众。
- 移动降低了人们对游戏的期望,因此也降低了游戏创作的工作量。
简单来说,如果你为 Xbox One 开发一款游戏,它将与《使命召唤》和《侠盗猎车手》等游戏竞争。虽然 Xbox 上有蓬勃发展的独立场景,但潜在的图形保真度仍然要高得多,甚至输入也更加复杂。
这就是为什么《无尽的奔跑吧》在手机上如此受欢迎,但在其他平台上却不太成功的原因。无尽的奔跑是主角向前奔跑的游戏…无止境…玩家所要做的就是在正确的时间点击“跳”。偶尔,他们可能还需要滑动来改变车道或点击另一个按钮来执行另一个动作。但最终,游戏由随机生成的障碍、最少的输入组成,没有传统意义上的“阶段”。在移动设备上,这是可以接受的,因为当你在银行排队时,它提供了理想的两分钟分心。但是大多数人都不想坐下来玩一段时间的无休止的跑步,因此他们在游戏机和个人电脑上相对较少。
现在问问你自己:作为一个新的开发者,你更愿意做一个超真实的 3D 角色扮演游戏(RPG)——还是一个无止境的跑步者?
当然,这并不意味着你可以制造垃圾。这只是意味着手机游戏玩家更加宽容,更喜欢小规模的娱乐活动。这意味着你可以用一些精致但相对简单简短的东西取得成功。显然,这种一口吃掉的性质通常会反映在价格上,但这只是意味着你可能会卖出更高的数量。当然,如果你想变得更有野心,没有什么能阻止你。像《侠盗猎车手》系列中的几款游戏这样的完整游戏已经成功移植到手机上,双截棍射手、RPG 和其他游戏也是如此(见图 1-6 )。
图 1-6。
Playing Geometry Wars 3 on an Android device
然而,批量销售把我们带到了下一点。移动平台如此受欢迎的另一个原因是它们拥有如此庞大的受众群体。如今,大多数人都有某种智能手机,可能运行 iOS 或 Android,这包括各种不同的人口统计数据。从祖父母到年幼的孩子,每个人都可以享受像愤怒的小鸟、糖果粉碎或与朋友交谈这样的游戏,这给了你大量的观众。
如果你把制作 Android 游戏作为一种爱好,你将能够与朋友分享它们,并获得大量反馈。如果你制作它们的目的是为了潜在的销售,那么你将拥有广泛的受众来推销它们。
为什么对开发者来说 Android 比 iOS 好
所以,手机很棒,但安卓更棒。为什么?首先,除了智能手机和平板电脑,Android 应用现在也可以出现在一系列设备上。Android 是一个完全开放的操作系统,这意味着 OEM(原始设备制造商)可以对其进行修改,以在电视、智能手表、电子书阅读器、洗衣机和其他各种硬件上运行。
好吧,所以大多数游戏短期内不会在洗衣机上运行。但是你当然可以创建一个在智能电视上运行的游戏,或者更好的是,像 Nvidia Shield 这样的游戏。更有可能的是,你将能够利用 Chromebooks 新的交叉兼容性。Chromebooks 是运行 Chrome OS 的计算机,Chrome OS 是一种基于浏览器的轻量级操作系统,自 2016 年以来一直能够原生运行 Android 应用。我会在后面的章节中详细讨论如何最大限度地兼容你的应用。
Android 相对于 iOS 的实际优势
选择 Android 而不是 iOS 也有实际优势。除此之外,当你选择 Android 时,整个过程要简单得多。目前,将应用上传到 Play Store 仍然是一个相对简单的过程,任何人都可以管理,不需要花费超过几个小时。你可以想出一个应用的想法,把一些东西放在一起,并让它在 24 小时内上线。
要想在安卓系统的游戏上赚到真金白银,你需要尝试进入谷歌 Play 商店(图 1-7 ),用户可以在那里搜索并下载应用。这样做需要一次性支付 25 美元的费用,并且全部自动处理。你只需打包并签署你的 APK 文件(图 1-8——别担心,我会告诉你怎么做)并使用简单的屏幕指示上传它。该应用然后上传并通过自动批准过程,几个小时内人们就可以开始下载它。你可以多次这样做,而不需要再次付费,并且你的应用只有在违反谷歌政策的情况下才会被删除。
图 1-8。
Success is just a click away
图 1-7。
Games in the Google Play Store
与苹果的流程相比,这是一件轻而易举的事情,苹果的流程首先需要每年 99 美元的重复费用。即使你只有一个应用,每个月下载几次,你仍然需要每年支付 99 美元来维持它的运行。此外,iOS 应用需要经过一个审批过程,其中涉及真正的人类版主和更严格的限制。许多应用就是不允许在 iTunes 上运行,给出的原因往往有些模糊或武断。我知道至少有一个开发人员拒绝了一个笑话应用,因为它“不够有趣”那肯定是个人品味的问题!
当然,苹果的做法确实会让 iTunes 商店的应用整体质量有所提高。一般来说,iTunes 上的应用至少会达到最低质量,而一些相当严重的糟粕会进入 Play Store(这不是我们的目标!).然而,iOS 的风险在于,你花了几个月的时间创建了一个你非常满意的应用,结果却是它被拒绝,永远见不到天日。更重要的是,当涉及到应用的内容和性质时,使用 Android 只会给你更多的创作自由和更多的选择。
哦,还有一件事:为了开发 iOS,你需要给自己买一台 Mac 电脑和一台 iOS 设备来测试。这与为 Android 开发形成对比,理论上你甚至可以不需要 Android 手机就可以使用模拟器。这些必需的购买增加了开始使用 iOS 所需的投资。
Android 与 iOS 的价格为$$$
如果你对创作游戏主要是为了赚钱感兴趣,你可能不太关心创作自由,而更关注哪个平台能让你赚最多的钱。这样的话,苹果确实有一点点优势。
首先是好消息:Android 用户比 iOS 用户多得多,但 iOS 用户在应用上的花费可能是 Android 用户的两倍半。简单来说,iOS 用户往往口袋里的钱多一点,也更倾向于使用它。实际上,这个 2.5 倍相当于每个应用 1.08 美元,而不是 0.43 美元。iOS 用户进行应用内购买的可能性也增加了 50%(根据 AppsFlyer 在 2016 年发布的应用内支出全球和区域基准状况),7.1%的 iOS 用户每月至少进行一次应用相关支付,而 Android 用户只有 4.6%。
有一类应用是 Android 有优势的,但不幸的是,它对我们这些游戏开发者来说毫无用处:这一类是实用程序(图 1-9 )。Android 用户更有可能在实用程序上花钱,这很可能是因为开发者和应用所有者在这方面获得了更大的自由,允许他们创建自定义启动器、内存/电池管理工具、多任务应用等。
图 1-9。
Multiscreen Multitasking, an old utility app I developed for Android several years ago
AppsFlyer 的报告还揭示了其他有趣的数据:例如,与其他地区相比,亚洲用户在应用内购买上的支出高出 40%。如果你打算使用免费增值商业模式,瞄准亚洲市场可能是一个不错的策略。后面的章节将更多地讨论如何最大化你的收益。
现在的问题是:考虑到 iOS 提供了更大的收入潜力,你还应该为 Android 开发吗?当然,这是你的决定,但对许多人来说,为 Android 开发的好处大于坏处。为 iOS 开发需要太多的投资和风险,进入门槛太高。另一方面,Android 允许你立即开始开发游戏,并以更多的创作自由和更少的创作被拒绝的机会接触到更多的观众。最重要的是,Android 的市场份额在不断增长,而 iOS 的市场份额在萎缩。考虑到成千上万的原始设备制造商正在生产 Android 设备,而只有一家制造商在生产 iOS 设备,这是显而易见的。总体而言,应用支出也在上升。这使得 Android 成为更“面向未来”的市场,因为你的受众(和收入)可能会随着时间的推移而增加。
因此,尽管 iOS 可能是利润稍高的平台,但为 Android 开发也很有可能赚很多钱。到目前为止,比平台更重要的是应用的性质、营销和推广。在本书的其余部分,你将学会如何熟练地处理所有这些。
Android 和 Unity:天作之合
希望你现在相信 Android 是独立游戏开发的首选平台。选择 Android,你可以通过消除限制和前期费用让自己的生活变得非常简单,这意味着在 Play Store 中有一个可用的应用并开始推广它之前,时间会更短。
我们战略的另一个关键部分是 Unity 3D。我们已经看到了 Unity 提供的一些令人难以置信的优势,通过选择使用 Unity 为 Android 开发,您大大降低了成为开发人员的门槛。在第二章你会学到更多关于 Unity 的工作原理。现在,请记住它是一个游戏制作工具,与其他工具相比,它可以让你在很短的时间内制作出更专业的游戏。有了 Unity,你可以在几天之内真实地组装一个无限转轮或太空射击游戏,它看起来就像一个经验丰富的大型发行商的任何东西一样令人印象深刻(如果你玩得好的话)。
Unity 的界面对初学者非常友好,允许你根据需要简单地在屏幕上拖放许多元素(图 1-10 显示了该界面的预览)。如果你担心编程,考虑一下你实际上可以完全避免使用前面提到的 Unity Assets Store 进行编码,尽管这样做在很大程度上限制了可能性。
图 1-10。
Developing with Unity
正如我们已经看到的,Unity 也是跨平台的,允许您只需轻触一个按钮就可以发布多种格式。因此,所有关于是针对 Android 还是 iOS 的辩论都不太相关,因为你可以简单地创建一个游戏,然后按一下按钮就发布到两个平台。事实上,您还可以发布到 PC (Windows Store 和 Steam)、Windows Phone、Linux、Xbox 等等。
创建你的游戏的过程在不同的平台上几乎是一样的,所以即使是 iOS 开发者也能从我们在这里讨论的内容中受益。
如何选择你的第一个项目
使用 Unity 3D,可能性几乎是无限的。您可以创建任何东西,从简单的益智游戏到完全实现的 3D 第一人称射击游戏。由于摩尔定律,现在普通智能手机的功能已经接近游戏机质量,可以放在我们的口袋里。
但这并不意味着你应该开始下一次使命召唤。使用 Unity 完全有可能为 Android 开发一个非常详细的 3D 游戏(见图 1-11 ),但这并不意味着你应该这样做。这是绝大多数第一次开发的人会犯的错误,也是你需要从这本书中学到的最重要的信息之一。
图 1-11。
Full 3D is also easy in Unity
简单地说:大多数第一次开发的人都有远远超过他们能力的抱负——他们只是忽略了在他们的第一个项目中控制他们抱负的需要。我并不想踩在任何人的梦想上,但这是一个更好的策略,开始创造一个可实现的和现实的目标,而不是承诺一个不可能的项目,最终占用你多年的生活,永远不会被看到完成。
想一想开发一个 3a 风格的标题所涉及的巨大事业。在一个城市环境中创建一个 3D 关卡,你需要 3D 建模每一个路人、每一辆车、每一个灯柱、垃圾箱、信箱、电话亭、街道上的垃圾、建筑物、敌人……不胜枚举。这些项目中的每一个都需要真实的物理、流体动画和相关的声音效果。你将需要过场动画、画外音、音乐、广阔的游戏区、多层次....对一个人来说,这是一个可能需要很多年的项目。到那时,技术将会继续发展,你所创造的一切可能会看起来过时。这就是发生在《毁灭公爵:永远》上的事情,这是一款背后有着经验丰富的大型工作室的游戏。这就是为什么最成功的独立游戏往往没有最逼真的图形;相反,他们选择吸引眼球的原创艺术风格,同时减轻创作者的工作量。
创造一个成功的独立作品的最佳策略
看看类似 Limbo 的东西,一款适用于 Xbox、Playstation、Windows 和 mobile 的游戏。这是一款早期的独立游戏,卖得非常好,在游戏社区引起了巨大的轰动。这在一定程度上要归功于完全使用剪影的艺术风格。这符合游戏的基调和形象,同时看起来令人惊叹,视觉上也很有趣。开发商(Playdead)无法通过使用超真实图形与顶级出版商竞争,而是走了一条完全不同的路线,提出了一些独特而非常有吸引力的东西。这些截图在应用商店滚动的过程中会非常突出,足以激起购物者的兴趣,并有可能让他们购买。
更好的是,通过选择使用轮廓,Playdead 大大减少了精灵和游戏元素所需的细节数量。游戏有绝对华丽的动画,这真的有助于销售的气氛,但这可能是唯一可能的,因为缺乏细节使团队能够将注意力集中在该地区。
当然,不是每个游戏都可以使用剪影,但是你需要跳出框框思考,变得有创意。一个特别受欢迎的选择是使用“像素艺术”。这是一种复古风格,模仿旧的 8 位和 16 位游戏机的图形,具有非常怀旧的吸引力。同样,它限制了所需的工作量。像《超级兄弟:剑与武器》EP 这样的游戏展示了这种风格是如何被运用到非常漂亮的效果中的。
考虑游戏性
同样的方法也应该用在游戏中,尤其是你的第一个项目。与其致力于制作需要你创建 3D 模型和充满细节的大地图的第一人称射击游戏,不如尝试一些 2D,最好是用程序生成或其他技术来减少你的工作量。(程序化生成是指游戏元素根据算法随机添加,去除了手动创建关卡的必要性。)
如前所述,无尽的奔跑在移动平台上非常受欢迎,这要归功于它们简单的游戏性和缺乏复杂的控制。在这里,当主角向前奔跑时,你所做的就是点击跳跃,这允许你躲避即将到来的障碍和敌人。所述障碍和平台是随着游戏向前滚动而随机产生的,并且玩家持续的时间越长,游戏的速度越快。可重复性来自于努力达到最高分,也来自于没有两次“跑”是完全相同的这一事实。像雷曼嘉年华运行,卡纳巴尔特和马里奥运行这样的游戏已经用这个非常简单的公式卖出了大量的游戏。Flappy Bird 也可以被视为传统无限转轮公式的变体。
其他游戏使用更简单的游戏机制。Terry Cavanagh 的神奇超级六边形利用了非常有创意的图形,由向屏幕中心移动的六边形组成(这产生了一种非常迷幻的效果),以及一个简单的目标,即按下屏幕的左右两侧,尝试将您的化身移动到每层的小间隙中。这个游戏看起来非常独特和催眠,难度使它非常吸引人。这足以使它成为一个巨大的成功,特里不需要设计一个单独的精灵或关卡地图。
或者山羊模拟器怎么样?这是一个独立游戏,正如它的名字所说:它允许你控制一只山羊,同时在沙盒环境中制造各种混乱。这款游戏是 3D 的,但是它愚蠢的本质意味着没有人会期待真实的图形或物理,甚至是挑战关卡设计。尽管如此,这款游戏还是非常成功,这要归功于其核心理念的吸引力以及所有游戏片段对 YouTuber 的友好性。在这种情况下,光是这个想法就胜过了对任何技术成就的需求。Android 上有许多游戏声称是“世界上最难的游戏”,这些游戏之所以成功是因为同样的原因:它们的概念天生就很吸引人,这促使人们下载。
如果你想把开发游戏作为一种爱好,那就随意摆弄你喜欢的任何夸张的项目。但是,如果你想赚些钱,或者至少从真实用户那里得到一些积极的反馈,寻找“容易的胜利”——至少在开始的时候。
所以这是我们要做的
暂时搁置那个改变世界的角色扮演游戏,转而考虑在你的第一个项目中做一些更小的东西。忘记 3D 屠龙者:史诗般的探索,多想想复古的抓捕游戏。
一个简单的益智游戏,一个基本的 2D 平台,或者一个你可以在几周内完成的无限跑者,将会给你机会将一些具体的东西带到这个世界,并在你前进的过程中发展你的技能。这样,你就不会在一些可能永远不会成功的事情上投入太多的时间和精力,你就能很快开始受益并完善你的策略。好消息是 Unity 有一个 2D“模式”,使得这个策略简单了很多,并且改变了用户界面和特性来更好地支持 2D 游戏开发。
在商业中,这种方法被称为快速失败。你不是创造一个需要多年研发和数千美元投资的产品,而是创造简单、容易、不需要成功的产品。如果产品失败了,你只需继续你的下一个想法。但是,如果它在市场上获得了牵引力,那么你就要花一些时间来发展这个想法,并进一步发展。
没有什么比把你生命中的岁月倾注在一款游戏上,只为了它获得十次下载和一星评价更能摧毁灵魂的了。所以,创建一个 MVP(最低可行产品),让它进入 Play Store,然后只有在它找到受众的情况下才能开发它。
如果你的艺术风格足够独特,游戏玩法足够新颖,你的营销技巧足够强大,你会惊讶于一个相对简单的游戏所能产生的影响。
你将从这本书中学到什么
考虑到所有这些,这本书将带你经历使用 Unity 设计、构建和发布一款全功能 Android 游戏的整个过程。具体来说,它带你通过创建一个 2D 平台或一个无止境的跑步者的基础,这包括从建立物理和精灵到签署你的 APK 准备上传到游戏商店的一切。
你将要做的项目将足够基本,你可以很容易地修改它以适应你自己的目的,通过改变几个精灵并给它一个新的标题,你将准备在几周内发布你的第一个游戏。游戏将会很简单,但是会有足够的功能来帮助你掌握 Unity 游戏开发的核心概念,这样你就可以将它们应用到未来的项目中。
您将发现以下内容:
- 如何安装和设置 Unity
- 如何使用 Android SDK(软件开发工具包)
- 如何在 Unity IDE(集成开发环境)中找到自己的路
- 如何创建和添加精灵
- 如何添加动画
- 如何用刚体 2D 介绍物理学
- 如何使用 Visual Studio 用 C++ 和 Java 编程
- 如何添加收藏品、音效、敌人、游戏机制等等
- 如何添加关卡、分数、关卡选择、菜单等等
- 如何添加动态摄像机
- 如何设计有趣且具有挑战性的关卡
- 如何创建已签名的 APK 文件以备上传
- 如何将应用发布到谷歌 Play 商店
- 如何为你的应用定价以获得最大利润
- 如何推广您的游戏并获得更多下载
在整个过程中,我们将使用少量的代码和大量的媒体(精灵、音乐、音效和背景),所有这些都将供您在自己的项目中使用,进行逆向工程,或者进行您认为合适的编辑。
在这本书的过程中,我们也将看到你可以用 Unity 创造和构建的各种不同的东西。目的不是限制你,所以你会发现如何在 3D 中工作,甚至为 Gear VR(图 1-12 )、Google Cardboard 和 Google Daydream 制作虚拟现实应用。这将给你一个坚实的知识基础,如果你想拓展自己的发展,并在未来承担更具挑战性的项目,这将成为你的起点。
图 1-12。
Developing for the Gear VR with Unity
是的,Unity 3D 可以用来创建应用和实用程序,如果你愿意,你在本书中学到的技能也可以让你这样做。
TLDR;这是成为独立开发者的大好时机。Android 和 Unity 为想要在 Play Store 中推出具体产品的初学者提供了完美的组合。这本书带领你完成一个简单游戏的开发,并提供你需要知道的一切,让你的第一个项目起步,并帮助你在未来成长为一名开发者。
准备好了吗?是时候开始了,玩家 1!
二、Unity 简介和设置
为 Android 开发已经对初学者和寻求赚钱的独立开发者有很大的意义。但 Unity 3D 让一切变得不同,因为它让这一切变得简单而实用。Unity 是一个令人难以置信的强大工具,它可以让你更快更容易地创建一些令人敬畏的游戏。
在这一章中,你将对 Unity 有一个更好的了解,它是什么,它从哪里来,以及你如何着手设置它,以便你可以开始使用它。
什么是 Unity?
第一章简要介绍了 Unity,但现在是时候更深入地了解 Unity 能为你做什么,以及它将如何影响你的工作流程。本章涵盖了什么是游戏引擎,什么是 IDE,以及如何设置和运行它。到这一章结束时,你将掌握基本知识,并准备开始动手实践。如果你已经熟悉了 Unity,并且它已经安装在你的电脑上,你可以跳过这一章。
作为游戏引擎的 Unity
从本质上来说,Unity 是一个游戏引擎,已经发展成为一个 IDE/快速开发工具。如果这一切听起来像天书,不要担心,我会分解它。
说得更详细一点,游戏引擎本质上是大量的代码,处理让游戏运行的所有无聊的部分。值得注意的是,这包括物理以及渲染,照明,基本的相机功能,等等。虚幻引擎是游戏引擎的另一个例子,CryENGINE 3 也是。其他的还有 Torque、Lumberyard、Ogre3D、Blender、JavaFX 等等。
如果你完全从零开始编写一个游戏,而不使用预先存在的游戏引擎,你将需要自己编写每一个细节,这意味着在你开始添加关卡等东西之前,需要进行大量的开发。当一个木箱已经被你处理好的时候,再去编码它应该如何坠落和破碎是没有任何好处的。
同样,这也是我们看到独立开发者复兴的原因。回到 ZX 光谱和 Amstrad 的时代,游戏引擎可以简单得多,大多数精灵是由大约 50 像素组成的。如果没有 Unity 这样的解决方案,当今游戏的复杂性将使一个人不可能单干。
Unity 也是交叉兼容的,这意味着它可以作为你的代码和你的目标设备之间的桥梁。编译您的游戏会压缩所有资源,并将其转换为正确的文件格式,以便添加到相应的分发平台。
简而言之,Unity 为你处理所有幕后的事情,并允许你开发一个伟大的游戏,而不用担心重新发明轮子或担心光线应该如何通过各种材料折射(见图 2-1 中我愉快地编码)。就好像宇宙的规律已经被创造出来了,你要做的就是把它填满。然后 Unity 会处理最后的必要的跑腿工作,把你的世界变成一个真正的游戏准备发行。
图 2-1。
Unity streamlines development (photo by Sophie Bunce)
今天,绝大多数开发人员——甚至是大工作室——都使用像 Unity 或 Unreal 这样的现成 ide。偶尔,一个游戏会使用一个定制的引擎(如“节奏暴力”游戏 Thumper),但这些通常有独特的游戏机制,保证创建一个定制的引擎,他们通常花很长时间进行开发。
因为 Unity 使生活变得更容易,而没有引入任何主要限制,所以没有理由不使用它(或类似的选项,如 Unreal)。“单干”只会让挑战变得更加困难,没有任何实际的好处。
Unity 作为 IDE
然而,让 Unity 成为开发者福音的是,它同时是一个游戏引擎和游戏制造商,拥有用户友好的界面,允许元素在屏幕上轻松拖放(图 2-2 )。
图 2-2。
An IDE is a single environment for developers to handle every aspect of creating their game
用更专业的语言来说,Unity 不是一个游戏制造商,而是一个 IDE。IDE 是 Integrated Development Environment(集成开发环境)的首字母缩写,它本质上是一套用于开发的综合工具,通过一个简单的界面就可以查看和修改一个程序的各个方面。如果你要创建一个没有 Unity 的 Android 应用,你需要使用另一个 IDE——很可能是 Android Studio,它将允许你查看代码、你的素材文件夹、调试信息、图形预览等等。在 Unity 的情况下,你可以看到场景的视图(本质上是关卡),场景中所有元素的层次结构(称为游戏对象),你选择关注的任何项目的细节,你的素材文件夹等等。我们将很快看看 Unity 提供的所有不同的窗口和视图。
统一 vs 虚幻 4(及其他)
我说过没有理由不使用 Unity,但这并不完全正确。有一个很好的理由让你选择不使用 Unity,那就是如果你打算使用其他各种游戏引擎/制造商的话。
或许最接近的比较可以用虚幻 4 来画(图 2-3 ),有很多相似的特点。两者都功能齐全,限制很少,这一点——加上它们相对简单——使它们成为独立工作室最受欢迎的两个 ide。那么两者哪个平台更好呢?为什么选择 Unity?一如既往,答案取决于你打算开发什么类型的游戏。在我们的例子中,我们打算为 Android 制作一个 2D 游戏,对于这个特定的任务,Unity 是首选。
图 2-3。
The Unreal logo (boo!)
虽然它并不多——而且往往只是归结于个人偏好——Unity 可以说对手机游戏开发和 2D 游戏开发有更好的内置支持。Unity 是手机上最受欢迎的游戏引擎,这反映了它的能力。这也确保了有一个巨大的社区为创作者提供支持,以及素材商店中几乎无限的定制素材,这可以大大加快开发速度。
许多人也喜欢 Unity 的流程,它允许你使用实体(游戏对象)和组件(脚本)的简单系统来构建游戏。这当然是一个见仁见智的问题,但足以说,Unity 在很大程度上是非常直观的,初学者也很容易使用。另一方面,虚幻的学习曲线更陡,组织性也不是很好。但虚幻 4 有更好的图形功能来开发 3a 外观的游戏。它也是开源的,这实际上意味着您可以访问源代码并对引擎本身进行更改。对于以移动设备为目标的独立开发者来说,这些都不是问题。所以 Unity 就是胜利。
统一的起源
Unity 由一家名为 Unity Technologies SF 的公司开发,该公司于 2004 年由大卫·赫尔加松、尼古拉斯·弗朗西斯和约阿希姆·安特在丹麦哥本哈根创立。图 2-4 为撰写时的官网。
图 2-4。
The Unity home page today
在此之前,这三位开发者自称为 Over the Edge Entertainment,并为 Mac 开发了一款名为 GooBall 的游戏,其游戏玩法类似于超级猴子球。尽管这款游戏没能引起轰动,但团队意识到该引擎可能对其他开发者有价值。因此,他们在 2006 年的 WWDC 贸易展上宣布为 OS X 推出 Unity 3D。
从那时起,Unity 经历了多次迭代和开发,现在就其支持的平台和包含的特性而言,已经更加全面了。1.1 版支持为微软视窗和浏览器创建游戏,同时支持 C/C++ 插件。2.0 版本增加了对微软 DirectX 的支持,2008 年发布了 Unity iPhone。
2010 年发布的 3.0 版本是另一个重要步骤,因为该团队希望该程序能够在 Windows 上运行,这需要从头开始重建。因此,3.0 版本整合了 Windows、iPhone,以及对 Wii 和许多其他平台的支持,而这些平台以前只能由单独的独立编辑器支持。现在 Unity 这个名字终于有意义了。是的,这也是我们支持 Android 的时候。
unity 4.3 版本见证了另一个重要的更新:Unity2D 包含了开箱即用的 2D 支持。到目前为止,开发人员基本上不得不通过使用固定的相机角度和向平面添加纹理来创建背景,从而“侵入”IDE 以支持 2D。现在,创作者可以更快更容易地利用精灵和其他更传统的方法来构建真正的 2D 游戏。
据 Unity Technologies 称,Unity 5.0 将是最大和最重要的版本,具有更好的全面性能,并对动画系统、混音器、着色器等进行了重大更新。因此,现在许多人把 Unity 称为统一。在撰写本文时,Unity 的最新版本是 5.5.0。它特别为 Android 做了许多改进,应该会提高性能。
如果你有新版本的 Unity 怎么办?
值得注意的是,Unity 正在不断开发其平台,并添加新的功能和改进。因此,根据你阅读这本书的时间,你可能会发现一些元素与这里描述的不同。也许你正在阅读遥远的未来,你正在使用 Unity 200。如果有,我希望你有一辆会飞的汽车。
然而,更有可能的是,你遇到的任何变化都是微小的。当然,像 Unity Technologies 这样的中间件开发人员努力避免在未来的更新中破坏代码,这意味着大多数基本功能仍然可以工作。
但在一些罕见的情况下,一行代码可能会在 Unity 中突出显示,并被描述为被否决。这意味着它被支持但不被鼓励。如果你注意到了这一点,快速的谷歌搜索会帮助你找到新的、正确的方法来处理这个功能。
执照
似乎 Unity 还不够棒,最棒的是它还可以完全免费使用(大部分情况下)。在未来,随着你的野心增长,你可能会发现自己需要额外的功能或收入超过 10 万美元的门槛,但大多数新手都会很好,除非他们的游戏大获成功。这是最糟糕的情况。
基本上,您可以创建几种不同类型的账户,如图 2-5 所示,每种账户都有不同的定价和不同的限制。
图 2-5。
For most people, the free personal plan will more than suffice
个人的
你首先要用的免费账户是个人账户。这个账号不用花钱,Unity 连版税都不拿。唯一的限制是你每年在这个许可证上的收入不能超过 10 万美元。不过,这没关系,因为一旦你开始交出大数字,你可以简单地切换到其他许可协议之一。请记住,这个 10 万美元的限制也适用于筹集的资金,这意味着如果你在 Kickstarter 上筹集的资金超过 10 万美元,那就算数。这条规则也适用于其他来源的利润,包括点击付费广告或应用内购买。
个人帐户还有一些缺失的功能和限制。例如,当游戏启动时,使用个人帐户的创作者被要求显示 Unity 闪屏(向用户显示你在 Unity 中制作游戏),而你将无法访问实时开发者分析或云构建。对于多人在线游戏,个人帐户一次只允许 20 名玩家。
Unity Plus
Unity Plus 目前每月收费 35 美元,取消了闪屏,同时将收入上限提高到每财年 20 万美元。它还增加了对大型开发人员有用的额外支持和功能,比如支持 50 个并发用户和打折的素材包。
Unity Pro
Unity Pro 将价格提高到每月 125 美元,并完全取消了收入上限,这意味着如果你愿意,你无需支付更多费用就可以变得非常富有。它还提供了许多专业服务,包括支持多达 200 个并发玩家,更多的分析和性能,支持大型团队,等等。
Unity 企业
最后,企业成员资格允许您挑选功能,以创建一个针对您的独立需求的定制开发平台。这是最高级的选择,价格实际上根本没有列出——这表明它非常昂贵。简而言之,大多数阅读本书的人暂时还不需要担心这个问题。事实上,对于绝大多数人来说,基本的个人帐户已经足够了,应该能够提供您需要的所有功能和灵活性。
Note
这些是我写这篇文章时的价格,但它们当然会有变化。也可以通过按年付费来省钱,如果你想买一个更高级的账户,你应该做进一步的研究。
下面是一个方便的表格,比较了各种功能:
| 个人的 | 加 | 赞成 | | --- | --- | --- | | 所有发动机特性 | 所有发动机特性 | 所有发动机特性 | | 所有平台 | 所有平台 | 所有平台 | | 持续更新 | 持续更新 | 持续更新 | | 免版税 | 免版税 | 免版税 | | MWU 闪屏 | 自定义闪屏 | 自定义闪屏 | | 收入上限为 10 万美元 | 收入上限为 20 万美元 | 没有收入上限 | | Unity 云构建的标准队列 | Unity 云构建的优先级队列 | Unity 云构建的并发构建 | | 个人分析 | Plus 分析 | 专业分析 | | 20 名并发玩家 | 50 个并发玩家 | 200 个并发玩家 | | Unity 应用内购买 | Unity 应用内购买 | Unity 应用内购买 | | 统一广告 | 统一广告 | 统一广告 | | Beta 访问 | Beta 访问 | Beta 访问 | | | 专业编辑用户界面皮肤 | 专业编辑用户界面皮肤 | | | 绩效报告 | 绩效报告 | | | 灵活的座位管理 | 灵活的座位管理 | | | 素材套件八折优惠 | 素材套件 40%折扣 | | | Unity 认证课件 1 个月使用权 | Unity 认证课件 3 个月使用权 | | | | 源代码访问(美元) | | | | 特优支持(美元) |下载 Unity 和所需组件
好吧,我想我已经说得够多了。让我们从技术层面开始吧。你当然需要设置 Unity 并让它在你的电脑上运行。这在很大程度上足够简单,但是请记住,您还需要一些额外的软件。具体来说,您需要下载并安装以下软件:
- Unity 本身
- Android SDK(以及 Android Studio 2.3)
- 爪哇 JDK
- 可视化工作室
Android SDK 是 Android 软件开发工具包。这是 Google 提供的一套软件工具,可以作为访问硬件功能的桥梁。换句话说,它提供了 Unity 需要的源代码,以使您的游戏在 Android 平台上兼容。它还包含一些其他工具,可能在未来对你有用——包括一个模拟器,允许你在桌面上测试 Android 应用。你还需要选择安装 Android 构建支持,但你是通过 Unity 安装程序来完成的,所以没有必要单独下载。
Java JDK(图 2-6 )是另一个开发工具包,这次是针对 Java 的。这是你的计算机支持 Java 开发所需要的,因为 Java 是 Android 的主要语言,你需要它来继续。我们会先下载这个。
图 2-6。
The Java logo
Visual Studio 是您在 Unity 中用来处理实际编程的工具。当您开始编写脚本时,您将在一个单独的 Visual Studio 窗口中编辑这些脚本——但是我们可以稍后再考虑这个问题。请注意,您实际上并不需要 Visual Studio,也可以使用 MonoDevelop 之类的替代选项。但是 Visual Studio 肯定是这两者中的首选,并且会让您的生活变得轻松一些。
下载 Unity
要开始下载 Unity,首先前往 Unity3D.com(https://unity3d.com
),然后点击立即获取 Unity。然后你可以选择你想要的计划(大多数情况下是个人的),在那里你只需点击立即下载,然后在下一页下载安装程序(图 2-7 )。一旦你这样做,下载将开始。
图 2-7。
This is where you will find Unity
您不需要单独下载 Visual Studio,因为您可以通过 Unity Downloader 来完成。这为你节省了一点时间和麻烦。
下载 Java JDK
如果你觉得有点不知所措,不要担心。一旦你安装了所有这些小部件,它们就会在后台自己运行,你可以把它们忘得一干二净。这是一个一次性的过程(除非你在一台新电脑上安装 Unity ),你再也不用担心了。
Java JDK 允许你的计算机和 Unity 理解和解释 Java 代码。前往 www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.html
并下载 Java SE 开发工具包(图 2-8 )。如果您的计算机支持 x64 版本,请确保选择该版本。
图 2-8。
The Java JDK download page
下载 Android SDK
要下载 Android SDK,请前往 https://developer.android.com/studio/index.html
(图 2-9 )。点击下载 Android Studio 按钮,接受条款和条件,开始下载。如果您运行的电脑硬盘相对较小,请点按“下载选项”。Android Studio 是用于创建常规应用的标准 Android IDE,使用 Unity 实际上并不需要它。为了节省大量不必要的文件,您可以下载命令行工具,然后使用附带的 SDKManager 来下载 SDK 的其余部分。你可以在 Android Studio 网站上找到如何做到这一点的说明。
图 2-9。
It’s easiest to download Android Studio and the Android SDK at the same time
不过这要复杂得多,Android Studio 肯定是一个有用的东西,所以如果你能负担得起空间,我建议你一旦有了 Java 就安装整个包。
安装 Unity 3D
一旦所有东西都下载完毕,开始安装就非常容易了。首先找到保存它们的文件,然后依次双击每个文件。从哪一个开始并不重要,所有必要的步骤都将在本节中解释。
一致
当您双击 Unity 安装程序并接受许可协议条款时,您需要选择下载哪个版本的 Unity:64 位或 32 位。最佳版本将取决于您的 Windows 版本,因为并非所有计算机都支持 64 位。要检查您是否拥有 x64 或 x32 位处理器,请前往这台 PC,右键单击,然后转到属性(图 2-10 )。如果您的计算机是 64 位的,它将显示:“64 位操作系统,基于 x64 的处理器。”
图 2-10。
This Surface Pro 3 is 64-bit
如果你能支持 64 位,那么这是一个去,因为它将启用控制台支持和其他功能。您将看到的下一个屏幕是下载助手屏幕。这可以让你选择你想安装的组件,它会显示你需要多少空间在你的电脑上。
在左边,很多不同的选项都被勾选了,还有一些框没有被勾选。默认情况下,您应该会发现选择了以下内容:
- Unity 5.5 0f3
- 文件
- 标准素材
- Microsoft Visual Studio 社区 2015
它看起来应该如图 2-11 所示。让这些都保持原样,因为它们都很重要。Unity 和文档是不言自明的,而 Microsoft Visual Studio Community 2015 将允许您在游戏中创建和编辑脚本(如前所述)。标准资源选项不是强制性的,但会非常方便——这是一个大的预制脚本、精灵、3D 模型、纹理等选择,您可以在自己的游戏中自由使用。如果你有空间,那么添加这些是一个非常好的主意。如果你认为你想为所列的任何其他平台开发(并且你有空间),那就去做吧,也勾选这些选项。
图 2-11。
Tick these boxes for smooth sailing later on
不过,还有一个默认情况下没有选中的选项,您希望确保获得:Android 构建支持。这将确保您可以创建 APK 文件并上传到 Play Store,并且在您测试和完成项目时将需要这些文件。确保勾选了。
您还会注意到这里的 iOS 构建支持,以及 Mac 构建支持、Windows 应用商店选项等。好消息是,如果需要,您可以稍后再回来添加这些内容。您会注意到需要多少空间(在撰写本文时大约为 10.3 GB),如果您的硬盘上有可用的空间,您可以继续操作并再次单击 Next。
现在选择您希望 Unity 安装在计算机上的位置。这完全是个人喜好的问题,但是一定要记住这一点。接受更多的协议条款,再次点击下一步,然后等待 Unity 安装。
去给自己冲杯咖啡吧,因为这的确会花去不少时间。
爪哇 JDK
安装 Java JDK 非常简单。只需双击文件,点击下一步两次,就开始了(图 2-12 )。
图 2-12。
The JDK installer
在几个进度条被填满后,你可以选择一个目标文件夹。写的时候默认是 C:\ Program Files \ Java \ JRE 1 . 8 . 0 _ 111(图 2-13 )。就这样吧,但你可能想记下来,以备后用。再次单击 Next,安装程序将完成,您将准备好进行下一步:安装 Android SDK。
图 2-13。
Set your destination folder
Android SDK
最后,你需要安装 Android SDK。为此,您需要安装 Android-Studio-Bundle。双击。然后单击欢迎屏幕上的下一步(图 2-14 )进入第一组选项。
图 2-14。
The Android Studio installer
在这里您将选择您想要安装的内容(图 2-15 )。恼人的是,你不能取消选择 Android Studio(因为谷歌),但你可以决定你是否想要 Android SDK 和虚拟设备。
图 2-15。
Decide whether you need the AVD
SDK 是我们需要的主要部分,所以请确保勾选它。同时,Android 虚拟设备是一个模拟器,你可以用它来运行应用。除非你正在制作一个非常简单的益智游戏,或者你有一个非常强大的游戏装备(这里我们说的是两个 4 GHz CPU 的 GTX1080s),否则你可能无法使用这么多来测试你完全实现的游戏。你也可以在你的 Android 设备上进行实时测试,所以如果你想为自己节省 1 GB 的数据,你可能需要取消选中这个。也就是说,它可以用于其他测试目的(例如,试验屏幕尺寸),所以这是你的电话。
无论哪种方式,单击下一步,然后同意条款和条件。在下一个屏幕上(图 2-16 ,您可以选择安装 SDK 和 Android Studio 的位置。如果后者比较迟钝(默认情况下,可能是:C:\ Users \ rushd \ AppData \ Local \ Android \ SDK),那么找一个更简单的地方安装它,那里有 3.2 GB+的空闲空间。记下这是哪里,因为您稍后会需要路径。我选择了 C:\AndroidSDK,因为路径不允许包含空格(很烦人)。
图 2-16。
Finally, set your destination folder
在下一个屏幕上再次单击 Next,然后安装将开始。
硬件和工作流程
在下载所有这些东西的同时,让我们利用这个短暂的插曲来考虑创建游戏和实现最佳设置的最佳硬件。
好消息是,你的电脑不需要特别强大来处理 Unity,但你至少需要相当现代的东西。我个人在 Dominator Pro GT72 6RE 和 Surface Pro 3 上运行 Unity(图 2-17 )。Dominator Pro 是一款非常新的 VR 游戏笔记本电脑,配有 GTX1070 GPU,在 Unity 上没有任何困难。就使用 Unity 进行 Android 开发的理想规格而言,Surface Pro 3 肯定处于低端。虽然我从来没有做不了任何事情,但当我编码时,系统确实变得相当热,一些 3D 游戏可以看到帧速率下降。
图 2-17。
A device like the Surface lets you work on the move
基于这一点,我想说 Surface Pro 3 (i5)型号代表了你想要使用的最低规格。这意味着:
- 1.9 GHz 处理器(睿频加速至 2.6 GHz)
- 集成显卡
- 4GB RAM
不过,Unity 网站推荐更高的版本,并建议开发者至少拥有某种形式的专用显卡或具有 9.3 功能级别的 DX11。更大的内存也是更好的选择,尤其是如果你打算使用其他软件如 Photoshop(或免费的替代品如 Fusion、DaVinci 或 GIMP)来创建大图像和在工具之间进行多任务处理。如果你计划用模拟器测试你的游戏,你也会想要更高的规格,尽管你甚至在使用非常强大的机器时也会为此而挣扎。
GPU 是一种图形处理器,用于更快地渲染 3D 场景。如果你主要在 2D 开发(这本书的大部分内容都将致力于此),这可能不是一个大问题,但是给自己一些选择肯定没有坏处。同样,它对使用 Blender 进行 3D 建模的部分也很有用。
除了 GPU,另一个有用的附加功能是同时拥有 HDD 和 SSD 硬盘。固态硬盘是固态硬盘,是硬盘驱动器的替代产品,速度非常快,但通常比速度较慢的老式硬盘要小一些。得到 128 GB 或 256 GB 左右的固态硬盘是标准配置。因为 Unity、Android SDK 和 Android Studio 都占用大量空间,所以为了加快速度,在硬盘上保留至少一些文件,同时在 SSD 上保留操作系统和游戏文件可能会很方便。不过,这是一个偏好,而不是一个要求。
简而言之,可以用一台中档电脑凑合,但使用某种游戏装备肯定会更好。除此之外,拥有一个能够玩游戏的装备对于研究来说是非常有用的。
创建你的战斗站(工作设置)
考虑你的实际设置和工作环境也是值得的,因为这可以给你在开发过程中带来很大的不同,并且可以让你以后不再头疼。例如,一个很大的优势是拥有一个大显示器,甚至可能是一个超宽的 21:9 显示器(图 2-18 )。Unity 有很多不同的窗口和面板,我们将在第三章中看到,能够同时看到所有这些是一个很大的优势,这样你可以更容易地处理任务。大屏幕将大大提高您的多任务处理能力,帮助您避免眼睛疲劳,并提高沉浸感(消除分心)。
图 2-18。
A superior setup
如果你喜欢在笔记本电脑上工作,确保它有更大的屏幕(18.3 英寸或更大),并且有足够的马力来应对。在咖啡馆边喝咖啡边工作是有好处的(我发现它能提高生产率),但为此,你最好想要一台雷蛇、华硕、戴尔、惠普、联想或东芝的新款笔记本电脑。Surface Book 或 MacBook 也能很好地完成这项工作。当然,我们的安装说明是针对 Windows 用户的,所以如果你将在 Mac 上运行,你需要经历一个稍微不同的过程来启动和运行。
当然,你需要一个舒适的键盘,一个精确的鼠标来打字和拖放。在电脑上测试游戏时,有线键盘更有利于提高响应速度。如果你是为了创造最好的设置而花钱,像海盗船游戏键盘这样的东西不仅可以用来打字,还可以用来测试和玩游戏。
否则,确保你有一把舒适的椅子,理想的是一个大的桌子空间来展开和安排笔记和草图,以及一个尽可能不受干扰的房间。如果你做到了这些,你就可以开始工作了,你会发现你的工作尽可能的顺利和愉快。如果你计划做大量的开发工作,投资一个好的工作空间是一个花钱的好方法,并且会更快地完成更好的最终产品。
开始你的第一个项目
安装完成?太好了。
此时,您应该已经在您的计算机上安装了 Unity 和它所需的所有必要组件,并准备就绪。希望你迫不及待地投入进来并开始行动。在这种情况下,让我们第一次启动 Unity,并做最后一点设置。
首先,您需要登录您在网站上创建的帐户。如果您还没有这样做,请单击链接(创建一个),您将被带到相关页面,在那里您可以设置一个。然后,你可以使用新的 Unity ID 登录,如果你想继续使用免费帐户,你需要确认你的公司的收入低于阈值。
一旦你通过了那个屏幕,你会看到一个窗口,让你从现有项目中选择或者开始一个新的项目。你现在可能什么都没有,因为这是你第一次使用它。因此,单击 New 并为您的项目选择一个名称和位置(图 2-19 )。你还需要确保你选择了 2D,这样游戏将自动支持 2D 格式。
图 2-19。
Starting your first project
在这一点上,你如何称呼游戏并不重要——这只是文件夹的名称。以后你可以把 APK 的名字改成你喜欢的任何名字。现在,我们姑且称之为简单的平台玩家。输入,勾选 2D,然后点击创建项目。稍等片刻,迎接你的将是一个相当空洞的 Unity 项目。有许多窗口,在这一点上可能看起来有点混乱,但是不要担心,我们将在下一章中讨论每件事情的作用。
设置路径
让我们做最后一点设置,告诉 Unity Android SDK 在我们系统上的位置。前往顶部菜单,找到编辑➤首选项➤外部工具。然后你会找到一个空间来输入 Android SDK(主文件夹)和 Java JDK(文件)的位置,如图 2-20 所示。如果您之前记下了这些路径,请将其复制并粘贴到此处。否则,点击浏览并导航到正确的位置。如果您的文件夹位于 AppData 中,它可能是隐藏的,所以告诉文件资源管理器显示隐藏的项目(在视图选项卡下),然后手动跟踪它。它就在某个地方——继续找。
图 2-20。
Setting up your paths
在你第一次来测试你的应用之前,你实际上不需要担心这个,但是在我们开始之前做好一切准备是很好的。如果你愿意,你现在可以跳过这个阶段,但是一定要在你的游戏第一次发布时再回来。
完成后,Unity 现在已经建立并准备就绪。是兴奋的时候了,因为在第三章我们将开始实际使用它,安排一些游戏对象,甚至介绍非常基础的物理学。
三、在 Unity 中寻找答案
因此,Unity 现在已经启动并运行,没有什么可以阻挡您。该走了。
在您构建任何东西之前,让我们首先让您熟悉不同的 UI 元素、控件和选项。你将在这里呆很长时间,所以熟悉一下是个好主意。
这是什么?熟悉 IDE
当你第一次看到 Unity(图 3-1 )时,它可能会因为许多不同的窗口、菜单和选项而显得相当混乱。幸运的是,一旦你开始行动,一切都比看起来简单得多。我们将在本章的课程中讨论这些不同视图的用途,同时我们将能够测试我们的第一个非常简单的应用。
图 3-1。
Welcome to Unity! You’ll be spending a lot of time here
首先是观点。
事件
Unity 的正中央是一个名为 Scene 的视图。这是你完成大量工作的地方,也是你移动各种游戏对象和安排一切的窗口。这将显示您在任何给定时间正在处理的级别/菜单屏幕的视图,并允许您选择和重新定位屏幕周围的元素。你可以放大和缩小,如果你在 3D 模式下,你还可以将相机移动 360 度。
素材商店
在场景窗口的顶部(图 3-2 )有两个选项卡,允许你在两个不同的功能之间切换。点击 Asset Store 选项卡,正如您所预料的,场景视图将被更改为 Asset Store 视图。在素材存储中,您可以浏览各种素材,包括脚本、游戏对象、精灵、效果等,并将其包含在您自己的应用中。这些素材由其他 Unity 开发者以及 Unity Technologies 开发。有些是免费的,有些是要花钱的;有些是对现有项目非常简单的补充,而另一些实际上是完全现成的游戏,供你随意编辑。
图 3-2。
Scene, Game, and Asset Store tabs
简而言之,通过确保您不需要手动创建每个脚本和精灵,素材存储使您的生活变得更加容易。
也就是说,这是你暂时不需要的东西,所以现在保持这个状态,不要担心它。
比赛
该窗口上可能还有第三个选项卡,如上图所示,名为 Game。在这个视图中,您可以看到真实的游戏,就像它在直播时一样。当你玩游戏时,这就是它的位置(除非你在玩游戏时选择最大化游戏)。
如果游戏不在同一个地方,它会在那里的某个地方。你可以自由地改变窗口的位置,有时在更新后,默认设置会改变。大多数人应该会发现所有东西都在同一个地方,他们可以按照这些指示去做。如果没有,您应该能够很快找到每个元素。
你不能将元素拖放到游戏视图中(就像在场景视图中一样),也不能选择或移动它们。也就是说,在很大程度上,游戏视图会反映你在场景视图中看到的一些不同。首先,视角将固定在游戏中的摄像头上,这意味着你将看到你的玩家在启动游戏时会看到的东西。同样,当有多个项目共享相同的 X 和 Y 坐标时,顶部的项目将是沿 Z 轴最靠近相机的项目,而不是被选择的项目。
如果这一切听起来有点混乱,不要担心——一旦你看到它是如何工作的,就明白了(这适用于所有的窗口)。
服务
通常位于场景视图右侧的是服务选项卡,它与检查器共享一个窗口。这包括诸如盈利广告、了解玩家如何享受游戏的分析、多人游戏等等。请注意,如果您有免费版本,这些功能中的一些将会丢失或受到限制。
现在,您可以完全忽略这个窗口。这些服务将主要在更雄心勃勃的项目中生效,并且只有在这些应用在 Play Store 中上线后才会生效。
检查员
接下来是经常与服务共享窗口的选项卡:检查器(图 3-3 )。检查器是你用来查看和编辑游戏对象细节的工具。因此,当你在场景视图中选择一个游戏对象,比如精灵,你就可以使用检查器来查看对象的名称、尺寸、可能附加的脚本等等。
图 3-3。
Inspector and Services
你会经常用到这个检查器,所以把它放在你能看到的地方。但是现在,它将会是完全空白的。
项目
通常位于屏幕底部的是项目和控制台标签的窗口(有时游戏也会在这里)。默认情况下,项目选项卡应该是打开的,在这里您可以看到与您的项目相关联的所有单个文件。窗口左侧是您可以选择文件夹的目录,右侧是该文件夹的内容。现在,您的项目只有一个名为 Assets 的文件夹。文件夹里什么都没有。
当你工作时,这将是一个有用的窗口,因为它允许你找到你用其他软件创建的精灵,并重命名或删除你游戏中需要的文件。
安慰
项目选项卡旁边是控制台选项卡(图 3-4 )。在这里,您可以获得有关 Unity 和您的应用状态的信息。您将能够看到调试信息、崩溃报告和错误,这可以帮助您识别代码中的问题或找出游戏无法运行或编译的原因。这将派上用场,但我们暂时不需要担心它,所以现在让 Project 在它前面可见。
图 3-4。
The Hierarchy and Console
等级制度
最后,UI 中最重要的元素之一是层级(如图 3-4 所示),它几乎总是位于场景视图的左侧。层级显示了在任何给定时间场景中所有游戏对象的列表,当你选择其中一个时,场景视图将集中在它上面;它也将在检查器中打开。这可以让你快速找到特定的游戏对象进行编辑,这也是你选择像检查点这样的“不可见”对象的唯一方法。当您想要选择多个对象(例如,可能是您的所有收藏品)时,层次结构也非常方便,并且具有用于快速检索特定项目的有用搜索工具。
保持一个整洁的层次结构是一个很好的实践,它将帮助你更快更有效地工作。
家政
在大多数情况下,我建议您保留窗口的默认配置。它们被这样安排是有原因的(它是有效的),这将使你更容易按照书中的指示去做。控制台或其他窗口可能不在同一个地方,但我们现在主要使用场景、游戏、项目和检查器窗口。其他的不用担心。
但是如果你发现用户界面感觉狭窄,或者你不喜欢它在任何一点的排列方式,你可以将鼠标指针悬停在任何分割线上来改变相对大小。您也可以将标签从一个窗口拖到另一个窗口,完全关闭它们,或者使用窗口菜单中的选项将它们带回来。你可能已经注意到,在窗口菜单中还有其他可以打开的窗口,包括混音器、动画器和精灵打包器。其中一些我们以后会用到,但是现在你不需要担心它们;您应该能够使用层次、场景、项目、游戏、素材存储、控制台和检查器窗口做几乎所有的事情。
接触物品和场景
理论到此为止——是时候付诸实践了。要真正理解这些窗口是如何工作的,以及你需要做些什么来开始,最好的办法是开始构建一些东西。一旦你这样做了,你将直接看到所有的东西是如何一起工作的,以及一旦你开始开发,你将如何管理你的工作流程。
添加精灵
为了开始,让我们从添加我们的第一个游戏对象开始。这将是一个简单的 2D 广场。
与处理 3D 对象不同,在 2D 中没有简单的形状可供您插入。这意味着你引入的任何 2D 物体必须首先被创建为精灵。创建一个正方形非常简单:我们可以创建一个新的 MSPaint 文件,将其大小调整为 50 x 50 像素,然后用一种颜色填充空间。将该文件保存为 PNG 文件,并将其从保存位置拖动到项目文件夹中。为简单起见,称它为正方形。图 3-5 可以看到我的正方形。
图 3-5。
Square. Not quite triple-A graphics.
为了帮助我们尽早养成良好的习惯,我们将首先在项目中专门为精灵创建一个文件夹。本着好习惯和好命名的精神,我们将把这个文件夹称为精灵。为此,右键单击项目窗口中的素材文件夹,然后创建➤文件夹(图 3-6 )并将文件夹命名为精灵。一旦你完成,它应该看起来像图 3-7 。
图 3-7。
Your Sprites folder should look like this. It’s a folder. Called sprites.
图 3-6。
Creating new folders is very simple
随着我们的进行,我们将为我们的脚本、声音、场景等创建更多的文件夹——创建大量文件夹来帮助将所有东西分开是明智的,这样我们可以在任何给定时间快速检索我们需要的文件类型。
一旦你创建了这个文件夹,你可以简单的把 square.png 文件从 Windows 资源管理器拖到你的 Sprites 文件夹中。请注意,在任何时候,您都可以右键单击项目窗口并选择 Show in Explorer。这将向您显示项目中的素材目录,并且一旦您刷新视图,您在此处输入的任何内容都将显示在项目窗口中。
这样,精灵现在是你的项目的一部分。你可以使用完全相同的过程,无论你想添加树精灵,收藏品,敌人,或其他任何东西到你的水平。真的就这么简单。
引入游戏对象的两种方式
有两种方法可以将这个简单的游戏对象添加到场景中,我们将在这里介绍这两种方法,因为我觉得这是一个很好的学习机会。
最简单的方法就是在你的项目窗口中点击精灵,然后把它直接拖到你的场景中。然后你会看到它出现在你的场景视图中,并且在你的右边的层次中列出(图 3-8 )。如果选择了它,有关方块的详细信息也会显示在您的检查器窗口中。点击你的游戏视图,你会看到背景中有不同蓝色阴影的方块。
图 3-8。
The scene, now with the square
另一种给你的场景添加精灵的方法是打开顶部菜单,然后点击游戏对象➤ 2D 对象➤精灵(图 3-9 )。当你这样做的时候,一个新的精灵将会出现在你的层次窗口中,但是你将不能在场景视图中看到它,因为它当前没有一个相关的图像文件。然而,每当它被选中时,它的坐标周围会有一个半透明的圆圈。
图 3-9。
The second way to insert your sprite
在层次中选择新的 Sprite 后,打开检查器窗口,注意名为 Sprite Rendered 的部分。在这里,第一行写着“Sprite: None (Sprite)”您可以简单地将您的方形文件拖放到此处,或者您可以单击框旁边的小圆形按钮,从文件选择器对话框中选择它(图 3-10 )。
图 3-10。
Using the Sprite Renderer
当你在检查器中的时候,为什么不借此机会把你的第二个精灵(现在是新精灵)的名字改成更有趣的名字…比如 Square 2。
尝试这两种方法,你会在场景窗口的屏幕上有两个不同的精灵:方块 1 和方块 2。它看起来应该如图 3-11 所示。
图 3-11。
If it looks something like this, you’re doing well
操纵游戏对象
正如你可能已经猜到的,你可以很容易地在场景视图中移动你的新精灵,只需点击它们并在屏幕上拖动它们。执行此操作时,您会注意到检查器中的坐标发生了变化。在变换下,位置的 X 和 Y 值随着你移动方块而改变,正如我在图 3-12 中所做的。
图 3-12。
Change the X and Y coordinates in the Inspector to move your sprites
这意味着您可以通过在检查器中输入数字来轻松更改位置。如果你需要完美地排列东西或者把它们放在一个非常特殊的位置(你会经常做的事情),这将会很方便。我们稍后会看到,虽然也有更有效的方法来确保一切都保持良好的排列,并在我们工作时捕捉到网格。
“那 Z 呢?”我听到你问了!嗯,在制作 2D 游戏时,Z 轴在很大程度上是多余的,尽管不是完全多余。我们将在后面看到,你可以使用这个选项来创建一个视差滚动效果,并且当有多个精灵位于同一个位置时,它也可以用于定义应该渲染哪个精灵给玩家看。这也可以通过改变 Z 顺序来控制,我们稍后也会看到。
旋转和缩放
您可能已经注意到,在检查器的“变换”下还有两个更有趣的选项:旋转和缩放。他们几乎做他们所说的,并允许你改变你的精灵的旋转和大小。我们现在将忽略旋转,因为我们暂时不需要它。但是你会发现,如果你把 X 和 Y 的缩放比例从 1 改为 2,你的精灵尺寸会翻倍。
在场景视图中操纵游戏对象
如果您想徒手完成此操作,您可以选择使用屏幕顶部的工具,就在层次窗口上方的左侧。这些工具包括一只手和做不同事情的各种箭头,如图 3-13 所示。
图 3-13。
The tools you’ll be using in the Scene view
这些工具改变了您与场景视图的交互方式。您只需单击想要缩进的工具并选择它:
最左边的那只手可以让你拖动屏幕,移动你的视图,这对于滚动一个大的级别很有用。
The tool that looks like four arrows on a compass is what you use to move specific GameObjects around the screen (see Figure 3-14). You can simply click the GameObject and start moving it around freely in the Scene view, or you can select it and then drag either the red or green arrow to move it solely in the X or Y axis respectively.
图 3-14。
The drag tool
两个弯曲的箭头表示旋转工具。选择此选项,一个圆圈将出现在所选的游戏对象周围,这将允许您在两个维度上旋转它。
然后你有缩放工具,它再次给你两个箭头,你可以用它来沿着每个轴缩放对象。
最后一个工具是你的万能工具。它可以让你拖动游戏对象,通过拖动角来调整它们的大小,或者画正方形来一次选择多个对象。
无论是使用工具徒手移动对象,还是在检查器中更改数字,您都可以按照自己喜欢的方式排列精灵,并创建一些好看的风景。
但是我们不要想得太多....
测试游戏和使用相机
如果 UI 中有一个控件很可能吸引了你的眼球,那就是播放按钮。好消息是,这正是你所希望的:它允许你测试游戏。
点击播放按钮,你的游戏将在游戏窗口中运行,显示两个位于浅蓝色背景上的方块。它可能不会风靡全球,但恭喜你,你刚刚运行了你的第一个工作程序(图 3-15 )!这是我们的“Hello World”,从这里开始,事情只会变得更加有趣。
图 3-15。
Your first “game”—congrats!
当游戏正在进行时,你可以在游戏视图中查看它(图 3-15 ),并且你可以通过检查器或在场景窗口中继续进行编辑。只要记住,当你这样做的时候,什么也救不了。如果你在游戏运行的时候移动精灵,当你再次停止它的时候,它会跳回到它最后的位置。使用它可以“实时”预览更改,但不能对代码进行永久性修改。试着现在就把这个记在你的脑子里。搬了一堆东西,改了一堆代码,却发现忘了先停止游戏运行,全输了,这种情况并不少见。
要随时停止游戏,只需再次点击播放按钮。请注意,如果您希望游戏在您点击播放时全屏显示,您可以点击播放时最大化。当你正确地测试你的游戏,或者你只是想好好玩一玩的时候,这是很有用的。图 3-16 是我们的游戏目前放大后的样子。
图 3-16。
The same game, only massive
照相机
细心的读者可能已经注意到在层次视图中有不止两个游戏对象。第三个对象叫做主相机,如果你选择它,你会看到这个白色的相机图标在你的场景视图中浮动。
主相机是一个游戏对象,就像你的精灵一样,但是有一个相机组件而不是精灵。当你在一个场景中添加一个这样的东西时,它定义了玩家的视角在哪里,以及他们将在屏幕上看到什么。试着四处移动相机,然后点击播放,你会发现屏幕上方块出现的位置发生了变化——方块没有移动,但你的视角发生了变化。
你的相机被当作精灵一样对待,这可能会让人觉得奇怪,但这是你需要了解的关于 Unity 的事情:一切都是游戏对象。除非附加到游戏对象上,否则脚本不会运行,如果你习惯了其他语言,这可能需要你重新考虑你的代码。但是一旦你掌握了它,它就是一种强大而灵活的工作方式。
附注:这不是面向对象编程的含义。这是相关的...但是我会在接下来的章节中解释更多。
既然我们在看摄像机,让我们编辑一些你可能会觉得沮丧的东西:你的游戏视图的背景颜色。点按相机,您会在检查器中看到一个名为背景的设置,当前为蓝色。如果您选择颜色,您将有机会将其设置为新的颜色。选择黑色——这样你的蓝色方块会更加醒目。
保存项目和场景
鉴于你可能对这个惊人的创造感到难以置信的高兴,是时候去拯救它了,以确保不会有什么不好的事情发生。
这里其实需要保存两个东西:项目和场景。场景就是你通过场景窗口看到的东西,它包含了你现在层次中的所有东西(两个方块和一个摄像机)。对于所有范围和目的,场景在大多数情况下是一个级别,尽管它也可以指标题屏幕或选项菜单。它本质上是你想在游戏中的某个时刻载入的游戏对象和脚本的集合。
要保存您的项目,请使用顶部的菜单并选择文件➤保存项目。
要保存场景,首先需要在“资源”文件夹中创建一个新的子文件夹,将其命名为 Scenes。现在转到文件➤保存场景(图 3-17 )。当对话框出现时,选择刚刚创建的场景文件夹,并将文件命名为 Level 1。当您的项目中有多个场景时,您只需在场景文件夹中双击它们,就可以在它们之间切换。
图 3-17。
Saving our scene
更有条理一点
因为我们是如此的有条理,并且很早就养成了好习惯,所以在我们继续之前,让我们再做一个文件夹。右键单击层次中的任何空白区域,然后单击创建空白区域。这将创建一个“空”的游戏对象,称为游戏对象。它应该已经被选中了,所以到层次结构,并重新命名为广场。理论上,你可以通过点击添加组件,然后选择相机组件或精灵渲染器,将这个空的游戏对象转换成任何其他类型的游戏对象。相反,我们保持它为空,这将允许我们附加其他游戏对象到它,从而使用它作为一个临时的分组(图 3-18 )。
图 3-18。
Start organizing your Hierarchy now and you’ll be very glad you did
将你的两个方块从它们所在的地方拖到你的空游戏对象上,它们现在会被归档到它下面。空游戏对象旁边的箭头现在也允许你展开和折叠这些项目。这不是真的有必要在这一点上,但相信我,当你有 200 个可收集的硬币,30 个敌人,和 700 个瓷砖在你的屏幕上,你会为这个组织感到高兴。
最后一件要设置的事情:捕捉网格设置
在第四章中,事情将会变得令人兴奋:我们将会在我们的方块中加入物理元素,这样它们就可以落下和反弹,这仅仅是个开始。但是,在我们这样做之前,我们应该处理更多的设置,并解释更多的 UI。
你可能已经注意到在你的场景视图中有一个网格,并想知道这是怎么回事。这个网格是由单元组成的,为了帮助你在屏幕上组织你的精灵,它们可以代表你想要的任何东西。你把一个充满单元的程序叫做什么?Unity!(好吧,这并不是这个名字的真正来源....)
现在,你会注意到你的正方形和单位没有关系。要改变这种情况,首先将两个方块上的比例 X 和比例 Y 设置回 1。现在打开你的精灵文件夹,选择正方形精灵,这样它会在你的检查器中打开(不要只是点击一个正方形的游戏对象,因为那不起作用)。
您应该注意到每单位像素的一个选项,默认情况下可能设置为 100。将该值更改为 50——不要忘记单击右下角的“应用”——您应该会发现两个方块现在立即变为与网格上的方块相同的大小(如图 3-19 )。记住,当我们制作这些精灵时,我们把它们制作成 50 x 50 像素。通过这样做,1 个单位= 50 个像素,我们现在有了完美大小的正方形。
图 3-19。
Make sure to set your pixels per unit for every new sprite
另一个有用的技巧是让精灵就位。拖动精灵时,按住 Ctrl,你会发现它从一点跳到另一点,而不是平滑移动。您可以通过选择编辑➤捕捉设置来更改这些点之间的距离。你会发现,对于 X 轴和 Y 轴,这可能分别被设置为 1。如果不是,则进行更改(参见图 3-20 )。
图 3-20。
Snap settings
关闭该对话框,自由移动其中一个方块,使其正好位于其中一个方格的边界内。现在按 Ctrl + C(复制),然后按 Ctrl + V(粘贴)。这将复制你的正方形游戏对象,在完全相同的地方创建一个完全相同的副本。按住 Ctrl 并向右拖动正方形。它应该恰好移动一个方块的宽度,与前一个方块齐平。你也可以通过右击并从层次视图中选择复制来复制和粘贴游戏对象。这样做几次,你就可以创建楼梯和其他结构。
在这一点上,这可能感觉很无趣,但实际上这是一项非常重要的技能。在游戏中创建关卡时,非常重要的一点是,所有的方块都要完美地相邻放置,并且它们之间不能有像素宽的缝隙。
为什么不试着用一个稍微复杂一点的精灵来制作一些看起来像关卡基础的东西呢?我在图 3-21 中做了一些草台阶。
图 3-21。
We’ll learn how to create sprites and even pixel art in future chapters
嘿,你看那个…我们有进展了。
现在让我们让这个世界充满活力,好吗?
四、添加物理学并开始编程
在这一点上,你现在有了一个看起来有点像传统电脑游戏关卡的世界。在这一章中,我们将通过添加一些基本的物理和运动来让它像传统的电脑游戏一样玩。为此,你将第一次尝试编码。祝你好运!
因此,我们有平铺的精灵,它们有草的图案,为了混合一些东西,我还创建了一个精灵来代表草下面的地面,这样我就可以用我的关卡设计来增加一点创造性(见图 4-1 和 4-2 )。
图 4-2。
Some tiles just ready to be climbed on!
图 4-1。
The second ground tile
我用和第一个完全一样的方法创建了第二个污垢精灵,然后在我的草地表面下任何我需要的地方复制它。您也可以这样做,只需记住按住 Ctrl 键,使用“捕捉到网格”功能来保持完美的距离。
现在我们要做一些更令人兴奋的事情:创造一个游戏角色,把他放到我们的世界里。首先,我们要做一个尽可能简单的角色精灵,这是另一个正方形。为了让事情变得更有趣,我们还会给他眼睛。
见方(图 4-3 )。
图 4-3。
Squarey, mate, you don’t look so good
Squarey 可以是你喜欢的任何尺寸,但为了简单起见,我建议保持与地面瓷砖相同的尺寸。
现在将 Squarey 添加到你的精灵文件夹中,就像你对其他的一样。确保你记得再次设置每单位像素为 50,然后把他拖到你的场景中。将这个新的游戏对象命名为 Player,并将其与 Squares 文件夹分开。你想把他拖到关卡的哪个位置并不重要,但我选择把他放在我创建的山顶(图 4-4 )。
图 4-4。
Squarey surveys his domain
恭喜你!你现在有一个主角了。问题是,他实际上什么也没做。
所以,接下来我们要做的是应用一些物理知识,让重力和其他东西影响我们的性格。你不会相信 Unity 让这一切变得多么容易。
使用刚体 2D
如前所述,使用 Unity 这样的物理引擎的全部意义在于,我们可以访问现成的脚本和元素,避免自己从头开始编写代码。这使得添加类似重力的东西变得非常简单:我们所要做的就是把一个脚本拖到我们的游戏对象上,让它生效。
选择播放器后,点按检查器中的“添加组件”。现在点击物理二维➤刚体二维。刚体 2D 是一个脚本的名字,它作用于 2D 精灵,并应用了我们想要的所有基本物理,如重力、摩擦力、动量、碰撞等等。
然而,最简单的方法就是把它付诸行动。所以在场景窗口中拖动 Squarey 稍微高一点,然后点击 play。如果一切都按计划进行,我们现在有一个看起来很悲伤的正方形,他只是从屏幕的顶部和底部掉了下来。Squarey,来见见地心引力!
使用对撞机
精明的人会注意到这里缺少了什么。当然,我们实际上并不希望 Squarey 从他下面的地面掉下去;我们想让他着陆。幸运的是,这也是一个简单的解决方法。
只需再次点击 Squarey,然后添加组件➤物理 2D ➤盒碰撞器。你现在应该可以在检查器中看到刚体 2D 和 2D。
图 4-5。
The green outline shows Squarey’s collider
此外,您还应该注意到 Squarey 周围有一条细细的绿色轮廓。这是碰撞器,它本质上定义了你的精灵的边界,并告诉刚体 2D 组成角色的物质开始和停止的位置。
点击播放,你会看到角色仍然从下面的地面掉下来。希望你已经猜到了,这是因为我们的贴图也需要附加碰撞器。为此,使用鼠标在场景视图中拖动一个方块,以便一次选择所有图块。你可以选择桌面上的一堆图标(图 4-6 ),或者在按住 Shift 键的同时点击层级中的第一个项目,然后点击最后一个项目。您也可以使用 Ctrl 进行批量选择,就像在大多数 Windows 程序中一样。这是一个经常会派上用场的技巧,尽管还有另一种方法来进行大规模更改,我们很快就会看到。确保取消选择相机,然后在检查器中添加一个碰撞器。这将会同时应用到所有选中的游戏对象上。
图 4-6。
Later we will discuss using prefabs to avoid having to select multiple GameObjects
请注意,我们没有将刚体 2D 添加到地面瓷砖中。那是因为我们不想让它们从屏幕上掉下来。
现在点击 play,如果一切按计划进行,我们的角色将会摔倒并落在地上(图 4-7 )。
图 4-7。
Success!
如果你想测试刚体 2D 有多详细,把它放在正方形的位置,让它部分悬在一个台阶的边缘,然后再次点击播放。当他落地的时候,他应该给小费,然后滚下楼梯。
在这一点上,我们现在已经有了一个按照我们期望的方式运行的游戏。下一步可能是让它具有交互性。准备好:这是实际编码的地方。
C#编程入门
在您开始编码之前,我们首先需要创建一个文件夹来包含 Assets 目录中的所有脚本。在项目视图中右键单击并选择创建➤文件夹,就像创建精灵和场景文件夹一样。调用这个新文件夹脚本,然后打开它。
在这里,您将再次右键单击,这次选择“创建➤ C#脚本”。呼叫此玩家,然后双击打开它。现在,您将第一次打开 Visual Studio。但首先,你需要登录。为此,您只需使用您的 Microsoft 帐户(您可能会使用该帐户登录 Windows 和 Hotmail)。如果您没有,您将有机会创建一个。
一旦你进入,用户界面看起来应该如图 4-8 所示。
图 4-8。
Welcome to Visual Studio
现在,我们将注意力集中在中间的大窗口上,在这里我们可以输入和编辑代码。我们在这里选择用 C#编码,因为它有点类似于 Java——大多数 Android 开发中使用的语言——只是稍微简单一点。用 C#编写代码时要记住的一件事是,每一行都需要以分号或开/闭花括号结束。如果你错过了这个,你会得到一个错误。你可能会发现很容易忽略这个关键的细节,然后花很长时间在你的代码中寻找为什么它不能运行。
您可能还会注意到,文档实际上不是空的,而是已经包含了几行代码。这些是函数,是在特定时间被调用的独立的代码集合。我们这里的两个函数将会出现在我们创建的每个脚本中,并为我们将要做的事情提供一点结构。
整个过程应该如下所示:
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
先不要担心前两行。第三行显示的public class
实际上只是命名我们创建的脚本,接下来的两个部分(void Start
和void Update
)是我们的函数。
另外两件要注意的事情是正斜杠。在 C#中,每当一行以两个正斜杠开始时,这意味着它是一个注释,不会对代码的运行方式产生任何影响。换句话说,如果你忘记了一行代码是做什么的,你就可以这样给自己写消息。当程序员团队一起工作时,像这样的注释对于确保每个成员都知道每件事是做什么的非常重要。
这里已经有两个注释描述了这些函数的功能。第一个写的是"Use this for initialization"
,所以每当脚本第一次被引入场景时void Start
就会运行。第二条评论说"Update is called once per frame"
,所以void Update
随着游戏刷新以非常快的速度反复运行。
为了证明这一点,让我们试着让我们的角色在屏幕上移动。为此,我们首先需要引用一些我们将在代码中使用的重要元素。在这种情况下,我们需要使用附加在我们的Player
游戏对象上的刚体 2D 脚本。为此,我们需要添加以下代码:
public class Controls : MonoBehaviour {
public Rigidbody2D rb;
void Start () {
rb = GetComponent<Rigidbody2D>();
}
这里发生的事情是,我们正在创建一个刚体 2D 的参考,并将其命名为rb
(刚体的缩写)。然后,当脚本初始化时,我们告诉它刚体 2D 的实例是我们的脚本附加到的游戏对象(一会儿,我们将把它附加到Player
游戏对象)。
不要担心这是不是有点混乱:你现在可以复制和粘贴代码,以后会更有意义。
最后,我们将向我们的Update
函数添加以下代码行:
void Update () {
rb.velocity = new Vector2(1, rb.velocity.y);
}
这只是将一个值为 1 的 veleocity 加到刚体的水平 X 坐标上(Vector
是一个坐标)。因为这是在Update
中,这意味着它应该在每次场景刷新时运行——这发生得非常快。整个过程应该如下所示:
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour {
public Rigidbody2D rb;
// Use this for initialization
void Start () {
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update () {
rb.velocity = new Vector2(1, rb.velocity.y);
}
}
请确保您记得保存您的工作!
现在剩下要做的就是回到 Unity,给玩家角色添加剧本。我们做的和我们添加刚体 2D 一样:选择Player
游戏对象,点击添加组件,然后编写➤玩家的脚本。现在点击播放,你应该会发现 Squarey 继续向右移动,然后从台阶上摔下来,走向他的最终厄运(图 4-9 )。
图 4-9。
Squarey now has Lemming AI
只有当你把脚本附加到你的游戏对象上时,它才会有任何效果,你也可以很容易地把它添加到地面瓷砖上来获得同样的效果。
引入变量
现在是我们玩一些变量的时候了。变量是编码中一个非常重要的概念,也是很多逻辑和通用性的来源。
本质上,变量是一段数据的简写,可用于在未来表示该数据(如健康或球员姓名)。机会是,如果你能回想在学校时的数学,你会发现你在过去遇到过变量。还记得这样的谜题吗?
10 + x = 13,求 x
在这种情况下,x 是一个恰好代表 3 的变量。但是如果我们这样写
10 + x =?
这将允许我们改变结果,简单地用一个键或类似的东西改变 x 的值。在 C#中处理变量时,我们可以做完全相同的事情。例如,我们可以用一个名为movespeed
的变量代替 1 来改变角色移动的速度。但是首先,我们需要通过初始化来定义movespeed
的含义。所以现在我们的代码看起来像这样:
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour {
public Rigidbody2D rb;
public int movespeed;
// Use this for initialization
void Start () {
rb = GetComponent<Rigidbody2D>();
movespeed = 2;
}
// Update is called once per frame
void Update () {
rb.velocity = new Vector2(movespeed, rb.velocity.y);
}
}
Squarey 将会像以前一样移动,但是这次是以两倍的速度。
当我们创建变量时,我使用了一些有助于理解的术语。例如Int
,是 integer 的简称,是一种存储整数的变量。每当你定义一个变量的时候,你总是需要告诉 C#你正在处理什么类型的变量。在这一点上,需要知道一些有用的信息:
- 整数:任何整数
- Float:浮点变量是一个带小数点的数字
- 布尔型:一个变量,可以是真或假,也可以是 1 或 0
- 字符串:文本变量
同时,public
意味着可以从脚本外部访问该变量。事实上,这意味着我们甚至可以从 Unity UI 中编辑我们的movespeed
。
为此,删除显示movespeed = 2
的行,然后在 Unity 中选择Player
游戏对象。你应该看到现在在检查器中有一个移动速度框,如图 4-10 所示。
图 4-10。
Changing Movespeed in the Inspector
尝试将该值设置为–1,您会看到 Squarey 现在向相反的方向移动,远离楼梯并朝向突然下降的方向。如果不从脚本中删除movespeed = 2
行,每次调用Start
函数时,它都会被覆盖。如果你不想让变量在你的代码之外被访问,那么就简单的用private
代替public
。
现在,让我们把movespeed
设为 3,因为我们想让 Squarey 在下一位快一点。虽然你可以不使用变量来做同样的事情,但是知道这些是非常有用的,当你在场景中添加更多的元素时,你会发现它会反复派上用场。
控制玩家角色
看到我们的角色在关卡中移动并与场景互动是非常令人兴奋的,但实际上我们希望能够控制角色。好消息是,这是我们可以很容易做到的事情,只需稍微改变我们已经拥有的代码。我们需要做的就是添加几个if
语句,如下面的代码所示:
void Update () {
if (Input.GetKey(KeyCode.LeftArrow))
{
rb.velocity = new Vector2(-movespeed, rb.velocity.y);
}
if (Input.GetKey(KeyCode.RightArrow))
{
rb.velocity = new Vector2(movespeed, rb.velocity.y);
}
}
这里发生的事情是,每当游戏更新时,脚本检查是否有输入。当然,KeyCode.LeftArrow
和KeyCode.RightArrow
指的是各自的键盘输入,然后我们通过movespeed
或–movespeed
来移动字符,这取决于哪一个被按下。"If"
语句本质上是告诉一段代码只有在特定参数为真时才运行。只有当括号内的行为真时,花括号内的代码才会运行。
如果我们用伪代码(使用普通英语术语的假“代码”更容易理解)来写,那么它将翻译如下:
If (Player is pressing right) {
Move character to the right
}
记住在if
语句的末尾关闭花括号是很重要的。如果你有使用 Excel 或其他电子表格软件的经验,使用像这样的if
语句可能会很熟悉。
Note
如果你现在要创建一个 APK(Android 的应用包——稍后会详细介绍)并在 Android 上运行,它实际上可以和蓝牙键盘一起工作。稍后,我们将看看如何实现触摸屏控件。
如果你点击“现在玩”,你应该有令人兴奋的机会实际尝试控制 Squarey——就像一个真正的游戏。虽然我们不得不在这里写一点代码,希望你同意这是非常简单的事情:只需几行代码,我们现在就有了一个好看的游戏世界和一个我们可以控制的角色。
更高级的逻辑和引入跳跃
Squarey 现在可以像专业人士一样左右移动,并且非常擅长从物体上跳下来。你应该感到自豪。但是为了能和马里奥、索尼克以及他们中的佼佼者同台竞技,他还需要学习一些跳跃技巧。不幸的是,这比左右移动要复杂一点。
你可以尝试实现跳跃,就像你处理左右移动一样。下面的代码将允许我们的英雄跳跃:
if (Input.GetKey(KeyCode.Space))
{
rb.velocity = new Vector2(rb.velocity.x, 5);
}
唯一的问题是这个代码也能让他飞起来。这是因为我们每次按空格键都会增加更多向上的速度,不管他是在地上还是在空中。这不好。
所以我们需要先确认他是否在陆地上。这就有点复杂了。
首先,我们需要创建一个新的转换。变换是空间中有自己的坐标和旋转(角度)的点。这个点也将有一个半径(这将是一个浮动),我们还需要一个图层蒙版。我们现在还创建了第一个布尔变量onGround
。
简而言之,您正在将以下所有代码添加到脚本中:
public Transform groundCheck;
public float groundCheckRadius;
public LayerMask whatIsGround;
private bool onGround;
现在,这可能看起来相当复杂,但不要担心,我会解释每一位是什么,以及我们进行的过程中它做了什么。
如果在这个阶段这还不够令人畏惧,我们还将在代码中添加另一个函数,名为FixedUpdate
。FixedUpdate
是一个类似于Update
的功能,除了Update
依赖于屏幕的刷新率,FixedUpdate
更加一致,因此对于与物理相关的代码更加可靠。
在这个函数中,您将添加以下内容:
void FixedUpdate()
{
onGround = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, whatIsGround);
}
现在还不要担心这是什么。回到 Unity,你现在可以看到我们在检查器中创建的新的公共浮动、布尔和图层蒙版。
在这里,您将创建一个新的空游戏对象。这是一个游戏对象,就像一个精灵或相机,但没有组件。在层次中右键单击并选择创建空。你将调用这个空的游戏对象Check Ground
,并使它成为Player
的子对象(通过在层次结构中将它拖到Player
的顶部——参见图 4-11 )。
图 4-11。
Check Ground
is an empty GameObject and child of Player
(more on what that means later)
现在再次选择玩家,在层级中找到写着地面检查的地方。目前它会显示 None (Transform ),但是你可以通过拖动Check Ground
游戏对象并把它放到那个盒子里来改变它。它看起来应该如图 4-12 所示。
图 4-12。
Ground Check is a transform that is now defined as the coordinates of Check Ground
记住:地面检查是一个变换,意味着一组坐标。通过将一个空的游戏对象放入这里,我们现在告诉我们的脚本将这些坐标设置为那些附属于Check Ground
的坐标。换句话说,我们已经定义了一个“点”,这就是我们将如何看待 Squarey 是否站在坚实的地面上。现在将半径值设置为 0.1,这意味着它将是一个非常小的点。最后,在场景视图中选择Check Ground
并使用移动工具将其定位在 Squarey 的正下方,这样它就可以检查他正下方的空间。
还有一件事,我们需要做的是创建一个新的层,并将其命名为地面。再次拖动并选择所有的地砖,然后在检查器的左上方寻找图层菜单。在下拉列表的底部,你会看到添加图层的选项(图 4-13 )。
图 4-13。
Adding the Ground layer
然后,您将有机会创建您的新层,只需在下一个可用空间键入其名称(unity 已经定义了几个层),如图 4-14 。
图 4-14。
Call the new layer Ground
在空白处键入 Ground。然后回到你的方块检查器,这一次从下拉菜单中选择地面,将它们全部设置为那个值。
现在再次查看检查器中的Player
游戏对象,这一次使用下拉菜单设置什么是地面到地面。这基本上意味着,在我们的脚本中,设置为地面层的任何东西现在都将被视为地面——这意味着 Squarey 可以从它上面跳下来。
完成所有这些后,我们现在可以简单地将最后一行代码添加到我们的脚本中:
if (Input.GetKey(KeyCode.Space) && onGround)
{
rb.velocity = new Vector2(rb.velocity.x, 5);
}
现在点击播放,你会发现 Squarey 可以跳,但只有当他在坚实的地面上。这意味着他不能在空中连续跳跃,也意味着在第五章我们可以开始引入一些真正的平台挑战。
进一步解释一下
你可能仍然对这里实际发生的事情有些困惑。让我们回顾一下正在发生的事情。
关键是我们放在FixedUpdate
函数中的这行代码:
onGround = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, whatIsGround);
语句Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, whatIsGround)
为真或假,询问groundCheck
变换是否与我们定义为地(whatIsGround
)的任何东西重叠。onGround
如果有重叠则为真,如果没有重叠则为假——请记住,这是一个只能为真或假的变量。因为这条线在FixedUpdate
中,它的值会随着游戏的进行而不断变化。
在伪代码中,我们这样说:
If the circle underneath the player is overlapping with ground material, then onGround is true. Otherwise, onGround is false.
然后,在我们的Update
函数中,每当玩家按下空格键时,我们检查onGround
是否为真:在 C#中,&&
简单地表示 and。通过在if
语句中使用&&
,我们测试两个参数是否为真。因此
if (Input.GetKey(KeyCode.Space) && onGround)
{
rb.velocity = new Vector2(rb.velocity.x, 5);
}
实际上意味着
If player presses jump and 'onGround' is true then add upward velocity.
我们也可以用一个变量代替 5,就像我们对movespeed
所做的那样。称之为jumppower
。
为了方便起见,整个播放器脚本现在应该是这样的:
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour {
public Rigidbody2D rb;
public int movespeed;
public int jumppower;
public Transform groundCheck;
public float groundCheckRadius;
public LayerMask whatIsGround;
private bool onGround;
void Start () {
rb = GetComponent<Rigidbody2D>();
movespeed = 3;
jumppower = 5;
}
void FixedUpdate()
{
onGround = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, whatIsGround);
}
void Update () {
if (Input.GetKey(KeyCode.LeftArrow))
{
rb.velocity = new Vector2(-movespeed, rb.velocity.y);
}
if (Input.GetKey(KeyCode.RightArrow))
{
rb.velocity = new Vector2(movespeed, rb.velocity.y);
}
if (Input.GetKey(KeyCode.Space) && onGround)
{
rb.velocity = new Vector2(rb.velocity.x, jumppower);
}
}
}
如果你现在复制它,你可以在尝试理解它的时候尝试修改线条和逆向工程。您应该会发现,其中很多内容实际上是不言自明的。
最后一点:让玩家保持直立
除了有一个小问题:Squarey 目前在楼梯上翻滚旋转,好像他喝了太多的伏特加。如果你跳起来,然后头着地,你将无法再次起飞,因为Check Ground
游戏对象现在将指向空中。
要解决这个问题,单击 Squarey 并在检查器中找到刚体组件下的约束选项。在这里,您会发现冻结旋转 z 的选项。勾选该框,Squarey 的角度将被锁定(图 4-15 )。
图 4-15。
Tick Freeze Rotation and your character will stop falling over
你会注意到这里还有一些其他选项,比如重力比例(控制 Squarey 受重力影响的程度)和其他一些选项。我们将在第五章中讨论这些。
现在你可以试着创建几个浮动平台,并尝试在不从屏幕上掉下来的情况下穿过它们。你可能需要稍微调整一下你的jumppower
或者你的重力等级,所以尽情享受吧。现在花一点时间来反思你已经取得的成就:只走了一小段路,你就已经有了一个可以在屏幕上跳跃的角色和一个导航起来非常有趣的关卡(图 4-16 )。我们才刚刚开始,所以想想在我们结束之前你还能做些什么吧!
图 4-16。
Only Chapter 4 and our game is already almost fun!
在第五章中,我们将会看到如何用预设和影响者创建更好的平台,我们将会用我们的相机做一些更有趣的事情。我们甚至可以开始放入收藏品和危险物品。
五、用预置、效果器和收藏品填充世界
在这一点上,事情开始走到一起,感觉有点像一个实际的游戏。我们有一个非常基本的 2D 水平,我们有一个角色,我们可以用箭头键和空格键移动,我们有工作的物理。
但目前仍有一些事情没有做到,我们没有遵循所有的最佳实践。这个世界也相当空旷,没有任何收藏品,风景,甚至天空。
在这一章中,我们将会看到如何开始添加像树、硬币和有特殊属性的平台这样的东西。我们还将看到如何更好地跟踪所有这些新元素,并通过预置来简化我们的游戏设计过程。到最后,你的游戏世界将看起来更加丰富多彩,你甚至可以开始创建一些基本的平台挑战。
使用效应器
你可能已经注意到,目前不太正确的一点是,我们可以贴在墙上。如果我们把 Squarey 撞在墙上,并一直按住那个方向的箭头键,那么他会把自己粘在表面上,停止下落。对于平台游戏来说,这是非常奇怪的行为,所以让我们阻止它发生。
首先,点按您想要编辑的平台,然后在检查器中查看它。现在单击添加组件➤物理二维➤区域效应器二维。然后你会被提示你需要勾选 2D 碰撞器下的被效应器使用的框。
这将在框的周围显示一个半圆,并在检查器中显示更多的选项。你应该有类似图 5-1 的东西。
图 5-1。
Platform Effector added to one of our tiles
现在试着把这个角色粘在木块上,你会发现他只是滑了下来。简而言之,平台效应器使平台表现得像一个平台。
试着从下面跳进平台,你会注意到别的东西:Squarey 可以穿过它们。你可能以前玩过这个功能的平台游戏,但是现在我们想关闭它。这样做的原因是,截至目前,执行这一举措将打破游戏。当 Squarey 穿过地板时,按住 jump 会使他第二次跳跃,这看起来很奇怪。
有很多方法可以解决这个问题:例如,通过使用光线投射来检查地面,而不是我们目前使用的系统,或者简单地让玩家在再次跳跃之前再次点击空格键。不过现在,最简单的方法是取消单向使用框(图 5-2 ),这也将允许一些游戏特性,如洞穴和死胡同。
图 5-2。
Use One Way has now been unticked
这是一个非常早期的例子,说明你的游戏将如何决定你对物理和代码的决定。你不能建立一个游戏引擎并单独设计你的关卡:游戏世界的行为方式应该由你的游戏设计决定,反之亦然。形式应该服从功能。
在这种情况下,问题是你是否想要设计一个更快节奏的游戏,允许玩家通过点击跳转无缝地扩展平台,或者你是否想要一些更多的谜题/探索,并保持将它们困在不同部分的能力。
(当然,你也可以有多种类型的平台,以不同的方式运行,但你的游戏设计的挑战是要确保从游戏开始就清楚地传达这些差异。)
出于我们的目的,我们将保持事情简单,并关闭此功能。
更多效果器
效应器是一种快速简单地让游戏元素以特定方式运行的好方法。当您选择平台效应器时,您可能已经注意到还有一些其他效应器可供选择。
例如,您可能发现了浮力效应器。这是一个效应器,让我们可以让瓷砖像水一样。
为了演示效应器有多有用,让我们设计一些看起来像水的新瓷砖(图 5-3 ),保持我们相同的 50 x 50 尺寸。
图 5-3。
A square of water
现在把它添加到你的精灵文件夹中,创建一个新的游戏资源叫做水广场(或者你想叫它什么都行)。记住将这个精灵的每单位像素设置为 50,并确保它有一个碰撞器。现在选择添加组件➤物理 2D ➤浮力效果器 2D。记住勾选“由效应器使用”,这次你也要点击“触发”(稍后我会解释这是什么意思)。复制并粘贴几个水方块,在它们的两边用堤岸围起来,创造一个水池。现在跳入水中,观察 Squarey 在水面下上下摆动(见图 5-4 )。
图 5-4。
Squarey bobbing just below the surface of the water
其他效应器可以让您创建风、传送带等等。
预置和更多的组织
不,我没忘记。现在,我们有一个很有趣的水池,但是只有一个瓷砖能正常工作。
你可以改变场景中的每一个方块来应用平台效应器。实际上,考虑到我们可以一次选择多个游戏对象,并以那种方式添加内容或改变设置,这甚至不会那么糟糕。但是现在想象一下,你有多个关卡,大量不同的游戏对象,以及庞大复杂的布局。如果你现在决定要改变一个价值观,所有这些都会让你的生活变得极其艰难。
我们要做的是创建一个预置。Prefab 是预制的缩写,本质上是一个具有预定义属性的游戏对象。我们可以把预设放到场景中,就像我们把精灵放到场景中一样,但是它们会携带所有现成的组件和值。
更好的是,当我们更改文件夹中的效应器时,这些更改将反映在游戏中对象的每个实例上。这将让我们改变我们对游戏设计方面的想法,并能够迅速实施这些变化。
首先,在素材中创建一个新文件夹,并将其命名为 Prefabs。这是相同的过程,因为它是为精灵文件夹和脚本文件夹(右键单击➤创建➤文件夹)。现在找到你制作的包含效应器的地面瓷砖,并把它从检查器拖到预设文件夹中。然后用另一个地面瓷砖和其中一个水瓷砖做同样的事情(图 5-5 )。
图 5-5。
Two prefabs down, one to go
现在到了可怕的部分——您将删除场景视图中的所有图块,除了玩家和相机之外什么都不留下。然而,所有的努力并没有白费,因为现在你将能够简单地将每块瓷砖拖放到你的游戏世界中。
这是一个很好的机会来创造一些更好的组织和重新设计我们的水平。在你再次开始放入瓷砖之前,首先选择预设并在检查器中查看它们。在这里,将名称更改为易于识别的名称。我走过了水、泥土和草地。这不会改变预置本身的名字,但是会改变游戏对象的名字。
你也可以借此机会创建单独的空游戏对象,作为你在旅途中拥有的三个元素的分组。我的是Top Soil
草地、Ground
泥土、Water
水。
现在将每个单幅图块拖回到场景中,按住 Ctrl 键以捕捉到栅格。确保每个项目的第一个实例位于正确的组中,这样重复的项目也会出现在此处。首先将物品从预设文件夹中拖出,放入正确的类别中——然后你就可以简单地复制并粘贴场景视图中的元素了。在这里,你正在制作符合预设规则的副本。不要忘记按住 Ctrl 键来隔开你的图块,并使它们与网格对齐。现在你可以随意重新设计关卡布局了。图 5-6 可以看到我的。
图 5-6。
A new, more organized setup using prefabs
最后,再创建一个空的游戏对象,并将其命名为Tiles
或类似的名称。
为了展示预设的力量,你现在可以试着在预设文件夹中选择草地并选择单向使用。点击播放,你会发现你现在可以从下面跳过任何一个草块。解开它,他们都会变回来。
这将使我们的工作流程变得更加简单,我们安排层级的方式也是如此。例如,试着点击水组,你会看到每一个水块都被高亮显示(图 5-7 )。点击瓷砖将会选中所有的水瓷砖、草瓷砖、土瓷砖(图 5-8 )。
图 5-8。
All tiles selected
图 5-7。
Water selected
这意味着你可以很容易地删除整个类别的瓷砖,在未来,这将有助于我们管理的东西,如收藏品,敌人和装饰品。好时机——本章的下一部分将开始增加更多种类。
故障排除:帮助!Squarey 总是卡住!
软件工程的乐趣之一(是的,就是这个)是事情会不断出错,你会被迫尝试去处理它们。有时会有一个简单的解决办法(在这种情况下,谷歌是你的朋友)。其他时候,问题可能在你的控制之外,迫使你想出一个新的解决方案。
现在,如果你在你的瓷砖和你的玩家角色上使用箱式碰撞器,你可能会发现你偶尔会被不应该被卡住的东西卡住——你的角色可能会停止移动,并压在稀薄的空气中。这个问题通常发生在位置相近的瓷砖之间的顶点,不幸的是,这似乎是 Unity 本身的一个小问题,而不是您可以修复的任何问题(显然,它是在 4.3.1 中引入的,在撰写本文时尚未解决)。
如果你确实遇到了这个问题,就试着为 Squarey 自己使用一个多边形碰撞器,通过点击编辑碰撞器来稍微改变形状,然后在轮廓上创建一个小凸起,如图 5-9 所示,这样你就可以“滑过”这些想象中的障碍。
图 5-9。
Editing the polygon collider
欢迎开发!
理解父母,做一个会动的相机
另一件有助于理解的事情是父母和孩子之间的关系。
层次结构中的这些分组实际上并不是文件夹。更确切地说,空的游戏对象被称为父对象,而你在它们下面的组被称为子对象。像真正的父母和孩子一样,在这种情况下,孩子将继承成人的属性。例如,如果您将Top Soil
向右移动,那么它的所有子节点都将向右移动相同的距离,确保它们彼此保持相对距离。这在很多情况下都是一个有用的特性——例如,如果你想让两个游戏对象以相同的速度向相同的方向移动。
为什么这可能是你想做的事?好吧,我们用这个把摄像机贴在球员身上怎么样?现在,Squarey 只能在屏幕的范围内探索,这对于我们可以进行的关卡设计来说是相当有限的。但是如果我们进入层次结构并选择摄像机,我们可以将它放到Player
上,从而使它成为游戏对象的子对象。现在,摄像机将始终相对于玩家移动!将它移动到播放器的中心,以确保它处于一个好的位置,可以从左右两边看到障碍物。为什么不在右侧增加一些水平来庆祝呢?
在图 5-10 中,你可以看到相机应该是什么样子,以及我添加的一些额外的关卡设计。
图 5-10。
With his camera attached, Squarey is now free to explore foreign lands
请注意,这是一个非常快速的破解,而不是我们真正想要的处理相机的方式。在一个已经完成的游戏中,摄像机应该做的不仅仅是盲目地跟随玩家。相反,它会加速或减速,放大和缩小,并根据游戏类型的不同而表现不同。这有利于游戏性,或者根据具体情况增加戏剧性和刺激性。我们将在后面的章节中更详细地讨论所有这些。
使用 Z 顺序装饰场景
现在,我们的关卡看起来很有游戏性——很明显这是一个电脑游戏关卡,感觉不太逼真,因为里面唯一的物体显然是设计用来跳跃的平台。为了改变这一点,我们需要添加一些装饰,让世界感觉像一个活生生的,会呼吸的地方。
为此,您首先需要创建一些可用于装饰的元素。我已经创建了一棵树(图 5-11 )和一丛灌木(图 5-12 )。
图 5-12。
Bush
图 5-11。
Tree
请注意,这两个图像都是 PNG 文件,边缘是透明的。我用免费的图像编辑软件 GIMP 做了这个,我们将在第十章更详细地讨论你如何做同样的事情。
现在,你想把这些加入到游戏中,就像你以前做过几次一样。将它们放入 Sprites 文件夹,确保改变每单位的像素,并将它们分组到一个名为Plants
的空游戏对象下。你可能也想再次创建预设,尽管这在这种情况下不太重要,因为它们的外观和行为都会有所不同。
现在把你的植物和树木分散到世界各地。您可以随意混合大小,坚持在网格内并不重要。事实上,让定位看起来有点随意是件好事,因为这样会显得更自然,有助于消除一些游戏的美感。
现在试着玩这个游戏,你会发现当你走过灌木丛和树木时,你可能会走到它们的前面或后面。事实上,如果你有一个旧版本的 Unity,你甚至可以在视图中闪烁。
问题是两个东西被画在同一个地方,你还没有定义哪个应该放在上面。要解决这个问题,您需要更改 Z 顺序,这可以在检查器中通过更改名为“层中顺序”的选项来完成。默认情况下,当你添加一个新的游戏对象时,它被设置为 0,但是你可以改变它来创建不同的效果。
如果您使数字变小,这意味着对象将在其他元素之后提前绘制。同样地,如果你把数字设得更高,这意味着它会被画在最后,在所有东西的上面。
我的建议是让一些灌木和树木出现在 Squarey 的后面(设置为–1),让一些出现在 Squarey 的前面(设置为 1)。这创造了一个有趣的效果,他看起来正在穿过茂密的树叶(图 5-13 ),尽管在你构建游戏时确保这不会让玩家感到困惑是很重要的。
图 5-13。
Squarey peeping out from behind some bushes
同样,你也需要试着避免让事情变得混乱。我的建议是保持你的播放器为 0,并以此作为你的参考点。我还建议确保瓷砖将留在前面,除非你特别打算添加装饰。这将允许你把像树这样的游戏物体藏在水面下一点,而不用担心难看的缝隙。您可以通过将它们在层中的顺序设置为 10 左右来实现这一点。
为什么你会想要在你的平台上放一些东西呢?一个原因是,如果你想添加一点更自然,看起来随机的细节。例如,我已经创建了这个爬行器(图 5-14 ,然后将 Z 顺序设置为 11。通过将 X 轴和 Y 轴上的比例分别更改为–1,我也可以水平和垂直翻转图像(图 5-15 )。
图 5-15。
This is how it looks when it’s in place
图 5-14。
This creeper adds a little detail to the game world
你可以随心所欲地添加许多这样的细节。就我个人而言,我觉得这些小小的触动对世界的外观和感觉产生了巨大的影响。我建议发挥一点创造力,看看你能想出什么。
当然,现在我们能够添加不同的层,这意味着是时候添加背景了,这将对美感产生很大的影响。我画了一个多云的天空(图 5-16 )并把它做得相当大,这样它就可以覆盖边缘没有黑色边框的空间。
图 5-16。
A nice cloudy sky
你可以使用像这样的大图片,也可以平铺背景——选择权在你。只要确保你永远不会跑出天空。显然,使用更大的图像对你的应用来说意味着以下几点:
- 它会占用更多内存
- 加载关卡需要更长的时间
将背景的 Z 顺序设置为-10,以确保您不会不小心将某些东西放在它后面,并在您的层次中创建一个名为 background 的组。
我们留下了您在图 5-17 中看到的内容。现在看起来是不是好多了?
图 5-17。
Much better!
透视视差滚动
如果你想变得更有趣,你可能会决定使用另一个额外的技巧:视差滚动。
您可能已经注意到,检查器不仅允许您更改对象的 X 和 Y 坐标,还允许您更改 Z 坐标。这可能会让你觉得奇怪,因为你在 2D 模式下使用 Unity。为什么不干脆取消这个选项呢?
简单的答案是,在某些情况下,您可能想要更改元素的 Z 位置以创建 3D 效果。要进行演示,请选择您刚刚创建的背景,并将检查器中的 Z 坐标更改为 30。现在选择相机,在检查器中点击名为投影的下拉菜单。将此从正投影更改为透视。
现在点击播放,看看会发生什么。你应该会发现天空现在看起来更远了,并且比关卡的其他部分移动得更慢。你刚刚创造了一个视差滚动效果!
说明这里发生的事情的最佳方式是将场景窗口切换到 3D 模式。点击顶部的 2D 按钮在两种模式之间切换,您应该会看到类似图 5-18 的内容。
图 5-18。
This is what the camera “really” sees
简而言之,我们现在有了一个 3D 视角,可以正面观察排列在 3D 空间中的纯 2D 元素。你可以用它做很多很酷的事情,你可以添加任意多的层和元素。你甚至可以在前景中看到半透明的云,或者在中距离看到以另一种速度移动的起伏的山丘。所有这些都能给你的场景增加深度和美感,但是要确保你不会以牺牲清晰度或性能为代价而失去理智。如果你在任何时候有太多的事情要做,你的玩家将不知道他们可以跳上什么或者走过什么,这甚至会让你头疼。
稍后,我们将更多地关注如何设计你的关卡,使其看起来更好,并去掉一些锋利的边缘。在那之前,让我们保持基本的东西。
添加收藏品和危险
现在你已经添加了一些无生命的物体和装饰,是时候考虑添加一些你实际上能够与之互动的元素了。我们可以从一些收藏品开始。
要做到这一点,我们当然需要从设计一些我们可以收集的东西开始。在这方面,一个流行的选择是金币,所以让我们从它开始。水雷如图 5-19 所示。
图 5-19。
A gold coin . Don’t you just want to collect it?
现在我们要再次把它添加到我们的场景中,就像我们把其他游戏对象添加到我们的场景中一样。这意味着我们需要导入精灵,设置每个单元的像素,然后将它制作成一个预置。我们在场景中散布的任何硬币都将按照以下等级分类:收藏品➤硬币➤金币。这一次我们也将创建一个碰撞器,我们将勾选检查器中的“触发”框。
触发器基本上是一个碰撞器,它的行为不像一个物理对象。换句话说,我们的硬币不会是我们可以走进去或跳下来的东西——相反,我们会直接穿过它,就好像它是由空气组成的一样。但与此同时,Unity 将“标记”这一事实,允许我们添加代码,告诉游戏如何响应这一事件。
将一两个你的新金币预置放到场景中,你应该会有看起来像图 5-20 的东西。
图 5-20。
The scene with some added gold coins
现在我们需要创建一个新的脚本,你应该记得怎么做。前往您的素材➤脚本文件夹,右键单击,并选择创建 C#脚本。将此集合命名为 CollectCoin(脚本名称不能有空格),双击它打开 Visual Studio,再编写一点代码。
在这里,我们将使用一个名为OnTriggerEnter2D
的函数。这个函数在附加的游戏对象被触发时被调用,所以你放在这里的任何东西都会在玩家接触到那个游戏对象时发生。
在这种情况下,我们希望硬币消失,所以我们需要销毁它。我们通过说Destroy(gameObject)
来做到这一点。当使用 Unity 时,带有小写 g 的gameObject
指的是脚本附加到的特定游戏对象。
完整的脚本应该是这样的:
public class CollectCoin : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
Destroy(gameObject);
}
}
}
在伪代码中,这是在说:当某个东西接触到 2D 对撞机时,如果那个东西有标签播放器,就自毁。您可能已经猜到了,这意味着我们现在也需要跳到 Unity 中,并将那个标签添加到 Squarey 中。选择 Squarey,然后在检查器顶部找到标签选项。它现在应该显示未标记,你要做的就是点击下拉菜单并选择播放器。
最后,不要忘记把收集硬币的脚本附在你的硬币上。通过单击添加组件➤脚本➤集合来完成此操作。
点击播放,现在当你走进硬币,他们应该立即消失。在图 5-21 中,你可以看到我如何排列我的硬币,以及我如何将玩家标签添加到 Squarey 中。
图 5-21。
Ready to do some collecting
如果我们想制造一些危险呢?在这种情况下,我们可以做完全相同的事情,除了我们想要移动我们的球员回到开始的位置或者可能结束游戏。
为此,我们需要一个新的精灵(图 5-22 )。
图 5-22。
Spikes
现在添加精灵,就像你之前做的那样制作一个预置。记得设置单位像素,并添加一个多边形碰撞器。你可能需要自己塑造它来很好地适应周围的钉子。
您将像上次一样添加一个脚本。这一次的代码也将非常相似,只是略有变化。现在它将显示以下内容:
public class Hazards : MonoBehaviour
{
private Player player;
// Use this for initialization
void Start()
{
player = FindObjectOfType<Player>();
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
player.transform.position = new Vector2(-6, 8);
}
}
}
将这个脚本组件附加到尖刺预设上,记住勾选 Is Trigger,然后就可以开始了。这个脚本与上一个非常相似,除了我们不是破坏游戏对象,而是移动玩家。首先,我们必须定义我们所说的“player”是什么意思,这是通过查找附加到 Player 脚本的对象来实现的。从那里,我们可以改变球员的变换(位置)到一个新的向量 2(一个有两个轴的坐标)。我们将玩家移动到位置(–6,8),因为那是 Squarey 在我的场景中开始的地方。看一下你的 Squarey 版本的起始位置,并改变坐标来匹配。
最后,像我在图 5-23 中所做的那样,在你的关卡中合理的位置放置一些尖刺。我还建议将它们组织在一个名为Hazards
的空游戏对象下。
图 5-23。
Beware the spike pit !
现在,当你点击播放,落在钉应该自动传送你回到开始的位置。这不是很有魅力,但它做了工作,说明了这一点。在接下来的章节中,我们将会看到如何正确地杀死和复活玩家,以及如何计算收藏品的点数。现在,虽然,这给了你一个很好的想法,如何使用触发器,以使各种各样的效果工作。我们可以使用完全相同的代码来制作一个传送门,把玩家送到下一关!或者我们可以用它来制造一些敌人…
介绍敌人
本质上,所有的敌人都是移动的危险。现在你知道该怎么做了:首先创建一个新的精灵,最好是看起来有点威胁的东西,如图 5-24 所示。
图 5-24。
Not sure who this guy is but he looks menacing
给他一个对撞机,给他做一个触发器,造一个预置。然后附上危险脚本,这样他的功能就和尖刺一样了。不同之处在于,我们还将添加另一个名为 BackAndForth 的脚本。进行修改,使其与之前的脚本完全相同(在 scripts 文件夹中右键单击),然后添加以下代码:
public class BackAndForth : MonoBehaviour
{
public double amountToMove;
public float speed;
private float startx;
private int direction;
// Use this for initialization
void Start()
{
direction = 0;
startx = gameObject.transform.position.x;
}
// Update is called once per frame
void Update()
{
if (gameObject.transform.position.x < startx + amountToMove && direction == 0)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x + speed, gameObject.transform.position.y);
}
else if (gameObject.transform.position.x >= startx + amountToMove && direction == 0)
{
direction = 1;
}
else if (gameObject.transform.position.x > startx && direction == 1)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x - speed, gameObject.transform.position.y);
}
else if (gameObject.transform.position.x <= startx && direction == 1)
{
direction = 0;
}
}
}
通读一遍,看看你能否弄清楚它是如何工作的。明白了吗?你当然知道。但是以防万一…本质上,我们有两个公共变量可以从 Inspector 中设置:速度(称为speed
)和对象将移动的距离(称为amountToMove
)。当对象在运行时被创建时,它也检查获取它的当前 x 坐标。方向可以是 1 或 0,并且像所有整数一样,在脚本启动时默认设置为 0。
因此,脚本会询问当前位置是否小于我的起点加上我必须移动的距离,以一定的速度向右移动——但前提是我向右移动。一旦物体经过那个点(>=
的意思是“大于或等于”),那么它就会把方向改变为 1。当 direction 为 1 时,应用反向逻辑,直到字符等于或小于起始位置,此时 direction 切换回 0,我们再次向右。
现在,如果你把这个脚本和危险脚本一起添加到坏人身上,你应该有一个左右移动的游戏对象,当它接触到他们时杀死玩家。只要记得首先在检查器中设置变量(见图 5-25 )。
图 5-25。
The bad guy in his starting position
请注意,无需重写大量代码,只需以独特的方式组合多个脚本,就能获得令人印象深刻的结果。天空是无限的。
可推动的对象
当我们添加所有这些不同种类的对象和元素时,让我们再添加一个非常好和简单的:一个可推的板条箱(图 5-26 )。
图 5-26。
Crate to finally meet you…
你需要做的就是引入一个有碰撞器和刚体的游戏对象(就像玩家一样)。这将允许你通过推动它或甚至将它丢入水中来与该物体互动,以观察它上下浮动(图 5-27 )。这创造了许多潜在的游戏机制和挑战,这再简单不过了。
图 5-27。
Push the crate into the water and see what happens
但是请记住:如果你的角色要能够从箱子上跳下来,他们需要将他们的层设置为地面。
使用材料
在我结束这一章之前,让我来解决一个小问题,它仍然与我们游戏世界中的物体不太相符:slidiness。你可能已经注意到 Squarey 喜欢像在冰上一样到处滑行,这看起来不太对,而且很难控制。
为了解决这个问题,我们将创建一种材料,并将其应用于表层土壤。为此,您将创建一个名为 Materials 的文件夹,然后创建一个新的物理材质 2D (RMB ➤创建➤物理材质 2D)。称之为地面。
现在选择这个材质在检查器中查看,你会发现你可以改变两个属性:弹性和摩擦力。你可能已经猜到了,摩擦力控制着特定表面的摩擦力。当 Squarey 落地时,将此值更改为 0.6 以获得更多的地面控制,将反弹度更改为 0.1 以获得不易察觉的抖动(图 5-28 )。
图 5-28。
Squarey lives in a material world
现在选择表层土预设,在检查器中找到盒子碰撞器 2D 下的材质选项(不是精灵渲染器下的那个)。选择您刚刚创建的地面材质,它应该会自动应用到您游戏中的所有草地瓷砖。现在试试跑跳,事情应该更容易控制。
像这样的材料提供了一种为各种游戏对象添加属性的有用方法,我们将在以后看到它们的更多用途。
现在,这足以开始打造一个充满平台挑战、障碍和收集物品的世界。然而,我们仍然有很多事情要做,在接下来的几章中,我们将会看到如何计算分数,添加 UI 元素,甚至引入漂亮的动画。一旦一切就绪,我们就可以开始在实际的 Android 设备上进行尝试了。
现在都凑在一起了!
六、添加动画、效果和 HUD
好的,我知道我们已经读了这本书的第六章了,但是在这一点上我们还没有真正接触到 Android 手机!这是用桌面 IDE 编码的讽刺,但是如果你感到不耐烦,不要担心——在下一章中,你将尝试在实际的 Android 手机或平板电脑上部署你的游戏。不过现在,我们将添加更多的内容,为我们未来的游戏开发奠定基础——比如动画、粒子效果和平视显示器(HUD)。当我们读到第七章时,我们会明白为什么要先这么做。
别担心。现在也有很多令人兴奋的事情:添加简单的东西,比如动画,将会让你真正地给你的游戏添加个性和魅力,并将它提升到一个看起来非常专业的程度。
处理死亡和使用粒子
每个人都有自己处理死亡的方式。现在,Squarey 的方式并不特别优雅。他不仅在游戏开始时立即“出现”,而且在这种情况发生时,他也没有给我们任何短暂的停顿。整件事太简短了,不能让它真正深入人心。
如果我们有某种死亡动画,或者更好的是血淋淋的爆炸,那就好了。
为了做到这一点,我们将使用粒子系统,这种效果可以让你在屏幕上分散像素,并让它们以不同的方式运行。我们将制作一个看起来像血液爆炸的粒子效果——但是你可以很容易地使用它来制作常规的爆炸、烟火、喷泉、电力等等。
要创建您的第一个粒子系统,选择游戏对象➤粒子系统。你应该会看到一个缓慢移动的白点喷泉出现在你的场景中,选项在检查器的右边(图 6-1 )。
图 6-1。
Your first particle effect
你需要调整这里的一些设置,以使效果看起来更像血。我们可以从改变开始颜色为红色(点击白色条)和开始大小为 0.2 开始。然后单击下面的形状选项,选择球体而不是圆锥体。在“排放”下,将“随时间变化的速率”改为 300。
现在展开渲染器菜单选项,并单击材质选项旁边的圆圈。选择精灵-默认,红色斑点应该变成红色方块。
接下来,展开“生命周期内的大小”(请注意,您需要先勾选“项目符号”框),然后拖动右侧,使线条向下倾斜。这意味着粒子在空气中传播时会变得越来越小,使它们以一种看起来自然的方式消失,而不是突然消失。类似地,你也可以在一生中对颜色做同样的事情。我让我的血液在向外扩张的时候变得稍微暗一点,只是为了让事情保持有趣。
我还施加了 0.2 的重力(这样粒子就往下掉了)。您可以随意使用其他选项——它们通常是不言自明的。
不过,你需要做的一件事是取消顶部的循环框,并将持续时间更改为 0.30。现在效果将只播放一次就结束,而不是立即循环。在任何时候,您仍然可以通过单击“模拟”按钮来测试动画,该按钮在选择粒子效果时浮动在场景视图上。完成后,你应该会看到类似图 6-2 的东西。
图 6-2。
A lovely cloud of blood
摧毁粒子系统
在我们写另一个新剧本的时候,我们将暂时把它放在一边。像往常一样,在脚本文件夹中创建它,并将其命名为 DestroyParticleSystem。你能猜出它是什么吗?(啊,太好了,罗尔夫·哈里斯的推荐信...)
该脚本将看起来像这样:
using System.Collections.Generic;
using UnityEngine;
public class DestroyParticleEffect : MonoBehaviour {
private ParticleSystem thisParticleSystem;
void Start()
{
thisParticleSystem = GetComponent<ParticleSystem>();
}
void Update()
{
if (thisParticleSystem.isPlaying)
{
return;
}
Destroy(gameObject);
}
}
这个脚本的目的只是在粒子效果播放完毕后将其销毁。首先,我们寻找粒子系统的特定实例(在面向对象的编程中通常称为"this"
),然后我们检查粒子系统是否在Update
方法中运行。
最后,一旦效果播放完毕,我们就销毁它。
为什么这很重要?因为否则,我们会在内存中保存无数粒子的实例,这最终会使事情陷入困境。过一会儿这个会更有意义一点。现在,请相信我的话,保存脚本,并将其作为组件添加到您之前创建的粒子效果对象中。
重命名所述粒子系统为血液,然后将其放入预设文件夹。现在从你的等级和场景中删除血。
使危害变得危险
不过,我们还没有完成。接下来,您需要向您的危险脚本添加额外的代码,如下所示:
public class Hazards : MonoBehaviour
{
private Player player;
public GameObject Blood;
void Start()
{
player = FindObjectOfType<Player>();
}
void Update()
{
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
Instantiate(Blood, player.transform.position, player.transform.rotation);
player.transform.position = new Vector2(-6, 8);
}
}
}
首先,我们在寻找一个被称为Blood
的公共游戏对象,然后我们实例化这个游戏对象。这意味着我们正在创建一个游戏对象的实例,在这种情况下,我们使用与玩家相同的坐标。不过,在我们移动球员之前,这一点很重要。
确保你还记得为你的每一个危险在等级中设置血液预置作为游戏对象。你需要在预设文件夹中这样做,这样它将会在每一个后续的尖刺或敌人(而不仅仅是那一个)中被反射。见图 6-3 。
图 6-3。
Adding the blood particle system to the Hazards script attached to the Spikes prefab
有了这些,你现在可以试着玩游戏,测试新的效果。仅尝试在场景视图中观看游戏,而不是选择全屏游戏视图。这样,您应该能够看到发生了什么:当 Squarey 走到一个钉坑上时,他爆炸了,并且在该位置创建了一个血液粒子效果的实例。此时,您会看到它出现在层次结构中。一旦序列结束,效果会在消失前自动消失。
这就是为什么我们需要 DestroyParticleEffect 脚本——否则,我们会有很多“完成的”粒子效果,这些效果来自我们死去的所有时间,这会占用不必要的内存。
如果我们创建子弹或敌人,我们同样可以使用类似的脚本,这样当我们用完数据时,数据就会被销毁。在这种情况下,我们可能有一个脚本,在一段设定的时间后或在与玩家的特定交互后销毁对象。
为什么不试着对你游戏中的硬币做些类似的事情呢?创建一个新的粒子效果并将其命名为 Sparkle,确保添加 DestroyParticleEffect 脚本,然后将其应用于游戏中的硬币。也不要忘记在 CollectCoin 脚本中添加必要的行。你也想让闪光出现在硬币的位置,而不是玩家的位置,但是我会让你自己去想。
两个额外的触摸
我们将在处理死亡的过程中增加两个小细节。第一个是在 Squarey 被杀和 Squarey 出现在他的新地点之间引入一个短暂的停顿。我们通过在 Hazards 脚本中添加以下代码来实现这一点:
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
StartCoroutine("respawndelay");
}
}
public IEnumerator respawndelay()
{
Instantiate(Blood, player.transform.position, player.transform.rotation);
player.enabled = false;
player.GetComponent<Rigidbody2D>().velocity = Vector3.zero;
player.GetComponent<Renderer>().enabled = false;
yield return new WaitForSeconds(1);
player.transform.position =new Vector2(-6, 8);
player.GetComponent<Renderer>().enabled = true;
player.enabled = true;
}
你现在不需要太担心这段代码中发生了什么,但可以说我们增加了一个延迟。是一个可以在其他事情正在进行时发生的例行程序,这意味着我们可以包含一个暂停,而不会让它看起来像是游戏已经崩溃了。我们正在实例化我们的爆炸,等待 1 秒(WaitForSeconds(1)
),然后将玩家移动到新的位置。
在这些事件之间,我们还关闭了玩家的可见性(player.GetComponent<Renderer>().enabled = false
),并且我们移除了所有的动量,这样玩家在重生(player.GetComponent<Rigidbody2D>().velocity = Vector3.zero;
)时就不会移动了。
点击播放并尝试一下。你应该会发现 Squarey 的死现在更有说服力了,因为他爆炸了,游戏暂停了,然后他又回到了起点(图 6-4 )。在接下来的一章中,我们将看看如何实现检查点,但是现在,这应该很好地完成了。
图 6-4。
Ouch, that has got to smart!
关于死亡,我们现在要做的最后一点接触是当 Squarey 从我们的水平边缘掉下时,阻止他无限下落。这很容易做到——我们要做的就是用一个盒子碰撞器创建一个不可见的游戏对象,并附上 Hazard 脚本。然后我们将拉伸它,在关卡下方创建一个屏障(见图 6-5 )。记得确保游戏对象是一个触发器。
图 6-5。
Adding our barrier underneath the level
为玩家制作动画
像这样添加粒子效果已经为我们的游戏做了一些重要的事情:它添加了一个基本的动画,这使世界感觉更加动态。
但并不是每部动画都包含大量分散在各处的小点。在传统的平台游戏中,物体像卡通一样被动画化,这样它们看起来就像真的在跑,在跳,或者在风中飘荡。是时候给我们的玩家角色添加这种动态动画了,所以考虑到这一点,我创造了一个小太空人,他可以探索我们将要设计的外星世界。我们将叫他凯文,和凯文·史派西同名。他的精灵如下图 6-6 所示。
图 6-6。
Kevin, your typical derivative platform hero!
你可能会注意到凯文的雪碧和你平常喝的雪碧有点不同。具体来说,Kevin 不是一个精灵,而是几个精灵——只不过所有这些精灵都在一个文件中。你可以在自己的游戏中使用凯文,也可以创建不同的精灵;只是要确保将它们都保存在一个图像文件中。
这就是我们所说的 sprite sheet,它只是一个包含游戏中单个角色或对象的所有动画帧的图像。这只是一种更有效的处理精灵的方式,你同样可以为游戏中的其他元素制作精灵表。像往常一样将它导入游戏,然后在检查器中打开它,将每单位像素设置为 50 后,将精灵模式设置为倍数。这告诉 Unity 文件在一个图像中包含多个不同的动画帧。精灵后面的棋盘图案代表图像中的透明区域。
现在点击精灵编辑器,然后切片(在左上角)。参见图 6-7 进行参考。
图 6-7。
The Sprite Editor
这个切片按钮很棒,因为它自动检测我们图像中的所有帧,并为我们将它们裁剪成多个不同的图像。您将看到由方框勾勒出的各个框架,如果您愿意,您可以选择手动调整。一旦你满意了,就点击顶部的应用。
现在你可以从精灵文件夹的序列中选择第一个图像(点击精灵旁边的小箭头将显示各个帧)并将其放入玩家角色的精灵框中。现在我们用凯文替换了 Squarey(我们会想念你的,Squarey),但当我们跑步时,他仍然会沿着地板滑行(图 6-8 )。
图 6-8。
Kevin enters the world
下一步是在 Unity 中再打开两个窗口。这些是动画和动画师。你可以通过选择窗口➤动画,然后使用顶部菜单选择窗口➤动画。这将打开两个新窗口,这两个窗口将首先浮动在用户界面的顶部。将这些标签拖到 Unity 中您想要的位置。我已经把动画放在了与场景和游戏相同的位置,我已经把 Animator 和项目标签一起放在了底部(见图 6-9 )。
图 6-9。
Animation and Animator windows in place
你会看到我已经选择了球员,你也应该这样做。在动画窗口中,你应该会看到一个创建按钮,我们可以用它来创建我们的第一个动画。我们称它为 Idle,当对话框打开让你定义它时,你还需要创建一个新的文件夹来存储它,名为 Animations。
你会注意到,一旦你这样做了,一个时间线出现在动画窗口中,以及一种“思维导图”出现在动画窗口中。我们一会儿就会讲到这个。现在,你要做的就是将第一个精灵(凯文完全静止的样子)拖放到时间线的开始,这样你就有了看起来如图 6-10 的东西。
图 6-10。
An idle Kevin
信不信由你,你刚刚创作了你的第一部动画。这感觉不太像动画,因为它只有一个单一的框架。但是如果你在玩游戏的时候看动画窗口,你会看到同样的图像在一遍又一遍的循环。
与动画师同行
我们更有趣的动画当然是行走的动画。要做到这一点,你需要在动画窗口中找到单词 Idle,旁边有上下箭头(在左上角)。单击它,然后单击创建新剪辑。调用这个行走,并确保它再次在动画文件夹中。
现在将行走动画的每一帧从 sprite 表放到时间轴中,确保它们的间距大致相等。如果您需要创建更多的空间,您可以通过向下滚动鼠标来实现,这将缩小视图。它看起来应该如图 6-11 所示。
图 6-11。
Kevin’s running animation, ready to go
现在我们有两个独立的动画,但目前 Unity 不知道我们何时要在它们之间切换。考虑到这一点,我们需要进入 Animator 中的流程图,该流程图目前直接从进入空闲状态(现在忽略任何状态——这仅在动画的更复杂交互中有用)。
我们需要做的是增加一个条件,在这个条件下,我们的流程图从空闲变为行走。为此,右键单击“空闲”并选择“新建过渡”。这将创建一个箭头,您可以将它拖动到行走状态。现在你的图表进入➤闲置➤行走。
确保选择了转换本身(箭头),然后找到读取参数的小选项卡并切换到该选项卡。你会在 Name 旁边看到一个加号按钮,如果你点击它,你就可以从不同类型的变量中进行选择。记住:变量是表示数字和字符串等数据的容器。我们正在创建一个新的 bool,Boolean 的缩写——一个可以为真或假、1 或 0 的变量。一旦你点击了加号,你就可以给它命名了,你应该称它为行走。如果你做的一切都是正确的,它看起来会如图 6-12 所示。
图 6-12。
Adding the walking Boolean
动画代码
我们现在需要再次做一点编码,所以打开播放器脚本并创建一个新的 Animator 引用,我们称之为anim
。然后我们将在Update
方法中添加一小段代码,它将检查是否按下了左键或右键,并适当地设置 Walking bool。
完成后,您的玩家脚本应该如下所示:
public class Player : MonoBehaviour {
public Rigidbody2D rb;
public int movespeed;
public int jumppower;
public Transform groundCheck;
public float groundCheckRadius;
public LayerMask whatIsGround;
private bool onGround;
private Animator anim;
void Start () {
rb = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
}
void FixedUpdate()
{
onGround = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, whatIsGround);
}
void Update () {
if (Input.GetKey(KeyCode.LeftArrow))
{
rb.velocity = new Vector2(-movespeed, rb.velocity.y);
anim.SetBool("Walking", true);
} else if (Input.GetKey(KeyCode.RightArrow))
{
rb.velocity = new Vector2(movespeed, rb.velocity.y);
anim.SetBool("Walking", true);
} else
{
anim.SetBool("Walking", false);
}
if (Input.GetKey(KeyCode.Space) && onGround)
{
rb.velocity = new Vector2(rb.velocity.x, jumppower);
}
}
}
注意我们使用了一个"else if"
语句。当这个语句紧跟在一个if
语句之后时,这意味着如果前面的语句为假,后面的语句为真,那么下面的代码就会运行。这允许我们仅在两个按钮都没有按下的情况下将Walking
设置为假,同时保持我们的逻辑完整。
或者,我们可以对线anim.SetBool("Walking", rb.velocity.x 1=0)
做一些类似的事情,这意味着如果下面的语句为真,如果玩家沿 X 轴的速度为零,变量Walking
将等于真。然而,这可能会让凯文原地慢跑,而他纯粹依靠动量向前滑行。
完成后,返回 Unity,选择从空闲到行走的过渡,并在检查器中找到标题“条件”。单击+,使用下拉菜单选择 Walking 作为条件,并将值设置为 True。这实质上意味着只要行走是真实的,转变就会发生。
当你在这里的时候,去掉写有离开时间的方框。这意味着 Unity 不会等到整个动画结束后再从一个过渡到另一个。
现在反向重复这些步骤,这样无论何时Walking = false
,你都可以从步行回到空闲状态。一旦一切就绪,它看起来应该如图 6-13 所示。
图 6-13。
Our flow chart is complete
正如你可能已经收集到的,没有什么可以阻止我们添加更多的分支到我们的流程图中,这样角色就有了跳跃、下落或其他我们在游戏后期添加的动作的动画。同样,我们可以添加动画,让树随风飘动,硬币原地旋转。水甚至可以在顶层轻轻波动。
该死的凯文
当然,目前这里有一个相当明显的遗漏,那就是凯文只有一个正确运行的动画。哦不!我们忘记了创建向左跑的精灵。
心理!幸运的是,我们没有必要把所有东西都做两遍,我们可以非常容易地创建向左跑的动画,只需在 Kevin 转身时翻转图像即可。为此,我们需要在播放器脚本中创建一个私有整数变量,并将其命名为facing
。在Start
方法中设置facing
为 1(让前进方向对应正值是有一定意义的,所以 1 要=右)。然后像这样更新Update
方法中的这段代码:
if (Input.GetKey(KeyCode.LeftArrow))
{
rb.velocity = new Vector2(-movespeed, rb.velocity.y);s
anim.SetBool("Walking", true);
if (facing == 1)
{
transform.localScale = new Vector3(-1f, 1f, 1f);
facing = 0;
}
} else if (Input.GetKey(KeyCode.RightArrow))
{
rb.velocity = new Vector2(movespeed, rb.velocity.y);
anim.SetBool("Walking", true);
if (facing == 0)
{
transform.localScale = new Vector3(1f, 1f, 1f);
facing = 1;
}
} else
{
anim.SetBool("Walking", false);
}
这里的关键行是显示transform.localscale
的行——这是通过将比例设置为 1 或–1 来翻转播放器精灵的行。我们还需要确保我们正在改变facing
的值,这样只有玩家第一次改变方向时才会发生。代码应该如图 6-14 所示。
图 6-14。
The new Player script
现在点击播放,你应该会发现凯文有一个不错的小跑步动画,可以切换方向。确保您的相机对象的坐标正好为 0 和 0,否则,当 Kevin 左右翻转时,视图会轻微晃动。
当然,目前这还远非完美。我们缺少跳跃的动画,动作有点生硬,镜头僵硬地跟着我们在屏幕上走。不要担心,您可以稍后解决所有这些问题。现在,我只是给你螺母和螺栓,这样你就可以开始自己玩了。随意开始制作你所有游戏元素的动画。图 6-15 显示了当凯文抓起一枚硬币准备跳入深渊时,他看起来是多么的激动人心。
图 6-15。
You go, Kevin
添加 HUD
在这一章中,我们一直在关注使用效果和动画来为玩家增加更多的反馈。现在是时候关注更直接和更基本的反馈了:玩家的分数和进度。
换句话说,是时候让玩家知道他们在我们为他们制作的游戏中表现如何,并记录诸如等级、分数等等。这将通过 HUD 显示玩家重要的细节来完成。稍后,我们将能够使用这个覆盖来显示所有其他种类的东西。
首先,让我们从记录玩家收集了多少硬币开始。为此,我们希望在播放器脚本中创建一个名为coins
的新公共整数。我们不需要在Start
方法中说coins = 0
,因为所有的数值变量在创建时都被默认设置为零。
现在我们将打开我们的CollectCoin
脚本并添加对player
的引用。然后,在OnTriggerEnter2D
事件中,我们将添加行:player.coins++
。这是player.coins = player.coins + 1
的简称。换句话说,我们将玩家硬币的价值增加 1。
整个事情将如下:
public class CollectCoin : MonoBehaviour {
// Use this for initialization
public GameObject Sparkle;
private Player player;
void Start () {
player = FindObjectOfType<Player>();
}
// Update is called once per frame
void Update () {
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
player.coins++;
Instantiate(Sparkle, gameObject.transform.position, gameObject.transform.rotation);
Destroy(gameObject);
}
}
如果你尝试一边玩游戏,一边观察检查器中的Coins
变量(玩家被选中),你会看到每次我们收集一个新硬币,它都会上升。
我们现在知道我们的玩家收集了多少硬币,但目前玩家还不知道。为了纠正这一点,我们将创建一个叫做画布的东西。
添加和使用画布
再次打开顶部菜单,创建一个新游戏对象。这一次选择游戏对象➤ UI ➤画布。在你的层次中双击它,你会看到你的场景突然缩小显示一个大的白色方框。这是画布,你可以在这里给你的游戏添加 UI 元素,比如 HUD 和触摸控件。顺便说一句,这就是为什么在创作第一部 APK 之前关注图形是恰当的。
现在右键单击层次结构中的画布,并选择 UI ➤文本来创建一个新的文本对象。在这里,让我们在检查器中写入级别 1,并将字体大小设置为 20 和粗体。我们可以改变字体,如果我们想简单地通过找到相关的 ODF 或 TTF 文件(通过下载字体,换句话说),并把它放在这里的盒子里,就像我们做精灵一样。不过,我们可以以后再担心。
此时我们需要做的最重要的事情是将这个 UI 元素锚定到屏幕的左上角。点击检查器左上角的方块图片,然后从下拉菜单中选择左上角的选项。现在把文本放在你想要的地方,它会一直锁定在屏幕的左上角。它看起来应该如图 6-16 所示。
图 6-16。
I chose a color that would match Kevin’s boots and be readable against a lot of backgrounds
将文本对象重命名为Level
。然后创建另一个,它将被放置在最上面一个的正下方,称为Coins
。使用相同的大小和颜色的字体,并使这个说硬币:0。
你猜怎么着?是时候再做一个剧本了。这个将被称为 Score,它将被附加到我们刚刚创建的Coins
对象上。内容如下:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class Score : MonoBehaviour
{
Text coins;
private Player player;
// Use this for initialization
void Start()
{
coins = GetComponent<Text>();
player = FindObjectOfType<Player>();
}
void Update()
{
coins.text = "Coins: " + player.coins;
}
}
注意写着using UnityEngine.UI
的那一行。这基本上告诉 Unity 我们引用了一个额外的类,以便获得更多的编码选项。
希望这个脚本的其余部分是不言自明的。我们所做的就是反复更新文本(这是一种字符串变量)来读取Coins:
和player.coins
整数。
将它作为一个组件添加到文本对象中,你会发现这个游戏让你知道凯文在他的冒险中收集了多少硬币。事实上,就像你在图 6-17 中看到的那样。
图 6-17。
Kevin now has two coins. Way to go, Kevin. Remember to add your coin’s sparkle effect, if you haven’t already.
添加声音效果
既然我们已经做了所有这些,我们也可以完成这项工作,并在收集硬币时添加一些音效。幸运的是,这很容易做到。
你当然需要一个文件来存放你的声音(你可以从本书的参考资料中获得或者自己制作),正如你所预料的,你会想把它添加到一个名为 Audio 的新文件夹中。我的音频文件叫 Bling.wav。
现在将一个公共音频源 bling 添加到您的 CollectCoin 脚本中,并在player.coins++
上方添加一行内容为bling.Play()
。这非常简单,除了我们不能直接把音频文件放到检查器的盒子里。取而代之的是,我们需要使用一个音频源,它是任何一个将音频作为组件附加的游戏对象。我见过很多人做的是将音频文件添加到空的Collectibles
游戏对象中,该对象是Coins
对象的父对象,然后将它拖到检查器的框中。(确保在唤醒时取消勾选播放)。
不幸的是,你需要为游戏中的对象实例做这件事,而不是预设。
还有其他方法可以做到这一点,但就目前而言,它应该看起来像图 6-18 ,并且每当你捡起一枚硬币时,它都会发出声音。
图 6-18。
The Collectibles
object now has Bling as a component
有了这些,我们现在准备在实际的移动设备上试用这个东西。除非你觉得自己很勇敢。
一些高级理论:类、对象和方法,天啊!究竟什么是对象?
你可能已经注意到了,我把 Unity 中的几乎所有东西都称为游戏对象。这个术语很方便,因为在游戏开发环境中,大多数东西都是对象,比如树、敌人、硬币和云。但实际上这里还有更深层的东西。
这是因为在编程语言中,对象也可以引用一种类型的数据。Unity 如此强大和方便的另一个原因是它使用了面向对象编程(OOP)。C#和 Java 都是面向对象的,Unity 以一种优雅而聪明的方式运行着。你可能以前听说过 OOP,也可能这是你第一次遇到它。无论哪种方式,OOP 都是一种编程的设计哲学,它代表了多年来编码中发生的某种进化。如果你用像 BASIC 这样的老派语言编写代码(就像我在 ZX 谱上做的那样!),那么您就应该以一种叫做命令式编程的方式进行编程。
目标对命令对功能
命令式意味着你写的所有东西都是连续的,你应该按照运行时执行的顺序来写语句。计算机会阅读你的代码,就像你阅读文章一样:从上到下。唯一的例外是当您使用命令GOTO
时,它会将解释器向前或向后发送到代码中的特定行号。这很容易理解,但随着程序开始达到数百万行(这种情况经常发生),管理起来也变得不可能。如果代码中有错误,你必须一页一页地去查找原因,如果你想重用某个部分,你唯一的选择就是复制粘贴。
然后,过程化编程出现了,它通过将代码包含在称为过程或子例程的离散部分中,解决了其中的一些问题。这意味着某些指令可以被一遍又一遍地调用,并与主代码分开编辑——但鉴于您必须调用这些过程,代码仍将经历一个漫长曲折的旅程,并在途中做出无数次停留。这就是现在所说的意大利面条代码。
OOP 只是从那里发展而来的下一步。它较少从命令的角度来看编程,而更多地从这些命令描述的数据和对象的角度来看编程。
解释的类和对象
在面向对象程序设计中,子程序被类代替,而类又被用来描述对象。对象是具有属性和行为的数据集合,这些行为被称为方法。
我们在代码中编写的所有脚本实际上都是类,因为它们描述了对象的属性和行为。我们的 CollectCoin 脚本(实际上是一个名为CollectCoin
的类)描述了硬币的行为(收集时消失,增加player.coins
值)和属性(硬币的位置、大小等等)。
这个类就像一个蓝图,可以创建任意多的硬币(物体)(就像一个房子的蓝图可以用来创建许多房子一样)。我们称之为对象的实例。当我们销毁我们的硬币时,我们销毁的是这些硬币的实例,而不是类本身。这就是为什么我们只有一个脚本(类)却有许多硬币的副本(实例)。这也是为什么我们需要在粒子效果播放结束时销毁它们,这样在任何给定的时间都不会有无数的粒子效果对象保存在内存中。
正如我们已经看到的,CollectCoin
( onTriggerEnter
)中的方法可以编辑Player
( coins
)中的属性,类能够通过访问彼此的方法和属性来相互交互。
一个类也不需要被附加到一个 sprite 上。一个类也可以仅仅用于处理数字和操作其他类。例如,您可以编写一个脚本(类)来控制时间限制,在这种情况下,对象将是时间限制——一个抽象的概念,而不是您可以移动的东西。但它仍然是一个对象,仍然由类定义。
当我们告诉 Unity 我们在每个脚本的开始使用某些类时,也会发生这种情况——这些类是 Unity 提供的,提供额外的功能,我们可以访问它们的方法和属性。当我们写这些行时,我们告诉 Unity 我们想要访问它为我们创建的一些方法和属性。
在 Unity 中实现面向对象的好处
这就是面向对象编程的高明之处:它允许我们以模块化的方式在编程语言和代码之间共享元素。如果你想在未来的游戏中加入可收集的硬币,你可以简单的把这个类放到你的新项目中。同样,当我们使用 Android SDK 时,我们实际上是在让 Unity 访问 Google 提供的类,以确保代码在 Android 设备上都能顺利运行。使用对象允许我们从其他程序员那里借用元素,并在我们自己的代码中实现它们。这也非常适合于更开放的源代码和协作形式的开发,这对整个软件行业都有好处。
对我们来说,OOP 还让我们将事物整齐地组织在逻辑块中。我们没有一个巨大的文件来决定我们游戏世界中所有事物的行为,相反,我们有游戏对象,它们都有自己的脚本。Unity 为我们提供了这些游戏对象的可视化表示,并隐藏了大部分复杂的东西,从而使事情变得更加清晰(另一方面,学习 Java 要复杂得多,主要是因为需要了解类、方法和对象)。在 Unity 中,很多时候对象确实是对象,我们可以在我们的项目中将它们作为有形的单元移动。Unity 是面向对象代码的完美入门,当你最终过渡到一个不那么可视化的代码类型时,它将帮助你想象你的类和对象以一种相似的方式存在(希望如此)。
如果我们过于迂腐,那么将死亡代码放在Player
脚本中可能更有意义,而不是放在Hazards
脚本中(它们实际上是类)。死亡是玩家的一种行为,因此它是该类的一个方法更有意义(记住,类描述对象和行为)。为此,我们只需将代码转移到一个公共方法(public void dying() {...
)中,并进行必要的修改。然后我们将通过编写player.dying();
从Hazards
内部调用该方法。我们也可以通过把信息放在括号里来传递信息。哦,私有方法和变量是不能被其他类访问的。
无论如何,如果你喜欢,就去改变吧——这将是一个很好的学习机会。但是如果您不愿意,代码仍然可以正常工作。
就我们的目的而言,尽可能保持代码的组织美观和高效并不重要。我们正在制作一个相对较小的游戏,大多数设备都能够运行,在这一点上,更重要的是你要跟上并了解正在发生的一切。然而,当代码变得更大时——当你作为一个团队工作时,或者当游戏更加资源密集时——在你的代码中尽可能优雅开始得到回报。让代码片段尽可能的短和高效实际上也能获得乐趣和回报。OOP?更像是强迫症编程。
不要担心,如果所有这一切都直接超过你的头。我花了多次重读才最终理解 OOP 在实际意义上的真正含义。不过,希望您现在至少对这个术语的含义有所了解——本质上是以模块化的方式编码——这将为您随着时间的推移扩展知识提供有用的基础。这将帮助你更像一个程序员一样思考,这总是一件好事。
现在,到了有趣的部分:让我们把这个东西变成一个应用。
七、制作 Android 应用
在这一点上,我们的游戏还远未完成,在接下来的几章中,你将学习如何添加关卡、关卡、菜单、更多的 UI 元素和许多其他功能。
但是我们有一个基本的游戏,现在可以作为一个游戏来玩,而且肯定是可以玩的。我们的画布已经就位——这将是重要的一点。简而言之,我觉得你已经等得够久了。是时候让这个东西在你的 Android 设备上运行了。
在本章中,您将学习如何创建 APK,如何在手机或平板电脑上测试游戏,以及如何在游戏中添加触摸控制。最终,你将能够把你制作的游戏带到任何地方,并把它放进你的口袋。理论上,你甚至可以把它公之于众。但是我不建议现在就这么做....
添加触摸控制
在我们开始构建 APK 之前,添加触摸控件是个好主意。现在,你可以在带有蓝牙键盘的 Android 设备上使用你的应用,但对大多数人来说,这不是一个非常方便的游戏方式。大多数人连蓝牙键盘都没有。你想让他们只用手机就能玩。幸运的是,添加触摸控件并不是一个太复杂的过程。
如果我们要做一个无止境的转轮,增加触摸控制会非常简单。在这种情况下,我们需要做的就是使用线Input.GetMouseButtonDown(0)
而不是Input.GetKey(KeyCode.Space)
或者任何我们用于跳转的东西。在 Unity 中,鼠标点击和触摸屏幕被注册为完全相同的事情,因为我们不需要知道用户在屏幕上点击的位置,这对于控制我们的游戏来说已经足够了。
我们将在未来的章节中探讨如何创造一个无止境的跑步者。如果这是您感兴趣的事情,您可以直接跳到下一节构建 APK。否则,请继续关注我,我们将看看如何实现适当的触摸控制。
设计控件
你要做的第一件事是设计一些触摸控件,当放在你的游戏上面时,看起来像一个部件。它们需要清晰且容易找到,但不分散玩家的注意力或掩盖游戏的任何重要元素也很重要。出于这个原因,选择一些看起来温和半透明的东西是一个不错的选择。
同样重要的是,按钮要符合你游戏世界的审美。你选择的颜色需要突出不同的层次,但又不与花哨的风格相冲突。随着玩家在游戏中的进展,游戏世界的调色板会发生变化,这是很正常的:也许一个关卡设置在水下,有许多蓝色和绿色,另一个关卡设置在空间,有许多黑色和白色。如果你把你的按钮做成红色或绿色,你会发现它们有时在游戏世界里看起来很丑。
出于这些原因,我把我的按钮做成浅灰色,轮廓略深一点。我还用图像编辑器 GIMP 应用了像素化滤镜,并将不透明度设置为 80%。结果应该是看起来不会太分散注意力,也不会觉得格格不入。你可以在图 7-1 和 7-2 中创建我的东西。
图 7-2。
A button
图 7-1。
An arrow
注意,我只需要创建一个方向箭头。这是因为我可以简单地反转图像来创建相反的箭头——不需要花时间画两个。
添加我们的控件
现在,我们需要将这些添加到我们的游戏中,并让它们做一些事情。首先,像处理其他图片一样,将图片添加到项目的 Sprite 文件夹中。现在右键单击你的画布下面的层级——你希望这个新元素成为Canvas
GameObject 的子元素——并选择 UI ➤图像。游戏中会出现一个看起来像白色大方块的图像。选择这个元素,在它显示源图像的地方,拖放你从你的精灵文件夹中创建的箭头精灵。上面写着锚的地方,选择左下角。拖动并定位箭头,使其位于画布的左上角(此时可能会显得很大),然后将水平刻度更改为负数,使箭头指向左边而不是右边。换句话说,将宽度从 1 更改为–1,这样它就会自己折回来。
取决于你画的箭头有多大,你需要试着确定这些图片的大小是正确的。一旦 APK 在你的手机上运行,你可以稍后对此进行调整,但现在我将我的设置为 X =–2 和 Y = 2(见图 7-3 )。
图 7-3。
Positioning the first control
现在对第二个箭头做同样的事情。这次位置会稍微偏右,主播还是左下方。当然,这次的规模将会是正数。之后,您可以添加跳转按钮,这将是我们的通用“按钮”图像。这一个将被锚定在屏幕的右下角。见图 7-2 。根据需要重命名按钮。
你会发现跳转按钮和右箭头可能会在这一点上重叠,或者看起来非常接近(如图 7-4 ),但你不需要担心这一点。通过将图像设置为锚定到屏幕的底部角落,您声明所有的位置信息都是相对于那个角落的。Unity 不知道你将要玩的手机屏幕或任何设备的尺寸,因此画布的形状可能会有点奇怪。但是只要跳转按钮被设置在离右上角一定距离的地方,箭头和左上角也是一样,一旦你点击播放,它们就应该在正确的位置。
图 7-4。
The buttons don’t look quite right yet, but have faith
当然,要进行预览,你可以点击播放,看看它在你的电脑屏幕上是什么样子(图 7-5 )。当放置你的箭头时,在边缘留一点空间是值得的,以确保它们不会太狭窄。
图 7-5。
See? Our arrows look lovely!!
控件编码
现在你已经有了你的按钮,是时候让它们真正做点什么了。考虑到这一点,我们需要创建一个空的游戏对象,作为这些元素的容器。右键单击画布,选择 Create Empty,然后将这个新对象锚定到屏幕底部。单击拉伸,使其与屏幕一样宽,然后将元素拖动到层次结构中的此处。调用您的新容器 TouchController。
进入你的Player
脚本(正如我们在第六章中了解到的,它实际上是一个类),我们将添加两个公共布尔。记住,bools 是可以为真或假的变量——1 或 0——因为它们是公共的,它们可以被我们游戏中的其他类(脚本)访问。
这些新变量将被称为moveRight
和moveLeft
,您将使用它们来完成这项工作(暂时不要粘贴这段代码):
if (moveright)
{
rb.velocity = new Vector2(movespeed, rb.velocity.y);
}
if (moveleft)
{
rb.velocity = new Vector2(-movespeed, rb.velocity.y);
}
请注意,这与手动按下左右箭头非常相似。
这些可触摸的图像元素的工作方式是,它们只允许我们在被点击和被释放时进行注册。这意味着我们不能问 Unity 按钮是否“被按下”相反,我们需要根据按钮何时被点击和何时被释放来设置我们的布尔值为真或假。
我告诉你不要粘贴代码的原因是有一种更简单的方法可以做到这一点。我们已经有一堆代码来处理玩家左右行走,目前它包括动画之类的东西——所以我们不想重复。
相反,我们将使用一个名为或的命令。这基本上允许我们询问两件事情中的一件是否正在发生。在这种情况下,我们要问的是玩家是否按下了箭头键,或者我们的布尔函数之一是否为真。在 C#中,我们编写或使用符号||
。
因此,我们的代码现在应该是这样的:
if (moveLeft || Input.GetKey(KeyCode.LeftArrow))
{
rb.velocity = new Vector2(-movespeed, rb.velocity.y);
anim.SetBool("Walking", true);
if (facing == 1)
{
transform.localScale = new Vector3(-1f, 1f, 1f);
facing = 0;
}
} else if (moveRight || Input.GetKey(KeyCode.RightArrow))
{
rb.velocity = new Vector2(movespeed, rb.velocity.y);
anim.SetBool("Walking", true);
if (facing == 0)
{
transform.localScale = new Vector3(1f, 1f, 1f);
facing = 1;
}
} else
{
anim.SetBool("Walking", false);
}
现在,当你按左右键时,你的角色应该还在移动。但是,如果您将其中一个布尔值设置为 true(记住,所有变量在第一次创建时默认为 0,即 false),那么玩家将自动移动。
同样,我希望您将处理玩家角色跳跃动作的代码移到一个新的公共方法中。公共方法是一种方法——一段指令代码——可以从其他类(脚本)中执行。这意味着我们现在可以通过从外部脚本激活来强迫玩家跳跃。
我们仍然希望在我们的Update
方法中注册按钮按压,但是我们没有包含跳转代码,而是引用了包含所述代码的新公共方法。
因此,您将像这样创建公共方法:
public void jump() {
if (onGround) {
rb.velocity = new Vector2(rb.velocity.x, jumppower);
}
}
然后在Update
方法中,您可以简单地这样说:
if (Input.GetKey(KeyCode.Space))
{
jump();
}
整个事情应该是这样的:
void Update() {
if (moveLeft || Input.GetKey(KeyCode.LeftArrow))
{
rb.velocity = new Vector2(-movespeed, rb.velocity.y);
anim.SetBool("Walking", true);
if (facing == 1)
{
transform.localScale = new Vector3(-1f, 1f, 1f);
facing = 0;
}
} else if (moveRight || Input.GetKey(KeyCode.RightArrow))
{
rb.velocity = new Vector2(movespeed, rb.velocity.y);
anim.SetBool("Walking", true);
if (facing == 0)
{
transform.localScale = new Vector3(1f, 1f, 1f);
facing = 1;
}
} else
{
anim.SetBool("Walking", false);
}
if (Input.GetKey(KeyCode.Space))
{
jump();
}
}
public void jump() {
if (onGround) {
rb.velocity = new Vector2(rb.velocity.x, jumppower);
}
}
这很重要,因为我们实际上给了自己一些访问点,可以用来从脚本之外控制玩家。我们将在控制按钮的脚本中利用这一点。如果你很难理解这里发生了什么,可以考虑重读第六章中关于面向对象编程(OOP)的部分。
看到了吗?边走边学理论总是好的。
好了,现在我们已经完成了。是时候让按钮有反应了。首先创建另一个新脚本,这次名为Touch
。Touch
将包含以下代码:
public class Touch : MonoBehaviour
{
private Player player;
void Start()
{
player = FindObjectOfType<Player>();
}
public void PressLeftArrow()
{
player.moveRight = false;
player.moveLeft = true;
}
public void PressRightArrow()
{
player.moveRight = true;
player.moveLeft = false;
}
public void ReleaseLeftArrow()
{
player.moveLeft = false;
}
public void ReleaseRightArrow()
{
player.moveRight = false;
}
public void Jump()
{
player.Jump();
}
}
这基本上是一个公共方法的集合,每个方法都会以某种方式与Player
脚本(类)交互。你可能已经猜到了,我们现在要让每个屏幕按钮触发其中一个方法。
现在回到 Unity,添加这个新的Touch
脚本作为我们之前创建的TouchController
空游戏对象的组件(见图 7-6 )。
图 7-6。
Add the Touch
script to the empty GameObject
现在,我们将向左箭头添加一个组件—这次是一个称为事件触发器的新组件。转到添加组件➤事件➤事件触发器。现在点击添加新事件类型➤指针向下。点击出现在右边的小加号(+),然后拿起TouchController
游戏对象并将其拖入无(对象)框。然后单击右侧的下拉菜单,选择触摸➤按左箭头()。基本上,你是在告诉 Unity 你希望指针向下事件(按下按钮的动作)触发Touch
脚本中的公共方法PressLeftArrow
。
点按“添加新事件类型”,然后选取“指针向上”。这记录了手指从箭头上抬起的动作。现在选择触摸➤释放左箭头()进入这里。如果一切正常,它看起来应该如图 7-7 所示。
图 7-7。
Event triggers added
正如您可能已经猜到的,您需要对右箭头做同样的事情,但是使用各自的右箭头方法。对于跳转按钮,您将做一些稍微不同的事情,忽略向上指针类型的事件,选择向下指针的Jump()
方法。
单击“播放”,您应该能够对此进行测试。如果你没有触摸屏笔记本电脑来试用,那么只需用鼠标点击按钮就可以达到同样的效果。如果它现在感觉不太响应,也不要担心——一旦它在 Android 设备上运行,应该会是一个不同的故事。
说到这里....
创造你的第一个 APK
现在你有了合适的输入形式,你终于可以在 Android 设备上测试你所有的努力了。
首先,确保你已经通过按 Ctrl+S 再次保存了你的场景。接下来,前往文件➤构建设置。您会在该窗口的顶部看到一个框,显示“构建中的场景”,这基本上是向您显示您创建的哪些场景希望包含在最终产品中。要添加你的级别 1,只需将它从项目窗口的场景文件夹中拖放到构建区域的场景中。它应该如图 7-8 所示。
图 7-8。
Level 1 is currently the only scene in our build
当你有更多的场景时(你会的),你需要确保顶部的场景是你想首先运行的场景。这通常意味着一个闪屏或某种菜单(但是记住,如果你有免费的 Unity 许可,你的闪屏之前会有一个 Unity)。
现在,不要担心纹理压缩。这对于创建 3D 游戏非常有用,并有助于优化您的应用。对于我们的目的来说,现在还没有必要(我们的应用很小,资源也不是很密集),并且不是所有的 Android 平台都支持所有类型的压缩。我将在本书的后面讨论纹理压缩。
你会注意到这个窗口也有选择平台的选项,现在它可能显示 PC、Mac 和 Linux 单机版。您需要通过单击 Android 选项,然后单击切换平台来更改这一点。
播放器设置
接下来,点击平台滚动框下面的播放器设置按钮,你会发现一些新的选项在检查器中为你打开。在这里,您可以定义将要构建的 APK 的许多属性:比如图标、包名和方向(参见图 7-9 )。
图 7-9。
Player Settings is where you set the properties for your new APK
在我们开始设置之前,请填写顶部的选项。在这里,您可以输入公司名称和应用名称。如果你让它保持原样,那么这个公司将会是 DefaultCompany,这个应用将会被叫做你的项目的名字。这里还有一个添加图标的选项。我们现在不会担心这个问题,我们将在讨论上传和营销您的应用时再次讨论这个问题。现在,我们将坚持使用默认的 Unity 图标。
现在,剩下的这些选项是做什么的?
分辨率和演示
我们首先要看的是分辨率和呈现方式。目前,默认方向可能设置为自动旋转,在它下面有一个勾框显示哪些方向是允许的——现在,答案可能是所有方向。
如果你想做一个益智游戏(在下一章讨论),你可能会想支持纵向。甚至还有少量类似 Fotonica 和 Sonic Jump 的人像动作游戏。但在大多数情况下,坚持横向更有意义,这将防止你的玩家感到太拥挤,这将显示最多的屏幕。在你玩游戏时握着手机的控制器也倾向于只支持横向。因此,要么在默认方向框中选择一个方向,要么取消选中下面的两个纵向选项。
图标
下一部分是图标部分。正如我前面说过的,图标是我们以后会用到的东西,但是正如你所看到的,这里有空间添加各种不同分辨率的图标。如果你想在这里放些东西,只使用一张图片就可以了,在这种情况下,最好使用高分辨率的图片,而不是低分辨率的。与缩小相比,放大会产生更好的图像质量。稍后我将对此进行更详细的讨论——暂时将它保留为空是很好的。图 7-10 显示了默认图标安装后的样子。
图 7-10。
Soon this will be your app
飞溅图像
接下来是 Splash Image,我们将再次保留空白——特别是当你应该在免费许可证上保留默认图像时。
其他设置
其他设置给了我们很多可以玩的东西。您可以更改与渲染相关的设置,以及最低 API 级别、写权限、安装位置等。这其中的大部分是不言自明的,其余的我们将在后面的章节中再来讨论。
您完全可以跳过这一部分,将所有内容都保留为默认值,但是这里有一两件事情值得注意。例如,包标识符是您输入包名的地方。正确的命名如下:com . your company here . your app name here。您需要在应用构建之前设置这个名称,所以请继续使用您自己的详细信息输入一个包名称。你现在选择什么并不重要,但是在发表之前一定要好好想想。先不说别的,应用一旦上传到 Play Store,你就不能再更改这个了。
版本和版本代码分别是为了我们和 Android。版本就是我们和我们的用户所看到的版本。不过,每次你在 Play Store 更新应用时,版本代码都需要更改。即使您做了最微小的更改,然后上传了一个新的 APK,您也需要确保新版本的代码高于上一个版本。
同时,最低 API 级别定义了你想要支持的 Android 的最低版本。默认情况下,这大概设置为 Android 2.3.1(姜饼)。在撰写本文时,谷歌刚刚发布了 Android O 的开发者预览版,可供用户使用的最新版本是 7.1(牛轧糖)。
你的 API 等级越低,就有越多的人能够下载你的应用。但如果你把它定得太低,你将无法访问 Android 的一些后期功能。同样,我将在本书的后面部分详细讨论这些内容。
准备您的手机
在你尝试在手机上运行游戏之前,还有一件事要做,那就是准备好手机。首先,这意味着你需要允许 USB 调试。不幸的是,我不能给你一步一步的指导,因为每部 Android 手机都是不同的(这就是与 Android 合作的奇妙之处和沮丧之处)。
USB 调试让您可以通过 USB 连接安装应用,然后获得关于它们运行情况的反馈。见图 7-11 。
图 7-11。
Allowing USB debugging
通常,这个选项可以在你的手机设置中的开发者选项下找到。在一些手机中,这是隐藏的,所以在谷歌上搜索一下,看看如何在你的特定硬件上打开 USB 调试。图 7-11 显示了三星 Galaxy 设备上的此选项。
您需要更改的另一个设置是“允许安装来自 Play Store 以外来源的应用”这通常有一个标题未知的来源,可以在您的设置的应用部分,或锁定屏幕和安全部分找到。再次,快速谷歌搜索将帮助你。正如您所料,此设置确保您的手机将接受来自其他来源的 APKs 如您的 PC-因此您需要将其打开。参见图 7-12 。
图 7-12。
You need to tick the Unknown Sources option
最后,确保你已经在电脑上为你的手机安装了驱动程序。这可能会发生在你第一次连接它来传输照片时,但为了以防万一,你可能需要做另一次搜索,并为你的手机获取这些驱动程序文件。但如果你不能解决这个问题,还有其他方法可以让应用在你的手机上运行。
扣动扳机
现在剩下要做的就是构建你的应用并运行它。继续通过 USB 端口将手机插入 PC,然后点击“构建并运行”。如果一切按计划进行,Unity 将构建 APK,然后安装到您的手机上。在显示启动画面后,它应该会直接出现在你面前。成功!
Technical Difficulties
不幸的是,当我这样做的时候,我遇到了一些技术难题,需要一段时间才能解决。对你来说幸运的是,我的工作就是处理这些事情,让你的生活更轻松。
在最近升级 Android SDK 工具后,兼容性似乎已经被破坏,构建停止工作。这意味着,如果你最近才安装 Android SDK,事情可能不会像它们应该的那样工作。解决办法是找一个旧版本的 Android SDK 工具,替换掉 SDK 根目录下的文件夹(把旧的改名为 ToolsXXX 什么的)。
希望当你读到这里的时候,这个小问题已经解决了。如果没有,你可能要做更多的谷歌搜索。不幸的是,这是开发的本质,尤其是在 Android 上的开发。但当它最终发挥作用时,确实会让一切变得更有价值。
如果一切都按计划进行,你现在应该有一个带有触摸控制的手机应用的工作版本。您可能会发现 UI 元素有点小,所以移动它们,并根据您的需要调整它们的大小。
图 7-13。
That UI is going to need to get a little larger
借此机会享受你的成就。你刚刚创建了你的第一个 Android 应用。去给妈妈看看。
但是不要对自己太满意——还有很长的路要走。在第八章中,我们将创建多个级别、菜单和保存文件。我们才刚刚开始。
图 7-14。
We did it!
八、通过检查点、关卡和保存文件来扩展游戏世界
这本书的很多内容都是可选的。真的,你已经可以构建一个接近完成的游戏了。它现在运行在 Android 上,有动画和声音,通过从你已经学到的东西中推断,你可能可以创建一堆新元素,并将其包装成一个“完整”的游戏。当然,我希望你能坚持到最后,因为我认为这会给你带来更好的成品和更多的编码知识。(此外,你还将学习如何构建虚拟现实应用。)
也就是说,如果你想让你的游戏感觉完整的话,至少还有几个元素我们还没有涉及到。这就是本章的内容。
首先,如果你的关卡超过了几个平台的长度,你就需要引入关卡,这样你的玩家就不会因为不断被送回起点而沮丧。第二,您可能还想创建多个级别,并找到在它们之间过渡的方法。如果你有不止一个关卡,你需要一些关卡选择系统(菜单)和保存玩家进度的方法。在你完成一个有挑战性和有趣的游戏之前,这些是你唯一需要学习的东西。所以让我们开始吧。
添加检查点
在你开始增加关卡之前,让你的关卡长一点是有意义的。这是有趣的一点,所以复制和粘贴一些更多的地面精灵,添加更多的钉子和水池,让你的想象驰骋。在第十章中,我们将讨论什么是好的关卡设计——所以现在还不要投入太多的时间和精力。把这个关卡设计当作一个占位符,在你玩的时候给你一些东西。
不过,一定要确保沿途有一些可以杀死玩家的元素。这是检查站发挥作用的必要条件。你可以在图 8-1 中看到我是怎么做的。
图 8-1。
My level 1 is very flat and horizontal, keeping things simple for new players
现在,我们将创建第一个检查点。这只是一个空的游戏物体,我们将把它放入游戏的不同位置。猜猜我们会把第一个叫做什么?1 号检查站。
处理关卡放置最明显的方法是在玩家遇到新的重大挑战之前或之后放置关卡。在游戏的后期,我们可能会尝试组合多种危险,以创造一系列的挑战,从而稍微增加难度。但是现在,让我们从一个新的检查点开始,并将其直接放置在第一个尖峰坑之前。
你想让这个空的物体成为一个触发器,你可能还记得,这意味着我们可以检测到什么时候有人通过碰撞笼,但不会撞到东西。首先,给它一个圆形碰撞器,然后勾选检查器中的触发框。这意味着我们可以知道用户何时越过检查点,但他们不会知道。
让这个对象变得相当大,因为玩家不意外跳过检查点是很重要的。您可以使用调整大小工具或在检查器中输入半径来完成此操作。我的是 3,大到足以防止它被绕过。参见图 8-2 。
图 8-2。
Behold the glory of checkpoint 1
您可能已经猜到了,是时候多写一点脚本(类)了。所以创建一个脚本,命名为Checkpoint
。我们还将编辑我们的Player
脚本,所以也在 Visual Studio 中打开它。
编写一个更合适的死亡剧本
事实上,我们首先要编辑参与人 1 的剧本。如果你读过第六章中关于面向对象编程的部分,那么你就会知道我们的Player
角色实际上是一个叫做对象的构造。这个对象有属性(变量)和方法(行为),正是通过这些,我们的其他对象才能与之交互。
如果我们想移动我们的球员,通过操纵Player
脚本中的变量来做是有意义的。我们首先创建两个公共浮动:startx
和starty
。当重生时,这些将是我们玩家的起点。
游戏的开始实际上是我们的第一个检查点,所以我们在游戏开始时繁殖我们的玩家应该做的第一件事是找出他们在世界上的什么地方,这样我们就可以在他们每次死亡时将他们送回这个确切的点。目前,我们把玩家送回我们编辑过的一组特定的坐标,如果我们要在场景视图中移动玩家,我们必须每次都更新这些数字。当我们开始创建多个关卡并使用相同的脚本时,这将会是一个更大的问题。
我们要做的是检查玩家在物体第一次被创建时的位置,并把它作为重生的位置。
为此,您只需向Start
方法添加以下代码:
startx = transform.position.x;
starty = transform.position.y;
前面的代码在第一次创建游戏对象时获取它的位置,并分别存储 X 和 Y 坐标。
现在找到你的Death
方法。这将会出现在你的Hazards
剧本或者你的Player
剧本中(如果你是我的一个高材生并且你移动了它,就是这样)。不管怎样,你现在要把数字换成新的变量。如果Death
方法在Player
脚本中,您可以简单地编写如下:
transform.position = new Vector2(startx, starty);
否则,如果它仍然在Hazards
脚本中,它将看起来像这样:
player.transform.position = new Vector2(player.startx, player.starty);
不管怎样,我们的球员现在回到了我们开始时读到的位置。我建议您现在移动您的Death
方法,以便它在Player
脚本中。如果你还没有找到这样做的方法,需要一点帮助,只需更新你的脚本如下。
Player
脚本:
public class Player : MonoBehaviour {
public Rigidbody2D rb;
public int movespeed;
public int jumppower;
public Transform groundCheck;
public float groundCheckRadius;
public LayerMask whatIsGround;
private bool onGround;
public int coins;
private Animator anim;
private int facing;
public bool moveLeft;
public bool moveRight;
public float startx;
public float starty;
public GameObject Blood;
void Start () {
rb = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
facing = 1;
startx = transform.position.x;
starty = transform.position.y;
}
void FixedUpdate()
{
onGround = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, whatIsGround);
}
void Update() {
if (moveLeft || Input.GetKey(KeyCode.LeftArrow))
{
rb.velocity = new Vector2(-movespeed, rb.velocity.y);
anim.SetBool("Walking", true);
if (facing == 1)
{
transform.localScale = new Vector3(-1f, 1f, 1f);
facing = 0;
}
} else if (moveRight || Input.GetKey(KeyCode.RightArrow))
{
rb.velocity = new Vector2(movespeed, rb.velocity.y);
anim.SetBool("Walking", true);
if (facing == 0)
{
transform.localScale = new Vector3(1f, 1f, 1f);
facing = 1;
}
} else
{
anim.SetBool("Walking", false);
}
if (Input.GetKey(KeyCode.Space))
{
Jump();
}
}
public void Jump() {
if (onGround)
{
rb.velocity = new Vector2(rb.velocity.x, jumppower);
}
}
public void Death()
{
StartCoroutine("respawndelay");
}
public IEnumerator respawndelay()
{
Instantiate(Blood, transform.position, transform.rotation);
enabled = false;
GetComponent<Rigidbody2D>().velocity = Vector3.zero;
GetComponent<Renderer>().enabled = false;
yield return new WaitForSeconds(1);
transform.position = new Vector2(startx, starty);
GetComponent<Renderer>().enabled = true;
enabled = true;
}
}
Hazards
脚本:
public class Hazards : MonoBehaviour
{
private Player player;
// Use this for initialization
void Start()
{
player = FindObjectOfType<Player>();
}
// Update is called once per frame
void Update()
{
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
player.Death();
}
}
}
编写检查点脚本
你可能已经知道接下来会发生什么了。我们需要做的就是在检查点进入碰撞器时改变startx
和starty
的值。
我们的新Checkpoint
脚本很简单:
public class Checkpoint : MonoBehaviour {
private Player player;
void Start()
{
player = FindObjectOfType<Player>();
}
void Update()
{
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
player.startx = transform.position.x;
player.starty = transform.position.y;
}
}
}
别忘了,您还需要将这个脚本附加到有问题的检查点。然后使它成为一个预置,这样你就可以在将来很容易地在关卡周围添加更多的关卡。试试看,你会发现你现在在关卡重生,而不是在游戏开始的时候。
事实上,你应该出现得足够快以至于被淋上你自己的血(见图 8-3 )。很好。
图 8-3。
Kevin returns!
现在在聪明的地方多做几个检查点,稍微玩一玩,看看什么效果最好。在你的层次结构中用逻辑的方式组织它们,也许给它们一个名为Checkpoints
的父对象。当然,你可以提供某种可见的指示器来指示你的检查点——比如在《刺猬索尼克》游戏中发现的帖子——但是现在玩家只是在游戏中的不同点重新出现是很常见的。当我们加载时,我们接受这一点作为暂停怀疑的一部分,它已经成为视频游戏语言的一部分。
更上一层楼
我们已经走了很长的路,但我们的游戏仍然只有一个水平。是时候引入某种形式的真正进展了。
要做到这一点,我们只需制作另一个代表关卡结束的游戏对象,并使其成为一个触发器。鉴于凯文是一名宇航员,他的关卡结束时成为某种太空火箭是有道理的。稍后,我们将制作动画。就目前而言,简单地到达太空火箭将结束这一水平。图 8-4 可以看到我的火箭。
图 8-4。
This rocket ship signals the end of each level
创造一个新的水平
在添加脚本之前,我们首先需要创建另一个关卡。在你这么做之前,一定要让你的Player
游戏对象成为一个预设(把Player
拖到预设文件夹中,并且一定要带上主摄像机和检查地面)。这样,您所做的任何更改都会在您所做的所有级别中得到全面反映。为你的Canvas
和它所有的孩子做同样的事情。
一旦你做到了这一点,尝试使用这个简单的小技巧,使一个新的水平:只需点击文件➤保存场景为,并呼吁它的水平 2。确保它和第一级放在同一个场景文件夹中(见图 8-5 )。
图 8-5。
A quick way to make a new level
这是一个很好的捷径,因为这意味着你已经有了所有的预置,你可以更快地设置和运行。只需删除一些元素,移动一些东西,然后按 Ctrl+S 保存新布局。
(或者,只需右键单击场景文件夹,然后选择创建➤场景,即可创建一个全新的场景)。
现在,如果您导航到项目窗格中的 Scenes 文件夹并双击 level one,它应该会在两个布局之间跳转。请注意,新场景中的一切都是新的——甚至是玩家和摄像机。尽管如此,它们仍然是存在于你的预设文件夹中的相同对象的所有实例,所以编辑脚本或属性将影响所有级别的一切。
逃离关卡
既然我们的游戏有不止一个关卡,我们就可以在它们之间转换了。回到第一关,像平常一样将精灵添加到第一关。用多边形碰撞器使其成为 GameObject,tick 为 Trigger,创建一个名为EndLevel
的新脚本,将其作为组件添加到火箭船中。
EndLevel
会是这样的:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class EndLevel : MonoBehaviour {
public string nextLevel;
void Start()
{
}
void Update()
{
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
SceneManager.LoadScene(nextLevel);
}
}
}
再简单不过了!不过,请注意,这次我用using
命令包含了代码的顶部。那是因为我使用了 Unity 的一个额外的类,叫做SceneManagement
。这个类让我们使用加载下一个场景的命令。与此同时,正在讨论的场景是一个公共字符串,我们将在检查器中将其命名为 Level 2。这将使我们更容易更新每个场景的关卡目标,同时仍然使用相同的脚本和对象。
在你点击播放之前,你还需要做一件事:回到构建设置,将场景 2 添加到你的游戏中(只需将它从你的场景文件夹中拖放到窗口中即可——见图 8-6 )。
图 8-6。
Drag Level 2 into the build settings so you can load it
现在尝试通过到达火箭完成水平。你会发现下一个关卡会立即加载,你已经准备好迎接下一个挑战:创建一个关卡选择。
构建级别选择屏幕
在大多数手机游戏中——包括 PC 和主机游戏——玩家可以直接进入给定的关卡,只要他们之前已经完成了上一关。这让他们重放他们最喜欢的时刻,回去寻找隐藏的秘密,并击败他们的最高分。为了实现这一点,我们需要为玩家提供一些查看和选择关卡的方法。换句话说,我们需要建立一个级别选择。
这意味着你需要创建另一个场景,但是这个场景将是完全空白。我们称之为等级选择。一旦准备好了,就该重新认识 Squarey 了,只是这次他失去了一点个性(图 8-7 )。
图 8-7。
This will be our selector
实际上,这根本不是正方形,而是一个指示器或选择器,意味着我们需要在中心有一个透明度。这将显示我们希望选择哪个级别,因此我们还需要两个视图,每个级别一个,大小相同。我做了这些 500 x 500 的。你可以从你的两个关卡中截取截图(忽略它们在这一点上本质上是相同的)并保存为精灵。
现在,将两个级别的图像排列到场景中,使它们在相机的视野中,并很好地对齐。然后把你的选择器放在最上面(精确的相同坐标),确保它有一个更高的层排序值。你应该有类似图 8-8 的东西。
图 8-8。
The beginnings of our Level Select scene
编写控制脚本
现在我们将创建一个新的控制脚本,它的工作方式很像Player
脚本。在很大程度上,我们将像控制玩家一样控制选择器。这是我们暂时的玩家角色。
创建脚本并将其命名为Selector
。然后使用下面的代码:
public class Selector : MonoBehaviour {
public bool moveLeft;
public bool moveRight;
void Start()
{
}
void Update()
{
if (transform.position.x > -5 && (moveLeft || Input.GetKeyDown(KeyCode.LeftArrow)))
{
transform.position = new Vector2(transform.position.x - 6, transform.position.y);
moveLeft = false;
}
else if (transform.position.x < 1 && (moveRight || Input.GetKeyDown(KeyCode.RightArrow)))
{
transform.position = new Vector2(transform.position.x + 6, transform.position.y);
moveRight = false;
}
if (Input.GetKey(KeyCode.Space))
{
Select();
}
}
public void Select()
{
}
}
我的关卡图像间隔 6 个单位,所以这是选择器每一步移动的距离。
正如你所看到的,这与我们通常的Player
脚本非常相似,尽管显然没有Death
方法或动画。还有一个或两个其他的差异,你也需要知道。我们已经创建了一个Select
方法,但是现在它还是空的。相反,当用户点击箭头键时,选择器向左或向右移动了 6 个单位。注意,我们现在使用的是GetKeyDown
,所以用户必须点击而不是按住箭头。出于同样的原因,我也在方块移动一步后立即将moveLeft
和moveRight
设置为false
。
最后,我添加了一点代码来检查选择器不会离开屏幕的左边缘或右边缘。你需要在每次添加一个新的关卡时更新它,或者使用类似于numberOfLevels * 6
的东西来计算选择器可以向右移动多远。
如果你拖动Main Camera
对象使其成为层级中选择器的子对象,那么屏幕将随着选择器的移动而“滚动”。现在,如果你测试它,只要你在电脑上使用光标键,它就应该工作。
现在您需要专门为Selector
创建一个新的Touch
脚本。我们可以将这段代码添加到我们已经创建的同一个Touch
脚本中,并检查它所附加的对象,但是创建新的东西可能更简单。
所以创建另一个新脚本,这次叫做LevelSelectTouch
。这个实际上是上一个Touch
的翻版,让事情变得简单明了:
public class LevelSelectTouch : MonoBehaviour {
private Selector selector;
void Start()
{
selector = FindObjectOfType<Selector>();
}
public void PressLeftArrow()
{
selector.moveRight = false;
selector.moveLeft = true;
}
public void PressRightArrow()
{
selector.moveRight = true;
selector.moveLeft = false;
}
public void ReleaseLeftArrow()
{
selector.moveLeft = false;
}
public void ReleaseRightArrow()
{
selector.moveRight = false;
}
public void Select()
{
selector.Select();
}
}
将这个脚本添加到你的层级中的TouchController
游戏对象——而不是预置。请记住,我们只希望此更改影响此触摸控件实例,而不影响级别 1 和级别 2 中使用的控件。
现在您只需要设置控件来使用这个脚本。打开右箭头、左箭头,然后在检查器中跳转,并为每个重新配置事件触发器,以便它们与新脚本中的正确方法相对应。如果你卡住了,回头看看我们上次是怎么做的——过程是完全一样的(在第七章)。当然,在这种情况下,跳转按钮将被绑定到Select
方法。
当我们破坏我们的预设时,从画布上删除Level
和Coins
游戏对象,因为它们在这个上下文中没有多大意义。
准备发射
尝试一下,你会发现你现在可以用光标或者屏幕上的控件来移动选择器。它看起来相当不错,尽管它可能会受益于某种更好的背景(图 8-9 )。
图 8-9。
There we go, much better!
它真正需要的是实际选择级别的能力。一个好的方法是给我们的关卡图像起一个合适的名字,这个名字将会和我们的场景名字一样(所以,关卡 1 和关卡 2 ),并且使选择器本身成为一个带有碰撞器的触发器。我们还将添加刚体,用于我们的碰撞检测。我们显然不希望我们的级别落在屏幕的底部,所以点击体型旁边的下拉菜单来选择运动学(图 8-10 )。
图 8-10。
Set Body Type to Kinematic .
现在像这样更新Selector
脚本,记住在顶部添加新的using
行:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Selector : MonoBehaviour {
public bool moveLeft;
public bool moveRight;
private string levelChoice;
void Start()
{
}
void Update()
{
if (transform.position.x > -5 && (moveLeft || Input.GetKeyDown(KeyCode.LeftArrow)))
{
transform.position = new Vector2(transform.position.x - 6, transform.position.y);
moveLeft = false;
}
else if (transform.position.x < 7 && (moveRight || Input.GetKeyDown(KeyCode.RightArrow)))
{
transform.position = new Vector2(transform.position.x + 6, transform.position.y);
moveRight = false;
}
if (Input.GetKey(KeyCode.Space))
{
Select();
}
}
public void Select()
{
SceneManager.LoadScene(levelChoice);
}
void OnTriggerEnter2D(Collider2D other)
{
levelChoice = other.name;
}
}
这段代码只是寻找一个冲突,然后获取违规对象的名称,作为一个名为levelChoice
的字符串进行存储。当你点击跳转按钮时,levelChoice
会像我们之前加载的一样被加载。尝试一下,你会发现你现在可以跳到你选择的任何一个级别。不要忘记将关卡选择场景添加到构建设置中。
让我们花一点时间来思考一下你在这里的成就:你使用了你已经使用过的所有相同的技巧,但是这次你在游戏中制作了一个菜单而不是一个关卡。这是 Unity 给我们的工具是多么多才多艺的早期迹象。想象创建一个益智游戏或者某种生产力工具并不是太难。
保存我们的进度
在我们能够保存玩家的进度之前,关卡选择没有多大用处。我们希望他们在玩这些关卡时有成就感和进步感,这意味着随着每一关的完成,可以选择继续玩下去。这种进步应该从一个游戏阶段持续到另一个游戏阶段,因为每次都要从头开始并不有趣。
因此,我们需要一种方法来保存我们的进展,Unity 实际上给了我们许多选择,从使用玩家偏好到序列化或创建文本文件。
从技术上讲,我们应该使用的是序列化——它可以让你更快地保存更多的信息。这里不涉及太多细节,这意味着将一个对象转换成字节。不过这有点复杂,所以现在我们要用PlayerPrefs
因为它又快又脏,而且更容易让你理解。
PlayerPrefs
应该是用来保存像图像质量这样的偏好,或者你是否想要打开声音——换句话说,就是设置。但老实说,很多独立开发者专门使用这种方法,如果你需要做的只是存储一些最高分和级别名称,它会做得很好。
当我们加载关卡时,保存它非常简单。只需更新 1 级火箭附带的EndLevel
脚本,如下所示:
public class EndLevel : MonoBehaviour {
public string nextLevel;
public int levelValue;
void Start()
{
}
void Update()
{
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
SaveLevel(levelValue);
SceneManager.LoadScene(nextLevel);
}
}
public void SaveLevel(int level)
{
PlayerPrefs.SetInt("farthestLevel", level);
}
}
那一行:PlayerPrefs.SetInt
("farthestLevel", level);
就是全部了。这用关键字farthestLevel
创建了一个新的整数,并把它放在PlayerPrefs
中。我们只需要在检查器中添加公共变量levelValue
(图 8-11 ,现在触摸火箭将加载下一个场景并更新保存的变量。
图 8-11。
The rocket has Level 2 value
为了利用这一点,我们需要让我们的关卡选择界面更智能一点,这样它就能告诉我们什么时候一个关卡还没有准备好被载入。创造一些东西,将显示在未来的水平。我使用了一个问号,它应该与我的背景很好地匹配(图 8-12 )。
图 8-12。
What’s behind door number two?
创建其中的两个,并将第一个放在第二级后面,在顺序中靠后一点(如果你做对了,它就看不见了)。第二个沿着右边走,第三层就在右边。这些不应该有对撞机;它们只是图像,当前面的图像丢失时会显示出来。你应该有类似图 8-13 的东西。
图 8-13。
There are actually two question marks here—the first one is behind Level 2
我们现在将创建另一个名为LevelLoader
的脚本,并将其附加到 2 级图像:
public class LevelLoader : MonoBehaviour {
public int thisLevel;
private Selector selector;
void Start () {
selector = FindObjectOfType<Selector>();
}
void Update () {
if (selector.farthestLevel < thisLevel) {
this.tag = "off";
GetComponent<Renderer>().enabled = false;
} else
{
this.tag = "on";
GetComponent<Renderer>().enabled = true;
}
}
}
所以,这些图像现在是我们的关卡加载器。当关卡选择场景被创建时,它们都会出现,然后它们会检查玩家是否走得够远。如果玩家没有,他们将消失(GetComponent<Rendered>().enabled = false
)并将他们的标签设置为off
。
将这个附加到 GameObject,然后在 Inspector 中输入公共整数thisLevel
,当然应该是 2。
如您所见,我们需要检查级别的代码将在Selector
脚本中(因为我们只想做一次),它有公共属性loadLevel
。要获得这个值,我们所要做的就是向脚本中的Start
方法添加一行代码:
PlayerPrefs.GetInt("farthestLevel");
当然,我们还需要在顶层定义公共整数。
现在,当选择器被创建时,它将通过查看PlayerPrefs
来检查玩家已经到达的最远等级,并将该值存储为一个公共整数。如果玩家还没有走得足够远,那么LevelLoader
图像就会消失。当玩家选择。他们仍然选择了一个“不可见的第二层”,但是因为标签被设置为off
,它不会被加载。
最后几点意见
试一试,你会发现一开始你只能玩 1 级。只有在你通过火箭到达 2 级,然后再次加载这个屏幕后,你才能选择任何一个。进步。
这不一定是最理想的处理方式。为了保持简单,我们的一些方法现在在不寻常的地方。相反,你可以做的是创建一个脚本,作为一种“游戏管理器”来存储进度,加载不同的级别,等等,这样可以让你的代码更整洁。我一直在威胁关于优化的那一章,这是我们将在那里触及的其他内容。
但是现在,我觉得你已经够努力了。这是一个复杂的章节,所以如果你挣扎过,试着不要担心。我实际上只教了你很少的新东西(如何加载场景以及如何在PlayerPrefs
中保存和加载变量)。大多数情况下,这只是用新的方式运用你已经学到的东西。因此,如果你已经走了这么远,你已经有工具来制作检查点和等级选择屏幕——这只是一个简单的问题,应用一点独创性,以拿出一个你喜欢的系统。这就是编程的乐趣所在:它本质上是一种足智多谋的练习。
在接下来的几章中,事情又会变得不那么技术性了。我们将引入一些常见的障碍、能量和能力来创造更多有趣的游戏可能性。然后我们将讨论什么是好的游戏设计。你已经完成了最难的部分(就目前而言)。是时候找点乐子了!
九、加入更多的游戏元素:弹簧、移动平台、人工智能等等
你已经花了最后几章努力创造一个工作的游戏世界。您已经开发了引擎,创建了保存文件,并使您的角色以应有的方式移动并与世界互动。希望你能从中得到乐趣,但是可能会觉得有点挑战性,而且在过程中的某些点上很有技术含量。
好了,现在是时候享受一下你的成就了。你创造了这个世界。让我们在里面找点乐子吧。
毕竟,一个典型的游戏会涉及大量不同的障碍,危险和能量,每一个通常都会创造独特的游戏挑战和有趣的遭遇。索尼克有弹簧,戒指,柱子,巴德尼克,钉坑,混沌祖母绿,和循环。马里奥有蘑菇,子弹法案,鬼,问号框,和耀西(s)。超级肉仔有传送门,巨型锯,导弹,还有一堆用过的针。
是时候让你变得有创造力,开始在你自己的游戏中引入更多的元素了。最棒的是。创造这些挑战几乎和以后经历它们一样有趣。
在这一章中,你将学习如何创造各种各样的环境危害和敌人,并且当你想在自己的游戏世界中加入其他元素时,你可以随时回头。我希望它也能成为灵感的源泉,帮助你想出自己的障碍和挑战。当然,在这个过程中,我们也会学到一些新概念。
最后,您还将学习如何掠夺素材商店,以便您可以访问他人精心创建的粒子效果、脚本和精灵,并在您自己的游戏中使用它们。
准备好了吗?让我们找点乐子!
一些常见的游戏对象及其行为
虽然每个平台都是不同的,你应该尽你所能使自己与众不同,脱颖而出,但某些比喻确实会不时出现。这在任何类型、任何形式的媒体中都是正常的,所以如果你发现自己又回到了“老一套”,也不用担心
因此,假设你将在游戏设计中使用一些更常见的资源和对象,这一节将向你展示如何构建基本元素,如弹簧和移动块。
弹簧
我们要创建的第一个游戏对象是弹簧,或“弹跳垫”可以说是由刺猬索尼克推广开来的,弹簧现在是平台游戏中一个常见的比喻,用来推动玩家上升一个等级。
环顾一下 Unity IDE,你可能会发现它似乎可以完成这项工作:你可以给物理材质 2D 添加一个“弹性”属性。不幸的是,这不是我们想要的,因为这将使地面看起来更像一个真实的弹性表面。也就是说,它将推动角色越往下坠越高,最终返回的能量越来越少。你可以用它做一些有趣的事情,但是它不会按照我们想要的方式运行。
相反,我们将创建一个 spring sprite(图 9-1 )并将它添加到你的关卡中,就像你现在习惯做的那样(图 9-2 )。注意,我们沿着顶部边缘使用了一个边缘碰撞器(而不是通常的盒子碰撞器)。
图 9-2。
A spring in a level
图 9-1。
A spring
接下来,您将创建一个Spring
脚本并添加以下代码:
public class Spring : MonoBehaviour {
private Player player;
// Use this for initialization
void Start()
{
player = FindObjectOfType<Player>();
}
// Update is called once per frame
void Update()
{
}
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Player")
{
player.SuperJump();
}
}
}
正如您可能已经猜到的,您还将把SuperJump
方法添加到您的Player
脚本中:
public void SuperJump()
{
rb.velocity = new Vector2(rb.velocity.x, jumpPower * 2);
}
当然,记得将新的Spring
脚本添加到你的游戏对象中,并使它成为一个预置——通常的东西。
现在当你碰到弹簧时,凯文将会被发射到两倍于他正常跳跃高度的空中。我一直保持高度与他的跳跃高度成比例,以防我们决定改变关卡的比例。如果你愿意,你也可以给弹簧添加动画和声音。
移动平台
任何平台游戏中的一个常见比喻是移动平台。你有左右移动的平台,带你越过深沟,你有上下移动的平台,就像电梯一样。
我们已经可以让事物左右移动——我们已经对我们的敌人做到了。问题是,如果你把这个运动脚本贴在一块地上,凯文就不会跟着它一起动了。相反,地面会从他下面移开,他会从上面掉下来。不好。
与此同时,如果平台上下移动,而你的玩家在上面,他将会颤抖和抓狂,并可能从地板上摔下来。我们本质上需要修改这个脚本,这样我们就可以贴着顶部表面,跟着它走。
我们如何做到这一点?
我给你一点时间思考…在本书中,我们之前使用了什么来允许一个游戏对象相对于另一个游戏对象移动?
明白了吗?
答案是我们需要让凯文成为他所站的游戏对象的孩子。为此,打开你的运动脚本(我们称之为BackAndForth
)并准备做一些改变。我们不仅改变了剧本,使我们的角色在接触平台时成为平台的孩子,我们还增加了另一个运动维度,使它也可以上下移动。direction
整数变量现在是公共的,这意味着我们可以从检查器中编辑它。在onStart()
方法中也不再设置为 0,但是请记住,如果未设置,整数总是从 0 开始。
这意味着我们的敌人行为不会改变——他们将继续左右移动,因为direction
变量将默认为 0。不过,对于平台,我们可以选择将其设置为 2 或 3,这将使它先向上再向下移动,或者先向下再向上移动。
做完这一切后,BackAndForth
现在应该是这样的:
public class BackAndForth : MonoBehaviour
{
public double amounttomove;
public float speed;
private float startx;
private float starty;
public int direction;
private Player player;
// Use this for initialization
void Start()
{
startx = gameObject.transform.position.x;
starty = gameObject.transform.position.y;
player = FindObjectOfType<Player>();
}
// Update is called once per frame
void Update()
{
if (gameObject.transform.position.x < startx + amounttomove && direction == 0)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x + speed, gameObject.transform.position.y);
}
else if (gameObject.transform.position.x >= startx + amounttomove && direction == 0)
{
direction = 1;
}
else if (gameObject.transform.position.x > startx && direction == 1)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x - speed, gameObject.transform.position.y);
}
else if (gameObject.transform.position.x <= startx && direction == 1)
{
direction = 0;
}
if (gameObject.transform.position.y < starty + amounttomove && direction == 3)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x, gameObject.transform.position.y + speed);
}
else if (gameObject.transform.position.y >= starty + amounttomove && direction == 3)
{
direction = 2;
}
else if (gameObject.transform.position.y > starty && direction == 2)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x, gameObject.transform.position.y - speed);
}
else if (gameObject.transform.position.y <= starty && direction == 2)
{
direction = 3;
}
}
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Player")
{
player.transform.parent = gameObject.transform;
}
}
private void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.tag == "Player")
{
player.transform.parent = null;
}
}
}
我还建议你为平台创建一个新的物理材料 2D,并将摩擦力设置得高一些。这是为了防止凯文在平台上滑动太多,配合动作看起来有点古怪。
使用边缘碰撞器并添加平台效应器也是一个好主意。勾选效应器使用和使用一种方式,这将防止我们的玩家被平台压碎或粘在一边,并作为平台的孩子移动。如果这阻止了玩家从平台上跳下,你可以稍微增加你的地面检查的半径。还有其他方法来完成同样的事情,可能更优雅一点,但这是一个简单的“修复”,将让您的移动平台启动并运行。
如果一切正常,凯文现在应该随着平台一起移动,不管它是向左向右还是向上向下(图 9-3 )。这创造了大量的平台挑战机会,所以尽情享受吧。
图 9-3。
Kevin going for a ride
折叠平台
你知道还有什么很棒吗?倒塌的平台。这些都是平台,当你在它们上面着陆时,它们会在脚下粉碎,从而鼓励你快速奔跑和跳跃,以避免落入你的厄运。
在这种情况下,向玩家传达他们即将面临的挑战的性质是非常重要的。在没有警告的情况下,让一个平台从你的球员下面掉出来,这是不公平的,因此,通常建议有一个视觉指示器来指示地面不太稳定。
出于这个原因,我设计了一个摇摇欲坠的平台,如图 9-4 所示。
图 9-4。
A crumbling platform tile
我们希望这个平台瓷砖在我们着陆时开始破碎,所以我们将创建一个名为Crumble
的新脚本。这个脚本将简单地在玩家触摸到物体时启动一个计时器,然后在计时器结束时让物体落下并消失。
代码如下所示:
public class Crumble : MonoBehaviour {
private Player player;
private Rigidbody2D rb;
public int timeToCollapse;
private int timeLeft;
public int timeToRestore;
private int restoreTime;
private float startY;
private float startX;
// Use this for initialization
void Start () {
rb = GetComponent<Rigidbody2D>();
player = FindObjectOfType<Player>();
startX = transform.position.x;
startY = transform.position.y;
timeLeft = -70;
}
// Update is called once per frame
void Update () {
if (timeLeft > -70)
{
timeLeft = timeLeft - 1;
}
if (timeLeft == 0)
{
rb.constraints = RigidbodyConstraints2D.None;
}
if (timeLeft == -62)
{
GetComponent<Renderer>().enabled = false;
restoreTime = timeToRestore;
}
if (restoreTime > 0)
{
restoreTime = restoreTime - 1;
}
if (restoreTime == 2)
{
transform.position = new Vector3(startX, startY);
transform.rotation = Quaternion.identity;
GetComponent<Rigidbody2D>().velocity = Vector3.zero;
rb.constraints = RigidbodyConstraints2D.FreezeAll;
GetComponent<Renderer>().enabled = true;
}
}
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Player")
{
timeLeft = timeToCollapse;
}
}
}
当玩家触摸碰撞器时,开始倒计时,这将是在检查器中选择的一个值。当倒计时过零时,刚体的约束被移除,允许它下落并在空中旋转。计时器继续从 0 倒数到-70,这样我们就有时间看到平台落下并消失。就在时间到达该点之前,对象将停止被渲染并变得不可见。这将开始一个新的倒计时:恢复计时器。这也是在检查器中设定的,当它倒数到零时,对象将返回到其原始位置,约束重新冻结,旋转设定为零,渲染返回。
从玩家的角度来看,这将创建一个瓷砖,在它从屏幕上掉落并最终消失之前,可以站立一段有限的时间。你可能想添加一个“隆隆”的动画和声音效果来增加戏剧性,然后你可以引入一些很酷的反身平台。它应该看起来像图 9-5 。
图 9-5。
Running along collapsing blocks … action!
更好的人工智能
另一件你可能想在游戏中加入的东西是一个更好的敌人 AI。现在,我们的敌人只是左右移动,并不比我们的移动平台更聪明。对玩家来说,更具挑战性的是一个会真正找到玩家并追捕他们的敌人。
我认为地面上的东西会更有趣,所以我创造了另一个敌人。这次我要带一种长相很贱的机械鼠(见图 9-6 )。为什么呢?可能是因为太晚了,我把它弄丢了。
图 9-6。
Look, it’s an alien planet. It doesn’t need to make sense
这个小家伙要用一个新的脚本,名字就叫GroundEnemy
。这个角色的基本行为是沿着地面跟随玩家。所以,如果我们的水平位置比玩家的大,我们减少 x 的值。如果它小,我们增加那个值。我们还需要老鼠在改变方向时翻转,就像玩家一样。
这个简单的脚本看起来像这样:
public class GoundEnemy : MonoBehaviour {
private Player player;
private int facing;
public float enemySpeed;
void Start () {
player = FindObjectOfType<Player>();
}
void Update () {
if (gameObject.transform.position.x > player.transform.position.x)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x - enemySpeed, gameObject.transform.position.y);
if (facing == 0)
{
facing = 1;
transform.localScale = new Vector3(.2f, .2f, 1f);
}
}
if (gameObject.transform.position.x < player.transform.position.x)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x + enemySpeed, gameObject.transform.position.y);
if (facing == 1)
{
facing = 0;
transform.localScale = new Vector3(-.2f, .2f, 1f);
}
}
}
}
有趣的事实:我制作的第一个游戏就是基于这个脚本的(除了 ZX 基础版)。这是我“了解”编程的起点。我做了一个可以在屏幕上移动的点,它会被第二个点追赶。玩家的目标是诱骗敌人降落在一个小地雷上(一个红点)。也许这不是一个有趣的事实…有时我会感到困惑。
咳咳。当然,如果你想让坏人真正致命,你也应该添加Hazards
脚本(你需要添加一个onCollissionEnter2D
方法,这样碰撞器和触发器都会杀死玩家)。图 9-7 中可以看到敌人在追击。
图 9-7。
Run Kevin, it's some kind of robot rat
无论如何,这个脚本目前有点太简单了。事实上,游戏一开始,敌人就会开始追逐玩家,很可能会被困在某个坑里。不仅如此,他很容易被愚弄,几乎会被任何障碍所阻碍。
为了解决这个问题,我们首先要让他在玩家到达一定距离后立即行动,然后在玩家离开后停止跟随。接近度将是一个公共整数,我们可以在检查器中设置。一个有用的提示是确保你在不同的范围和不同的速度下玩耍。理想情况下,你不希望敌人开始移动,直到玩家可以在屏幕上看到他们。同样,理想的速度应该是能让玩家在紧张的追逐后逃脱的速度。
请注意调整这些数字是如何稍微改变游戏的节奏和紧张程度的。这类似于成为一名电影导演,我们将在下一章更多地讨论这些方面。
既然如此,为什么我们不多一点创意,让我们的敌人有更多穿越环境的能力呢?例如,如果 Roborat(是的,我应该叫他 Roborat)能跳过障碍物试图够到玩家,如果它能自己从坑里出来,那就太好了。为此,我们将使用一个新功能:光线投射。
使用光线投射
光线投射有点像你车上的倒车传感器;它们发出一束检查碰撞的光束,如果有碰撞就返回“真”。我们想做的是给我们的敌人一种能力,看看是否有什么东西挡住了它的道路,然后跳过它。这意味着它也需要自己的groundCheck
(这样它就不会一直跳来跳去)。用和你为玩家做的完全一样的方法来处理这个:创建一个小半径的空游戏对象,然后让它检查地面来设置一个布尔值。您可以直接复制并粘贴代码,一旦代码就位,它应该看起来像这样:
public class GoundEnemy : MonoBehaviour {
private Player player;
private int facing;
public float enemySpeed;
private bool chaseIsOn;
public int attackRange;
public Transform groundCheck;
public float groundCheckRadius;
public LayerMask whatIsGround;
private bool onGround;
void Start () {
player = FindObjectOfType<Player>();
}
void FixedUpdate()
{
onGround = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, whatIsGround);
}
在检查器中你也应该有类似图 9-8 的东西。
图 9-8。
Remember, you’re creating an empty GameObject just below the character to use as a transform and then adding it to the Inspector
光线投射是一条看不见的线,我们将使用更多的变换和空的游戏物体来定义它的位置。为了检查这是否有效,我们将使用一个debug
函数在两点之间画一条直线。这是 Unity 的一个便利特性,可以让你以一种只在场景视图中可见的方式直接在屏幕上绘图。玩家不会看到它,但我们可以用它来测试我们的游戏。
因此,创建两个新的空游戏对象,它们是老鼠的孩子(这听起来像一本奇怪的书名:老鼠的孩子)。第一个应该是死点,我们就叫Sight Start
。第二个将在 rat 前面两个单位,称为Sight End
。
现在我们要创建两个新的公共转换,分别是enemySightStart
和enemySightEnd
。我们将再次使用检查器将刚刚创建的两个空对象放入其中。如果你做对了,你应该可以添加这一行:
Debug.DrawLine(enemySightStart.position, enemySightEnd.position, Color.red);
然后看到场景视图中两点之间出现一条红线(见图 9-9 )。现在我们要把这条线换成光线投射。
图 9-9。
Once the transforms are set, your rat should look like he's jousting
我们的光线投射将精确地到达线当前所在的位置,但是无论如何保持线是有用的,因为光线投射是完全不可见的,否则很难可视化。
幸运的是,使用我们的光线投射非常简单——特别是因为你熟悉使用重叠圆。
我们将使用Physics2D.Linecast
来完成这项工作。还有其他类型的光线投射,如Circlecast
,但对于一个规则简单的 2D 游戏来说,一条线是最有效的选择。我们需要给这个函数一个起点和一个终点(就像我们对线条所做的那样),然后我们还要提供一个图层蒙版。我们不希望敌人跳过玩家,所以它要找的层是地面。
这将进入更新,并且只有当chaseIsOn
布尔值为真时(也就是说,如果玩家已经被看见)才会起作用:
if (Physics2D.Linecast(enemySightStart.position, enemySightEnd.position, whatIsGround)) {
Jump();
}
如你所见,我也创建了一个Jump
方法,就像我们为玩家做的一样。这应该很熟悉:
private void Jump()
{
if (onGround)
{
rb.velocity = new Vector2(rb.velocity.x, jumpHeight);
}
}
这实际上是我们的敌人现在能够跨越障碍所需要的一切(见图 9-10 )。现在没有什么能阻止他,他就像一个终结者。
图 9-10。
Leaping rats—a common sight for an ex-Londoner like myself
好消息是,当老鼠转身时,Sight End
对象也会翻转,因为它是老鼠的孩子。让老鼠尝试跳过裂缝也不需要太多的代码;我们只需要第二个光线投射来观察第一个下方的地面。加上这一点,并确保敌人在该点不与地面重叠时跳跃(图 9-11 )。
图 9-11。
Our rat friend looking for the floor
编码敌人的行为
我还添加了一些东西,本质上是来自BackAndForth
脚本的相同代码。我想让老鼠左右移动,这样它就“巡逻”了,直到它开始追逐玩家。让一个完美的敌人一直保持到玩家被看到,这看起来并不特别自然…尽管这可能会令人毛骨悚然,我承认。不过,我们会让这个代码更智能一点,如果敌人接近边缘,我们会让它改变巡逻方向,这样它就不会离开平台或撞到墙上。
我还将稍微移动一下代码,这样它就不会全部位于Update
函数中——这看起来有点难看。如果您想走简单的路线,您可以复制并粘贴这段代码来创建您自己的地面敌人:
public class GoundEnemy : MonoBehaviour {
private Player player;
private int facing;
public int jumpHeight;
public float enemySpeed;
private bool chaseIsOn;
public int attackRange;
public Transform groundCheck;
public Rigidbody2D rb;
public float groundCheckRadius;
public LayerMask whatIsGround;
private bool onGround;
public Transform enemySightStart;
public Transform enemySightEnd;
public Transform enemySightEnd2;
private float startX;
public double amountToMove;
void Start () {
player = FindObjectOfType<Player>();
rb = GetComponent<Rigidbody2D>();
startX = gameObject.transform.position.x;
facing = 3;
}
void FixedUpdate()
{
onGround = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, whatIsGround);
Debug.DrawLine(enemySightStart.position, enemySightEnd.position, Color.red);
Debug.DrawLine(enemySightStart.position, enemySightEnd2.position, Color.green);
}
void Update()
{
if (gameObject.transform.position.x - player.transform.position.x < attackRange && gameObject.transform.position.x - player.transform.position.x > -attackRange && chaseIsOn == false)
{
chaseIsOn = true;
}
if (gameObject.transform.position.x - player.transform.position.x > attackRange || gameObject.transform.position.x - player.transform.position.x < -attackRange && chaseIsOn == true)
{
if (chaseIsOn)
{
startX = gameObject.transform.position.x;
}
chaseIsOn = false;
}
if (chaseIsOn)
{
Pursuit();
} else
{
Patrol();
}
}
private void Patrol()
{
if (facing == 3)
{
facing = 0;
transform.localScale = new Vector3(-.2f, .2f, 1f);
}
if (gameObject.transform.position.x < startX + amountToMove && facing == 0)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x + enemySpeed / 2, gameObject.transform.position.y);
}
else if (gameObject.transform.position.x >= startX + amountToMove && facing == 0)
{
facing = 1;
transform.localScale = new Vector3(.2f, .2f, 1f);
}
else if (gameObject.transform.position.x > startX && facing == 1)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x - enemySpeed / 2, gameObject.transform.position.y);
}
else if (gameObject.transform.position.x <= startX && facing == 1)
{
facing = 0;
transform.localScale = new Vector3(-.2f, .2f, 1f);
}
if (Physics2D.Linecast(enemySightStart.position, enemySightEnd2.position, whatIsGround) == false || Physics2D.Linecast(enemySightStart.position, enemySightEnd.position, whatIsGround))
{
if (facing == 1)
{
facing = 0;
transform.localScale = new Vector3(-.2f, .2f, 1f);
}
else
{
facing = 1;
transform.localScale = new Vector3(.2f, .2f, 1f);
}
}
}
private void Pursuit()
{
if (Physics2D.Linecast(enemySightStart.position, enemySightEnd.position, whatIsGround) || Physics2D.Linecast(enemySightStart.position, enemySightEnd2.position, whatIsGround) == false)
{
Jump();
}
if (gameObject.transform.position.x > player.transform.position.x)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x - enemySpeed, gameObject.transform.position.y);
if (facing == 0 || facing == 3)
{
facing = 1;
transform.localScale = new Vector3(.2f, .2f, 1f);
}
}
if (gameObject.transform.position.x < player.transform.position.x)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x + enemySpeed, gameObject.transform.position.y);
if (facing == 1 || facing == 3)
{
facing = 0;
transform.localScale = new Vector3(-.2f, .2f, 1f);
}
}
}
private void Jump()
{
if (onGround)
{
rb.velocity = new Vector2(rb.velocity.x, jumpHeight);
}
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Enemy")
{
Physics2D.IgnoreCollision(collision.collider, GetComponent<Collider2D>());
}
}
}
这仍然是相当简单的敌人人工智能去,但它导致一些相当愉快的行为。我们的坏蛋现在会慢慢地巡逻(我把这个设置为半速)直到玩家接近。作为一只老鼠,他能闻到凯文的气味,所以一旦凯文靠得太近,老鼠就会紧追不舍,跳过障碍物和深坑紧追不舍。如果他碰凯文,我们就死定了。如果凯文及时逃走,老鼠就会失去兴趣,在他所在的任何地方巡逻。
最后一种方法——onCollission2D
方法——是为了防止老鼠相互碰撞。我把这个包括进来,这样你就可以为毛出因子做一个老鼠的“坑”。不过,你需要将老鼠标记为敌人,它才能工作。
如果 Roborat 发现自己被困在一个平台上(见图 9-12 ),他通常只会僵住。所以,他并不完美。但他仍然很有趣,而且充满活力,足以创造许多游戏机会。
图 9-12。
So long, sucker!
并感到自豪:你刚刚创造了你的第一个人工智能。一天的工作。
武装玩家
我们的老鼠已经被证明是一个相当卑鄙的威胁,肯定是一个足够的挑战,给我们的球员一段艰难的时间。是时候给我们的球员一个反击的机会了。
创建一个玩家可以发射的子弹是相对容易的,尽管我们需要做一些杂耍来确保我们引用了正确的Bullet
对象实例。请允许我解释。首先,我们需要创建一个新的游戏对象,名为Bullet
。这是我们的子弹,它的方向和速度有公共变量。它也将有一个对撞机。该脚本如下所示:
public class Bullet : MonoBehaviour {
public float speed;
public int direction;
private int timeLeft;
public GameObject Blood;
void Start () {
timeLeft = 100;
}
void Update () {
timeLeft = timeLeft - 1;
if (timeLeft < 1)
{
Destroy(gameObject);
}
if (direction == 0)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x - speed, gameObject.transform.position.y);
} else if (direction == 1)
{
gameObject.transform.position = new Vector2(gameObject.transform.position.x + speed, gameObject.transform.position.y);
}
}
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Enemy")
{
Destroy(other.gameObject);
Instantiate(Blood, transform.position, transform.rotation);
}
else if (other.gameObject.tag == "Player")
{
Physics2D.IgnoreCollision(other.collider, GetComponent<Collider2D>());
}
else
{
Destroy(gameObject);
}
}
}
请注意,杀死一只老鼠会造成与玩家死亡时相同的血腥粒子效果。这意味着你需要像以前一样在检查器中添加那个公共游戏对象。还要注意,我们的onCollision
方法检查对象标签,这样老鼠会被血杀死,玩家会被忽略,其他任何东西都会破坏子弹。so 在设定的持续时间后超时,并自毁以将其自身从内存中移除,就像之前的粒子效果一样。
同样,我们还想在Player
脚本中创建一个名为Bullet
的新公共对象。你需要通过检查员把子弹预置放在那里。
然后,您将添加以下代码:
if (Input.GetKeyDown(KeyCode.LeftControl)) {
var newBullet = Instantiate(bullet, transform.position, transform.rotation);
var bulletScript = newBullet.GetComponent<Bullet>();
bulletScript.direction = facing;
}
这是新的部分。这里,我们不仅要实例化一个新对象,还要在创建对象时为它设置一些属性。为此,我们需要使用GetComponent
来从这个实例中获取对脚本的引用。从那里,我们可以访问公共变量并更改它。
最终,你会得到一颗可以穿透敌人的子弹,如图 9-13 所示。
图 9-13。
Alas, poor Roborat
(当然,稍后您会想要进入画布并添加一个“fire”按钮。我没有说这个,因为我认为子弹让我们的球员有点被制服了。这一部分只是供你参考,如果你想的话,你可以在游戏中添加子弹和枪支。同样,你也需要给你的玩家精灵添加一把枪,可能还需要一个新的动画。)
使用素材存储中的素材
我可以继续告诉你如何创建不同的对象和行为,直到我脸色发青,但我永远不会告诉你如何制作你可能需要的一切。传送玩家的传送门怎么样?开门的开关呢?会飞的敌人呢?还是双跳?还是开机?
希望你现在能自己弄清楚这些东西。本章介绍了光线投射,更详细地介绍了实例化,并增加了您的知识。利用这些新信息,在你已经知道的基础上,你应该能够对你能想到的几乎任何问题提出创造性的解决方案。记住,没有缺乏资源这种事,只有缺乏足智多谋。
但是如果你自己想不出来,或者你只是没有时间或者兴趣,你可以找到别人(包括 Unity Technologies)已经做好的预制体,添加到你自己的项目中。这就是素材存储的用武之地(图 9-14 )。
图 9-14。
The Asset Store in all its glory
要开始浏览此处,只需选择“素材商店”选项卡并四处查看。你会看到各种各样的东西——从粒子效果,到脚本,到精灵包,到整个游戏演示。你会注意到有滑块让你设置价格(许多素材是免费的)和文件大小,你可以从右边的类别中选择。如果你点击一个出版商的名字,比如 Unity Technologies,你可以看到他们所有的素材和软件包。
我想让你从 Unity Technologies 找到名为 2D 平台的素材选择(图 9-15 )。这基本上是一个完整的 2D 游戏,但现在让我们试着选择一个我们想要的元素,而不是使用整个游戏。具体来说,我们抓一个音效文件:Player-jump1.wav。
图 9-15。
This will do just nicely
在商店中点按“下载”,然后点按“导入”。Unity 会警告你,你可能会覆盖你的辛苦工作,但是不要担心——你可以在下一个屏幕上准确地选择你想要添加的内容。所以点击 OK,在打开的窗口中取消选择列表中的所有内容(点击 None),然后手动重新选择声音效果(图 9-16 )。
图 9-16。
Only select what you want
现在点击导入,一秒钟后你会在你的音频文件夹中找到新的子文件夹。点击这些,你最终会得到你想要的声音效果。
您现在可以像以前一样创建一个音频源,并让它在 Kevin 跳跃时播放。这种声音对他来说绝对是错误的,但希望你看到这里的可能性——你可以在素材商店找到几乎任何你想要的东西,尽管你可能需要支付一些费用,但你会发现它通常不会太贵。通常,这里的资源的质量和专业性将超过你自己所能做的,这将加速开发,同时产生具有更高生产价值和更多“光泽”的最终产品我不想推荐任何具体的东西,因为商店的内容一直在变化,这可能会使这本书过时。然而,现在有一个 2D 精华精选,其中包括一些很酷的东西,如天气粒子效果,动态照明,反射 2D 水和“专业相机 2D”
希望你的头脑现在对这些可能性感到震惊,但最重要的是你不要忘乎所以。好的游戏设计不仅仅意味着向玩家扔你能扔的所有酷的东西——它和其他东西一样需要克制。权力越大,责任越大。
图 9-17。
My Level 2 looks like this right now, which is all wrong. Find out why in the next chapter.
这就是为什么第十章为你提供了优秀游戏设计、技巧和风格的基本介绍。你已经掌握了基本技能——现在是时候学习如何利用它们了。
十、让游戏变得有趣和优化
恭喜你,你现在可以用 Unity 做游戏了!
不,说真的,如果你现在停止阅读,我相当有信心你可以建立一个完整的游戏关卡和一切。你甚至可以在浏览了一会儿之后找到如何在 Play Store 上发布它的方法(尽管我会在第十二章解释)。
是的,你可以建立一个游戏。但是你能开发一个好的游戏吗?因为这是两码事。记住:权力越大,责任越大。我觉得,如果我教你如何制作游戏,然后在没有任何关于如何让游戏变得有趣的指导的情况下让你自由,我会对这个世界造成伤害。
这就是我们将在这一章中探讨的内容。我们也会讨论一些关于优化的问题(让你的游戏运行更流畅,占用更少的空间),甚至如何让你的关卡看起来更漂亮。这是放在编程蛋糕上的樱桃。走吧!
入职和教程
还记得你从商店买了一个电脑游戏,然后在服装店等着你妈妈买完东西的日子吗?你可能很高兴坐在那里,因为你有手册可以阅读,里面充满了背景故事、提示和关于游戏中一切是如何工作的解释。它让你对游戏充满期待,并确保你在插入卡带/磁盘后就知道如何开始游戏。
如今,游戏很少配有手册,尤其是手机游戏。但这并不意味着你可以假设你的玩家马上就知道怎么玩了。事实上,你甚至不应该假设他们以前玩过电子游戏。因为你的一些玩家可能不会有,而那些仍然是你想留下来的客户。每个游戏都是某人的第一次。所以你的工作就是“在工作中”教玩家,这意味着你需要一个教程级别。
实际上,教程水平几乎和指导手册本身一样是一个时代错误。如果你还记得你真正喜欢的上一个教程级别,请举手。不,我不这么认为!
一个好的第一关应该在没有明确告诉玩家任何事情的情况下指导玩家如何操作一切。这意味着你将需要使用视觉线索,以及游戏性的比喻,创造一个知识的基础,然后在此基础上建立。
剖析完美的开放水平
虽然缺乏手册可能是一个现代问题,但在有史以来最经典的游戏之一《超级马里奥兄弟》中可以找到一个完美的开放级别的例子,它含蓄地教玩家如何玩。这款游戏的开放级别,称为世界 1-1,是游戏历史上最受分析和高度赞扬的级别之一,这是有充分理由的。让我们看看它是如何工作的。
为了您的方便,我已经使用来自我们简单平台的素材重新创建了第一级布局(图 10-1 )。这可能是也可能不是亵渎。
图 10-1。
Hmm, this is oddly familiar…
从第一个屏幕开始,玩家就开始学习如何玩游戏。在这里,他们受到他们的主角马里奥(或者在我们的情况下,凯文)的欢迎。马里奥被放置在屏幕的最左边,镜头向前推到右边。这立即无声地告诉玩家:向右走。
当马里奥向右走时,他会看到一个愤怒的古姆巴向他走来(我们已经用我们的机器人代替了它)。你知道吗,从进化上来说,每当有东西直接向我们靠近时,我们就会感到压力。这就是为什么通勤是一场噩梦。这种运动模式结合古姆巴人愤怒的眉毛应该足以告诉我们需要避开敌人。我们唯一能做的就是跳——否则,我们会死(图 10-2 )。
图 10-2。
Jump or die
跳跃是马里奥的主要机制,这种开放确保了玩家在进入下一步之前明白这是如何运作的。如果他们失去了所有的生命,回到起点,那么他们什么也没有失去,因为他们还没有取得任何进展。所以这是一个很好的实验场所。
马里奥接下来会遇到一个问号框。这个盒子正乞求被一个强有力的视觉暗示所触动,这个视觉暗示就是问号。这个通用符号在说,“哦,这是什么?”(“哦”是可选的)。
当马里奥通过弹跳进入问号来触摸它时,他会发现它会产生一个蘑菇。下一关的设计是马里奥几乎被迫收集蘑菇。它将从盒子中出现,在马里奥上方向右移动,然后从管道中反弹向左。马里奥此时很可能在下一个平台的下面,所以即使他试图跳起来避开蘑菇,它仍然可能击中他。在我们的游戏中,图 10-3 近似于这种情况。
图 10-3。
Were this Mario, a mushroom would now be appearing
因此,玩家学会了如何成为“超级马里奥”
这一关以这种方式继续,以简单、无言的方式教玩家他们需要的每一项技能。接下来的几个关卡会给玩家足够的时间来练习这些技能,然后将一些障碍串联起来,以进行真正的挑战。随着游戏的进行,必须按顺序绕过的障碍数量会增加,最终会开始引入新的怪癖和曲折。
确保你的玩家理解你的游戏
这可能对你来说听起来像是常识(难道你不聪明),但是当你在关卡设计的阵痛中,很容易忽略这些点。
看看你已经创造了什么。我知道在这一点上你只是在玩,但我敢打赌你已经设置了一些相当残忍的陷阱。人们很容易被这种想法冲昏头脑,错误地认为辛苦=有趣。这种哲学会导致很多人在给你一个合适的机会之前就放弃你的游戏。如果玩家没有太多的游戏经验,这一点尤其正确。
那么,你必须经常做的事情就是把你的游戏交给人们去尝试。为 Android 开发的好处是,你可以把你的手机带到酒吧,在周围传递,看看你的朋友们过得怎么样。你可能会发现,对你来说似乎显而易见的事情对第一次玩游戏的人来说是迟钝或不公平的。你会看到你的玩家在哪里卡住了,在什么时候他们会考虑放弃。如果你做对了,他们至少应该能够通过最初的几关而不会过度沮丧。他们至少需要这么长时间才会上瘾。
马里奥和世界 1-1 背后的天才头脑 Shigeru Miyamotu 的建议是,最后设计你的第一关。这使得后退一步,避免在关卡设计中变成虐待狂的诱惑变得容易多了。
难度曲线
不过,你确实需要在游戏的某个阶段开始引入重大挑战,否则会变得很无聊。乐趣在于不可能和太容易之间的平衡点。
为什么呢?因为从神经学的角度来看,只要我们在学习,游戏就是有趣的。是的,我要深入了。
你的大脑进化来帮助你生存。是什么让人类如此擅长生存?我们适应和学习的能力。我们茁壮成长,因为我们学会了如何利用我们周围的环境,应对不断变化的气候和环境。我们的反应随着练习变得更好,固定的运动模式通过重复变得根深蒂固。
大脑想要不断学习,所以它通过释放某些神经递质和激素来奖励这种学习。当你朝着一个目标努力时,大脑会释放多巴胺让你保持专注。当你完成这个目标时,它会释放内啡肽——感觉很棒。这鼓励大脑重新连接自己,这样你就有更好的机会再次完成同样的事情。游戏使用声音效果来表示奖励,这加强了这种反应。
如果你给大脑的刺激或挑战太少,它就会变得无聊。无聊对我们有害,因为它会导致大脑萎缩。无聊的时候,我们会很快找点别的事情做。
同样,当你向大脑提出一个不可能的挑战时,它会很快泄气并放弃。
但是如果给你一个足够困难的挑战,需要做大量的工作,但又不会困难到不可能,那么这可以在大脑学习、适应和成长的过程中刺激和吸引你。如果一款游戏做到了这一点,那么玩家将进入神经科学中所谓的心流状态——一种我们完全专注于手头任务的精神状态,以至于我们周围的时间似乎都在膨胀和变慢。脑成像研究显示,大脑在这一点上的工作方式发生了一些令人着迷的变化;它进入了一种被称为额叶功能减退的状态,在这种状态下,大脑的额叶区域受到抑制,我们开始纯粹凭本能行事。这种平衡如图 10-4 所示。
图 10-4。
For your players to be engaged and have fun, your difficulty curve must perfectly match their level of ability
你可能在某个时候经历过这种情况,在一场子弹地狱射击中,你恍惚地在屏幕上的数百颗子弹周围跳舞,或者在一场激烈的 boss 战斗中,你只剩下一条命了。
大脑参与其中是因为它在学习和成长,当你回到早期水平,发现新的肌肉记忆使以前看似不可能的挑战变得容易时,你可以感受到这种劳动的成果。
作为一名游戏设计师,你的工作是确保挑战的强度随着玩家技能和经验的提高而完美提升,最终目标是让他们保持在最佳状态。更好的是,你应该给你的游戏深度,以便他们可以回到早期水平,并使用他们的新技能获得更好的时间或找到隐藏的收藏品。
(也就是说,节奏也很重要,你确实需要给球员提供偶尔的喘息空间,以便他们能够恢复。)
让你的游戏变得有趣的其他方法
所以,当玩家在游戏中前进时,你要不断地教他们,并保持挑战的公平性和回报性,这是非常重要的。
但这并不是让你的游戏变得有趣的唯一方法。另一个有用的工具是多样性。在现实世界中刺激心流状态的一种方法是把一个人放在一个新奇的环境中,这是我们可以利用的优势。当周围环境不熟悉时,大脑会醒来并集中注意力,因为这再次代表了一个学习的机会。
这就是为什么您应该不断引入新的机制并升级您的环境。这也是为什么在游戏中看到“雪级”和“火山级”如此常见的原因。当然,你可以更有创造性,但最重要的是你要不断改变调色板和色调。这创造了一种发现的感觉,并鼓励你的球员想要不断向前推进。
解谜是玩家在游戏中喜欢的另一个比喻。再一次,这是一种神经上的回报,来自于那个“发现”的时刻,来自于让一切都到位。
那么如何设计一个好的拼图呢?答案是在你的游戏中引入一些元素,然后让玩家寻找新的方法来组合和使用这些元素。所以,你用来爬壁架的盒子变成了你可以扔向敌人的武器(见图 10-5 )。这需要横向思维,并挑战大脑克服功能固定性——只在最初介绍的背景下查看对象和元素的诱惑。
图 10-5。
Tee hee!
增加谜题挑战性的最好方法是逐渐增加玩家解决谜题所需的步骤数。
最后,奖励你的玩家并进一步吸引他们的一个很好的方法是让他们以某种方式对他们周围的世界产生影响。这通常与你游戏的中心挂钩有关——使你的游戏与众不同并允许你的角色以独特的方式在世界中导航的机制。如果这种机制碰巧让玩家看到他们对周围环境的影响,那么这将有助于让他们感到更强大,从中可以获得很多乐趣。这就是为什么像《愤怒的小鸟》或《正义事业》这样的游戏本质上是围绕着造成大量破坏而展开的。它让玩家感到强大。其他游戏如 Godus 更进一步,让玩家扮演上帝。
也就是说,削弱玩家创造紧张、孤立和危险感的能力是增加他们注意力和注意力的好方法,并使他们的胜利更有回报。这一点在《地狱边缘》这样的游戏中得到了完美的体现。
紧急游戏
其他需要考虑的是你的游戏中你无法设计的方面。你的世界将是一个不断变化的排列系列,它将基于随机事件和你的玩家的行动。你不能预测每一个单一的场景,这意味着一些游戏的可能性将超出你的控制。但这并不是一件坏事。其实是一件很棒的事情。这就是紧急游戏是如何诞生的:当你创造的元素以意想不到的方式相互作用,为玩家创造新的挑战和独特的情况。例如,如果 Roborat 能够触发下落的方块,在正确的情况下,这可能会导致玩家和老鼠跳过下落的碎片。紧急游戏是惊人的,因为它给每个玩家自己独特的故事来讲述,并确保每个游戏环节都是不同的。你只需要创造元素,在一个大锅里搅拌它们,然后等待奇迹发生。
硬件、游戏引擎、格式和游戏性之间的相互作用
在第五章中,我提到你创造的游戏物理和元素将与游戏性和挑战密不可分。我的意思是,这里有一个双向互动,在你的设计过程中必须加以考虑。你对游戏世界的运作所做的决定将会对你的游戏方式和可能的挑战产生直接的影响。例如,你在一个表面上增加的摩擦力会改变一系列移动平台带来的困难,就像我们之前看到的那样。同样,屏幕上方向键的大小和玩家自己的手指也是如此。所有这些都需要在创建一个具有挑战性的序列和设计游戏物理的时候考虑在内。
正如我们将会看到的,这种双向关系要深入得多....
创造一个伟大的相机
游戏机制和你的游戏引擎的编程如何交叉的一个最好的例子可以用相机来看。
现在,你的Camera
非常简单:它是Player
的孩子,因此以与玩家完全相同的速度移动。你可能没有考虑这么多,但如果你现在回到你最喜欢的平台游戏,你可能会注意到这不是大多数游戏的行为方式。
例如,我们在早期马里奥的例子中看到,摄像机从玩家的右边开始,指示他们应该移动的方向。这也是你在任何“无止境奔跑游戏”中看到的相机位置,在这种情况下,相机采取这个位置是为了确保玩家有很多机会看到即将到来的障碍,因此有更多的时间做出反应。在这种类型的游戏中,玩家不能向后跑,那么在他们的左边有很多无用的空间有什么用呢?
游戏的节奏越快,为了展示更多即将发生的事情,摄像机应该越往后拉,FOV(视野)越宽。
在有很多平台的游戏中,防止恶心是很重要的。在这种情况下,相机有时会在中心有一个中立区,玩家可以在其中移动,然后只有当他们离开这个中心时才能滚动。其他平台解决这个问题的方法是让相机“捕捉”到角色在任何给定时间接触的平台。
在图 10-6 中,黑色方框表示我们的中立区。上下空间很大,左右空间就没那么大了。因此,如果玩家向左或向右移动,相机会很快跟踪,只有轻微的延迟(这意味着他们在躲避障碍时会感觉更快)。然而,会有更多的空间让玩家上下跳动,而不会让相机疯狂地上下摆动。这将有助于更少的垂直水平设计,有许多跨越间隙的跳跃。看到相机行为是如何反映关卡布局的了吗,反之亦然?这是在 2D 刺猬索尼克游戏中看到的相同类型的相机行为,这实际上是至关重要的,因为在那些游戏中有许多小山和梯度。如果摄像机只是简单地跟随音速,它会不停地上下移动到令人作呕的程度——尤其是在这样的速度下。
图 10-6。
A different approach to our camera
在其他情况下,相机可以用来产生戏剧性的效果——暗示前方的危险,或者在玩家接近一个大挑战时放慢速度。如果镜头停止向前移动,玩家会立刻怀疑他们是否应该继续,并开始想知道他们视野之外是什么(FOV)。
所以,如果你的游戏设计不像你想象的那样,考虑一下你想象的世界和相机的运动之间是否有适当的协同。通过在你的相机中编码一些更高级的行为,甚至只是稍微向后移动,游戏会变得更有趣吗?
硬件和业务模式
在你的游戏设计中,不仅仅是物理和代码定义了什么是可能的,什么是有趣的。它也是你瞄准的硬件和你想要使用的商业模式。
要了解硬件和货币化如何直接影响游戏的玩法,只需看看你当地的游戏机就知道了。街机游戏通常非常困难,而且有生命系统,因为他们希望玩家投入更多的硬币。同样,它们必须易于学习,掌握起来具有挑战性,这样人们才会不断回到高分排行榜的首位。
当游戏迁移到 PC 上时,它们开始变得更加复杂和错综复杂。随着保存文件和更强大的硬件的引入,它们变得更加精细。
有意思的是,手机游戏把事情又往后退了一点。小屏幕上的移动游戏本身更适合“一口大小”的游戏(见图 10-7 ),而“免费游戏”等替代货币化选项的引入意味着游戏需要再次激励我们保持支出。
图 10-7。
Breath of the Wild works well as a portable game due to the ability to so easily dip in and out
与此同时,在线功能意味着“高分”名单再次变得更加重要。这里的要点是,你的游戏中不应该包含任何东西,因为“游戏就是这样做的。”每件事都应该有一个目的,而这个目的将由多个不同的因素来定义。
无论你是想创建一个针对平板设备的一次性支付的“坐下来”游戏,还是为休闲游戏玩家创建一个免费的无休止的跑步者,都将彻底改变你的关卡设计方式。这意味着在你开始设计第一关之前,你需要对你的整个游戏有一些概念。
想想你就要开始把东西丢在某个地方。
让你的游戏看起来棒极了
虽然游戏性可以说比外观更重要,但两者兼而有之仍然非常重要。我们已经看到,游戏中的图形会对游戏的运行方式产生影响;图形可以传达一种场所感,并为互动提供线索。与此同时,虽然,它将是截图和游戏镜头比任何东西都更有助于你出售你的游戏。
换句话说,是时候给我们构建的游戏添加一点色彩了。我们有哪些方法可以改善它在照片中的表现?
让你的游戏更吸引人的简单方法
如果你看看我们创造的东西,公平地说,目前它并不那么有吸引力。它看起来还不太像一个职业游戏,这就是我们想要解决的问题(见图 10-8 )。
图 10-8。
The current look
但是到底哪里出了问题?少了什么?
第一个问题是,一切都是非常无机的。这些平台是由直线构成的,而且都是一样的。改变这种情况的一个快速方法是将它们中的一些旋转 90 或 180 度。这是重用相同素材和保持较小文件大小的有效方法,但它仍然会给外观带来一些变化。同样,我们应该考虑在平台的边缘使用一些更细致的精灵。这将给出自然腐烂的效果,并迅速使事物看起来更真实。
我们可以添加更多的细节,就像我们之前使用的藤蔓,让每一块土地看起来都有点不同。基本上,我们希望一切看起来尽可能随机,我们可以用一点代码来实现。
图 10-9 好看多了。
图 10-9。
Not much has changed, but it looks slightly more organic
事实上,我们游戏世界的另一个问题是它是静态的。看看你的窗外,你会发现有东西一直在动,不管是风中吹动的树枝,还是从管道滴落的雨水。最好的游戏也是如此,这就是为什么几乎所有的东西都是动画的,从背景中的花朵到星星。这不仅会让你的世界充满活力,还会给你的游戏带来更多的特色和个性,让它看起来更有趣。
然而,有一个分界点。我们不想让我们的球员在重要的元素上分心。
当然,目前我们在游戏中缺少动画。你将会想要给你的坏人动画和你的玩家动画做像跳跃或射击的事情。这是帮助玩家感受到他们正在与世界互动的另一种方式。当弹簧被弹开时,它会摆动。
最后一个问题是游戏缺乏深度。我们设计的背景非常平坦,在到达云层或太阳之前缺乏趣味性。这让你感觉一切都是从纸板上切下来的,所以你应该通过增加几层来改善它。
在图 10-10 中,我在前景添加了一些透明的云彩,当关卡滚动时,它们会比中间的地面移动得更快。我还在背景中添加了一些,并引入了一层山脉。这些山脉以另一种速度移动,它们有助于确保我们的游戏世界永远不会在背景中看起来完全空白。
图 10-10。
With these few changes, our game is starting to look more interesting
如何创建好看的精灵并为你的游戏选择设计语言
虽然添加这些元素可以改善游戏的外观和感觉,但它们都要求你在创建自己的精灵时具备一些基本技能。如果你身体里没有艺术的骨头,你会怎么做?
一种选择是外包你的作品。像 Fiverr、Freelancer 和 UpWork 这样的网站可以让你与提供包括艺术和设计在内的广泛服务的自由职业者建立联系。这些也是获得背景音乐和音效的好地方。
选择二是制作一个风格化的游戏,使用独特的艺术风格,大大减少你需要做的工作量。如今很多游戏使用黑白艺术风格、轮廓(就像前面提到的地狱边缘),或者各种复古外观(就像在 VVVVVV 中看到的,它看起来像是根据 ZX 光谱设计的)。图 10-11 向我们展示了如果我们的游戏是为一个游戏男孩设计的,它会是什么样子。
图 10-11。
Retro-style Kevin
使用像这样的特定艺术风格可以让你的游戏在 Play Store 中脱颖而出并吸引眼球,同时也给它一个强大的身份。如果你选择一些最简单的东西,你也会为自己节省很多时间,并且不再需要在设计上精益求精。
对于我们的游戏,我们采用了像素艺术风格。这是另一个复古风格的外观,让我们的游戏有一种怀旧的感觉,让我们不再需要创建逼真的精灵。
那么如何实现这种风格呢?答案很简单:使用任何图像编辑软件,如 GIMP 甚至 MSPaint,然后尽可能放大。如果可能,请在设置中选择“显示网格线”。现在,使用 100%不透明度的铅笔工具,你可以开始为你的精灵绘制轮廓了。您应该能够在绘图时看到单个像素。
当你画精灵的时候要花时间和精力,并确保留意任何形成的图案。例如,如果你正在画一个渐变,你可能会注意到像素向上移动一个,每次移动三个。这将导致一些看起来更加一致和可控的东西。幸运的是,如果你犯了任何错误,你可以按 Ctrl+Z。另一个技巧是考虑使用可用的图层(GIMP 和 Photoshop 提供这个功能,但 MSPaint 没有),这样就可以描绘出你想要变成像素艺术的图像。
图 10-12。
An early app I made used a pastel color palette and a Sudoku-inspired look: Debugger: Brain Untraining
你可以勾勒出你的精灵或者你可以使用颜色块。我也推荐加底纹。这通常意味着你将使用三种颜色:一种用于主要填充,一种用于阴影,一种用于高光。确保你的所有精灵在同一个场景中的阴影都在同一边——否则会看起来很混乱,因为不清楚光源在哪里。
最后,导出你的图片。现在,你可能会发现当你这样做时,它看起来很小,但当你把它导入 Unity 并设置单位像素和比例时,这是可以解决的。把一个小图像放大,像素真的会变大。
最佳化
这一章是关于如何把你的功能游戏变成一个令人敬畏的游戏。为此,我们的议程上还剩下一个项目:优化。我们已经看了表面的细节——现在我们需要再看一看内在的东西。
首先,我说的优化到底是什么意思?本质上,我说的是让你的游戏运行流畅,易于编辑、改进和更新。好的代码应该使用尽可能少的行,一切都组织得井井有条,这样你就很容易找到你需要的任何元素。
tipsForBetterCode
无论何时你写代码,你都需要着眼于未来。有一天,你会想要更新你的游戏来修复一个 bug 或者增加一个新的功能(这在移动设备上也很常见),并且在离开一段时间后会回来。在理想的世界里,这应该是一种无痛的体验。一切都很容易理解,你不需要花时间眯着眼睛看屏幕。你应该知道一切都在哪里,你需要改变什么才能达到你想要的结果。如果你在团队中工作,这变得更加重要。
如前所述,更好的代码也意味着更少的代码。页面上的代码越多,就越难找到你要找的东西,每个过程可能需要的步骤也越多。步骤越多=执行越慢。
那么如何开始制作更优雅的程序呢?以下是一些帮助你开始的建议:
将多个变量放在一行:
public float startX; public float startY;
变成
public float startX, startY;
一定要确保使用合理的名称来描述变量的功能。这听起来很明显,但是你会惊讶程序员使用完全随机标签的频率。如果你的变量告诉角色他们应该跳多高,它应该被称为类似于
jumpHeight
的东西。这也意味着要避免缩写(jh
),缩写会很快变得生硬和令人困惑。事实上,理想的情况是你的变量让你的代码读起来像英语。特别是在使用布尔值时,它可以是真或假,这意味着你可以创建这样的行:if (playerIsGrounded) {
这告诉了我们需要知道的一切,即使我们不知道一行编程。
使用驼色外壳。这意味着变量中的每个新单词都以大写字母开头,以便帮助读者分解它(有时这不包括第一个单词)。比如:
jumpheight
要么写成jumpHeight
要么写成JumpHeight
。这不仅进一步提高了可读性,而且当您在检查器中查看变量时,您还会看到 Unity 将这些变量分解为单个单词。避免使用“神奇数字”换句话说,不要通过赋予一个数字随机的重要性来规避编码挑战。我在第九章中这样做了,当时我使用了一个计时器,这个计时器对下落的砖块计时超过了零。计时器到达–70 时停止。为什么减七十?避免这种情况的一种方法是使用常量。常量是一种具有固定值的变量,一旦定义就不能更改。这没有内存开销,其主要目的通常是为了易读性。例如,我们可以创建一个值为–70 的常量整数,并将其命名为
endOfFallAnimation
。现在我们的滑车将停在endOfFallAnimation
而不是–70。更有意义!还记得我们的Player
脚本和它用 1 代表“右”,用 0 代表“左”吗?如果你从代码中抽出一段时间,然后再回来,这也可能会很混乱。所以为什么不用这个来代替:const int left = 0, right = 1;
现在我们可以说
if (facing == right)
,这对于我们来说更容易回读。(然而,这在检查器中仍然显示为 0 和 1。)使用常量的另一个优点是,如果以后需要修改,搜索和替换值要容易得多。
描述为什么而不是什么。写注释时,描述方法的目的比描述方法的作用重要得多。这个功能的相关性是什么?它和剧本的其他部分有什么关系?
尽可能避免重复编写相同的代码。可以放在不同方法中的代码越多,就越容易快速找到要找的内容,并且需要键入的内容也越少。使用方法还允许您将整个代码块从一个脚本复制并粘贴到下一个脚本。
使用循环!循环是一段不断重复的代码,直到满足或中断某个条件。比如一个
while
循环看起来是这样的:int count = 1; while (count <= 4) { count = count + 1; }
这只是数到四然后停止,但是我们可以用这个结构执行同一个命令四次。然而实际上,对于使用增量变量的循环,使用
"for"
通常是有意义的。这是一个用更少的代码行完成同样事情的例子。一个for
循环看起来是这样的:for ( init; condition; increment ) { statement(s); }
无论你使用哪种类型的循环,它们都有类似于方法的作用,帮助你将代码分段,防止你重复编写大量的函数。
使用智能标记和层。就像你需要对变量命名惯例有所了解一样,在 Unity IDE 中你也需要对你所分配的名字有所了解。到目前为止,你已经知道使用正确的养育方式和创建预设,而不是处理实例。
性能和兼容性
前面的技巧将有助于使你的代码更有逻辑性和可读性,在某些情况下还会更快。不过,实际上,速度方面的主要瓶颈将在您的脚本之外。
较小的图像
例如,你需要确保你使用的图片不是太大。图像越大,应用的文件就越大,加载时间也越长。我想告诉你,你的应用的大小并不重要,但这是一个谎言:当 APK 尺寸变得太大时,我个人已经收到了来自我自己用户的多个负面评论,所以这是人们真正关心的事情。
注意,如果有必要,你可以显示一个加载屏幕,从一个协同例程中加载场景(就像我们在Player.Death
方法中使用的那样),然后在旧场景上显示一个加载 UI。然而,我们仍然希望加载时间尽可能短,所以你应该避免在你的场景中粘贴不必要的大图片。这是选择像素艺术风格很有意义的另一个原因:它让你保持较小的文件大小,然后放大它们,而不必担心像素化。选择正确的图像压缩方式(JPG 图像格式,而不是 PNG 格式,这样可以降低一点质量)也会有所帮助。重用素材也是如此,这就是为什么之前旋转磁贴是一个好的举措。
Unity 将在您构建 APK 时为您的图像添加额外的压缩,您可以在构建设置中设置想要使用的纹理压缩类型。这种额外的压缩会影响应用的速度和大小,还会影响它的兼容性以及它是否支持 alpha(透明度)。来自 Unity 自己的文档:
| 纹理格式 | 纹理使用了什么样的内部表示。这是尺寸和质量之间的权衡。 | | RGB 压缩 DXT1 | 压缩的 RGB 纹理。由 Nvidia Tegra 支持。每像素 4 位(256 x 256 纹理为 32 KB)。 | | RGBA 压缩 DXT5 | 压缩 RGBA 纹理。由 Nvidia Tegra 支持。每像素 6 位(256 x 256 纹理为 64 KB)。 | | RGB 压缩等 4 位 | 压缩的 RGB 纹理。这是 Android 项目的默认纹理格式。ETC1 是 OpenGL ES 2.0 的一部分,受所有 OpenGL ES 2.0 GPUs 支持。它不支持 alpha。每像素 4 位(256 x 256 纹理为 32 KB) | | RGB 压缩 PVRTC 2 位 | 压缩的 RGB 纹理。由 Imagination PowerVR GPUs 支持。每像素 2 位(256 x 256 纹理为 16 KB) | | RGBA 压缩 PVRTC 2 位 | 压缩 RGBA 纹理。由 Imagination PowerVR GPUs 支持。每像素 2 位(256 x 256 纹理为 16 KB) | | RGB 压缩 PVRTC 4 位 | 压缩的 RGB 纹理。由 Imagination PowerVR GPUs 支持。每像素 4 位(256 x 256 纹理为 32 KB) | | RGBA 压缩 PVRTC 4 位 | 压缩 RGBA 纹理。由 Imagination PowerVR GPUs 支持。每像素 4 位(256 x 256 纹理为 32 KB) | | RGB 压缩 ATC 4 位 | 压缩的 RGB 纹理。由高通骁龙支持。每像素 4 位(256 x 256 纹理为 32 KB)。 | | RGBA 压缩 ATC 8 位 | 压缩 RGBA 纹理。由高通骁龙支持。每像素 6 位(256x256 纹理 64 KB)。 | | RGB 16 位 | 六万五千种没有 alpha 的颜色。使用比压缩格式更多的内存,但可能更适合 UI 或没有渐变的清晰纹理。256 x 256 纹理需要 128 KB。 | | RGB 24 位 | 真彩色但没有 alpha。256 x 256 纹理需要 192 KB。 | | 阿尔法 8 位 | 高质量的 alpha 通道,但没有任何颜色。256 x 256 纹理需要 64 KB。 | | RGBA 16 位 | 低质量真彩色。带有 alpha 通道的纹理的默认压缩。256 x 256 纹理需要 128 KB。 | | RGBA 32 位 | 带有 alpha 的真彩色-这是带有 alpha 的纹理的最高质量压缩。256 x 256 纹理需要 256 KB。 | | 压缩质量 | 选择“快速”可获得最快的性能,“最佳”可获得最佳的图像质量,“正常”可在两者之间取得平衡。 |碰撞
如果你在 Unity 中制作 2D 游戏,性能应该不是一个大问题。除非你在屏幕上有无数的元素,都在运行复杂的动画和脚本,否则大多数 Android 手机将能够处理你扔给它们的大多数东西。
但这并不是说尽可能降低应用的要求没有好处(例如,考虑电池消耗和将其他应用保存在内存中),而且你肯定希望避免应用变得无响应的任何机会。
那么,在运行时,需要考虑的最重要的事情之一就是你有多少个碰撞器。对撞机的大小并不重要,但问题是对撞机的数量和这些对撞机的复杂性。例如,我们的瓷砖使用单独的碰撞器,这使得开发更容易,并允许我们使用预置。这在很大程度上是我们的最佳实践,因为我们添加未来更新的灵活性和方便性远远超过了性能成本。参见图 10-13 。
图 10-13。
I have drawn a single box collider around a bunch of tiles here
你可以用碰撞器使积木成为孩子,并把它们保存为一个预置,以便快速地在你的游戏中实现它们。或者,你可以简单地用他们自己的更大的碰撞器画更大的平台盒子。
请记住,表面下的瓷砖实际上不需要碰撞器。从这里移除碰撞器可能是让我们的应用运行得更好的最快最简单的方法之一。
比拥有许多小型碰撞器更糟糕的是使用具有许多不同点和角度的复杂多边形碰撞器(见图 10-14 )。这为 Unity 创造了更多的数学,因为它需要计算出每个点如何与它碰到的表面相互作用。这就是为什么对你的角色使用一个长方体碰撞器(或者是一个稍微变形的长方体的多边形碰撞器)比使用一个完全符合角色轮廓的多边形碰撞器更有意义。
图 10-14。
An overly complex collider
即使是图 10-14 中过于复杂的碰撞器也不太可能导致任何明显的减速,但是如果你有很多这样的碰撞器,事情可能会变得有点不稳定。归根结底,这是一种浪费,因为它不会对玩家实际玩游戏的方式产生任何有意义的影响。
制作其他类型的游戏
在这一章中,我们已经讨论了很多关于游戏机制、设计和硬件之间的相互作用。但到目前为止,我们还没有真正考虑我们正在开发的平台的性质。
毕竟,该平台可追溯到 NES 和其他早期计算机,并不自然地适合移动设备的触摸屏输入。Android 平台游戏肯定还有市场,这是一个特别好的教程选择,因为它允许我们尝试许多不同的概念。
但是如果你想涉足一个更适合手机的类型,你可能会选择开发一个无限跑者。除了一个关键的不同,这看起来和行为都像一个平台玩家:玩家不断向前跑。很好的例子包括 Canabalt、Sonic Runners、Super Mario Run、Temple Run 和 Jetpack Joyride。在这里,玩家只需要一个输入——跳转——就可以清理屏幕(不再有箭头键遮挡游戏空间),并提供完美的快速进出游戏。
要使您构建的程序成为无限运行程序,您只需修改Player
脚本,使其自动向前运行。然后你可以在设计关卡时考虑到这一点,或者如果你想让关卡真正“无限”的话,让你的关卡自己动态生成(称为过程生成)。这意味着你需要引入一种算法来随机实例化新的平台(并且很可能破坏旧的),同时确保玩家总有一条穿越的路线。使用更大的平台瓷砖通常是一个好主意,当然你需要难度和速度来逐渐增加。
你同样可以把重力从物理引擎中移除,把它变成某种太空射击游戏,甚至是自上而下的游戏。
益智游戏和更多
Android 平台上的平台游戏、第一人称射击游戏和赛车游戏的潜在问题是,它们本质上涉及到在新硬件上改造旧游戏类型。相反,可以说最有创造性和最有趣的 Android 游戏是那些找到新方法来利用硬件的游戏。
《愤怒的小鸟》就是一个很好的例子,因为它以一种非常自然的方式利用触摸屏来开辟新的游戏可能性。房间和纪念碑谷甚至更进一步,让玩家通过伸出手触摸、扭转和拖动各种元素,甚至有时包括倾斜控制,直接与游戏世界互动。还记得我们说过玩家喜欢感觉他们正在影响游戏世界吗?
你可以让你的游戏像这样简单地使用手机的加速度计:
rb.velocity = new Vector2(Input.acceleration.x, rb.velocity.y);
如果倾斜手机会导致敌人和收藏品滑过屏幕怎么办?
同样,您也可以非常轻松地使用多点触控,这开启了一系列其他可能性:
void Update ()
{
Touch myTouch = Input.GetTouch(0);
Touch[] myTouches = Input.touches;
for(int i = 0; i < Input.touchCount; i++)
{
//Do something with the touches
}
不要被“游戏”必须是什么的旧观念所限制。你可以设置任何条件来结束这一关,无论是让一个球滚到一个像触发器一样的目标上,还是当玩家收集到屏幕上的每一个硬币时进行计数。甚至根本不需要有一个“玩家”对象——看看俄罗斯方块就知道了,这是最早的移动热门游戏。
哦,说到不同种类的手机游戏,我在下一章为你准备了一些令人兴奋的东西。首先,我们将讨论如何为 Android 创建一个具有逼真图形的 3D 游戏(是的,你可以做到)。然后,我们将讨论如何使用三星 Galaxy Gear 或谷歌的 Daydream View 耳机进入那个世界。
对于移动开发者来说,这是一个令人兴奋的新领域,我们将确保你正处于这一浪潮的顶峰。
图 10-15。
Now that's starting to look like a game I want to play!
十一、3D 游戏开发和虚拟现实导论
第十章最后讨论了如何使用 Unity 创建其他类型的游戏,如谜题、无限跑者等等。所有需要做的就是从我们简单的平台上调整一些脚本和游戏对象。你可以改变目标、输入法和整个体验。你甚至可以用这种方式制作工具。
但是还有另外一类游戏我们还没有解决,这需要一种完全不同的方法:3D 游戏。虽然 3D 控制在小触摸屏上有时会很困难,但是有很多游戏已经成功地绕过了这个限制;有些已经变得非常受欢迎。热门 3D 游戏包括 N.O.V.A .系列、沥青 8:空降、几何战争 3 和《我的世界》:口袋版等等。
更令人兴奋的是,一旦你学会了创建 3D 游戏所需的技能,你就可以开始为 Galaxy Gear VR 和谷歌 Daydream 耳机创建自己的虚拟现实体验。
创造一个 3D 世界
要开始,你需要做的第一件事是为你的角色创建一个 3D 环境来开始探索。和凯文说再见可能感觉怪怪的,但是是时候向前看了。
点击文件➤新项目但这次选择开始一个 3D 项目(图 11-1 )。然后你会看到一个类似的设置,除了视角是 3D 的。视角的改变也影响了网格,现在可以从上方以一个角度观察网格(图 11-2 )。
图 11-2。
The Unity you know and love, now in 3D
图 11-1。
Create your new 3D project
UI 的行为基本上和以前一样。主要区别是在场景窗口的右上角包含了透视小部件。点击此按钮,您可以将视图的角度更改为自上而下(图 11-3 )、侧面等。起初,处理 3D 对象可能会很棘手,您会发现自己一直在与透视做斗争。一个快速提示是,没有什么可以阻止你打开多个场景窗口:右键单击任何选项卡,然后选择添加选项卡➤场景。这样,您可以拥有多个窗口,每个窗口都有不同的视角(自上而下、侧面等等)。但是像以前一样,由您来决定哪种设置最适合您的工作流程。
图 11-3。
This angle can be useful for aligning ground tiles for instance
否则,您仍然可以使用手形工具拖动视图,或者像以前一样使用滚轮放大视图。
不过,我们要做的第一件事是将一个 3D 物体放入这个世界。导航到游戏物体➤三维物体➤平面。你猜对了,这将把一个 3D 平面放到场景中(见图 11-4 )。然后我们就可以通过检查器来改变这个平面的大小,移动它,或者改变它的属性,就像我们之前处理 2D 精灵一样。
图 11-4。
Let there be a plane
现在在游戏中加入另一个元素。这次来个立方体怎么样?点击 GameObject ➤ 3DObject ➤立方体,你的场景中就会出现一个立方体(见图 11-5 )。
图 11-5。
And now a cube
到目前为止,很简单。但是点击播放,你会看到立方体只是无限期地悬浮在空中。和以前一样,我们需要应用一些物理知识。所以选择立方体,然后添加组件➤物理➤刚体。注意这次没有 2D 后缀。Unity 最初是专门为 3D 开发而开发的,所以“默认”脚本和对象都是 3D 的,不需要明确说明事实。
一旦游戏对象被添加,他们已经有一个网格碰撞器附加,所以如果你现在按下播放,立方体将下降到平面上,并停止在其轨道上。太棒了。
精灵和天空盒
为了让这个世界看起来更好,你可以在你的素材目录中创建一个名为纹理的子文件夹,然后放一些精灵进去。现在把精灵从那里拖到你的游戏对象上,在检查器中展开着色器菜单。在这里你可以设置纹理重复的次数(平铺)以及反射率(金属)、平滑度等等。
在图 11-6 中,我使用了游戏 2D 版本的精灵。
图 11-6。
A crate over some dirt
现在我们想为我们的世界添加一个更好的背景。为此,我们需要创建一个灯箱。在项目中找到 Materials 文件夹,然后在任意位置单击鼠标右键,创建一个名为 Sky 的新材质。在检查器中,使用下拉菜单将着色器设置为天空盒,然后选择程序。这份遗嘱;改变背景中天空的颜色和外观,因此设置大气厚度、曝光、阳光大小等等,就像你想要的那样。我打算在黄昏时把我的场景设在一个海滩式的地方。
现在选择窗口➤照明,并使用场景选项卡来设置天空框为你刚刚创建的天空材质。你应该立即看到你的游戏变化的样子(见图 11-7 )。注意,如果你选择 6 面作为天空盒,你可以使用任何你想要的纹理作为背景。
图 11-7。
Setting up some mood lighting
添加玩家
准备好再次对 Unity 印象深刻吧:在我们的游戏世界中添加一个玩家非常容易,因为有另一个现成的素材可以帮助我们做到这一点:FPSController。这为第一人称玩家处理控制、物理和更多。
右键单击“素材”文件夹,然后选择“导入包➤字符”。保持所有选择不变,点击导入。这将需要几秒钟的时间,但是一旦完成,您将在项目中有一个名为 Standard Assets 的新文件夹,其中将有各种子目录。我们现在感兴趣的是第一人称角色。稍后,为了减小项目的大小,您可以选择仅导入您需要的元素。
找到标准素材➤人物➤第一人称人物➤预置,然后选择 fps 控制器。你现在已经知道什么是预置了,所以你可能已经猜到这本质上是一个现成的 FPS 角色,我们可以把它放到我们的场景中。这样做,确保角色在地面上,然后删除多余的Main Camera
游戏对象。然后点击播放。你应该有类似图 11-8 的东西。
图 11-8。
A strangely serene view…
这是圣诞奇迹!很容易,我们就有了一个 FPS 游戏。你可以用鼠标四处查看,用 W、A、S 和 D 键查看,用空格键跳跃。甚至还有行走的声音效果,你可以和盒子互动来推它。若要退出,请按 Esc 键,然后您可以将鼠标指针向上移动到停止按钮。
如果您想尝试,请恢复Main Camera
,删除 FPSController,并加入第三个 PersonController。这是一个更详细的 3D 对象,有复杂的动画,但没有着色器,它可以用相同的控件移动,所以你可以感受一下创建 3D 平台的感觉。尽管如此,这种素材组合使得游戏看起来相当怪异(图 11-9 )。
图 11-9。
If Damien Hirst made computer games …
目前,我们将继续使用 FPSController。
触摸控制
准备好再次被打动吧:在 Unity 中添加触摸控制也是一样简单的。再一次,我们有一个现成的预制构件;这个叫做 DualTouchControls。这在 CrossPlatformInput ➤预设中,你需要做的就是把它放到你的场景中,然后添加一个事件系统。您还需要在构建设置中将平台切换到 Android,这样才能工作。
通过使用画布并锚定到屏幕上,您可以在屏幕上随意设置这些触摸区域,就像您之前所做的一样(图 11-10 )。一旦准备就绪,尝试在智能手机上构建并运行该应用,看看实际使用起来是什么样的。如果一切都按计划进行,你应该可以通过拖动屏幕的左侧来四处走动,同时使用屏幕右侧的隐形触摸板来查看。底部的横杠是用来跳跃的。你可以在图 11-11 中看到它们是如何组合在一起的。
图 11-11。
Not exactly easy controls, but controls nonetheless
图 11-10。
Set up touch controls on the canvas as you want them to appear
请注意,一旦您将平台设置为 Android,游戏将停止响应您的键盘和鼠标输入。所以你可能想在开发的时候把它换回来。
使用 3D 模型
当我们在构建我们的 2D 平台时,我们会不时地从编码中抽出时间来创建某种精灵。这让我们的游戏世界变得栩栩如生,当然,我们不希望将我们的 3D 努力局限于简单的立方体和球体。
然而,现在我们将把 3D 模型添加到我们的游戏世界中,而不是精灵,这将为我们创造无限的机会。Free3D.com 是一个提供大量免费 3D 模型供你下载并放入游戏的网站,包括家具、怪物、野生动物等等。当然,你也可以在素材商店里找到很多,而且很多都是 Unity 免费提供的。一定要确保无论你使用什么,你肯定许可延伸到商业使用——如果你计划出售你的最终创作,那就是。
利用 Free3D.com 的资源,我用 3D 技术重建了我的客厅(见图 11-12 )。我妻子和我打算在挑选之前用它来试试墙壁的颜色。不过,它需要先做一些工作。
图 11-12。
My living room,or near enough. I am a SIM apparently
请原谅我在等级制度上的拙劣组织。这只是一点乐趣。
不要客气!请记住,在您完成应用之前,务必确保您在法律上被允许使用、分发和从任何潜在的版权作品(如 3D 模型)中获利,这一点非常重要。
想要制作自己的 3D 模型吗?最好的方法是使用名为 Blender 的免费软件。这很难掌握,但是一旦你知道你在做什么,几乎任何事情都是可能的,你甚至可以开始创作动画。不知不觉中,你就要为皮克斯工作了。
图 11-13。
But which is the real world?
另一个新领域
或者,你可以用另一种方式来创造一个稍微有点不同和更有趣的世界,那就是使用 Unity 的一些内置功能来创造郁郁葱葱的自然景观。
开始一个新场景或新项目,这次选择游戏对象➤ 3D 对象➤地形。迎接你的将是另一片平原,尽管这一片比之前的要大得多。不过,地形真正的巧妙之处在于,它可以让你插入山脉、丘陵、树木等等,从而创造出看起来更加有机和自然的东西。
您需要为此导入另一个包。单击素材➤导入包➤环境。在这里,你可以找到树、草和其他各种环境物品供你玩耍。
现在选择场景中的地形对象,你会注意到检查器中一些有趣的图标。这些以山、画笔、树木等为特色。尝试点击带有山脉和向上箭头的图标(见图 11-14 ),然后在地形上拖动鼠标指针——根据您施加的速度和压力,看起来相当自然的山脉开始爆发。
图 11-14。
These are fun
您也可以尝试在风景上绘制树木,首先选择您想要添加的树木类型(这是这些资源派上用场的地方),然后以不同的密度绘制它们。您也可以使用画笔工具绘制纹理,同样是在首先从资源中选择看起来像零件的东西之后。试一试,你会很快意识到你可以多快地创造出一个渴望被探索的世界。参见图 11-15 。
图 11-15。
Breath of the Wild, eat your heart out
添加枪
几乎每一个第一人称游戏都涉及到某种形式的射击,那么你会如何添加一把枪呢?
这其实很简单,基本上涉及到你已经熟悉的与 2D 共事的技巧。你应该熟悉一些变化。我们可以从创建/定位一把枪的 3D 模型开始,然后让它成为Player
角色的孩子。我们将它对齐,使它处于正确的位置,就好像角色拿着它一样。
从那里,我们将继续添加一个脚本到 gun 对象,这样它将响应鼠标点击。这将与屏幕上任何地方的点击(或 Gear VR 上的侧按钮)相对应。
然后我们会以相同的角度在枪内实例化一颗子弹:
if (Input.GetKeyDown(KeyCode.Mouse0))
{
Instantiate(blast, gameObject.transform.position, gameObject.transform.rotation);
}
子弹会有自己的脚本,以确保它在一段时间后自我毁灭,并保持前进。唯一的不同是,子弹现在是在三维空间中运动。我们将使用transform.forward
,这样子弹将会以它当前面对的任何角度向前移动(这反过来与枪相同):
public class Forward : MonoBehaviour {
private float timetodestroy;
void Start () {
timetodestroy = 3;
}
void Update () {
timetodestroy = timetodestroy - Time.deltaTime;
gameObject.transform.position += transform.forward * Time.deltaTime * 30;
if (timetodestroy < 0)
{
Destroy(gameObject);
}
}
}
从那里,我们可以使用onTriggerEnter
或onCollisionEnter
,就像我们通常会做的那样(除了 2D ),让我们的子弹爆炸物品,翻转开关,以及做任何其他事情。
正如你所看到的,这是一件相当简单的事情,就像你上次做的那样,把这个基本的设置构建成一个完整的游戏。你可以从这些说明中推断出你的人工智能,弹簧和其他东西。
步入虚拟现实
但是你知道吗?第一人称射击游戏(也称为 FPS)在使用触摸输入的手机上没有那么有趣。那么,让自己熟悉 3D 到底有什么意义呢?
当然,你可能会发现你可以制作其他类型的 3D 游戏。也许你对使用倾斜控件制作弹球游戏或赛车游戏感兴趣。或者也许你只是很固执,无论如何你都要做 FPS。国家志愿队系列赛看起来还是不错的...
但是你也可以做一个更令人兴奋的虚拟现实应用(见图 11-16 )。
图 11-16。
Developing for the Gear VR in a library in Radolfzell, Germany. Good times!
由于三星 Gear VR 和谷歌 Daydream 耳机,虚拟现实在移动设备上是一件大事。事实上,我更倾向于预测这可能是虚拟现实的未来所在(图 11-17 )。在移动设备上的采用已经是最大的了(由于与 PC 虚拟现实相关的高昂成本和技术挑战),但更令人兴奋的是,移动设备现在能够解决虚拟现实面临的最大挑战之一:位置跟踪。
图 11-17。
Is mobile VR the future?
位置跟踪指的是不只是环顾四周,而是在 3D 空间中实际起身走动的能力。跳跃、躲避、奔跑和倾斜。Oculus Rift 和 HTC Vive 使用精心设计的“由外向内”解决方案来解决这一问题,这种解决方案涉及在房间内安装传感器。Gear VR 和 Daydream 目前只提供头部追踪。
但在最近的谷歌 I/O 大会上,HTC 推出了一款“独立”耳机,它将使用一种名为 WorldSense 的东西来提供一种完全不受限制的位置跟踪解决方案,没有设置,也没有外部传感器。这是从里到外的追踪。该设备将与 Daydream 体验相关联,因此我们可以预计它可能会运行 Android,或者至少与当前的设置非常相似。
这种设备还没有上市,也不是全新的技术。这很可能是谷歌项目 Tango 的一种演变——努力创造出内置足够多传感器的手机,以便能够看到我们所看到的世界。该技术已经在联想 Phab Phone 2 中可用,并将在不久的将来应用于更多设备(当您阅读本文时,可能已经有了)。
简而言之,手机将很快拥有使用“计算机视觉”的能力,以便感知他们面前的东西以及你如何在空间中移动——从而能够跟踪虚拟现实中的移动,并确保玩家不会将自己置于任何危险之中。
但谁知道呢,也许移动 VR 不会是下一个大事情。也许 VR 根本不会腾飞。无论发生什么,我们肯定都同意,创造虚拟世界,然后步入其中是很酷的。
创建 Gear VR/Google Daydream Ready 应用
那么,我们如何将我们刚刚创建的 3D 景观之一变成一个可以在 Gear VR(见图 11-18 )或谷歌 Daydream 上运行的 VR 应用呢?
图 11-18。
Insert your head here
这实际上再简单不过了——只要它能工作,就是这样。实际上,在撰写本文时,Unity 还需要解决一些问题,所以事情并不完全顺利。试图为 Gear VR 构建应用会导致 AndroidManifest 文件无法合并的问题(这些文件包含关于你的应用的信息,如版本、名称等——见图 11-19 )。该团队承诺会解决这个问题,所以希望在你读到这篇文章的时候,一切都已经启动并运行了。对于 Google Daydream 或 Cardboard 来说,不存在这样的问题。我与你分享这些是因为这是编码的现实。有时有一个变通办法,但其他时候你不得不等待专业人士提出解决方案。幸运的是,Unity 在发布补丁方面往往很快。
图 11-19。
Gee thanks, Unity. At least the frowny face shows true remorse.
假设 Unity 玩得很好,你只需要在你的玩家偏好上做一些改变。具体来说,你会想要勾选支持 VR 的方框,然后选择 Oculus 或 Daydream 作为你的 SDK(图 11-20 )。当然,如果你愿意,你也可以试试其他的,包括纸板。Unity 会知道它需要下载和添加到插件中的东西,以使事情正常运行。
图 11-20。
Pretty simple!
为了让你的应用在虚拟现实硬件上运行,并尝试在虚拟现实中探索你的风景,这就是你需要做的全部事情。点击构建和运行,然后插入。
图 11-21。
The VR version is much tidier
获取您的 Oculus 签名文件
如果你在为 Gear VR 开发,事情会稍微复杂一点。这是因为 Oculus 对其平台有点保护,不希望人们通过 Play Store 以外的渠道分发自己的应用。因此,它引入了一个系统来禁止肆意分享应用,这是他们的“签名文件”基本上,每个 APK 只能在一个设备上工作,这将由您在构建时添加的文件来定义。
要获得 Oculus 签名文件,您首先需要获得您的设备 ID。这是你的特定硬件的标识符,所以任何时候你想在新设备上测试你的应用,你都需要再次经历这个过程。为此,导航到 PC 上 Android SDK 安装的 platform-tools 文件夹。在这里,您会发现一个名为 adb.exe 的可执行文件,这是 Android Debug Bridge 的首字母缩写。按住 Shift 键,右键单击该文件夹中的任意位置,然后选择“在此打开命令行”。在打开的 shell 中,您现在要输入 adb 设备,并插入您的 Android 设备。这将列出所有连接硬件的设备 id,如图 11-22 所示。
图 11-22。
My device ID. Don’t … steal it?
无论如何,熟悉 ADB 是一件很方便的事情,因为它有许多其他用途。
现在前往 developer.oculus.com,找到 Oculus 签名文件(osig)生成器。在写的时候,这是在 https://dashboard.oculus.com/tools/osig-generator/
。按照提示在框中输入您的设备 ID 号,然后点击下载文件(图 11-23 )。现在,您只需将该文件放入项目中的特定目录:
图 11-23。
You'll need to create a new account first
Assets > Plugins > Android > assets
是的,它区分大小写,是的,这意味着您需要一个大写的 Assets 文件夹和一个小写的版本。不,这些文件夹不会已经存在,所以你必须创建它们。
这是一个麻烦,但一旦完成,你就可以开始在自己的硬件上测试你的新虚拟现实应用,而且你暂时不需要再担心它了。
无限的可能性
创建虚拟现实内容是一门艺术,而且是一种仍在不断发展的艺术形式。在屏幕上工作良好的东西不一定能很好地转化为 VR,反之亦然。然后还有输入和恶心等问题。但事实上,这个空间是如此未被探索,这使得它如此令人兴奋。这里的可能性是无限的,你有很多机会偶然发现一些改变游戏规则的东西。
有了它,你的所有技能都有了用武之地。您将在实践中学习,并在实践中成长为一名开发人员。但我觉得你已经准备好接受这些挑战,开始寻找自己的路。我们还需要做一件事:让你的作品在谷歌 Play 商店上运行起来,让其他人也能欣赏它们。
十二、如何发布和推广你的安卓应用
你已经成功地走了这么远,现在你在最后的障碍。你知道如何制作应用,你知道如何让它们在你的智能手机上运行…见鬼,你甚至知道如何在虚拟现实中探索它们。现在剩下要做的就是发布这些应用,这样你就可以与世界其他地方分享它们,并获得你应得的赞美/报酬。
这可能是一个令人伤脑筋的过程。把你的应用发布出去会让你容易受到批评,而且总有可能会失败,一次下载也没有。但是不要让恐惧冻结你。Android 应用的伟大之处在于它们永远不会结束:你可以自由地上传更新,并在产品发布后不断迭代(更新)你的产品。没什么好害怕的:如果你第一次没有做对,你只需要不断尝试,最终将你的应用发展成人们喜欢的东西。
无论如何,你的应用的成功只是部分与它的质量有关。同样重要(如果不是更重要的话)的是你营销和撰写店铺清单的方式。我们将在这里看看如何做到这一切。
创建您的签名 APK
一旦你完成了你的游戏并且对它感到满意,你需要做的第一件事就是创建你的签名 APK。APK 是 Android 用来在其他人的手机上安装你的应用的包文件,在测试你的游戏时你已经建立了几个。
不过,到目前为止,如果你一直使用构建和运行选项,并且将播放器设置保留为默认设置,你将一直运行该应用的调试版本。为了实际发布你的创作,你需要返回到构建设置➤播放器设置,以便“签署”它,为大时代做好准备(以及做一些其他更改)。
首先,为你的应用添加一些图标。然后确保你有一个满意的包名。软件包名称是其他 Android 应用将会看到的内部文件名(如主屏幕启动器),但您的用户在大多数情况下不会看到它,除非他们获得技术支持。然而,这应该是明智的,因为你以后不能改变它。这些都可以在检查器中的其他设置标题下找到(图 12-1 ),通常的格式是包括你的公司名称,然后是应用名称。
图 12-1。
Setting a package name for the platformer
产品名称是您的用户将看到的内容(也称为标签)。这是你的品牌将去的地方,这一点以后是可变的。换句话说,这是你游戏的标题,但是如果你以后改变主意了,你也不用担心。
您的版本代码是为您和您的用户准备的。这是他们将看到的版本号,您可能希望在每次推出新版本时递增更新。错误修复和小调整可能会导致增量调整(1.0.1),但增加新功能和级别的大变化可能会导致整个版本的升级(2.0)。请注意,alpha 和 beta 产品的版本代码应该低于 1。
另一方面,捆绑版本代码是一个内部计数器,用于跟踪您的应用版本。每次你上传一个新版本的 APK,这个必须增加 1。这使得谷歌可以跟踪并确保你的用户得到最新的版本。因此,您的用户可能会看到版本 1.1.4,但是您的包版本代码可能是 8。
你还需要考虑你想瞄准的 Android 版本。有些插件和功能会要求你针对更高版本的 Android 或者设置更高的最低 API。比如 VR 应用就是如此。但是允许尽可能多的人安装和使用你的应用对商业是有好处的。令人惊讶的是,大量用户仍然在使用旧版本的 Android,所以为了最大化你的影响力,低 API 是明智的。你可以在 https://developer.android.com/about/dashboards/index.html
查看这方面的统计数据。
尝试在你的设备上安装新的 APK,你应该看到它现在在你的应用抽屉里有正确的名称和图标。我们越来越近了——我能感觉到!
创建密钥库
在发布设置下,您将看到创建密钥库的选项。这是一种特殊的数字证书,将识别和授权您的 APK 文件。换句话说,它将是一个带有密码和用户名的文件,你需要在每个新版本的应用中包含这些密码和用户名,以证明它确实是你的应用。如果您丢失此文件,您将永远无法发布未来的更新,因此请将它放在一个非常安全的地方!(有一个选项可以让谷歌把它存储在云上,我稍后会讨论这个选项。)
尽管 keystore 方案听起来可能很严格,但它对用户和开发人员来说都是一项重要的安全措施。否则,拥有你的开发者帐户密码的人可能会上传你的应用的新“版本”,简单地用恶意软件替换它。这可能会伤害你的用户,破坏你的声誉,并摧毁你的业务。
所以这很令人沮丧,但是要确保你记得你把文件保存在哪里,你不应该有任何问题。
要创建您的密钥库,只需选择 Create New Keystore,输入用户名和密码,并选择保存文件的目的地(图 12-2 )。
图 12-2。
Never lose this!
上传你的应用
一旦你有了你的 APK 并正确签名,你就可以将它上传到 Play Store。为此,请前往 https://play.google.com/apps/publish
或搜索 Google Play 开发者控制台。
如果您还没有注册,您需要注册。幸运的是,你可以使用你的谷歌帐户,这将使事情变得又好又容易。目前的注册费为 25 美元,但这是一次性费用,肯定比 iOS 开发者不得不忍受的更昂贵的年费要好。希望,就投资回报而言,这将是你花的最好的 25 美元之一。即使是一个虚荣的项目,25 美元也不会让你倾家荡产。
开发者控制台(图 12-3 )是你能够看到你的应用和它们的统计数据的地方,一旦你有一些活的。您将能够检查用户评论,应用更新,并检查收入和错误报告。不过,你还没有任何应用,所以这将是相当空的。
图 12-3。
My developer console
创建你的商店列表
要开始,找到并单击创建应用。你将被要求选择一种语言并给它一个标题,然后你将被带到另一个页面,在那里你可以输入更多的细节,包括描述、翻译和各种图形素材(见图 12-4 )。我们要先填好这个。
图 12-4。
Write a compelling description that will help your game stand apart
您可以输入标题、描述和其他内容。一会儿我们将更详细地讨论在这里放什么可以获得最佳效果。至于图像,包括一个高分辨率图标(512 x 512),将显示在 Play Store 中您的应用列表旁边,一个功能图形(1024 x 500),将显示在页面顶部,以及一个宣传图形(180 x 120)。宣传画主要用于旧版本的 Android,不需要提交,但值得花时间为每个类别创建图像。你花了这么长时间来构建你的应用,不要在最后一关摔倒。
如果你也使用宣传视频(这是值得做的),那么你的特征图形顶部会有一个播放图标。对于那些创建 Daydream 应用的人来说,你需要创建一个立体的 360 度视频。祝你好运!Android TV 应用需要 1280 x 720 的横幅图像。
你还会注意到,你可以添加游戏截图,并且可以为手机、平板电脑、电视和 Android Wear 选择不同的截图。这是你要把你的游戏在行动中的镜头。你可以在图 12-5 中看到我使用的图片。
图 12-5。
How could anyone not want to download and play all two levels?
继续向下滚动页面,您将能够选择应用类型(游戏)、类别(在这种情况下可能是“动作”)、内容分级和您的联系方式。稍后,您需要返回到内容分级页面,要获得证书,您需要填写各种问题。
接下来输入您的联系方式。如果你认为你的游戏有可能大获成功,我建议你建立一个新的电子邮件地址。任何人都可以通过这里联系你,如果你的应用启动,你可以期待得到很多通信。这包括漂亮的赞美,以及一些完全愚蠢的批评和大量的废话。所以在透露你的个人信息时要小心。
然后,如果您愿意,您可以选择提交隐私政策,或者暂时将其留空。如果你正在开发一款不会从用户那里收集任何隐私数据的手机游戏,你可能没有必要经历这一步。
上传 APK
要上传您的 APK,请转到应用发布(位于左侧),然后单击管理制作。当然,这是可以改变的,但是无论如何你应该有一个创造释放的选择。
在这里,您可以单击上传 APK,如果一切按计划进行,它应该通过测试,并被上传到商店(它还没有上线,不要担心)。您应该能够看到正确的版本代码和发布名称,与您在播放器设置中输入的内容相对应。你的应用的标题应该在页面的左上角,在你选择的图标旁边。当您想要添加更新时,您可以回到这里,并且您可以添加“此版本中有什么新内容?”告诉你的用户发生了什么变化。如果这不管什么原因都不起作用,问题很可能出在你的密钥库上,所以再看一下本章的第一节。此时,它看起来应该如图 12-6 所示。
图 12-6。
So far, so good
现在,您只需将它保存为草稿。当直播开始时,你可以马上回来。
更多设置
控制台中还有许多其他设置需要考虑,有强制的也有可选的。在这一部分,我们将快速浏览其中的几个。
内容分级
为了让您的游戏能够广泛使用,您需要完成内容分级过程。点按左侧的“内容分级”,选取“继续”,然后回答问题。它们是不言自明的(图 12-7 )。
图 12-7。
You might want to reconsider those Nazi references
点击保存调查问卷,然后计算评级,以获得您在各个不同地区的分类,最后应用评级。
定价和分销
你还需要决定你的应用收费多少,或者你是否想让它免费。请注意,如果你的应用是为了赚钱而出售的,你可以随时让它免费。但是一旦你让它免费,就没有回头路了,除非你上传一个全新的商店列表(图 12-8 )。
图 12-8。
Consider the best business model for your app if you want to make a profit from it
此页面的另一部分允许您选择您希望您的应用在哪些国家可用。你可能想让它随处可用,因为这样最大数量的人将能够享受你的创作(并付钱给你)。
你还需要回答屏幕下方的一些问题,例如该应用是否包含广告,以及它是否符合 Android 内容指南和美国出口法律。同样,这应该是不言自明的。
其余的
快到了。完成应用发布、商店列表、内容分级、定价和分发部分后,您的应用就可以发布了。但是如果你想知道其他部分是干什么的,请继续阅读。
设备目录显示哪些设备可以运行你的应用,如果你不希望这些设备上有应用,你可以选择过滤掉一些。翻译服务允许您为其他地区翻译您的应用。服务和 API 是用于访问 Firebase(谷歌的“后端服务”——不用担心)等外部工具,优化技巧只是基于你当前的列表分享建议(值得一读)。在应用内产品中,您可以管理您的应用内购买,但如果您的应用没有任何应用内购买,则在这里没有任何事情可做。神器库允许你下载你的 APK 和其他已经上传到游戏商店的东西。
Android Instant Apps 是一个相对较新的功能,允许用户运行应用,而不必永久下载和安装它们,但它在这里并不适用。这个特性不仅不是对每个人都可用,而且大部分游戏涉及的大文件大小(更不用说它们的本质)意味着它不完全适合大多数开发者。最后,应用签名是您可以注册 Google 的应用签名计划,将您的密钥库存储在云中,这样它就永远不会丢失。这是一个方便的选择,但它不是强制性的,所以您可以决定是否要采取这一额外的步骤。如果您愿意,您可以随时决定以后再做这件事。然而,一旦你走上这条路,你就不能回头了。
当您的应用处于活动状态时,您可以随时返回控制台查看您发布的所有项目。点击一个按钮,你就可以看到一大堆数据,包括收入、下载次数等等。
出发时间
有了它,你就可以出版了。这是一个重要的时刻,尤其是如果你已经在你的应用上工作了几个月或几年,所以去给自己倒点香槟酒吧。
回到 App Releases ➤编辑发布,向下滚动到底部,点击开始推广到生产(图 12-9 )。
图 12-9。
Do it!
确认,然后…你的应用就发布了!或者说,差不多....
实际上,你的应用现在将接受审查,你会看到它在页面顶部写着“待发布”。发布过程是自动的,这意味着它是由算法处理的,而不是由实际的人类管理员处理的(不像苹果的应用商店)。这是一个好消息,因为这意味着你的应用应该在接下来的几个小时内上线,准备好让人们开始下载和评论。在这个阶段拒绝任何东西是非常罕见的。当然,这不是忽视条款和条件的许可——你的应用仍然可能在以后被删除。
祝贺你,亲爱的读者!你刚刚成为正式的游戏开发者。
创建更多下载
一旦你的应用上线,你的工作还远远没有结束。你不仅有道德义务不断更新和改进你的应用,而且你还需要确保你在积极推广你的创作并鼓励下载。这不是“建造它,他们就会来”的情况。今天,Android 上有太多优秀的应用,市场已经高度饱和。更确切地说,这取决于你把这个消息传出去,并确保人们兴奋地检查你的创作。这是你欠自己的。
那么,你如何确保人们真的找到并下载你的应用呢?这一部分讲述了一些可能有所帮助的营销技巧。
想想 SEO
SEO 代表搜索引擎优化。这在互联网营销界是一件大事,但它在开发者中也有作用。这是因为谷歌 Play 商店本质上是一个搜索引擎,人们经常会通过搜索找到新的应用(见图 12-10 )。
图 12-10。
Kevin in Space is open for business
这里要理解的关键概念是关键字的使用。关键词是人们可能会搜索以找到你的应用的单词或短语。在我们的案例中,好的例子可能包括以下内容:
- 2D 平台
- 侧滚游戏
- 像素类型
- 复古游戏
为了增加我们被发现的机会,我们可以尝试在描述中多次包含这些术语。不要做得太过分,因为这看起来很垃圾,可能会让你的应用被删除。试着自然地加入一些内容。在完整描述中写得越多,这就越容易,添加更长的描述也是谷歌鼓励的做法。真正推销你的应用,告诉人们为什么他们应该感兴趣。
另外,考虑到竞争的激烈程度,一个太流行的术语将更难排名。寻找最佳点:有需求但不够模糊的术语,已经没有大量的可用内容。
明智地选择你的名字
当然,确保你的关键词排名靠前的最好方法之一就是用这个词来命名你的应用。例如,你可以把你的游戏叫做复古 2D 平台或者类似的名字。
不过要小心:这样做也意味着你的应用不会有同样多的个性或强大的品牌供你推广。这也可能让用户感到不快,让你更难在 Play 商店之外进行推广。
一个能传达你的游戏的名字并不是一个坏主意,但是要有创造性和趣味性。选择一个能传达某种情感(比如《地狱边缘》或《愤怒的小鸟》)并能激起兴趣(比如《VVVVVV》或《托马斯独自一人》)的名字。理想情况下,有人应该立刻知道你的游戏可能是关于什么的,或者一听到游戏的名字就想了解更多。
寻找通往市场的路线
营销也意味着偶尔出去大声宣传你令人惊叹的新游戏(图 12-11 )。做到这一点的最好方法是找到一条通往市场的路线,市场基本上是可能感兴趣的人经常去的任何地方。例如,这可能是一个致力于某个游戏流派的脸书小组或 Subreddit(也可以试试 www.reddit.com/r/playmygame/
)。
图 12-11。
Your game is now available for millions of people to download
市场路线也是在游戏的早期设计阶段需要考虑的事情。当然,你应该制作你想制作的游戏,让你兴奋,但是也要考虑你的游戏会有什么营销机会。通过瞄准一个特定的利基市场,你可以避免成为大池塘里的一条小鱼,避开竞争。更重要的是,通过锁定某个用户(称为人物角色),你可以给自己提供更具体的市场路线。
事实上,《太空中的凯文》很难营销,因为它并不突出,对任何人都没有吸引力。但如果这是一个关于自由跑英雄的游戏,我们可以把它发布在一个针对自由跑者的论坛上。如果这是一个有着令人敬畏的 synthwave 原声音乐的游戏,我们可以尝试从 synthwave 网站获得宣传。
同样,想想你目前拥有的人脉和资源。当然,你应该尝试让你的朋友下载你的应用(并留下好评!),但也许你认识某个为大网站写稿的人,比如?
获得好评
Play Store 上的 SEO 与 Google.com 上的 SEO 略有不同,因为它考虑了一系列其他因素。其中就有你的用户留下的评价,正面评价越多=下载越多。获得好的评论是创建一个你引以为豪的伟大游戏的问题,但是让你的用户用偶尔的弹出窗口来评论它也是一个好主意。解释它将如何真正帮助你,但不要尝试任何不光彩的策略来迫使一个积极的评论——谷歌不赞成这样做。
如果你得到一个差评,快速回复总是一个好主意。这样做不仅表明你是一个真正关心用户的细心的开发者,而且如果你提供一个解决方案,你甚至会发现用户改变了他们的分数。
千万不要为评论买单。这种做法会让你的应用被删除,只会让用户不高兴。
定期更新
定期更新你的应用也很重要。它不仅鼓励更多积极的评论,还让你的应用在 Play Store 的新游戏+更新游戏部分获得了曝光的机会。更多这样的曝光意味着人们有更多的机会发现你的应用并尝试一下。
选择正确的图片和文字
如果有人意外发现了你的应用,他们可以选择点击阅读更多内容,或者直接从你身边经过而不停下来阅读。影响这一决定的最大因素很可能是你选择的图标,所以不言而喻,这需要是好的。目的是脱颖而出,吸引人们的兴趣,同时准确地传达你的游戏是为谁设计的。不要试图吸引每一个人,而是拥抱你所选择的流派、定位和风格。
想想你自己的习惯:在浏览新游戏时,什么样的图片会引起你的兴趣?对我来说,这绝对会是一个看起来未来派,动作密集,独立的游戏。我远离诸如《部落冲突》或其他明显针对休闲市场的精美免费游戏。这只是我的看法——但是通过了解你的用户在寻找什么,你就可以通过你的图片来确定要传达什么。当然,你也需要用你的特写图片和截图把它敲出公园。
你的描述也是如此。使用几个关键词可能是一个不错的策略,但更重要的是为用户而写。这样做意味着用强有力的开场白迅速抓住注意力,用要点推销你游戏的关键功能,用情绪化的语言试图鼓励快速点击。熟读说服性写作,因为这能带来真正的改变。再次强调:了解你的用户,把你的推销目标对准他们。向所有人呼吁意味着不向任何人呼吁。
制造轰动
最后,通过向游戏网站和 Android/移动渠道提交新闻稿,努力为你的应用创造声势。尝试向 YouTube 名人提供免费的 apk,以换取评论或“让我们玩吧”视频。瞄准更小的个性和更容易回应的渠道,如果你的游戏很好,更大的渠道会注意到你。
考虑为你自己的游戏创建一个网站,你可以单独推广。社交媒体页面也是如此。如今使用 WordPress ( www.wordpress.com
)创建一个网站出奇的容易。
也要考虑在你发布之前制造一个声势。你可以通过创建一个开发者博客,谈论你的应用的创建,或者通过向小网站和频道发布挑逗性的新闻来做到这一点。
如果你真的雄心勃勃,尝试在 Kickstarter 或 Indiegogo 等众筹网站上发起一场运动,人们会提供资金支持,帮助你完成游戏。众筹不仅能为你的项目买单,还能确保围绕你的游戏发布会有一个大的、活跃的、积极参与的社区。此外,它更容易引起游戏媒体的注意。记住,网站和杂志不想给你提供免费的推广,但是他们想报道有趣的故事。
尽管如此,为了实现这一点,你需要一个令人信服的 USP——无论这意味着创造一个心爱的专营权的精神继承者,复兴一个被遗忘的流派,还是尝试一些令人难以置信的新的和令人信服的东西。让人们有理由支持你的游戏,把它变成一项运动,而不仅仅是另一个产品。-让他们相信并为之兴奋!
结束语
就这样,你只能靠自己了。我已经带你走得够远了,剩下的就看你的了。我相信你会找到自己的方式,创造出你引以为豪的东西,并得到应有的关注和赞誉。
图 12-12。
The rest is up to you
只要记得从小处着手,然后从那里开始积累。创造一些不同和独特的东西。最重要的是,享受做这件事的乐趣。如果你真的享受创作过程,如果你全身心地投入到自己的激情项目中,它会在最终产品中表现出来。做你想玩的游戏,不要害怕拥抱你自己的身份。
如果你的游戏不成功呢?继续下一个产品。制作一部轰动一时的电影也有很多机会。
我祝你在你的项目上好运,希望你绝对能成功。当你富有和出名的时候记得我!