站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2ME 专题J2ME再现华容道(2)-汉土网络

J2ME再现华容道(2)-汉土网络

摘要: J2ME再现华容道(2)-汉土网络
<tr> <td> <tr> <td colspan="3">IT技术资料馆|编程语言|java系列|J2ME资料|J2ME再现华容道(2)</td> </tr> <tr> <td height="38" colspan="3" align="center">

J2ME再现华容道(2)

</td> </tr> <tr> <td colspan="3" align="center"></td> </tr> <tr> <td colspan="3" class="text">三、需求分析

这部分叫做需求分析,听起来挺吓人的,其实就是搞清楚我们要做什么,做成什么样,那些不做。下面我引领着大家共同来完成这一步骤。首先,我们要做一个华容道的游戏,华容道的故事这里不再赘述了,但其中的人物在这里限定一下,如上面Images类里的定义,我们这个版本只提供曹操(Caocao)、关羽(Guanyu)、张飞(Zhangfei)、赵云(Zhaoyun)、黄忠(Huangzhong)、马超(Machao)和卒(Zu)。我们这里也限定一下游戏的操作方法:首先要通过方向键选择一个要移动的区域(就是一张图片),被选择的区域用黑色方框框住;选好后按Fire键(就是确定键)将这块区域选中,被选中的区域用绿色方框框住;然后选择要移动到的区域,此时用红色方框框住被选择的区域;选好要移动到的区域之后按Fire键将要移动的区域(图片)移到要移动到的区域,并去掉绿色和红色的方框。这里需要强调的概念有选择的区域、选中的区域、要移动的区域和要移动到的区域,这四个概念请读者注意区分,当然也应当把这一部分记入数据字典之中。为了使文章的重点突出(介绍如何制作一个J2ME的收集游戏),我们这里限定一些与本主题无关的内容暂不去实现:过关之后的动画(实现时要用到TimerTask或Thread类,后续的系列文章中我会详细介绍动画方面的知识)、关面之间的切换(其实很简单,当完成任务之后重新再做一边)、暂停和保存等操作(这部分的内容介绍的资料很多,我也写不出什么新的东东来,难免抄袭,故此免掉)。
需求分析基本完成,离下午还有一段时间,马上动手用ACDSee把从网上找来的BMP文件,调整其大小为271*177(我的这个图片是两个部分合在一起,所以比手机实际屏幕大了),另存为PNG格式。半天时间刚刚好,不但搞清楚了要做的东东,还把要用的图片准备好了。
四、概要设计
概要设计是从需求分析过渡到详细设计的桥梁和纽带,这一部分中我们确定项目的实现方法和模块的划分。我们决定将整个项目分成五个部分,分别是前面介绍的Images、Draw,还有Map和Displayable1和MIDlet1。Images和Draw类功能简单、结构固定,因此很多项目我们都使用这两各类,这里直接拿来改改就能用了,前面已经介绍过这里不再赘述。Map类是用来从外部文件读入地图,然后保存在一个数组之中,这部分的内容是我们在本阶段讨论的重点。Displayable1是一个继承了Canvas类的画布,它用来处理程序的主要控制逻辑和一部分控制逻辑所需的辅助函数,主要函数应该包括用来绘图的paint()函数、用来控制操作的keyPressed()函数、用来控制选择区域的setRange()函数、用来控制选择要移动到区域的setMoveRange()函数、用来移动选中区域的Move()函数和判断是否完成任务的win()函数,更具体的分析,我们放到详细设计中去细化。MIDlet1实际上就是一个控制整个J2ME应用的控制程序,其实也没有什么可特别的,它和我们前面介绍的"Hello World"程序大同小异,这里就不展开来说了,后面会贴出它的全部代码。
Map类主要应该有一个Grid[][]的二维数组,用来存放华容道的地图,还应该有一个read_map()函数用来从外部文件读取地图内容填充Grid数据结构,再就是要有一个draw_map()函数用来把Grid数据结构中的地图内容转换成图片显示出来(当然要调用Draw类的paint方法)。说到读取外部文件,笔者知道有两种方法:一种是传统的定义一个InputStream对象,然后用getClass().getResourceAsStream()方法取得输入流,然后再从输入流中取得外部文件的内容,例如
InputStream is = getClass().getResourceAsStream("/filename");
if (is != null) {
byte a = (byte) is.read();
}
这里请注意文件名中的根路径是相对于便以后的class文件放置的位置,而不是源文件(java)。第二种方法是使用onnector.openInputStream方法,然后打开的协议是Resource,但是这种方法笔者反复尝试都没能调通,报告的错误是缺少Resource协议,估计第二种方法用到J2ME的某些扩展类包,此处不再深究。由于以前已经做过一些类似华容道这样的地图,这里直接给出Map类的代码,后面就不再详细解释Map类了,以便于我们可以集中精力处理Displayable1中的逻辑。Map类的代码如下:
package huarongroad;

