使用J2ME技术开发《贪吃蛇》游戏
陈跃峰
本文全文已经在《软件报》发表,严禁转载
《贪吃蛇》(Greedy Snake)游戏是一款经典的益智游戏,也是Nokia手机上的第一款内置手机游戏,以其操作简单、可玩性强等特点而受到广大玩家的喜爱,以致现在还有大量的游戏玩家,而且还出现了各种各样的版本,甚至还有专门的《贪吃蛇Online》网络游戏。本文将详细介绍如何利用Java语言的J2ME技术开发手机上的贪吃蛇游戏,使读者可以进入多姿多彩的游戏编程世界,领略游戏编程的无穷乐趣。
《贪吃蛇》游戏的程序虽然比较简单,但是还是要遵循整个游戏的开发流程进行开发,下面简单介绍一下游戏的开发流程。游戏程序的一般开发步骤如下:
1.
界面和游戏操作设计
本步骤主要是将设计游戏的程序界面布局,以及实现游戏操作按键的设定。
2.
游戏核心数据结构的设计
本步骤主要是抽象游戏中的数据,并选择合适的存储方式进行存储。数据结构的设计影响后续游戏逻辑的实现,所以该步设计尽量不要修改,否则将对项目的进度产生比较大的影响。
3.
游戏逻辑的分解
本步骤主要是将游戏中逻辑(游戏规则)分解为一系列的功能方法,通过对游戏逻辑的分解,可以结构化项目的代码,也可以实现对于项目进度的准确把握,方便项目进度的控制。
4.
游戏测试
游戏制作公司一般有专门的游戏测试人员,对于游戏的功能、执行效率、操作友好性等各个方面进行详细的测试,然后程序开发人员针对测试出的bug进行修改,然后由测试人员再次进行测试。
本文将以《贪吃蛇》游戏为例讲解游戏程序开发中的前三个步骤,阅读本文需要具备一定的程序设计基础,最好具备Java语言的基础知识,并且对于J2ME技术有一定的了解。
一、界面和游戏操作设计
1、界面设计
游戏界面是一个游戏展现给玩家的平台,所以界面设计是否美观是很多玩家选择一款游戏的重要标准,一般游戏公司中的游戏界面都有专门的美工制作相关的图片资源文件,从而使界面显得更加美观。
游戏中界面设计和软件界面设计的要求是一致的,主要有以下几条要求:
l 界面美观
l 界面条理
l 符合玩家操作习惯(界面友好)
由于本文主要介绍《贪吃蛇》程序的制作,所以在实际实现时简化了界面的外观,本游戏实现的界面如下图所示:
《贪吃蛇》游戏界面图
在本界面中,包含贪吃蛇、食物和两个按钮文字这4个部分,在本界面设计中,根据手机上的操作习惯,将两个按钮的位置分别设置在手机的左下角和右下角,而将整个屏幕(包含按钮文字显示区域)作为游戏区域,以最大限度的利用手机上的屏幕空间,所以本游戏未从屏幕中划分出独立的游戏区域。
对于简单的益智游戏,一般不需要设计滚屏,所以该游戏在设计界面上设计为简单的单屏游戏。
2、游戏操作设计
游戏操作指玩家以怎样的形式参与游戏,每个游戏的操作需要根据游戏的规则等进行设计,不过在设计游戏操作以前首先需要考虑的问题,这款游戏中有哪些是需要玩家进行参与的,然后才是以如何的形式进行参与,其次需要考虑不同平台以及不同设备上的操作特点,一定要满足硬件的要求。游戏操作设计的规则如下:
l 操作简单
l 操作方便
l 符合用户操作习惯
l 符合设备硬件要求
针对手机设备来说,输入设备一般为:单手键盘、双手键盘(Nokia N-Gage QD)和触摸屏,而大部分的手机都是普通的单手键盘,也就是一般使用单手进行操作的键盘,本游戏就以最常见的单手键盘为基础进行游戏操作设计。
在本游戏中,需要玩家参与的操作有:控制贪吃蛇移动的方向、暂停和退出功能。在该游戏中,贪吃蛇的速度、程序是否结束等则由游戏程序本身进行控制。基于以上需要控制的内容,本游戏设计的操作方式如下:
1)
使用手机键盘上的4个方向键控制方向,按照手机上的操作习惯,也可以使用数字键2、4、6和8分别控制上、左、右和下。
注意:当用户按下和当前移动方向相反的方向键时,程序不改变方向。例如当前贪吃蛇的移动方向是向上,而玩家按下向下的控制键时,则程序不改变贪吃蛇的移动方向。
2)
使用手机键盘上的左软键控制游戏的“暂停”和“继续”功能,使得该游戏可以中断。可中断性强也是手机游戏的特点,是操作友好性的一个体现。当然,由于该游戏的控制比较简单,也可以将导航键的中键和数字键5作为暂停的控制,这样更方便玩家的操作,本游戏中未实现该功能。
3)
使用手机键盘上的右软键控制游戏的“退出”功能,使得游戏可以结束。当然,在手机上也可以通过挂机键退出程序,但是一般不推荐玩家使用该操作实现退出。因为在退出以前需要程序进行一些清理工作,而直接按挂机键则不一定能够执行该类代码。
本部分介绍了游戏程序的开发设计过程,详细介绍了游戏设计中的界面设计和操作设计,在接下来的文章中将介绍游戏开发中的数据设计方法以及针对《贪吃蛇》游戏进行的数据设计。
二、游戏核心数据结构设计 在完成游戏的界面和操作设计以后,就是设计游戏的核心数据结构了。数据是一个程序的灵魂,数据的存放方式被称之为数据结构(Data Structure),不同的程序需要根据自身的需要,设计不同的数据存储方式,而数据结构有将对后续的程序算法产生直接的影响,所以数据结构设计的好坏,对于整个项目的影响是很严重的。 在程序开发中,设计数据结构的步骤一般如下: l 分析需要存储的信息 l 将这些信息抽象为程序中的数据 l 根据程序中的数据进行结构设计 下面以《贪吃蛇》程序为例来介绍如何进行游戏核心数据结构的设计。 1、 分析需要存储的信息 在程序中需要存储的信息一般分为两部分:界面控制信息和逻辑控制信息。界面控制信息用于控制界面上各个元素的显示等,逻辑控制信息用于进行程序内部的逻辑处理,一般界面控制信息是可见的,而逻辑控制信息在界面上不是直接可见的。 在《贪吃蛇》游戏中,界面控制信息主要包含两个部分:贪吃蛇的位置信息,存储贪吃蛇的具体位置,另外一个就是闪烁的食物的位置。 而逻辑控制信息主要包含三个部分:贪吃蛇的移动方向、闪烁控制以及程序暂停控制。 由于《贪吃蛇》游戏比较简单,所以在实际存储时,分析出来需要存储的信息不多,但是这种分析数据的方法,值得大家在实际的开发过程中进行借鉴。 2、 将这些信息抽象为程序中的数据 程序中需要存储的信息抽象出来了以后,就是以什么类型的数据来存储这些信息的问题了,这里是计算机编程中对于数据的抽象。 对于界面控制信息的存储,计算机编程中使用的知识和数学上是一样的,都是利用坐标系的知识来存储位置信息。对于平面游戏(2D游戏)来说,存储位置时使用的也是直角坐标系(笛卡尔坐标系),只是坐标系的形式和数学上的坐标系不完全一致。在计算机中,一般以屏幕的左上角作为坐标原点,以水平向右的方向为x轴的正方向,以垂直向下的方向作为y轴的正方向,这样整个屏幕中的所有点均位于坐标系的第一象限中。 有了坐标系的知识以后,就方便了界面中位置的存储了。对于贪吃蛇来说,以为其在屏幕上可以到处移动,而且可以在屏幕上转弯等,所以需要对于其位置分开进行存储。将贪吃蛇的每个节点进行分开存储,换句话说,存储贪吃蛇的位置,也就是存储贪吃蛇上每一个节点的位置。另外,由于每个节点都是一个区域,程序中一般存储每个节点左上角的坐标,而将节点的宽度和高度处理成常量。这样每个贪吃蛇的节点就需要两个整数分别存储x坐标和y坐标了,而贪吃蛇的整个结构则需要一组这样的整数进行实际的存储了。 对于食物的位置则比较简单,只需要存储食物的x坐标和y坐标即可。 对于逻辑控制信息的存储,贪吃蛇的移动方向在实际存储时,需要进行抽象,在该游戏中,贪吃蛇的移动方向不外乎四种:上、下、左、右。在程序中只需要找出能够存储四种状态的类型即可,一般选择整数型,而为了便于程序的阅读,一般将四种方向声明为程序中的常量。闪烁食物的控制变量和暂停控制变量都是开关变量,也就是只需要两个状态即可,在程序中,一般使用boolean类型来进行存储。 3、 根据程序中的数据进行结构设计 将数据抽象成程序中的数据以后,就需要设计使用什么样的结构来存储这些数据了。 对于贪吃蛇节点的存储,可以采用的数据结构有很多,例如数组、链表等线性的结构都可以,由于贪吃蛇各个节点之间常见的操作是节点的添加以及节点的遍历,所以无论使用数组或链表都不是完全合适,这里为了从控制方便以及遍历的效率考虑,选择数组进行实现。由于贪吃蛇的节点需要经常变化,所以在使用数组时,首先声明一个长度比较大的数组,开始只使用其中的一部分,当节点添加以后,变化使用的数据即可。则设计出的结果如下: /** 贪吃蛇节点坐标,其中第二维下标为0代表x坐标,下标是1代表y */ int[][] snake = new int[200][2]; /** 已经使用的节点数量 */ int snakeNum; /** 蛇身单元宽度 */ private final byte SNAKEWIDTH = 4; 这里使用snake[0]存储贪吃蛇第一个节点,其中snake[0][0]存储第一个节点的x坐标,snake[0][1]存储第一个节点的y坐标,snake[1]存储贪吃蛇第二个节点,其中snake[1][0]存储第二个节点的x坐标,snake[1][1]存储第二个节点的y坐标,依次类推。snakeNum用来表示使用了snake数组中的多少个节点。例如初始时snakeNum的值是7,则代表snake[0]到snake[6]是已经使用的数组元素。 闪烁的食物的位置比较简单,设计出的结果如下: /** 食物的X坐标 */ int foodX; /** 食物的Y坐标 */ int foodY; 贪吃蛇的移动方向使用一个int类型进行表示,设计结果如下: /** 贪吃蛇运动方向*/ int direction; /** 向上 */ private final int DIRECTION_UP = 0; /** 向下 */ private final int DIRECTION_DOWN = 1; /** 向左 */ private final int DIRECTION_LEFT = 2; /** 向右 */ private final int DIRECTION_RIGHT = 3; 而闪烁食物和暂停只需要分别使用一个boolean类型的值代表即可,设计结果如下: /** 是否处于暂停状态,true代表暂停 */ boolean isPaused = false; /** 食物的闪烁控制 */ boolean b = true; 本部分主要介绍了游戏程序中数据结构设计的方法,并且以《贪吃蛇》游戏为例详细介绍了设计的过程,在下一部分将进行游戏逻辑(规则)实现的讲解。
三、游戏逻辑的分解 在游戏的核心数据结构设计完成以后,就可以针对设计出的数据结构进行游戏逻辑的实现了。游戏逻辑即游戏规则,是游戏编程中最核心的部分,也是最难实现的部分,在游戏程序的开发过程中,大部分时间都是用在游戏逻辑的实现上。 游戏逻辑基于游戏数据结构,从程序开发角度来看,游戏逻辑就是对于游戏数据的规则变换。当然,这些数据的变换需要根据游戏规则进行实现。然后把最终变化的结果以界面的形式显示给最终用户,对于游戏程序来说也就是游戏玩家。 进行游戏逻辑的设计,首先要把游戏规则分析出来,所谓游戏规则,就是在游戏中需要程序设计人员实现的规定和控制,这些可以根据游戏的功能进行实现。例如《贪吃蛇》游戏需要实现的游戏规则如下: l 游戏初始化 l 贪吃蛇的移动 l 贪吃蛇方向控制 l 贪吃蛇和食物的碰撞和处理 l 食物坐标的随机生成 l 游戏结束的判别 l 游戏暂停的控制 在程序实际实现时,一般使用方法来组织游戏逻辑相关的代码,也就是将对应的游戏逻辑转换为一个方法或一组方法。由于以上逻辑都比较简单,所以在实际实现时都转换为一个方法。下面依次来讲解以上游戏逻辑的实现,并介绍实现时需要注意的一些问题。 1、 游戏初始化 游戏初始化实现的功能是初始化游戏的相关数据,一般在游戏开始以及过关游戏的关卡切换时调用。 实现该功能首先需要清晰的知道需要初始化那些数据,如何进行初始化。在《贪吃蛇》游戏中,需要初始化的主要数据是贪吃蛇的位置、方向和食物的位置,另外还包含一些系统控制变量,例如暂停的控制变量等。 在本游戏中,采用如下的策略进行初始化:将贪吃蛇基本初始化在屏幕的中央,初始移动方向和贪吃蛇节点的排列顺序一致,食物的坐标固定位置。 则游戏初始化的代码如下: /**初始化开始数据*/ private void init() { // 初始化节点数量 snakeNum = 7; // 初始化节点数据 for (int i = 0; i < snakeNum; i++) { snake[i][0] = 100 - SNAKEWIDTH * i; snake[i][1] = 40; } // 初始化移动方向 direction = DIRECTION_RIGHT; // 初始化食物坐标 foodX = 100; foodY = 100; isPaused = false; //初始化暂停 } 首先初始化贪吃蛇的节点数量为7个,然后按照在屏幕上从右向左的顺序依次初始化各个节点的x和y坐标,并且初始化贪吃蛇的移动方向为向右,然后硬性初始化食物的坐标为(100,100),最后初始化暂停控制变量为正常运行状态。 2、 贪吃蛇的移动 贪吃蛇的移动是贪吃蛇游戏的核心规则,也是需要考虑时间比较长的规则。贪吃蛇移动的规则如下: l 除第一个节点以外,其它每个节点跟随前一个节点移动 跟随的实现只需要将前一个节点的坐标赋值给下一个节点即可,赋值的时候注意顺序,下面的代码实现从对最后一个节点的赋值开始。 l 贪吃蛇第一个节点沿着移动方向移动一个单位 判断贪吃蛇的移动方向,然后根据方向改变第一个节点对应的x坐标或y坐标。 实现贪吃蛇移动的代码如下: /**贪吃蛇移动*/ private void move() { // for (int i = snakeNum; i > snake[i][0] = snake[i - snake[i][1] = snake[i - } // switch (direction) { case DIRECTION_UP: snake[0][1] = break; case DIRECTION_DOWN: snake[0][1] = snake[0][1] break; case DIRECTION_LEFT: snake[0][0] = break; case DIRECTION_RIGHT: snake[0][0] = break; }
蛇身移动
0; i--) {
1][0];
1][1];
第一个单元格移动
snake[0][1] - SNAKEWIDTH;
+ SNAKEWIDTH;
snake[0][0] - SNAKEWIDTH;
snake[0][0] + SNAKEWIDTH;