1、 基于 Nokia 手机的移动游戏开发步步通一、序言昨天在网上闲逛,发现一篇讲解用 delphi 实现华容道游戏的文章,颇受启发于是,产生了将华容道游戏移植到手机中去的冲动现在手机游戏琳琅满目,不一而足,华容道的实现版本也很多正巧不久前笔者对 J2ME 下了一番功夫,正想借这个机会小试牛刀。选用 J2ME 的原因还有一个就是目前 Java 开发大行其到,无线增殖业务迅猛发展,J2ME的应用日渐活跃起来,也希望我的这篇文章能够为 J2ME 知识的普及和开发团队的壮大推波助澜。由于长期受 ISO 规范的影响,这次小试牛刀我也打算遵照软件工程的要求,并采取瀑布式的开发模式来规划项目,也希望借此机会向
2、各位没有机会参与正式项目开发的读者介绍一下软件开发的流程。这里我们先定义项目组的人员体制(其实只有我一个人):技术调研、需求分析、概要设计、详细设计、编码、测试均有笔者一人担任;美工这里我找了个捷径,盗用网上现成的图片,然后用 ACDSee 把它由 BMP 转换成 PNG 格式(我出于讲座的目的,未做商业应用,应该不算侵权吧);至于发布工作,由于缺少 OTA 服务器,此项工作不做(但是我会介绍这步如何做)。接下来,我们规划一下项目实现的时间表,以我个人经验,设想如下:技术调研用2天(这部分解决项目的可行性和重大技术问题,时间会长一些),需求分析用半天(毕竟有现成的东东可以参照,只要理清思路就行
3、了,况且还有很多以前用过的设计模式和写好的代码),概要设计再用半天(有了需求,概要只不够是照方抓药),详细设计要用2天(这一步要把所有的问题想清楚,还要尽可能的准确描述出来),编码用2天(其实1天就够了,技术已经不是问题,多计划出一天来应付突发事件),测试用2天(测试应该至少占全部项目的四分之一,不过这个项目只是一个 Demo,也太简单了),发布也要用上半天(尽管我们不去实际发布它,但是还要花点时间搞清楚应该如何做),最后就是项目总结和开庆功会(时间待定)。二.利其器“公欲善其事,必先利其器”,做项目之前第一步是前期调研我们要做的华容道这个东东随处可见,我们要调研的是两个方面:1、游戏的内容:
4、游戏本身很简单,就是有几个格子,曹操占据其中一个较大的格子,然后被几个格子包围,这些格子形状不一定相同,但是挡住了曹操移动的方向游戏者需要挪动这些格子最终把曹操移动到一个指定的位置才算是过关更具体的分析我们放在后面需求分析和概要设计中讨论。2、技术储备:谈到技术,这里简单介绍一下 J2ME.Java 有三个版本,分别是J2ME(微型版).J2SE(标准版).J2EE(企业版)J2ME 是一个标准,采用层结构设计最低层是配置层(Configuration)也就是设备层,其上是简表层(Profile),再上是应用层(Application).MIDP 就是移动信息设备简表,目前主流手机支持 MID
5、P1.0,最新的是 MIDP2.0,它比前一个版本增加了对游戏的支持,在javax.microedition.lcdui.game 包中提供了一些类来处理游戏中的技术,比如我们后面会用到的 Sprite 类,它是用来翻转图片的.权衡再三,笔者决定使用 MIDP2.0来做开发首先需要安装一个 J2ME 的模拟器,我们就用 Sun 公司的 WTK2.0,我觉得 Sun 的东西最权威当然你也可以使用 Nokia.Siemens 或是 Motolora 等其他模拟器,但是他们的JDK 不尽相同,写出来的程序移植是比较麻烦的Sun 公司的 WTK2.0可以到A href=“http:/here/下“ht
6、tp:/here/下/A载,当然要想成功下载的前提是你要先注册成为 Sun 的会员(其实这样对你是有好处的)当下来之后就是按照提示一步一步的安装安装好了之后,我们用一个“Hello World“程序开始你的 J2ME 之旅我们启动WTK2.0工具集中的 KToolBar,然后点击 New Project 按钮,在弹出的输入框中输入Project Name 为 HelloWorld,MIDlet Class Name 为 Hello,然后点击 Create Project,开始生成项目,工具会弹出 MIDP 配置简表,这里接受生成的默认值(以后还可以修改)点击 OK,工具提示我们把写好的 Jav
7、a 源程序放到WTK_HOMEappsHelloWorldsrc 目录之下我们编辑如下代码,并保存在上述目录之下,文件名为 Hello.java。import javax.microedition.midlet.*;import javax.microedition.lcdui.*;public class Hello extends MIDletprivate Display display;public Hello()display =Display.getDisplay(this);public void startApp()TextBox t = new TextBox(“Hello“
8、,“Hello“,256,0);display.setCurrent(t);public void pauseApp()public void destroyApp(boolean unconditional)保存好了之后,点击 Build 按钮,工具会为你编译程序,如无意外再点击 Run 按钮,会弹出一个手机界面,剩下的就不用我教了吧(用鼠标对手机按键一顿狂点)。呵呵,你的第一个 J2ME 程序已经 OK 了.什么?你还一点都没懂呢(真是厉害,不懂都能写出 J2ME程序来,果然是高手)我这里主要是介绍 WTK2.0工具的使用,程序并不是目的,不懂的话后面还会有详细的解说,这里只是带你上路什么
9、?你不懂 Java!那也没有关系,后面我再讲得细一点。跳过 J2ME,我们先来讲点游戏的理论具体到华容道这个游戏,主要有三个方面,贴图游戏操作逻辑判断这里讲讲贴图,其他两方面放在概要设计和详细设计里讲所谓的贴图,其实就是画图,就是在要显示图形的位置上输出一副图片,(要是牵扯到动画就要麻烦一些,可以使用 TimerTask.Thread 或 Rannable 之类的技术),这副图片可以是事先准备好的也可以是临时处理的在 J2ME 中有一个 Image 类,专门用于管理图片,它有 createImage()方法,可以直接读取图片文件(J2ME 只支持 PNG 格式的图片),也可以截取已有的图片的一
10、部分(这样我们可以把很多图片放在一起,然后一张一张的截下来,好处是节省存储空间和文件读取时间,对于手机这两者都是性能的瓶颈)J2ME 还有一个 Graphics 类,专门用于绘图,它有 drawImage()方法,可以把一副图片在指定的位置上显示出来,它还有 drawRect()方法和 setColor()方法,这两个方法在后面我们进行游戏操作时就会用到,这里先交代一下有了图片和绘图的方法,还需要知道把图画到谁身上,J2ME 提供了一个 Canvas 类,字面意思就是画布,它有一个 paint()方法用于刷新页面,还有一个 repaint()方法用于调用 paint()方法听着有些糊涂是吧,不
11、要紧,我来结合具体程序讲解一下为了今后编程的方便,我们创建两个类 Images 和 Draw,Images用于保存一些常量值和图片,Draw 主要是用于画图,这两个类的源代码如下。Images 类的源代码如下:package huarongroad;import javax.microedition.lcdui.*;import javax.microedition.lcdui.game.*;public class Images /保存常量/绘图位置常量public static final int UNIT = 32;/方块的单位长度public static final int LEFT
12、 = 10;/画图的左边界顶点public static final int TOP = 9;/画图的上边界顶点/地图位置常量public static final int WIDTH = 4;/地图的宽度public static final int HEIGHT = 5;/地图的高度/地图标记常量public static final byte CAOCAO = (byte) a; A href=“file:/曹“file:/曹/A操的地图标记public static final byte MACHAO = (byte) b;/马超的地图标记public static final byt
13、e 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;/空白的地图标记public static fi
14、nal byte CURSOR = (byte) i;/光标的地图标记/地图组合标记常量public static final byte DLEFT = (byte) 1; A href=“file:/组“file:/组/A合图形左边标记public static final byte DUP = (byte) 2; A href=“file:/组“file:/组/A合图形上边标记public static final byte DLEFTUP = (byte) 3; A href=“file:/组“file:/组/A合图形左上标记/图片常量public static Image image_
15、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 I
16、mage image_Blank;/空白的图片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);/Sprite 类是用来翻转图片的,是 MIDP2.0新新增加的
17、支持游戏的特性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
18、.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_
19、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;Draw 类的源代码如下:package huarongroad;import javax.microedition.lcdui.*;public class Draw /绘制游戏中的图片public Draw(Canvas
20、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 点转化成画布的绝对坐标,绘图return true;catch (Exception ex) return false;public static boolean paint(Graphics g, byte img, int x, int y, int unit) try switch (img) cas
21、e Images.CAOCAO:/画曹操/变成绝对坐标,并做调整g.drawImage(Images.image_Caocao, Images.LEFT + x * unit,Images.TOP + y * unit,Graphics.TOP | Graphics.LEFT);break;case Images.GUANYU:/画关羽g.drawImage(Images.image_Guanyu, Images.LEFT + x * unit,Images.TOP + y * unit,Graphics.TOP | Graphics.LEFT);break;case Images.HUANG
22、ZHONG:/画黄忠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:/画张飞g.drawImag
23、e(Images.image_Zhangfei, Images.LEFT + x * unit,Images.TOP + y * unit,Graphics.TOP | Graphics.LEFT);break;case Images.ZHAOYUN:/画赵云g.drawImage(Images.image_Zhaoyun, Images.LEFT + x * unit,Images.TOP + y * unit,Graphics.TOP | Graphics.LEFT);break;case Images.ZU:/画卒g.drawImage(Images.image_Zu, Images.L
24、EFT + 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:/画光标g.drawRect(Images.LEFT + x * unit,Images.TOP + y * unit,Images.UNIT,I
25、mages.UNIT);break;return true;catch (Exception ex) return false;其中 Images 类存的是绘图位置常量(也就是在画图时每个格子的长度和相对坐标原点位置要进行的调整)、地图位置常量(地图的长、宽),地图标记常量(人物对应的记号),地图组合标记常量(后面会细说),图片常量(存放人物的图片);Draw 类主要负责在制定的位置画出人物图片。下面我来说说 Images 类中的地图标记常量和地图组合标记常量。为了能够灵活的安排各个关面的布局,我们决定把游戏布局的信息存储在外部文件中,然后程序启动后把它读进来。这样我们制定了一套存储图片的代码
26、,这就是地图标记常量,如上面 Images 类中定义的 Caocao(曹操)用 a 字符来表示,当程序读到 a 字符时就能将它转化成曹操对应的图片,并在读到 a 字符的位置上进行显示。但是从实际观察中我们发现所有的图片并不是统一大小的,有的占4个格子,有的占2个格子,还有的占1个格子,而且即便同是占两个格子的图片还有横、竖之分。有鉴于此,我们引入了地图组合标记常量,就是说在遇到占有多个格子的时候,值1(也就是 Images.LEFT)表示它的左边是一个真正的地图标记,值2(也就是 Images.UP)表示它的上边是一个真正的地图标记,值1(也就是 Images.LEFTUP)表示它的左上边是一
27、个真正的地图标记。地图组合标记常量其实就是用来占位置的,与实际显示无关,当后面我们将到移动时还会再来分析组合标记的使用。Draw 类主要是用来在画布上画出图形,它有两个 paint 方法,这是很常见的函数重载。但是程序中实际上只用到了4个参数的 paint 方法,它直接获得要画图片的相对坐标位置信息,然后调用5个参数的 paint 方法。5个参数的 paint 方法将相对坐标位置信息转换成绝对位置,并实际调用 Graphics.drawImage()方法,将 Images 中的图片画了出来。这种实现方法的好处是灵活和便于扩展,但你需要画图的位置并不能够对应到格子中的相对坐标位置时,你就可以直接
28、调用5个参数的 paint 方法,而不必再去修改这各类;但你添加新的图片时,只要在 Images 中增加对应的常量,然后向 Draw 中5个参数的 paint 方法添加一条处理就可以了。写到这里,两天的时间刚好用完。三、需求分析这部分叫做需求分析,听起来挺吓人的,其实就是搞清楚我们要做什么,做成什么样,那些不做。下面我引领着大家共同来完成这一步骤。首先,我们要做一个华容道的游戏,华容道的故事这里不再赘述了,但其中的人物在这里限定一下,如上面 Images 类里的定义,我们这个版本只提供曹操(Caocao)、关羽(Guanyu)、张飞(Zhangfei)、赵云(Zhaoyun)、黄忠(Huang
29、zhong)、马超(Machao)和卒(Zu)。我们这里也限定一下游戏的操作方法:首先要通过方向键选择一个要移动的区域(就是一张图片),被选择的区域用黑色方框框住;选好后按 Fire 键(就是确定键)将这块区域选中,被选中的区域用绿色方框框住;然后选择要移动到的区域,此时用红色方框框住被选择的区域;选好要移动到的区域之后按 Fire 键将要移动的区域(图片)移到要移动到的区域,并去掉绿色和红色的方框。这里需要强调的概念有选择的区域、选中的区域、要移动的区域和要移动到的区域,这四个概念请读者注意区分,当然也应当把这一部分记入数据字典之中。为了使文章的重点突出(介绍如何制作一个 J2ME 的收集游
30、戏),我们这里限定一些与本主题无关的内容暂不去实现:过关之后的动画(实现时要用到 TimerTask 或 Thread 类,后续的系列文章中我会详细介绍动画方面的知识)、关面之间的切换(其实很简单,当完成任务之后重新再做一边)、暂停和保存等操作(这部分的内容介绍的资料很多,我也写不出什么新的东东来,难免抄袭,故此免掉)。需求分析基本完成,离下午还有一段时间,马上动手用 ACDSee 把从网上找来的 BMP文件,调整其大小为271*177(我的这个图片是两个部分合在一起,所以比手机实际屏幕大了),另存为 PNG 格式。半天时间刚刚好,不但搞清楚了要做的东东,还把要用的图片准备好了。四、概要设计概
31、要设计是从需求分析过渡到详细设计的桥梁和纽带,这一部分中我们确定项目的实现方法和模块的划分。我们决定将整个项目分成五个部分,分别是前面介绍的Images、Draw,还有 Map 和 Displayable1和 MIDlet1。Images 和 Draw 类功能简单、结构固定,因此很多项目我们都使用这两各类,这里直接拿来改改就能用了,前面已经介绍过这里不再赘述。Map 类是用来从外部文件读入地图,然后保存在一个数组之中,这部分的内容是我们在本阶段讨论的重点。Displayable1是一个继承了 Canvas 类的画布,它用来处理程序的主要控制逻辑和一部分控制逻辑所需的辅助函数,主要函数应该包括用
32、来绘图的 paint()函数、用来控制操作的 keyPressed()函数、用来控制选择区域的 setRange()函数、用来控制选择要移动到区域的 setMoveRange()函数、用来移动选中区域的 Move()函数和判断是否完成任务的 win()函数,更具体的分析,我们放到详细设计中去细化。MIDlet1实际上就是一个控制整个 J2ME 应用的控制程序,其实也没有什么可特别的,它和我们前面介绍的“Hello World“程序大同小异,这里就不展开来说了,后面会贴出它的全部代码。Map 类主要应该有一个 Grid的二维数组,用来存放华容道的地图,还应该有一个read_map()函数用来从外
33、部文件读取地图内容填充 Grid 数据结构,再就是要有一个draw_map()函数用来把 Grid 数据结构中的地图内容转换成图片显示出来(当然要调用Draw 类的 paint 方法)。说到读取外部文件,笔者知道有两种方法:一种是传统的定义一个 InputStream 对象,然后用 getClass().getResourceAsStream()方法取得输入流,然后再从输入流中取得外部文件的内容,例如InputStream is = getClass().getResourceAsStream(“/filename“);if (is != null) byte a = (byte) is.re
34、ad();这里请注意文件名中的根路径是相对于便以后的 class 文件放置的位置,而不是源文件(java)。第二种方法是使用 onnector.openInputStream 方法,然后打开的协议是Resource,但是这种方法笔者反复尝试都没能调通,报告的错误是缺少 Resource 协议,估计第二种方法用到 J2ME 的某些扩展类包,此处不再深究。由于以前已经做过一些类似华容道这样的地图,这里直接给出 Map 类的代码,后面就不再详细解释 Map 类了,以便于我们可以集中精力处理 Displayable1中的逻辑。Map 类的代码如下:package huarongroad;import
35、java.io.InputStream;import javax.microedition.lcdui.*;public class Map /处理游戏的地图,负责从外部文件加载地图数据,存放地图数据,并按照地图数据绘制地图public byte Grid;/存放地图数据public Map() /构造函数,负责初始化地图数据的存储结构this.Grid = new byteImages.HEIGHTImages.WIDTH;/用二维数组存放地图数据,注意第一维是竖直坐标,第二维是水平坐标public int read_map(int i) A href=“file:/从“file:/从/A外
36、部文件加载地图数据,并存放在存储结构中,返回值是光标点的位置/参数是加载地图文件的等级int a = new int2;/光标点的位置,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.Gridkj = (byte) is.read();i
37、f ( this.Gridkj = Images.CURSOR ) /判断出光标所在位置a0 = j;/光标水平位置a1 = k;/光标竖直位置this.Gridkj = Images.BLANK;/将光标位置设成空白背景is.read();/读取回车(13),忽略掉is.read();/读取换行(10),忽略掉is.close();else /读取文件失败a0 = -1;a1 = -1;catch (Exception ex) /打开文件失败a0 = -1;a1 = -1;return a;public boolean draw_map(Graphics g) /调用 Draw 类的静态方法
38、,绘制地图try for (int i = 0; i Images.HEIGHT; i+) for (int j = 0; j Images.WIDTH; j+) Draw.paint(g, this.Gridij, j, i);/绘制地图return true;catch (Exception ex) return false;对于像华容道这样的小型地图可以直接用手工来绘制地图的内容,比如:fa1c2232bd1e2gg2gihg但是,如果遇到像坦克大战或超级玛莉那样的地图,就必须另外开发一个地图编辑器了(我会在后续的文章中介绍用 vb 来开发一个地图编辑器)。五、详细设计详细设计是程序开发
39、过程中至关重要的一个环节,好在我们在前面的各个阶段中已经搭建好了项目所需的一些工具,现在这个阶段中我们只需集中精力设计好 Displayable1中的逻辑。(两天的时间当然不只干这点活,还要把其他几个类的设计修改一下)Displayable1这个类负责处理程序的控制逻辑。首先,它需要有表示当前关面的变量level、表示当前光标位置的变量 loc、表示要移动区域的变量 SelectArea、表示要移动到的区域的变量 MoveArea、表示是否已有区域被选中而准备移动的变量 Selected 和 Map类的实例 MyMap。然后,我们根据用户按不同的键来处理不同的消息,我们要实现keyPresse
40、d()函数,在函数中我们处理按键的上下左右和选中(Fire),这里的处理需要我展开来讲一讲,后面我很快会把这一部分详细展开。接下来,是实现 paint()函数,我们打算在这一部分中反复的重画背景、地图和选择区域,这个函数必须处理好区域被选中之后的画笔颜色的切换,具体讲就是在没有选中任何区域时要用黑色画笔,当选重要移动的区域时使用绿色画笔,当选择要移动到的区域时改用红色画笔(当然附加一张流程图是必不可少的)。再下面要实现的 setRange()函数和 setMoveRange()函数,这两个函数用来设置要移动的区域和要移动到的区域,我的思路就是利用前面在 Images 类中介绍过的地图组合标记常
41、量,当移动到地图组合标记常量时,根据该点地图中的值做逆向变换找到相应的地图标记常量,然后设置相应的 loc、SelectArea 和 MoveArea,其中 setMoveRange()函数还用到了一个辅助函数 isInRange(),isInRange()函数是用来判断给定的点是否在已选中的要移动的区域之内,如果 isInRange()的返回值是假并且该点处的值不是空白就表明要移动到的区域侵犯了其他以被占用的区域。有了 setRange()和 setMoveRange()函数,Move()函数就水到渠成了,Move()函数将要移动的区域移动到要移动到的区域,在移动过程中分为三步进行:第一.复
42、制要移动的区域;第二.将复制出的要移动区域复制到要移动到的区域(这两步分开进行的目的是防止在复制过程中覆盖掉要移动的区域);第三.用 isInRange2()判断给定的点是否在要移动到的区域内,将不在要移动到的区域内的点设置成空白。下面我们详细的分析一下 keyPressed()函数的实现方法:首先,keyPressed()函数要处理按键的上下左右和选中(Fire),在处理时需要用 Canvas 类的 getGameAction 函数来将按键的键值转换成游戏的方向,这样可以提高游戏的兼容性(因为不同的 J2ME 实现,其方向键的键值不一定是相同的)。接下来,分别处理四个方向和选中.当按下向上时
43、,先判断是否已经选定了要移动的区域(即 this.selected 是否为真),如果没有选中要移动区域则让光标向上移动一格,然后调用 setRange()函数设置选择要移动的区域,再调用 repaint()函数刷新屏幕,否则如果已经选中了要移动的区域,就让光标向上移动一格,然后调用 setMoveRange()函数判断是否能够向上移动已选中的区域,如果能移动就调用 repaint()函数刷新屏幕,如果不能移动就让光标向下退回到原来的位置。当按下向下时,先判断是否已经选定了要移动的区域,如果没有选中要移动的区域则判断当前所处的区域是否为两个格高,如果是两个格高则向下移动两格,如果是一个格高则向下
44、移动一格,接着再调用 setRange()函数设置选择要移动的区域,而后调用 repaint()函数刷新屏幕,否则如果已经选中了要移动的区域,就让光标向下移动一格,然后调用setMoveRange()函数判断是否能够向下移动已选中的区域,如果能移动就调用 repaint()函数刷新屏幕,如果不能移动就让光标向上退回到原来的位置.按下向左时情况完全类似向上的情况,按下向右时情况完全类似向下的情况,因此这里不再赘述,详细情况请参见程序的源代码。当按下选中键时,先判断是否已经选中了要移动的区域,如果已经选中了要移动的区域就调用 Move()函数完成由要移动的区域到要移动到的区域的移动过程,接着调用r
45、epaint()函数刷新屏幕,然后将已选择标记置成 false,继续调用 win()函数判断是否完成了任务,否则如果还没有选定要移动的区域则再判断当前选中区域是否为空白,如果不是空白就将选中标记置成 true,然后刷新屏幕.这里介绍一个技巧,在开发程序遇到复杂的逻辑的时候,可以构造一格打印函数来将所关心的数据结构打印出来以利调试,这里我们就构造一个 PrintGrid()函数,这个函数纯粹是为了调试之用,效果这得不错.至此我们完成了编码前的全部工作。六.编码整个项目共有五个类,有四个类的代码前面已经介绍过了,而且是在其他项目中使用过的相对成熟的代码.现在只需全力去实现 Displayable1
46、类.Displayable1类的代码如下:private void Move() A href=“file:/将“file:/将/A要移动的区域移动到刚选中的区域if (this.MoveArea0 = -1 | this.MoveArea1 = -1 |this.SelectArea0 = -1 | this.SelectArea1 = -1) /没有选中区域else /已经选中了要移动的区域和要移动到的区域byte temp = new bytethis.SelectArea3this.SelectArea2;A href=“file:/复“file:/复/A制要移动的区域,因为这块区域可
47、能会被覆盖掉for (int i = 0; i this.SelectArea2; i+) for (int j = 0; j this.SelectArea3; j+) tempji =this.MyMap.Gridthis.SelectArea1 +jthis.SelectArea0 + i;A href=“file:/PrintGrid“file:/PrintGrid/A(“1“); / 调试信息A href=“file:/将“file:/将/A要移动的区域移动到刚选中的区域(即要移动到的区域)for (int i = 0; i this.SelectArea2; i+) for (in
48、t j = 0; j this.SelectArea3; j+) this.MyMap.Gridthis.MoveArea1 + jthis.MoveArea0 + i = tempji;A href=“file:/PrintGrid“file:/PrintGrid/A(“2“);/ 调试信息A href=“file:/将“file:/将/A要移动的区域中无用内容置成空白for (int i = 0; i this.SelectArea3; i+) for (int j = 0; j this.SelectArea2; j+) if (!isInRange2(this.SelectArea0 + j,this.SelectArea1 + i) /该点是不在要移动到A href=“file:/的“file:/的/A区域之内,需置空this.MyMap.Gridthis.SelectArea1 + ithis.SelectArea0 + j = Images.BLANK;else A href=