HTML5 游戏开发实战 | 推箱子
经典的推箱子是一个来自日本的古老游戏,目的是在训练玩家的逻辑思考能力。在一个狭小的仓库中,要求把木箱放到指定的位置,稍不小心就会出现箱子无法移动或者通道被堵住的情况,所以需要巧妙地利用有限的空间和通道,合理安排移动的次序和位置,才能顺利地完成任务!
经典的推箱子是一个来自日本的古老游戏,目的是在训练玩家的逻辑思考能力。在一个狭小的仓库中,要求把木箱放到指定的位置,稍不小心就会出现箱子无法移动或者通道被堵住的情况,所以需要巧妙地利用有限的空间和通道,合理安排移动的次序和位置,才能顺利地完成任务。
推箱子游戏功能如下:
游戏运行载入相应的地图,屏幕中出现一个推箱子的工人,其周围是围墙、人可以走的通道、几个可以移动的箱子和箱子放置的目的地。让玩家通过按上、下、左、右键控制工人推箱子,当箱子都推到了目的地后出现过关信息,并显示下一关。推错了玩家可以撤销移动或者重新玩这关,直到通过全部关卡。
推箱子游戏的运行界面如上图所示。
本游戏使用的图片元素的含义如图 9-2 所示。
01、箱子游戏设计的思路
先来确定一下开发难点。对工人的操作很简单,就是 4 个方向移动。注意在工人移动时箱子也移动,此效果对按键处理的要求也比较简单。当箱子到达目的地位置时,需会产生游戏过关事件,需要一个逻辑判断。那么仔细想一下,这些所有的事件都发生在一张地图中。这张地图包括了箱子的初始化位置、箱子最终放置的位置,以及围墙障碍等。每一关地图都要更换,这些位置也要变。所以每一关的地图数据是最关键的,它决定了每一关的不同场景和物体位置。那么下面就重点分析一下地图。
假设把地图想象成一个网格,每个格子就是工人每次移动的步长,也是箱子移动的距离,这样问题就简化多了。首先设计一个 16×16 的二维数组 curMap。按照这样的框架来思考。对于格子的 X,Y 两个屏幕像素坐标,可以由二维列表下标换算。
每个格子状态值分别用值(0)代表通道 Block,(1)代表墙 Wall,(2)代表目的地 Ball,(3)代表箱子 Box,(4)代表工人 CurMan,(5)代表放到目的地的箱子 redBox。文件中存储的原始地图中格子的状态值采用相应的整数形式存放。
在玩家通过键盘控制工人推箱子的过程中,需要按游戏规则进行判断是否响应该按键指示。下面分析一下工人将会遇到什么情况,以便归纳出所有的规则和对应算法。为了描述方便,可以假设工人移动趋势方向为向右,其他方向原理是一致的。如图 9-4 所示,P1、P2 分别代表工人移动趋势方向的前两个方格。
■ 图 9-4 工人移动趋势(向右)
游戏规则判断如下。
(1) 判断 P1 是否出界,出界则退出规则判断,布局不做任何改变。
(2) 前方 P1 是围墙。
如果工人前方是围墙(即阻挡工人的路线)
{退出规则判断,布局不做任何改变;
}
if(curMap[p1.y][p1.x] == 1return false; //如果是墙,不能通行(3) 前方 P1 是箱子,如图 9-5 所示。
■ 图 9-5 工人前方是箱子
(3) 前方 P1 是箱子,如图 9-5 所示。
在前面的情况中,只要根据前方 P1 处的物体就可以判断出工人是否可以移动,而在第 3 种情况中,需要判断箱子前方 P2 处的物体才能判断出工人是否可以移动。此时有以下几种可能。
① P1 处为箱子或者放到目的地的箱子,P2 处为墙或箱子。
如果工人前方 P1 处为箱子或者放到目的地的箱子,P2 处为墙或箱子,退出规则判断,布局不做任何改变。
② P1 处为箱子或者放到目的地的箱子,P2 处为通道。
如果工人前方 P1 处为箱子,P2 处为通道,工人可以进到 P1 方格,P2 方格状态为箱子。修改相关位置格子的状态值。
③ P1 处为箱子或者放到目的地的箱子,P2 处为目的地。
如果工人前方 P1 处为箱子,P2 处为目的地,工人可以进到 P1 方格,P2 方格状态为放置好的箱子。修改相关位置格子的状态值。
综合前面的分析,可以设计出整个游戏的实现流程。
02、推箱子游戏设计的步骤
游戏页面 pushbox.html
游戏页面主要设置图片素材对应的 id 。例如,箱子图片的 id 是“box" ,目的地图片的 id 是“ball" ,通道图片的 id 是“block" ,已在目的地的箱子 id 是“redbox" ,墙图片的 id 是“wall" 。人物的上下左右方向图片的 id 分别是“pleft" 、“pright" 、“pup" 、“pdown" 。
界面上添加 5 个功能按钮,实现“上一关”“下一关”“撤销移动”“重玩本关”“游戏说明”功能。
设计脚本( pushbox1.js )1. 设计游戏地图整个游戏在 16×16 区域中,使用二维数组 curMap 存储游戏的状态。其中,方格状态值 0 代表通道,1 代表墙,2 代表目的地,3 代表箱子,4 代表工人,5 代表放到目的地的箱子。例如图 9-1 所示推箱子游戏界面的对应数据如下:
每关地图方格状态值采用 levels 数组存储,如 levels[0]存储第一关,levels[1]存储第二关,以此类推。本游戏存储 100 关信息,所以把数组 levels 单独放置在"mapdata100.js"脚本文件中。
第一关如下:
第二关如下:
程序初始时,获取对应的图片,并将本关 iCurLevel 的地图信息 levels[iCurLevel]复制到当前游戏地图数据数组 curMap 和 CurLevel。curMap 初始与 CurLevel 相同,游戏中记录不断改变游戏状态。CurLevel 是当前关游戏地图数据,游戏中不变,主要用来获取箱子目的地和判断游戏是否结束。
initLevel()函数将本关地图信息复制到当前游戏地图数据数组 curMap 和 CurLevel,并在屏幕上画出通道、箱子、墙、人物、目的地信息。
为了保存工人所在位置,使用 per_position 保存。初始位置在(5,5)坐标。当然,在绘制游戏时会根据地图信息修改工人所在位置 per_position。
2. 绘制整个游戏区域图形绘制整个游戏区域图形就是按照地图 level 存储图形代号,获取对应图像,显示到 Canvas 上。全局变量 per_position 代表工人当前位置(x,y),从地图 level 读取时如果是 4(工人值为 4),则 per_position 记录当前位置。游戏中为了达到清屏效果,每次工人移动后重画屏幕前,用通道重画整个游戏区域,相当于清除原有画面后再绘制新的图案。
3. 按键事件处理游戏中对用户的按键操作,采用 Canvas 对象的 KeyPress 按键事件来处理。KeyPress 按键处理函数 DoKeyDown(event)根据用户的按键消息,计算出工人移动趋势方向前两个方格位置坐标 p1、p2,将所有位置作为参数调用 TryGo(p1,p2)方法判断并进行地图更新。
TryGo(p1,p2)方法是最复杂的部分,实现前面所分析的所有的规则和对应算法。
CheckFinish()函数用于判断是否完成本关。如果原始地图目标位置上没放箱子(也就是此位置不是放到目的地的箱子 curMap[i][j]!=5),则表明有没放好的箱子,游戏还未过关,反之游戏过关。
5. 撤销功能游戏中 oldMap 用于保存每次移动前的地图信息,执行撤销就是把 oldMap 恢复到当前地图 curMap 中。同时根据地图中记录的信息找到工人位置,修改 per_position 记录的工人位置信息,最后重新绘制整个游戏屏幕就可以恢复到上一步的状态。
6. 选关功能游戏中有“上一关”“下一关”“重玩本关”这 3 个选关功能,这 3 个选关功能实现方法是一样的。参数 i 如果是 1,则是“下一关”;参数 i 如果是-1,则是“上一关”;参数 i 如果是 0,则是“重玩本关”。主要根据关卡号 iCurLevel,调用 initLevel()函数初始化本关地图,并在屏幕上画出箱子、墙、人物、目的地信息。
至此,完成经典的推箱子游戏。
版权声明: 本文为 InfoQ 作者【TiAmo】的原创文章。
原文链接:【http://xie.infoq.cn/article/dedaef9a89598411b0b9ac33e】。文章转载请联系作者。
评论