贪吃蛇游戏制作教程

本篇教程我们将学习贪吃蛇游戏的制作方法。
点我下载游戏工程
点我下载游戏素材

制作思路

先设计流程,在开始制作

在制作游戏时,我们要明确需要实现的功能,然后在去考虑每个功能具体的实现逻辑。对于刚开始制作游戏的新人来说,可以将要实现的功能画成流程图。 在流程图中做完游戏的设计后,我们就可以在制作当中尽量保证各个功能之间的独立性,避免逻辑相互影响所引发的bug。

整体流程图

在细分功能之前,我们需要先知道制作的游戏的大致流程。在本篇贪吃蛇教程中,游戏功能可以大致分为以下4种:

  • 墙壁的生成
  • 食物生成
  • 贪吃蛇的移动
  • 贪吃蛇撞墙或重叠时的失败判断

根据在游戏中运行的顺序,可以简单的画成如下图所示的流程图

在游戏开始后,先生成阻挡贪吃蛇的墙壁,在生成一个食物,然后进行贪吃蛇的移动。在贪吃蛇移动结束之后,判断是否撞到墙壁或者与身体重叠(也就是失败条件),如果失败,游戏结束,如果没有失败,则重复之前的步骤。

如上图所示,我们将一个完整的游戏拆分成了几个功能模块。接下来我们要做的就是针对每个模块,在进行详细的划分,然后就可以动手制作了。

关于游戏制作的一些小提示

需要注意的是,在划分流程之后,并不一定要严格按照从上到下的顺序去制作。流程图是为了清晰制作思路,在理想情况下,从整体流程中拆分出来的【游戏流程块】是相互独立的。

例如,A功能结束后,并不会在A功能的结尾处开始B功能。而是由一个整体的流程事件(这里简称主事件)去调用的。主事件在运行时,在特定情况下,调用A功能,然后在A功能执行完毕后,再次调用B功能。

这样做的好处是,在功能相互独立之后,如果一个游戏功能出现了bug,只需要去查找改功能的事件即可,可以排除多数其他与该功能不相关的游戏功能影响。

可以关注下该教程中事件表的写法与自己平时的写法是否有不同之处

生成墙壁

创建【墙壁】精灵

首先,我们需要生成阻挡贪吃蛇通过的墙壁,为此我们新建一个精灵对象,命名为【墙壁】,并设置大小为32 * 32 (也是本教程中一个单位的默认大小)
将锚点设置到左上角(方便对齐坐标)


为了让【墙壁】对象可以通过事件表动态生成,需要先将该对象放入任意场景中。为此我们新建一个场景,名为【实例仓库】,并将【墙壁】放入场景中

场景开始时生成墙壁

新建全局事件表【贪吃蛇行为】,用于处理贪吃蛇相关的游戏逻辑。

墙壁只是在游戏开始时才会生成,因此在游戏开始时,我们调用动作组“生成墙壁”

动作组的逻辑实现,可以专门用一个全局事件表来处理(动作组是在调用时才会生效独立模块,会在下面的教程中实现具体逻辑)

管理全局事件表

全局事件表需要引入到场景中,我们有两个事件表需要引用,如果游戏场景有多个,每个场景都引用两个事件表比较麻烦,这个时候我们可以选择将需要用到的全局事件表统一放到一个新的全局事件表中。这样游戏场景只需要引用那个新的事件表即可。

实现【生成墙壁】动作组

墙壁需要在场景的四周生成。我们先勾选场景属性中的显示网格对齐网格

将默认存在的图层组命名为:墙壁

在勾选了显示网格后,场景中的网格显示如下图所示。横向有30个格子,纵向有17个格子。

在全局事件表【动作组事件表】中,实现【生成墙壁】动作组逻辑。

loopX和loopY循环次数便是场景中网格的数量,我们要在第一排和最后一排处生成墙壁,因此加入判断:

当loopX是0或者loopX是29或者loopY是0或者loopY是16时,代表当前格子是位于边缘,因此可以在该位置生成墙壁。

格子与格子之间的默认距离为32,为了方便更改,我们通过变量来控制间隔。新建全局变量:【格子间隔】

加入创建墙壁实例的动作

接下来预览游戏,可以看到墙壁已经在四周生成了。

实现贪吃蛇移动

接下来我们实现贪吃蛇的移动

移动思路

贪吃蛇的移动要包括以下几个流程

  • 每隔一段时间触发移动动作
  • 移动前,判断下一个要移动到的目标坐标
  • 通过按键改变移动方向
  • 移动结束后,如果吃掉了食物,则生成新的身体
  • 移动结束后,判断是否失败

