游戏设计与开发:Unity实战完全自学教程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.4.2 游戏的状态划分和数据结构的设计与实现

只要有数据就有数据结构,该游戏的数据结构相对比较复杂,需要进行额外说明。另外,有限状态机思想能让代码逻辑更加清晰,并且便于管理,很多游戏都会使用状态机来管理游戏在不同情况下的运行逻辑,这是学习游戏开发必须掌握的技能。

下面对该游戏的状态划分和数据结构的设计与实现进行介绍,并利用它们最终实现该项目的核心玩法和动态效果。

1.游戏的状态转换

该游戏被划分为7个状态,分别为:

① 糖果掉落状态(Down)。当棋盘中存在空位时,糖果自然掉落。

② 检测状态(Match)。检测是否存在可删除的糖果组合。

③ 删除糖果状态(Clear)。对满足条件的连续糖果进行删除。

④ 合成特殊糖果状态(CreateMatchCandy)。满足某些条件时(例如四连、五连等),合成特殊糖果。

⑤ 等待操作状态(Play)。等待玩家操作的状态。

⑥ 洗牌状态(Shuffle)。当发现玩家无论交换哪两个糖果都无法删除糖果时,则进入洗牌状态。

⑦ 动画状态(Anima)。用来做动画表现的状态,稍后进行说明。

游戏的状态转换如图 3-10 所示。可以看到图中绘制了不同状态之间的跳转关系,在主线之外有一个额外的状态:Anima。

图3-10

Anima是动画状态,该状态作为一个中转状态,由动画之前的一个状态决定动画结束后进入的状态。具体地说,就是在每次需要播放动画时插入动画状态,并设置好动画播放完后进入哪个状态。

一般来讲,开始游戏后由于棋盘上面没有糖果(也就是整个棋盘都是空的),所以先进入糖果掉落状态(Down)让糖果铺满整个棋盘,然后进入检测状态(Match),检测是否存在可删除的糖果组合。

如果检测结果为存在可删除的糖果组合,则进入删除糖果状态(Clear),将所有需要删除的糖果进行删除处理,最后再看看是否需要合成特殊糖果。合成特殊糖果的操作独立为一个状态—合成特殊糖果状态(CreateMatchCandy)。

由于删除糖果的操作会使棋盘产生空缺,此时需要再次回到糖果掉落状态(Down)重新填满棋盘,并重复以上步骤,直到检测不到可以删除的糖果组合。

如果检测结果不存在可删除的糖果组合,则需要判断是否存在可操作对象(即通过一次交换实现删除糖果),如果存在,则进入等待操作状态(Play),否则进入洗牌状态(Shuffle)。

游戏在这几个状态之间来回切换、不断循环,通过简单的状态机模式在Unity中实现这部分功能。在编写代码时,先利用枚举值区分不同的状态,再针对不同状态编写相应的方法,在Update生命周期中根据当前的游戏状态调用对应的状态方法,具体代码如下。

代码位置:见源代码目录下Assets\Scripts\GameManager.cs。

2.游戏的数据结构

该游戏的主要数据结构有棋盘数据结构、糖果数据结构、合成特殊糖果数据结构、删除糖果数据结构。其中,用到了多种不同类型、不同作用的二维数组,下面统一将二维数组的数组访问操作符“array[i,j]”中的i表示为棋盘在Y轴上的坐标(行),j表示为棋盘的X轴上的坐标(列)。也就是说,“array[i,j]”这一数据项表示了棋盘上X值为“j”、Y值为“i”的坐标的数据。具体数据结构设计如下。

(1)棋盘数据结构

在棋盘数据结构中有3个二维数组,这3个二维数组分别记录了棋盘上的数据信息、糖果引用和横纵连续相同糖果的数据。这3个二维数组的大小相同,其数据是和游戏中的棋盘格一一对应的。

第一个是int类型的二维数组,字段名是“candyColorInBoard”。该二维数组用于记录棋盘的数据信息,数据和状态对照如表3-3所示。数组数据n的值表示当前棋盘对应格子的状态:n小于0表示该格子不可操作;n的值在[0,糖果颜色总数)范围内表示该格子上是对应颜色的糖果;当n的值为int类型的最大值时,表示该格子为空,可以被填充糖果;当n的值为糖果颜色总数时,代表该格子存在一个全部删除糖果。

