??xml version="1.0" encoding="utf-8" standalone="yes"?>
昨天在网上闲逛,发现一讲解用delphi实现华容道游戏的文章Q颇受启发.于是Q生了华定w游戏UL到手Zȝ冲动Q现在手机游戏琳琅满目,不一而Q华定w的实现版本也很多Q正巧不久前W者对J2ME下了一番功夫,正想借这个机会小试牛刀。选用J2ME的原因还有一个就是目前Java开发大行其刎ͼ无线增殖业务q猛发展QJ2ME的应用日渐活跃v来,也希望我的这文章能够ؓJ2ME知识的普及和开发团队的壮大推L助澜。由于长期受ISO规范的媄响,q次试牛刀我也打算늅软g工程的要求,q取瀑布式的开发模式来规划目Q也希望借此Z向各位没有机会参与正式项目开发的读者介l一下Y件开发的程?/p>
q里我们先定义项目组的h员体?其实只有我一个h)Q技术调研、需求分析、概要设计、详l设计、编码、测试均有笔者一人担任;工q里我找了个捷径Q盗用网上现成的囄Q然后用ACDSee把它由BMP转换成PNG格式(我出于讲座的目的Q未做商业应用,应该不算侉|?Q至于发布工作,׃~少OTA服务器,此项工作不做(但是我会介绍q步如何??/p>
接下来,我们规划一下项目实现的旉表,以我个hl验Q设惛_下:技术调研用2?q部分解决项目的可行性和重大技术问题,旉会长一?Q需求分析用半天(毕竟有现成的东东可以参照Q只要理清思\p了,况且q有很多以前用过的设计模式和写好的代?Q概要设计再用半?有了需求,概要只不够是照方抓药)Q详l设计要??q一步要把所有的问题x楚,q要可能的准确描述出来)Q编码用2?其实1天就够了Q技术已l不是问题,多计划出一天来应付H发事g)Q测试用2?试应该臛_占全部项目的四分之一Q不q这个项目只是一个DemoQ也太简单了)Q发布也要用上半?管我们不去实际发布它,但是q要q旉搞清楚应该如何做)Q最后就是项目ȝ和开庆功?旉待定)?/p>
二、利其器
“公Ʋ善其事Q必先利其器”,做项目之前第一步是前期调研Q我们要做的华容道这个东东随处可见,我们要调研的是两个方面:
1. 游戏的内容:游戏本n很简单,是有几个格子,Ҏ占据其中一个较大的格子Q然后被几个格子包围Q这些格子Ş状不一定相同,但是挡住了曹操移动的方向Q游戏者需要挪动这些格子最l把ҎUdC个指定的位置才算是过养I更具体的分析我们攑֜后面需求分析和概要设计中讨论?/p>
2. 技术储备:谈到技术,q里单介l一下J2ME.Java有三个版本,分别是J2MEQ微型版Q?J2SEQ标准版Q?J2EEQ企业版Q.J2ME是一个标准,采用Q层l构设计Q最低层是配|层QConfigurationQ也是讑֤层,其上是简表层QProfileQ?再上是应用层QApplicationQ?MIDP是Ud信息讑֤表,目前L手机支持MIDP1.0Q最新的是MIDP2.0,它比前一个版本增加了Ҏ戏的支持Q在javax.microedition.lcdui.game包中提供了一些类来处理游戏中的技术,比如我们后面会用到的Spritec,它是用来{囄?权衡再三Q笔者决定用MIDP2.0来做开发.首先需要安装一个J2ME的模拟器Q我们就用Sun公司的WTK2.0Q我觉得Sun的东西最权威Q当然你也可以用Nokia.Siemens或是Motolora{其他模拟器Q但是他们的JDK不尽相同Q写出来的程序移植是比较ȝ的.
Sun公司的WTK2.0可以到搜索引擎寻找下载,当然要想成功下蝲的前提是你要先注册成为Sun的会员(其实q样对你是有好处的)Q当下来之后是按照提示一步一步的安装Q安装好了之后,我们用一?Hello World"E序开始你的J2ME之旅Q我们启动WTK2.0工具集中的KToolBarQ然后点击New Project按钮Q在弹出的输入框中输入Project Name为HelloWorld,MIDlet Class Name为Hello,然后点击Create ProjectQ开始生成项目,工具会弹出MIDP配置表,q里接受生成的默认|以后q可以修改)点击OKQ工hC我们把写好的Java源程序放到[WTK_HOME]\apps\HelloWorld\src目录之下Q我们编辑如下代码,q保存在上述目录之下Q文件名为Hello.java?/p>
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class Hello extends MIDlet
{
private Display display;
public Hello(){
display =Display.getDisplay(this);
}
public void startApp(){
TextBox t = new TextBox("Hello","Hello",256,0);
display.setCurrent(t);
}
public void pauseApp(){
}
public void destroyApp(boolean unconditional){
}
}
保存好了之后Q点击Build按钮Q工具会Z~译E序Q如无意外再点击Run按钮Q会弹出一个手机界面,剩下的就不用我教了吧Q用鼠标Ҏ机按键一狂点)。呵呵,你的W一个J2MEE序已经OK?什么?你还一炚w没懂呢(真是厉害Q不懂都能写出J2MEE序来,果然是高手)Q我q里主要是介lWTK2.0工具的用,E序q不是目的,不懂的话后面q会有详l的解说Q这里只是带你上路.什么?你不懂JavaQ那也没有关p,后面我再讲得l一炏V?
跌J2MEQ我们先来讲Ҏ戏的理论Q具体到华容道这个游戏,主要有三个方面,贴图Q游戏操作.逻辑判断Q这里讲讲脓图,其他两方面放在概要设计和详细设计里讲Q所谓的贴图Q其实就是画图,是在要昄囑Ş的位|上输出一副图片,Q要是牵扯到动画pȝ一些,可以使用TimerTask.Thread或Rannable之类的技?Q这副图片可以是事先准备好的也可以是临时处理的.在J2ME中有一个Imagec?专门用于理囄Q它有createImage()ҎQ可以直接读取图片文ӞJ2ME只支持PNG格式的图片)Q也可以截取已有的图片的一部分Q这h们可以把很多囄攑֜一P然后一张一张的截下来,好处是节省存储空间和文gd旉Q对于手两者都是性能的瓶颈)Q?
J2MEq有一个Graphicsc,专门用于l图Q它有drawImage()ҎQ可以把一副图片在指定的位|上昄出来Q它q有drawRect()Ҏ和setColor()ҎQ这两个Ҏ在后面我们进行游戏操作时׃用到Q这里先交代一下.有了囄和绘囄ҎQ还需要知道把囄到谁w上QJ2ME提供了一个Canvasc,字面意思就是画布,它有一个paint()Ҏ用于h面Q还有一个repaint()Ҏ用于调用paint()ҎQ听着有些p涂是吧Q不要紧Q我来结合具体程序讲解一下.Z今后~程的方便,我们创徏两个cImages和Draw,Images用于保存一些常量值和囄QDraw主要是用于画图,q两个类的源代码如下?
Imagescȝ源代码如下:
package huarongroad;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
public class Images {//保存帔R
//l图位置帔R
public static final int UNIT = 32;//方块的单位长?
public static final int LEFT = 10;//d的左边界点
public static final int TOP = 9;//d的上边界点
//地图位置帔R
public static final int WIDTH = 4;//地图的宽?
public static final int HEIGHT = 5;//地图的高?
//地图标记帔R
public static final byte CAOCAO = (byte) ′a? QA href="file://?Qfile://曹</AQ操的地图标?
public static final byte MACHAO = (byte) ′b?//马超的地图标?
public static final byte HUANGZHONG = (byte) ′c?//黄忠的地图标?
public static final byte GUANYU = (byte) ′d?//关羽的地图标?
public static final byte ZHANGFEI = (byte) ′e?//张飞的地图标?
public static final byte ZHAOYUN = (byte) ′f?//赵云的地图标?
public static final byte ZU = (byte) ′g?//卒的地图标记
public static final byte BLANK = (byte) ′h?//I白的地图标?
public static final byte CURSOR = (byte) ′i?//光标的地图标?
//地图l合标记帔R
public static final byte DLEFT = (byte) ?? QA href="file://l?Qfile://l</AQ合囑Ş左边标记
public static final byte DUP = (byte) ?? QA href="file://l?Qfile://l</AQ合囑Ş上边标记
public static final byte DLEFTUP = (byte) ?? QA href="file://l?Qfile://l</AQ合囑Ş左上标记
//囄帔R
public static Image image_base;//基本囄
public static Image image_Zhaoyun;//赵云的图?
public static Image image_Caocao;//Ҏ的图?
public static Image image_Huangzhong;//黄忠的图?
public static Image image_Machao;//马超的图?
public static Image image_Guanyu;//关羽的图?
public static Image image_Zhangfei;//张飞的图?
public static Image image_Zu;//卒的囄
public static Image image_Blank;//I白的图?
public static Image image_Frame;//游戏框架的图?
public Images() {//构造函?
}
public static boolean init() {//初始化游戏中用到的图?
try {
image_base = Image.createImage("/huarongroad/BITBACK.png");
image_Frame = Image.createImage(image_base, 126, 0, 145, 177,
Sprite.TRANS_NONE);
//SpritecL用来{囄的,是MIDP2.0新新增加的支持游戏的Ҏ?
image_Zhaoyun = Image.createImage(image_base, 0, 0, UNIT, 2 * UNIT,
Sprite.TRANS_NONE);
image_Caocao = Image.createImage(image_base, UNIT, 0, 2 * UNIT,
2 * UNIT, Sprite.TRANS_NONE);
image_Huangzhong = Image.createImage(image_base, 3 * UNIT, 0, UNIT,
2 * UNIT,
Sprite.TRANS_NONE);
image_Machao = Image.createImage(image_base, 0, 2 * UNIT, UNIT,
2 * UNIT,
Sprite.TRANS_NONE);
image_Guanyu = Image.createImage(image_base, UNIT, 2 * UNIT,
2 * UNIT, UNIT,
Sprite.TRANS_NONE);
image_Zhangfei = Image.createImage(image_base, 3 * UNIT, 2 * UNIT,
UNIT, 2 * UNIT,
Sprite.TRANS_NONE);
image_Zu = Image.createImage(image_base, 0, 4 * UNIT, UNIT, UNIT,
Sprite.TRANS_NONE);
image_Blank = Image.createImage(image_base, 1 * UNIT, 4 * UNIT,UNIT,
UNIT,
Sprite.TRANS_NONE);
return true;
}catch (Exception ex) {
return false;
}
}
}
Drawcȝ源代码如下:
package huarongroad;
import javax.microedition.lcdui.*;
public class Draw {
//l制游戏中的囄
public Draw(Canvas canvas) {//构造函?
}
public static boolean paint(Graphics g, byte img, int x, int y) {
//在地囄x,y点绘制img指定的图?
try {
paint(g, img, x, y, Images.UNIT);//把地图x,y点{化成d的绝对坐标,l图
return true;
}
catch (Exception ex) {
return false;
}
}
public static boolean paint(Graphics g, byte img, int x, int y, int unit) {
try {
switch (img) {
case Images.CAOCAO://L?
//变成l对坐标Qƈ做调?
g.drawImage(Images.image_Caocao, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.GUANYU://d?
g.drawImage(Images.image_Guanyu, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.HUANGZHONG://画黄?
g.drawImage(Images.image_Huangzhong, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.MACHAO://画马?
g.drawImage(Images.image_Machao, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.ZHANGFEI://d?
g.drawImage(Images.image_Zhangfei, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.ZHAOYUN://画n?
g.drawImage(Images.image_Zhaoyun, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.ZU://d
g.drawImage(Images.image_Zu, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.BLANK://ȝ?
g.drawImage(Images.image_Blank, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.CURSOR://d?
g.drawRect(Images.LEFT + x * unit,
Images.TOP + y * unit,Images.UNIT,Images.UNIT);
break;
}
return true;
}catch (Exception ex) {
return false;
}
}
}
其中Imagescd的是l图位置帔RQ也是在画图时每个格子的长度和相对坐标原点位置要进行的调整Q、地图位|常量(地图的长、宽Q,地图标记帔RQh物对应的记号Q,地图l合标记帔RQ后面会l说Q,囄帔RQ存放h物的囄Q;DrawcM要负责在制定的位|画Zh物图片。下面我来说说ImagescM的地图标记常量和地图l合标记帔R。ؓ了能够灵zȝ安排各个关面的布局Q我们决定把游戏布局的信息存储在外部文g中,然后E序启动后把它读q来?
q样我们制定了一套存储图片的代码Q这是地图标记帔RQ如上面ImagescM定义的Caocao(Ҏ)用a字符来表C,当程序读到a字符时就能将它{化成Ҏ对应的图片,q在da字符的位|上q行昄。但是从实际观察中我们发现所有的囄q不是统一大小的,有的?个格子,有的?个格子,q有的占1个格子,而且即便同是占两个格子的囄q有横、竖之分。有鉴于此,我们引入了地囄合标记常量,是说在遇到占有多个格子的时候,?(也就是Images.LEFT)表示它的左边是一个真正的地图标记Q?(也就是Images.UP)表示它的上边是一个真正的地图标记Q?(也就是Images.LEFTUP)表示它的左上Ҏ一个真正的地图标记。地囄合标记常量其实就是用来占位置的,与实际显C无养I当后面我们将到移动时q会再来分析l合标记的用?
DrawcM要是用来在画布上d囑ŞQ它有两个paintҎQ这是很常见的函数重载。但是程序中实际上只用到?个参数的paintҎQ它直接获得要画囄的相对坐标位|信息,然后调用5个参数的paintҎ?个参数的paintҎ相对坐标位|信息{换成l对位置Qƈ实际调用Graphics.drawImage()ҎQ将Images中的囄M出来。这U实现方法的好处是灵zd便于扩展Q但你需要画囄位置q不能够对应到格子中的相对坐标位|时Q你可以直接调?个参数的paintҎQ而不必再M改这各类Q但你添加新的图片时Q只要在Images中增加对应的帔RQ然后向Draw?个参数的paintҎd一条处理就可以了。写到这里,两天的时间刚好用完?
三、需求分?
q部分叫做需求分析,听v来挺吓h的,其实是搞清楚我们要做什么,做成什么样Q那些不做。下面我引领着大家共同来完成这一步骤。首先,我们要做一个华定w的游戏,华容道的故事q里不再赘述了,但其中的人物在这里限定一下,如上面Imagesc里的定义,我们q个版本只提供曹?Caocao)、关?Guanyu)、张?Zhangfei)、n?Zhaoyun)、黄?Huangzhong)、马?Machao)和卒(Zu)。我们这里也限定一下游戏的操作ҎQ首先要通过方向键选择一个要Ud的区?是一张图?Q被选择的区域用黑色Ҏ框住Q选好后按Fire?是定?这块区域选中Q被选中的区域用l色Ҏ框住Q然后选择要移动到的区域,此时用红色方框框住被选择的区域;选好要移动到的区域之后按Fire键将要移动的区域(囄)Ud要移动到的区域,q去掉绿色和U色的方框。这里需要强调的概念有选择的区域、选中的区域、要Ud的区域和要移动到的区域,q四个概念请读者注意区分,当然也应当把q一部分记入数据字典之中?
Z使文章的重点H出(介绍如何制作一个J2ME的收集游?Q我们这里限定一些与本主题无关的内容暂不d玎ͼq关之后的动?实现时要用到TimerTask或Threadc,后箋的系列文章中我会详细介绍动画斚w的知?、关面之间的切换(其实很简单,当完成Q务之后重新再做一?、暂停和保存{操?q部分的内容介绍的资料很多,我也写不Z么新的东东来Q难免抄袭,故此免掉)?
需求分析基本完成,M午还有一D|_马上动手用ACDSee把从|上找来的BMP文gQ调整其大小?71*177(我的q个囄是两个部分合在一P所以比手机实际屏幕大了)Q另存ؓPNG格式。半天时间刚刚好Q不但搞清楚了要做的东东Q还把要用的囄准备好了?
四、概要设?
概要设计是从需求分析过渡到详细设计的桥梁和U带Q这一部分中我们确定项目的实现Ҏ和模块的划分。我们决定将整个目分成五个部分Q分别是前面介绍的Images、DrawQ还有Map和Displayable1和MIDlet1。Images和Drawcd能简单、结构固定,因此很多目我们都用这两各c,q里直接拿来Ҏp用了Q前面已l介l过q里不再赘述。MapcL用来从外部文件读入地图,然后保存在一个数l之中,q部分的内容是我们在本阶D讨论的重点。Displayable1是一个承了CanvascȝdQ它用来处理E序的主要控刉辑和一部分控制逻辑所需的辅助函敎ͼ主要函数应该包括用来l图的paint()函数、用来控制操作的keyPressed()函数、用来控刉择区域的setRange()函数、用来控刉择要移动到区域的setMoveRange()函数、用来移动选中区域的Move()函数和判断是否完成Q务的win()函数Q更具体的分析,我们攑ֈ详细设计中去l化。MIDlet1实际上就是一个控制整个J2ME应用的控制程序,其实也没有什么可特别的,它和我们前面介绍?Hello World"E序大同异Q这里就不展开来说了,后面会脓出它的全部代码?
MapcM要应该有一个Grid[][]的二l数l,用来存放华容道的地图Q还应该有一个read_map()函数用来从外部文件读取地囑ֆ容填充Grid数据l构Q再是要有一个draw_map()函数用来把Grid数据l构中的地图内容转换成图片显C出?当然要调用DrawcȝpaintҎ)。说到读取外部文ӞW者知道有两种ҎQ一U是传统的定义一个InputStream对象Q然后用getClass().getResourceAsStream()Ҏ取得输入,然后再从输入中取得外部文g的内容,例如
InputStream is = getClass().getResourceAsStream("/filename");
if (is != null) {
byte a = (byte) is.read();
}
q里h意文件名中的根\径是相对于便以后的class文g攄的位|,而不是源文g(java)。第二种Ҏ是用onnector.openInputStreamҎQ然后打开的协议是ResourceQ但是这U方法笔者反复尝试都没能调通,报告的错误是~少Resource协议Q估计第二种Ҏ用到J2ME的某些扩展类包,此处不再q。由于以前已l做q一些类似华定wq样的地图,q里直接l出Mapcȝ代码Q后面就不再详细解释MapcMQ以便于我们可以集中_֊处理Displayable1中的逻辑?
Mapcȝ代码如下Q?
package huarongroad;
import java.io.InputStream;
import javax.microedition.lcdui.*;
public class Map {
//处理游戏的地图,负责从外部文件加载地图数据,存放地图数据Qƈ按照地图数据l制地图
public byte Grid[][];//存放地图数据
public Map() {//构造函敎ͼ负责初始化地图数据的存储l构
this.Grid = new byte[Images.HEIGHT][Images.WIDTH];
//用二l数l存攑֜图数据,注意W一l是竖直坐标Q第二维是水q_?
}
public int[] read_map(int i) {
QA href="file://?Qfile://从</AQ外部文件加载地图数据,q存攑֜存储l构中,q回值是光标点的位置
//参数是加载地图文件的{
int[] a = new int[2];//光标点的位置Q?是水q位|,1是竖直位|?
try {
InputStream is = getClass().getResourceAsStream(
"/huarongroad/level".concat(String.valueOf(i)));
if (is != null) {
for (int k = 0; k Q?Images.HEIGHT; k++) {
for (int j = 0; j Q?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();//d回RQ?3Q?忽略?
is.read();//d换行Q?0Q?忽略?
}
is.close();
}else {
//d文gp|
a[0] = -1;
a[1] = -1;
}
}catch (Exception ex) {
//打开文gp|
a[0] = -1;
a[1] = -1;
}
return a;
}
public boolean draw_map(Graphics g) {
//调用Drawcȝ静态方法,l制地图
try {
for (int i = 0; i Q?Images.HEIGHT; i++) {
for (int j = 0; j Q?Images.WIDTH; j++) {
Draw.paint(g, this.Grid[i][j], j, i);//l制地图
}
}
return true;
}catch (Exception ex) {
return false;
}
}
}
对于像华定wq样的小型地囑֏以直接用手工来绘制地囄内容Q比如:
fa1c
2232
bd1e
2gg2
gihg
但是Q如果遇到像坦克大战或超U玛莉那L地图Q就必须另外开发一个地囄辑器?我会在后l的文章中介l用vb来开发一个地囄辑器)?
五、详l设?
详细设计是程序开发过E中臛_重要的一个环节,好在我们在前面的各个阶段中已l搭建好了项目所需的一些工P现在q个阶段中我们只需集中_֊设计好Displayable1中的逻辑?两天的时间当然不只干q点z,q要把其他几个类的设计修改一?
Displayable1q个c负责处理程序的控制逻辑。首先,它需要有表示当前关面的变量level、表C当前光标位|的变量loc、表CUd区域的变量SelectArea、表CUd到的区域的变量MoveArea、表C是否已有区域被选中而准备移动的变量Selected和Mapcȝ实例MyMap。然后,我们Ҏ用户按不同的键来处理不同的消息,我们要实现keyPressed()函数Q在函数中我们处理按键的上下左右和选中(Fire)Q这里的处理需要我展开来讲一Ԍ后面我很快会把这一部分详细展开?
接下来,是实现paint()函数Q我们打在q一部分中反复的重画背景、地囑֒选择区域Q这个函数必d理好区域被选中之后的画W颜色的切换Q具体讲是在没有选中M区域时要用黑色画W,当选重要移动的区域时用绿色画W,当选择要移动到的区域时改用U色ȝ(当然附加一张流E图是必不可的)?
再下面要实现的setRange()函数和setMoveRange()函数Q这两个函数用来讄要移动的区域和要Ud到的区域Q我的思\是利用前面在ImagescM介绍q的地图l合标记帔RQ当Ud到地囄合标记常量时Q根据该点地图中的值做逆向变换扑ֈ相应的地图标记常量,然后讄相应的loc、SelectArea和MoveArea,其中setMoveRange()函数q用C一个辅助函数isInRange(),isInRange()函数是用来判断给定的Ҏ否在已选中的要Ud的区域之?如果isInRange()的返回值是假ƈ且该点处的g是空白就表明要移动到的区域R犯了其他以被占用的区域。有了setRange()和setMoveRange()函数QMove()函数水到渠成了,Move()函数要Ud的区域移动到要移动到的区?在移动过E中分ؓ三步q行:
W一.复制要移动的区域Q?
W二.复制出的要Ud区域复制到要Ud到的区域(q两步分开q行的目的是防止在复制过E中覆盖掉要Ud的区?Q?
W三.用isInRange2()判断l定的点是否在要Ud到的区域?不在要Ud到的区域内的点设|成I白?下面我们详细的分析一下keyPressed()函数的实现方?首先,keyPressed()函数要处理按键的上下左右和选中(Fire),在处理时需要用CanvascȝgetGameAction函数来将按键的键D{换成游戏的方?q样可以提高游戏的兼Ҏ?因ؓ不同的J2ME实现,其方向键的键g一定是相同??
接下?分别处理四个方向和选中.当按下向上时,先判断是否已l选定了要Ud的区?即this.selected是否为真),如果没有选中要移动区域则让光标向上移动一?然后调用setRange()函数讄选择要移动的区域,再调用repaint()函数h屏幕,否则如果已经选中了要Ud的区?p光标向上Ud一?然后调用setMoveRange()函数判断是否能够向上Ud已选中的区?如果能移动就调用repaint()函数h屏幕,如果不能Udp光标向下退回到原来的位|?
当按下向下时,先判断是否已l选定了要Ud的区?如果没有选中要移动的区域则判断当前所处的区域是否Z个格?如果是两个格高则向下Ud两格,如果是一个格高则向下Ud一?接着再调用setRange()函数讄选择要移动的区域,而后调用repaint()函数h屏幕,否则如果已经选中了要Ud的区?p光标向下Ud一?然后调用setMoveRange()函数判断是否能够向下Ud已选中的区?如果能移动就调用repaint()函数h屏幕,如果不能Udp光标向上退回到原来的位|?按下向左时情况完全类似向上的情况,按下向右时情况完全类似向下的情况,因此q里不再赘述,详细情况请参见程序的源代码?
当按下选中键时,先判断是否已l选中了要Ud的区?如果已经选中了要Ud的区域就调用Move()函数完成pUd的区域到要移动到的区域的Udq程,接着调用repaint()函数h屏幕,然后已选择标记|成false,l箋调用win()函数判断是否完成了Q?否则如果q没有选定要移动的区域则再判断当前选中区域是否为空?如果不是I白将选中标记|成true,然后h屏幕.q里介绍一个技?在开发程序遇到复杂的逻辑的时?可以构造一格打印函数来所兛_的数据结构打印出来以利调?q里我们构造一个PrintGrid()函数,q个函数Ua是ؓ了调试之?效果q得不错.x我们完成了编码前的全部工作?
六、编?
整个目共有五个c?有四个类的代码前面已l介l过?而且是在其他目中用过的相Ҏ熟的代码.现在只需全力d现Displayable1c?Displayable1cȝ代码如下:
package huarongroad;
import javax.microedition.lcdui.*;
public class Displayable1 extends Canvas implements CommandListener {
private int[] loc = new int[2]; QA href="file://?Qfile://光</AQ标的当前位|,0是水q位|,1是竖直位|?
private int[] SelectArea = new int[4];//被选定的区域,卌Ud的区?
private int[] MoveArea = new int[4];//要移动到的区?
private Map MyMap = new Map();//地图c?
private boolean selected;//是否已经选中要移动区域的标志
private int level;//但前的关?
public Displayable1() {//构造函?
try {
jbInit();//JBuilder定义的初始化函数
}catch (Exception e) {
e.printStackTrace();
}
}
private void Init_game(){
//初始化游戏,d地图Q设|选择区域Q清IUd到的区域
this.loc = MyMap.read_map(this.level);//d地图文gQƈq回光标的初始位|?
//0为水q位|,1为竖直位|?
this.SelectArea[0] = this.loc[0];//初始化选中的区?
this.SelectArea[1] = this.loc[1];
this.SelectArea[2] = 1;
this.SelectArea[3] = 1;
this.MoveArea[0] = -1;//初始化要Ud到的区域
this.MoveArea[1] = -1;
this.MoveArea[2] = 0;
this.MoveArea[3] = 0;
}
private void jbInit() throws Exception {//JBuilder定义的初始化函数
QA href="file://?Qfile://初</AQ始化实例变?
this.selected = false;//讄没有被选中的要Ud区域
this.level = 1;
Images.init();//初始化图片常?
Init_game();//初始化游戏,d地图Q设|选择区域Q清IUd到的区域
setCommandListener(this);//d命o监听Q这是Displayable的实例方?
addCommand(new Command("Exit", Command.EXIT, 1));//d“退出”按?
}
public void commandAction(Command command, Displayable displayable) {
//命o处理函数
if (command.getCommandType() == Command.EXIT) {//处理“退出?
MIDlet1.quitApp();
}
}
protected void paint(Graphics g) {
//d函数Q用于绘制用L面,xC图片,勄选中区域和要Ud到的区域
try {
g.drawImage(Images.image_Frame, 0, 0,
Graphics.TOP | Graphics.LEFT);//画背?
MyMap.draw_map(g);//按照地图内容d
if ( this.selected )
g.setColor(0,255,0);//如果被选中Q改用绿色画选中的区?
g.drawRect(this.SelectArea[0] * Images.UNIT + Images.LEFT,
this.SelectArea[1] * Images.UNIT + Images.TOP,
this.SelectArea[2] * Images.UNIT,
this.SelectArea[3] * Images.UNIT);//d选择区域Q?
QA href="file://?Qfile://如</AQ果被选中Q就用绿?
QA href="file://?Qfile://否</AQ则Q用黑?
g.setColor(255,255,255);//恢复ȝ颜色
if (this.selected) {//已经选中了要Ud的区?
g.setColor(255, 0, 255);//改用U色
g.drawRect(this.MoveArea[0] * Images.UNIT + Images.LEFT,
this.MoveArea[1] * Images.UNIT + Images.TOP,
this.MoveArea[2] * Images.UNIT,
this.MoveArea[3] * Images.UNIT);//d要移动到的区?
g.setColor(255, 255, 255);//恢复ȝ颜色
}
}catch (Exception ex) {
}
System.out.println(Runtime.getRuntime().freeMemory());
System.out.println(Runtime.getRuntime().totalMemory());
}
private void setRange() {
//讄Ud后能够选中的区?
//调整当前光标位置到地囄M|,卌录h物信息的位置
if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFT) {
this.loc[0] -= 1;//向左?
}else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DUP) {
this.loc[1] -= 1;//向上?
}else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFTUP) {
this.loc[0] -= 1;//向左?
this.loc[1] -= 1;//向上?
}
this.SelectArea[0] = this.loc[0];//讄光标的水q位|?
this.SelectArea[1] = this.loc[1];//讄光标的竖直位|?
//讄光标的宽?
if (this.loc[0] + 1 Q?Images.WIDTH) {
this.SelectArea[2] = this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] != (byte) ???
1 : 2;
}else {
this.SelectArea[2] = 1;
}
//讄光标的高?
if (this.loc[1] + 1 Q?Images.HEIGHT) {
this.SelectArea[3] = this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] != (byte) ???
1 : 2;
}else {
this.SelectArea[3] = 1;
}
}
private boolean setMoveRange() {
//讄要移动到的区域,能够Udq回true,否则q回false
for (int i = 0; i Q?this.SelectArea[2]; i++) {
for (int j = 0; j Q?this.SelectArea[3]; j++) {
if (this.loc[1] + j Q? Images.HEIGHT ||
this.loc[0] + i Q? Images.WIDTH ||
(!isInRange(this.loc[0] + i, this.loc[1] + j) &&
this.MyMap.Grid[this.loc[1] + j][this.loc[0] + i] !=
Images.BLANK)) {
return false;
}
}
}
this.MoveArea[0] = this.loc[0];
this.MoveArea[1] = this.loc[1];
this.MoveArea[2] = this.SelectArea[2];
this.MoveArea[3] = this.SelectArea[3];
return true;
}
private boolean isInRange(int x, int y) {
//判断l定的(xQyQ点是否在选定区域之内Qx是水q_标,y是竖直坐?
if (x Q? this.SelectArea[0] &&
x Q?this.SelectArea[0] + this.SelectArea[2] &&
y Q? this.SelectArea[1] &&
y Q?this.SelectArea[1] + this.SelectArea[3]) {
return true;
}else {
return false;
}
}
private boolean isInRange2(int x, int y) {
//判断l定的(xQyQ点是否在要Ud到的区域之内Qx是水q_标,y是竖直坐?
if (x Q? this.MoveArea[0] &&
x Q?this.MoveArea[0] + this.MoveArea[2] &&
y Q? this.MoveArea[1] &&
y Q?this.MoveArea[1] + this.MoveArea[3]) {
return true;
}else {
return false;
}
}
protected void keyPressed(int keyCode) {
//处理按下键盘的事Ӟq是Canvas的实例方?
switch (getGameAction(keyCode)) {//按键的D{化成方向帔R
case Canvas.UP://向上
if (!this.selected) {//q没有选定要移动的区域
if (this.loc[1] - 1 Q? 0) {//向上q有UdI间
this.loc[1]--;//向上Ud一?
setRange();//讄光标Ud的区域,该函数能光标移动到地图M|?
repaint();//重新l图
}
}else {//已经选定了要Ud的区?
if (this.loc[1] - 1 Q? 0) {//向上q有UdI间
this.loc[1]--;//向上Ud一?
if (setMoveRange()) {//能够UdQ该函数能够讄要移动到的区?
repaint();//重新l图
}else {//不能Ud
this.loc[1]++;//退回来
}
}
}
break;
case Canvas.DOWN://向下
if (!this.selected) {//q没有选定要移动的区域
if (this.loc[1] + 1 Q?Images.HEIGHT) {//向下q有UdI间
if (this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] ==
Images.DUP){//该图片有两个格高
this.loc[1]++;//向下Ud一?
if (this.loc[1] + 1 Q?Images.HEIGHT) {//向下q有
QA href="file://U?Qfile://U</AQ动I间
this.loc[1]++;//向下Ud一?
setRange();//讄光标Ud的区域,
QA href="file://?Qfile://该</AQ函数能光标移动到地图M|?
repaint();//重新l图
}else {//向下没有UdI间
this.loc[1]--;//退回来
}
}else {//该图片只有一个格?
this.loc[1]++;//向下Ud一?
setRange();//讄光标Ud的区域,
QA href="file://?Qfile://该</AQ函数能光标移动到地图M|?
repaint();//重新l图
}
}else {
}
}else {//已经选定了要Ud的区?
if (this.loc[1] + 1 Q?Images.HEIGHT) {//向下q有UdI间
this.loc[1]++;//向下Ud一?
if (setMoveRange()) {//能够UdQ该函数能够讄要移动到的区?
repaint();//重新l图
}else {//不能Ud
this.loc[1]--;//退回来
}
}
}
break;
case Canvas.LEFT://向左
if (!this.selected) {//q没有选定要移动的区域
if (this.loc[0] - 1 Q? 0) {//向左q有UdI间
this.loc[0]--;//向左Ud一?
setRange();//讄光标Ud的区域,该函数能光标移动到地图M|?
repaint();//重新l图
}
}else {//已经选定了要Ud的区?
if (this.loc[0] - 1 Q? 0) {//向左q有UdI间
this.loc[0]--;//向左Ud一?
if (setMoveRange()) {//能够UdQ该函数能够讄要移动到的区?
repaint();//重新l图
}else {//不能Ud
this.loc[0]++;//退回来
}
}
}
break;
case Canvas.RIGHT://向右
if (!this.selected) {//q没有选定要移动的区域
if (this.loc[0] + 1 Q?Images.WIDTH) {//向右q有UdI间
if (this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] ==
Images.DLEFT) {//该图片有两个格宽
this.loc[0]++;//向右Ud一?
if (this.loc[0] + 1 Q?Images.WIDTH) {//向右q有
QA href="file://U?Qfile://U</AQ动I间
this.loc[0]++;//向右Ud一?
setRange();//讄光标Ud的区域,
QA href="file://?Qfile://该</AQ函数能光标移动到地图M|?
repaint();//重新l图
}else {//向右没有UdI间
this.loc[0]--;//退回来
}
}else {//该图片只有一个格?
this.loc[0]++;//向右Ud一?
setRange();//讄光标Ud的区域,
QA href="file://?Qfile://该</AQ函数能光标移动到地图M|?
repaint();//重新l图
}
}else {
}
}else {//已经选定了要Ud的区?
if (this.loc[0] + 1 Q?Images.WIDTH) {//向右q有UdI间
this.loc[0]++;//向右Ud一?
if (setMoveRange()) {//能够UdQ该函数能够讄要移动到的区?
repaint();//重新l图
}else {//不能Ud
this.loc[0]--;//退回来
}
}
}
break;
case Canvas.FIRE:
if (this.selected) {//已经选定了要Ud的区?
Move();//要Ud的区域移动到刚选中的区?
repaint();//重新l图
this.selected = false;//清除已选定要移动区域的标志
if ( win()) {
System.out.println("win");
}
}else {//q没有选定要移动的区域
if (this.MyMap.Grid[this.loc[1]][this.loc[0]] ==
Images.BLANK) {//要移到的位置是一个空?
}else {//要移到的位置不是I白
this.selected = true;//讄已选定要移动区域的标志
}
repaint();//重新l图
}
break;
}
}
private boolean win(){
QA href="file://?Qfile://判</AQ断是否已经救出了曹?
if ( this.MyMap.Grid[Images.HEIGHT - 2 ][Images.WIDTH - 3 ] == Images.CAOCAO )
return true;
else
return false;
}
private void PrintGrid(String a) {
QA href="file://?Qfile://打</AQ印当前地图的内容,用于调试
System.out.println(a);
for (int i = 0; i Q?Images.HEIGHT; i++) {
for (int j = 0; j Q?Images.WIDTH; j++) {
System.out.print( (char)this.MyMap.Grid[i][j]);
}
System.out.println("");
}
}
private void Move() {
QA href="file://?Qfile://</AQ要Ud的区域移动到刚选中的区?
if (this.MoveArea[0] == -1 || this.MoveArea[1] == -1 ||
this.SelectArea[0] == -1 || this.SelectArea[1] == -1) {//没有选中区域
}else {//已经选中了要Ud的区域和要移动到的区?
byte[][] temp = new byte[this.SelectArea[3]][this.SelectArea[2]];
QA href="file://?Qfile://复</AQ制要移动的区域Q因块区域可能会被覆盖掉
for (int i = 0; i Q?this.SelectArea[2]; i++) {
for (int j = 0; j Q?this.SelectArea[3]; j++) {
temp[j][i] =
this.MyMap.Grid[this.SelectArea[1] +j]
[this.SelectArea[0] + i];
}
}
QA href="file://PrintGrid"Q?a href="file://PrintGrid/">file://PrintGridQ?AQ?"1"); // 调试信息
QA href="file://?Qfile://</AQ要Ud的区域移动到刚选中的区域(卌Ud到的区域Q?
for (int i = 0; i Q?this.SelectArea[2]; i++) {
for (int j = 0; j Q?this.SelectArea[3]; j++) {
this.MyMap.Grid[this.MoveArea[1] + j]
[this.MoveArea[0] + i] = temp[j][i];
}
}
QA href="file://PrintGrid"Q?a href="file://PrintGrid/">file://PrintGridQ?AQ?"2");// 调试信息
QA href="file://?Qfile://</AQ要Ud的区域中无用内容|成I白
for (int i = 0; i Q?this.SelectArea[3]; i++) {
for (int j = 0; j Q?this.SelectArea[2]; j++) {
if (!isInRange2(this.SelectArea[0] + j,
this.SelectArea[1] + i)) {//该点是不在要Ud?
QA href="file://?Qfile://的</AQ区域之内,需|空
this.MyMap.Grid[this.SelectArea[1] + i]
[this.SelectArea[0] + j] = Images.BLANK;
}else {
}
}
}
QA href="file://PrintGrid"Q?a href="file://PrintGrid/">file://PrintGridQ?AQ?"3");// 调试信息
this.SelectArea[0] = this.MoveArea[0];//重置选中位置的水q_?
this.SelectArea[1] = this.MoveArea[1];//重置选中位置的竖直坐?
this.MoveArea[0] = -1;//清空要移动到的位|?
this.MoveArea[1] = -1;//清空要移动到的位|?
this.MoveArea[2] = 0;//清空要移动到的位|?
this.MoveArea[3] = 0;//清空要移动到的位|?
}
}
}
代码的相兛_?在详l设计阶D已l讲q?代码中有比较相近的注?误者自行研d?全部的代码写好,用wtk2.0自带的Ktoolbar工具建立一个工E?接下来把M源文件放到正位|下,然后点击build,再点run,完成了E序的编?当然如果有错误还要修改和调试?
七、测?
作ؓ一个真正的产品要经q单体测试、结合测试和pȝ试。由于项目本w简?而且大部分代码已l是相对成熟?我们跌单体试Q又׃W者的实际环境所?无法搞到Java手机,无法架设OTA服务?因此我们也只能放弃系l测试。那么就让我们开始结合测试吧。测试之前要先出一个测试式样书,也就是测试的计划。我们将它简化一?只测试如下几U情?W一、对各种形状的区域的选择和移?W二、͘q边界区域的选择和移?W三、同一区域的反复选择和反复移?W四、非法选择和非法移动。有了测试的目标,接下来的工作是用wtk2.0自带的Run MIDP Application工具q行试。打开q个工具,加蝲huarongRoad的jad文g,E序׃自动q行,选择launch上MIDlet1q个E序,华容道游戏就会跃然屏q之?接下来的工作是左三?右三?拇指扭扭,来做试。测试过E中发现M的问?立刻发一个bug给自己,然后又是痛苦的调试和修正bug,如此如此?
八、发?
谈到发布,其实是个关键,再好的品不能很好的发布出去也只是个产品而已,变不成商品也得不到回报.׃W者的条g所?q里只能是纸上谈?不过q是希望能够使读者对q一q程有所了解(|上的资料也很多)?
J2ME的程序发布一般都是通过OTA(Over The Air),你只需要一台有公网IP的主机和一个普通的web Server可以了(管要求很低,但笔者还是没?Q这里我们以apacheZ介绍一下OTA服务的配|,首先是安装好了apache服务器,然后在conf目录下找到mime.types文gQ在该文件中加入如下两行
application/java-archive jar
text/vnd.sun.j2me.app-descriptor jad
然后重vapache服务器就可以了。接下来的工作就是修改jad文g中MIDlet-Jar-URL:后面的参敎ͼ它改ؓURL的绝对\径,卻IA href="http://***/"Q?a href="http://***/">http://***/Q?AQhuarongroad.jar(其中***是你的域名或IP地址)。在下面是用java手机下蝲jad文gQ它会自动部|相应的jar文gq加载它。剩下的工作和在模拟器上操作是一L了?
九、项目ȝ
xQ我们已l完成了一个J2ME游戏的全部开发过E,E序中涉及到了调研、分析、设计、编码、测试和发布{方面的问题Q其实在实际的工作中q有很多更ؓ具体的问题,毕竟技术只在Y件开发过E中占据很有限的一部分Q这里限于篇q的限制无法一一具体展开。今后,W者计划再写一用J2ME开发手机屏保的文章Q借此Z向读者展CJ2ME动画技术;然后再写一J2ME|络应用的文章,做一个类似开心辞兔RL知识问答游戏Q以便向读者展CJ2ME的网l技术;待这两方面的技术交待清楚之后,我将引领读者制作一个稍大一些的游戏?/p>