创建贪吃蛇对象

贪吃蛇对象分为两部分,头部和身体。分别创建这两个对象,大小为32*32, 锚点都在左上角

将两个对象加入同类组中统一管理

加入到场景中

为游戏场景添加新图层组【贪吃蛇】,并将一个头部和身体加入场景。注意要对齐网格

每隔一段时间触发移动

为了实现每隔一段时间触发移动,我们在【头部】对象上,添加定时器能力

添加全局变量:【移动间隔】,单位为秒

在【贪吃蛇行为】全局事件表中实现,在场景开始时开启定时器。

当定时器到达指定时间后,触发移动动作,并重新开启定时器。

【贪吃蛇移动】动作组是整个移动动作的开始,移动位置、生成新身体、判断失败胜利都会包含在其中。

实现【贪吃蛇移动】动作组

进入【动作组事件表】,添加新事件

获取下一个目标点坐标

根据移动的流程图,在移动前,需要先为每个身体部位分别获取下一个要移动到的目标点坐标。
所以我们先在贪吃蛇同类组中,添加两个实例变量:【目标X】、【目标Y】。分别代表下一个要移动到的位置的X和Y坐标。

头部的目标坐标是根据目前移动的方向而定的,而身体的目标坐标是上一个贪吃蛇身体部件的坐标。 为了获取某个特定的身体部件,我们在为同类组添加【编号】变量。从0开始

修改场景中已经存在的头部和一个身体的【编号】。头部为0,身体为1

添加全局变量【贪吃蛇身体数量】,默认为1,用于保存当前贪吃蛇身体的数量

在【贪吃蛇移动】动作组中,调用新的动作组【更新目标坐标】。

实现【更新目标坐标】

先更新头部的目标坐标,编号为0的同类组成员为头部。

头部的目标坐标是根据移动方向而定,我们同样需要新建全局变量来保存移动方向

初始值为左,变量值的改变会在后面的教程中讲到。 目前先我们专注于实现移动

根据移动方向的不同,为实例变量设置不同的值。

更新头部后,我们循环更新身体的目标坐标
编号从1开始,循环所有场景中的身体,设置目标坐标为上一个身体坐标。(编号1的身体目标坐标为头部的坐标)

下图是动作组【更新目标坐标】的全图,该动作组实现了贪吃蛇下个目标坐标的位置获取。

贪吃蛇移动

在成功获取目标坐标后,就可以进行移动。 移动通过【设置位置】动作实现

生成新身体

生成新身体的逻辑是,在移动后,在移动前的最后一个身体处,生成新的身体。新建全局变量【生成身体】,用于判断是否生成新身体。 默认为0,会在吃到食物的时候设置为1。 吃食物的逻辑会在下面实现。

同时我们还需要记录要生成的身体的坐标位置(因为生成新的身体是在移动的动作组中触发的。当移动的动作组结束后,才会在外面继续判断是否吃到食物,因此必须要等到下次移动才会生成身体,所以要记录当时吃到食物时的坐标)

继续在【贪吃蛇移动】动作组中添加事件,当生成身体=1时,调用动作组【生成新躯干】

实现动作组【生成新躯干】

身体的生成逻辑就很简单了,只需要根据【新身体X】、【新身体Y】生成身体对象,并重置【生成身体】变量为0,然后【贪吃蛇身体数量】+1,最后将新生成的身体实例的【编号】设置为最新的【贪吃蛇身体数量】即可。

可以发现,该动作组中只负责创建和赋值,并没有涉及条件判断。关于何时生成新身体,以及【新身体X】、【新身体Y】的变量值,我们会在其他位置去实现。

检查失败

在移动之后,我们要检查游戏是否失败。

当头部的坐标与墙壁或者身体重叠时,则游戏失败。

本教程中,游戏失败的逻辑是显示UI图层组,销毁贪吃蛇,最后在两秒后重启场景。


UI图层要在游戏开始时初始化为隐藏状态。同时在场景开始时重置变量,避免游戏重新开始后由于变量错误导致游戏出现bug。

游戏开始时,要先调用一次【更新目标坐标】动作组

由于游戏刚开始时,身体和头部的目标坐标变量都是0,我们需要先手动调用一次【更新目标坐标】动作组,否则贪吃蛇会移动失败。

以上就是贪吃蛇移动的全部逻辑。 运行后我们可以看到,贪吃蛇会一直不断的往左边移动。

想要理解上述内容,重点还是要搞清楚移动的流程。 本教程中将不同的功能划分成了不同的动作组来调用。