import java.io.InputStream;
import javax.microedition.lcdui.*;

public class Map {
//处理游戏的地图,负责从外部文件加载地图数据,存放地图数据,并按照地图数据绘制地图

public byte Grid[][];//存放地图数据

public Map() {//构造函数,负责初始化地图数据的存储结构
this.Grid = new byte[Images.HEIGHT][Images.WIDTH];
//用二维数组存放地图数据,注意第一维是竖直坐标,第二维是水平坐标
}

public int[] read_map(int i) {
file://从外部文件加载地图数据,并存放在存储结构中,返回值是光标点的位置
//参数是加载地图文件的等级
int[] a = new int[2];//光标点的位置,0是水平位置,1是竖直位置
try {
InputStream is = getClass().getResourceAsStream(
"/huarongroad/level".concat(String.valueOf(i)));
if (is != null) {
for (int k = 0; k < Images.HEIGHT; k++) {
for (int j = 0; j < Images.WIDTH; j++) {
this.Grid[k][j] = (byte) is.read();
if ( this.Grid[k][j] == Images.CURSOR ) {
//判断出光标所在位置
a[0] = j;//光标水平位置
a[1] = k;//光标竖直位置
this.Grid[k][j] = Images.BLANK;//将光标位置设成空白背景
}
}
is.read();//读取回车(13),忽略掉
is.read();//读取换行(10),忽略掉
}
is.close();
}else {
//读取文件失败
a[0] = -1;
a[1] = -1;
}
}catch (Exception ex) {
//打开文件失败
a[0] = -1;
a[1] = -1;
}
return a;
}

public boolean draw_map(Graphics g) {
//调用Draw类的静态方法,绘制地图
try {
for (int i = 0; i < Images.HEIGHT; i++) {
for (int j = 0; j < Images.WIDTH; j++) {
Draw.paint(g, this.Grid[i][j], j, i);//绘制地图
}
}
return true;
}catch (Exception ex) {
return false;
}
}
}
对于像华容道这样的小型地图可以直接用手工来绘制地图的内容,比如:
fa1c
2232
bd1e
2gg2
gihg
但是,如果遇到像坦克大战或超级玛莉那样的地图,就必须另外开发一个地图编辑器了(我会在后续的文章中介绍用vb来开发一个地图编辑器)。
看看时间,刚刚好有过了半天。休息,休息一下,明天再见。
五、详细设计
详细设计是程序开发过程中至关重要的一个环节,好在我们在前面的各个阶段中已经搭建好了项目所需的一些工具,现在这个阶段中我们只需集中精力设计好Displayable1中的逻辑。(两天的时间当然不只干这点活,还要把其他几个类的设计修改一下)
Displayable1这个类负责处理程序的控制逻辑。首先,它需要有表示当前关面的变量level、表示当前光标位置的变量loc、表示要移动区域的变量SelectArea、表示要移动到的区域的变量MoveArea、表示是否已有区域被选中而准备移动的变量Selected和Map类的实例MyMap。

