public class Images {//保存常量 //绘图位置常量 public static final int UNIT = 32;//方块的单位长度 public static final int LEFT = 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 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´;//空白的地图标记 public static final 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_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;//空白的图片 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新新增加的支持游戏的特性 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);
public class Draw { //绘制游戏中的图片 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点转化成画布的绝对坐标,绘图 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://画曹操 //变成绝对坐标,并做调整 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.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://画张飞 g.drawImage(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.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://画光标 g.drawRect(Images.LEFT + x * unit, Images.TOP + y * unit,Images.UNIT,Images.UNIT); break; } return true; }catch (Exception ex) { return false; } } } 其中Images类存的是绘图位置常量(也就是在画图时每个格子的长度和相对坐标原点位置要进行的调整)、地图位置常量(地图的长、宽),地图标记常量(人物对应的记号),地图组合标记常量(后面会细说),图片常量(存放人物的图片);Draw类主要负责在制定的位置画出人物图片。下面我来说说Images类中的地图标记常量和地图组合标记常量。为了能够灵活的安排各个关面的布局,我们决定把游戏布局的信息存储在外部文件中,然后程序启动后把它读进来。这样我们制定了一套存储图片的代码,这就是地图标记常量,如上面Images类中定义的Caocao(曹操)用a字符来表示,当程序读到a字符时就能将它转化成曹操对应的图片,并在读到a字符的位置上进行显示。但是从实际观察中我们发现所有的图片并不是统一大小的,有的占4个格子,有的占2个格子,还有的占1个格子,而且即便同是占两个格子的图片还有横、竖之分。有鉴于此,我们引入了地图组合标记常量,就是说在遇到占有多个格子的时候,值1(也就是Images.LEFT)表示它的左边是一个真正的地图标记,值2(也就是Images.UP)表示它的上边是一个真正的地图标记,值1(也就是Images.LEFTUP)表示它的左上边是一个真正的地图标记。地图组合标记常量其实就是用来占位置的,与实际显示无关,当后面我们将到移动时还会再来分析组合标记的使用。 Draw类主要是用来在画布上画出图形,它有两个paint方法,这是很常见的函数重载。但是程序中实际上只用到了4个参数的paint方法,它直接获得要画图片的相对坐标位置信息,然后调用5个参数的paint方法。5个参数的paint方法将相对坐标位置信息转换成绝对位置,并实际调用Graphics.drawImage()方法,将Images中的图片画了出来。这种实现方法的好处是灵活和便于扩展,但你需要画图的位置并不能够对应到格子中的相对坐标位置时,你就可以直接调用5个参数的paint方法,而不必再去修改这各类;但你添加新的图片时,只要在Images中增加对应的常量,然后向Draw中5个参数的paint方法添加一条处理就可以了。 写到这里,两天的时间刚好用完。 三、需求分析 这部分叫做需求分析,听起来挺吓人的,其实就是搞清楚我们要做什么,做成什么样,那些不做。下面我引领着大家共同来完成这一步骤。首先,我们要做一个华容道的游戏,华容道的故事这里不再赘述了,但其中的人物在这里限定一下,如上面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;