移动的主动作组是【贪吃蛇移动】,由该动作组连接了更新坐标、生成身体以及判断失败。

实现移动方向的改变。

通过键盘控制移动

本教程通过WASD键来控制方向。
在【贪吃蛇行为】全局事件表中,新建控制事件。当按下指定按键时,调用动作组,并传入方向参数。
在贪吃蛇的游戏中,是不能向相反的方向进行移动。 可以注意到,在本事件中并没有对移动做限制,我们会在动作组中做具体的移动判断

实现【改变移动方向】动作组

实现原理

改变移动方向的逻辑其实很简单,如果当前是在往下方移动,就不可以往上方移动。如果在往左侧移动,则不能往右侧移动。

一个误区

可以先思考一下这个流程是否有问题。
当前方向为,并且动作组的参数不为时,将【下次移动方向】的值设置为动作组参数指定的方向。然后在下次【更新目标坐标】时,就会根据【下次移动方向】的值去修改目标坐标位置。

如果是正常的操作,其实是没有问题的。 但是问题在于,在下次移动开始前的这一小段时间内,玩家是可以随时更改方向的。
如果目前移动方向是左,我先按下W,将方向修改为上,这个时候还没有到达移动的时间,由于【下次移动方向】被修改为了,因此我可以正确的按下D去将方向修改为右。 这时问题就出现了,由于玩家连续进行了两次快速操作,导致的实际效果是:【移动方向为左,但是我可以向右侧移动。】

修改优化

为了避免这种情况,我们除了要记录下次要进行的移动方向外,还要记录当前正在移动的方向。 我们将这个值放入【头部】对象的实例变量中去。

现在我们的流程变成了这样:
通过【当前移动方向】去判断是否可以设置【下次移动方向】。 在移动时,根据【下次移动方向】的值,设置【当前移动方向】

下面是【改变移动方向】动作组的具体实现

上面的事件中我们成功的限制了移动方向只在非相反移动时生效。 同时我们还需要在更新目标坐标的同时更新【当前移动方向】。
为此我们修改动作组【更新目标坐标】,添加新的动作

在做完上述操作后,预览游戏。 可以发现,贪吃蛇已经可以成功的改变方向移动了。

生成食物与吃掉食物

我们在上面的事件中有涉及到新身体的生成,接下来,我们实现生成食物以及吃掉食物之后的逻辑。

生成食物

原理

生成食物的逻辑很简单,检查所有贪吃蛇可以移动的区域,随机挑选一个区域生成食物即可。为了实现这一效果,我们首先需要用数组来保存可生成区域。
新建数组对象,将属性中的宽度设置为0,高度设置为2

创建食物对象

大小为32 *32,锚点为左上角。由于需要用事件表创建,需将其放入【实例仓库】场景中。

游戏开始时调用动作组【生成食物】

在游戏开始时,就要先生成一个食物。 为此我们调用动作组【生成食物】

实现【生成食物】动作组

在存放可生成区域到数组之前,要先清空原来的数据

然后根据场景大小,进行循环。(与生成墙壁的原理差不多)

我们要判断目标位置是否有被贪吃蛇占用。 新建局部变量local_选中实例数量。

判断条件,选中坐标在指定位置的贪吃蛇部件


当条件满足时,代表存在位于指定位置的贪吃蛇部件。这时我们将local_选中实例数量的值设置为pickcount,也就是条件筛选出来的实例的数量。

local_选中实例数量 为0 时,代表这个坐标没有贪吃蛇占用,将该坐标放入数组。


在数组中随机选取一个位置,创建食物。



吃到食物逻辑

在食物创建出来后,我们要实现吃掉食物的逻辑。
当头部的坐标与食物坐标一致时,调用动作组【吃掉食物】

【吃掉食物】的动作组实现如下。

  • 先摧毁当前食物。
  • 将【生成身体】变量设置为1(贪吃蛇移动中会根据该变量决定是否生成身体)
  • 将【新身体X】、【新身体Y】的值设置为最后一个贪吃蛇身体的坐标
  • 最后生成新的食物

最后总结

以上就是全部贪吃蛇的内容,预览游戏就可以正常游玩。
游戏的逻辑分布在两个全局事件表中

  • 【贪吃蛇行为】负责处理数据初始化以及移动、改变方向、吃掉食物的触发判断
  • 【动作组事件表】负责处理具体游戏实现逻辑。

在学习过程中,先观看【贪吃蛇行为】摸清整体运行思路,在挨个去研究动作组中的实现逻辑。在游戏开发的过程当中,也要自己去思考如何优化自己的事件表,使之容易理解,并方便查找bug以及修改逻辑。