然后,我们根据用户按不同的键来处理不同的消息,我们要实现keyPressed()函数,在函数中我们处理按键的上下左右和选中(Fire),这里的处理需要我展开来讲一讲,后面我很快会把这一部分详细展开。接下来,是实现paint()函数,我们打算在这一部分中反复的重画背景、地图和选择区域,这个函数必须处理好区域被选中之后的画笔颜色的切换,具体讲就是在没有选中任何区域时要用黑色画笔,当选重要移动的区域时使用绿色画笔,当选择要移动到的区域时改用红色画笔(当然附加一张流程图是必不可少的)。

再下面要实现的setRange()函数和setMoveRange()函数,这两个函数用来设置要移动的区域和要移动到的区域,我的思路就是利用前面在Images类中介绍过的地图组合标记常量,当移动到地图组合标记常量时,根据该点地图中的值做逆向变换找到相应的地图标记常量,然后设置相应的loc、SelectArea和MoveArea,其中setMoveRange()函数还用到了一个辅助函数isInRange(),isInRange()函数是用来判断给定的点是否在已选中的要移动的区域之内,如果isInRange()的返回值是假并且该点处的值不是空白就表明要移动到的区域侵犯了其他以被占用的区域。

有了setRange()和setMoveRange()函数,Move()函数就水到渠成了,Move()函数将要移动的区域移动到要移动到的区域,在移动过程中分为三步进行:第一.复制要移动的区域,第二.将复制出的要移动区域复制到要移动到的区域(这两步分开进行的目的是防止在复制过程中覆盖掉要移动的区域),第三.用isInRange2()判断给定的点是否在要移动到的区域内,将不在要移动到的区域内的点设置成空白.
下面我们详细的分析一下keyPressed()函数的实现方法:首先,keyPressed()函数要处理按键的上下左右和选中(Fire),在处理时需要用Canvas类的getGameAction函数来将按键的键值转换成游戏的方向,这样可以提高游戏的兼容性(因为不同的J2ME实现,其方向键的键值不一定是相同的).接下来,分别处理四个方向和选中.当按下向上时,先判断是否已经选定了要移动的区域(即this.selected是否为真),如果没有选中要移动区域则让光标向上移动一格,然后调用setRange()函数设置选择要移动的区域,再调用repaint()函数刷新屏幕,否则如果已经选中了要移动的区域,就让光标向上移动一格,然后调用setMoveRange()函数判断是否能够向上移动已选中的区域,如果能移动就调用repaint()函数刷新屏幕,如果不能移动就让光标向下退回到原来的位置.当按下向下时,先判断是否已经选定了要移动的区域,如果没有选中要移动的区域则判断当前所处的区域是否为两个格高,如果是两个格高则向下移动两格,如果是一个格高则向下移动一格,接着再调用setRange()函数设置选择要移动的区域,而后调用repaint()函数刷新屏幕,否则如果已经选中了要移动的区域,就让光标向下移动一格,然后调用setMoveRange()函数判断是否能够向下移动已选中的区域,如果能移动就调用repaint()函数刷新屏幕,如果不能移动就让光标向上退回到原来的位置.

按下向左时情况完全类似向上的情况,按下向右时情况完全类似向下的情况,因此这里不再赘述,详细情况请参见程序的源代码.当按下选中键时,先判断是否已经选中了要移动的区域,如果已经选中了要移动的区域就调用Move()函数完成由要移动的区域到要移动到的区域的移动过程,接着调用repaint()函数刷新屏幕,然后将已选择标记置成false,继续调用win()函数判断是否完成了任务,否则如果还没有选定要移动的区域则再判断当前选中区域是否为空白,如果不是空白就将选中标记置成true,然后刷新屏幕.这里介绍一个技巧,在开发程序遇到复杂的逻辑的时候,可以构造一格打印函数来将所关心的数据结构打印出来以利调试,这里我们就构造一个PrintGrid()函数,这个函数纯粹是为了调试之用,效果这得不错.至此我们完成了编码前的全部工作.

(未完待续)

</td> </tr> <tr>
↑返回目录
前一篇: J2ME再现华容道(3)-汉土网络
后一篇: J2ME再现华容道(1)-汉土网络