表3-3

该游戏中使用的棋盘是无障碍物的,所以没有出现n小于0的情况,在示例代码中也不会做相应的操作,读者可以自行扩展。

第二个是CandyControl类型的二维数组,字段名是“candiesInBoard”。在3.4.1节的步骤(13)创建的糖果预制体都挂载了CandyControl脚本,由于二维数组的数据和游戏中的棋盘格是一一对应的,用二维数组 candiesInBoard 引用 CandyControl 对象可以在删除糖果时通过坐标信息在candiesInBoard上快速找到对应的糖果,并进行所需要的操作。

二维数组的下标和游戏物体的对应关系如图3-11所示,图中举例了假设需要删除1行0列格子上的糖果,通过引用找到游戏物体“糖果4”并进行操作。

图3-11

第三个是Vector2Int类型的二维数组,字段名是“tempVec2Int”。该二维数组用于记录棋盘上的糖果在横向和纵向上连续且相同的个数,Vector2Int是由两个int类型的数据组成的数据结构,该二维数组上的每个元素具有(mn)两个值,m表示对应的格子在横向上有m个连续且相同的糖果,n表示对应的格子在纵向上有n个连续且相同的糖果。

tempVec2Int 数组内容要通过扫描棋盘得到,可以先扫描每一行,再扫描每一列。发现一行内有连续出现的相同糖果时,就增加 m 的值,发现一列内有连续出现的相同糖果时,就增加 n的值。通过后文介绍的扫描算法,就能得到tempVec2Int数组中每个格子对应的值。扫描示例如图3-12所示,通过扫描下面左图的棋盘,就能得到下面右图的扫描结果。

图3-12

(2)糖果数据结构

糖果数据结构分为糖果颜色和糖果类型。糖果颜色用数字表示,从0开始依次编号。糖果类型用一个枚举类型的数据表示,该枚举类型列举了所有的糖果类型,分别为Ordinary(普通糖果)、Diamond(菱形删除糖果)、Vertical(纵向删除糖果)、Horizontal(横向删除糖果)、AllIn(全部删除糖果)。

代码位置:见源代码目录下Assets\Scripts\CandyControl.cs。

(3)合成特殊糖果数据结构

检测棋盘上的糖果信息,筛选出可以删除的组合后,如果有可以合成的特殊糖果,则需要记下这个糖果的颜色、坐标、糖果类型等信息,以便在合成特殊糖果状态中使用。为了实现此功能,需要声明一个结构体“CandyInfo”,通过一个CandyInfo类型的列表管理合成特殊糖果的各种信息。声明CandyInfo的代码如下。

代码位置:见源代码目录下Assets\Scripts\GameManager.cs。

(4)删除糖果数据结构

检测棋盘上的糖果信息并筛选出所有需要删除的糖果后,需要将这些糖果的信息传递到删除糖果状态,所以需要一个Vector2Int类型的列表存放所有需要删除糖果的坐标数据。

因为在删除糖果的过程中,如果删除的是特殊糖果,就会产生新的需要删除的糖果,所以这里需要两个Vector2Int类型的列表,一个用于存储等待删除的糖果坐标数据,另一个用于存储正在删除的糖果坐标数据。

糖果删除流程如图 3-13 所示,当游戏进入删除糖果状态时,会将等待删除糖果列表中的糖果复制到正在删除糖果的坐标列表,并且将等待删除糖果列表清空。而在删除糖果时有可能产生新的需要删除的糖果,将这些糖果的坐标添加到等待删除糖果的坐标列表中,直到没有新的需要删除的糖果为止。换一种说法就是:由于删除糖果时会产生新的需要删除的糖果,所以这里可能需要交替反复使用这两个列表。

图3-13

至此,该游戏的主要数据结构介绍完毕,除此之外,还有一些其他数据结构,将在实现具体功能时进行介绍,读者可以查阅本游戏的源代码。