目 录

J2ME RPG游戏边学边做(七)--丰富人物和剧情

在J2ME RPG游戏边学边做(二)中我们已经成功的完成地图和英雄的编写。这次我们将为英雄加入碰撞检测和人物对话。

在开始前,我们需要确定在地图中哪些地方不允许走动,那些地方可以触发对话,这就需要在地图中事先把这些事件定义好。我们改变先前的Scene类。利用二维数组为地图加入事件。

Scene.java
//这次的代码和上次有点出入,这次我们利用getMap()方法来读取地图数组,这样方便以后改为
//从外部文件读取
package brave;
import javax.microedition.lcdui.game.TiledLayer;
import javax.microedition.lcdui.Image;
public class Scene
{
public static TiledLayer createTiledLayerByBackground(Image image)
{
  TiledLayer tiledLayer = new TiledLayer(10, 8, image, 48, 64);
  tiledLayer.fillCells(0, 0, 10, 8, 2);
  return tiledLayer;
}
public static int[][] getMap()
{
  //生成地图数组,在原来的每个地图元素后面都加了一个事件。
  //事件id为99是不允许通过
  //事件id为98是激活对话
  //其实在这里定义二维数组并不是很方便,个人感觉还是三维比较直观和方便,
  //这里为了方便,只定义二维数组
  int[][] maplist =
  {
   //30*32
   {0 ,0}, {0 ,0}, {0 ,0}, {0 ,1}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
   {0 ,0}, {28,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99},
{29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99},
{29,99},
{29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99},
{29,99}, {30,0},
{0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {1 ,99}, {2 ,99}, {3 ,99}, {4 ,99}, {5 ,99}, {0
,0}, {0 ,0}, {0 ,0}, {1 ,99}, {2 ,99}, {3 ,99}, {3 ,99}, {26,99}, {3 ,99}, {3 ,99},
{4 ,99}, {5
,99}, {0 ,0}, {0 ,0}, {1 ,0}, {2 ,0}, {3 ,0}, {4 ,0}, {5 ,0}, {0 ,0}, {36,99},
{0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {7 ,99}, {8 ,99}, {46,99}, {10,99}, {11,99}, {0
,0}, {0 ,0}, {0 ,0}, {7 ,99}, {8 ,99}, {47,99}, {31,99}, {32,99}, {33,99}, {47,99},
{10,99},
{11,99}, {0 ,0}, {0 ,0}, {7 ,0}, {8 ,0}, {46,0}, {10,0}, {11,0}, {0 ,0}, {36,99},
{0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {13,99}, {14,99}, {15,99}, {16,99}, {17,99}, {0
,0}, {0 ,0}, {0 ,0}, {13,99}, {14,99}, {14,99}, {37,99}, {38,99}, {39,99}, {14,99},
{16,99},
{17,99}, {0 ,0}, {0 ,0}, {13,0}, {14,0}, {15,0}, {16,0}, {17,0}, {0 ,0}, {36,99},
{0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {19,99}, {20,99}, {21,99}, {22,99}, {23,99}, {6
,99}, {0 ,0}, {0 ,0}, {19,99}, {20,99}, {20,99}, {43,99}, {44,99}, {45,99},
{20,99}, {20,99},
{23,99}, {0 ,0}, {0 ,0}, {19,0}, {20,0}, {21,0}, {22,0}, {23,0}, {0 ,0}, {36,99},
{0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {24,99}, {24,99}, {24,99}, {13,99}, {15,99}, {17,99}, {24,99},
{24,99}, {24,99},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {19,99}, {21,99}, {23,99}, {0 ,0}, {0 ,0},
{0 ,0}, {0
,0}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {12,0}, {12,0}, {12,0},
{12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0},
{12,0}, {12,0},
{12,0}, {12,0}, {12,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{12,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0},
{25,0}, {12,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {1 ,0}, {2 ,0}, {3 ,0}, {4 ,0}, {5 ,0}, {0 ,0},
{12,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0},
{25,0}, {12,0},
{0 ,0}, {1 ,0}, {2 ,0}, {3 ,0}, {4 ,0}, {5 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {7 ,0}, {8 ,0}, {46,0}, {10,0}, {11,0}, {0 ,0},
{12,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0},
{25,0}, {12,0},
{0 ,0}, {7 ,0}, {8 ,0}, {46,0}, {10,0}, {11,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {13,0}, {14,0}, {15,0}, {16,0}, {17,0}, {0 ,0},
{12,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0},
{25,0}, {12,0},
{0 ,0}, {13,0}, {14,0}, {15,0}, {16,0}, {17,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {19,0}, {20,0}, {21,0}, {22,0}, {23,0}, {0 ,0},
{12,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0},
{25,0}, {12,0},
{0 ,0}, {19,0}, {20,0}, {21,0}, {22,0}, {23,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0},
{12,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0},
{25,0}, {12,0},
{0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0},
{12,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0},
{25,0}, {12,0},
{0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0},
{12,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0}, {25,0},
{25,0}, {12,0},
{0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {12,0}, {12,0}, {12,0},
{12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0},
{12,0}, {12,0},
{12,0}, {12,0}, {12,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {12,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {12,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {1 ,0}, {2 ,0}, {3 ,0}, {4 ,0}, {5 ,0}, {0 ,0},
{12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {1 ,0}, {3 ,0}, {5 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {12,0},
{0 ,0}, {1 ,0}, {2 ,0}, {3 ,0}, {4 ,0}, {5 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {7 ,0}, {8 ,0}, {46,0}, {10,0}, {11,0}, {0 ,0},
{12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {7 ,0}, {48,0}, {11,0}, {0 ,0}, {0 ,0},
{0 ,0}, {12,0},
{0 ,0}, {7 ,0}, {8 ,0}, {46,0}, {10,0}, {11,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {13,0}, {14,0}, {15,0}, {16,0}, {17,0}, {0 ,0},
{12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {6 ,0}, {13,0}, {15,0}, {17,0}, {0 ,0}, {0 ,0},
{0 ,0}, {12,0},
{0 ,0}, {13,0}, {14,0}, {15,0}, {16,0}, {17,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {19,0}, {20,0}, {21,0}, {22,0}, {23,0}, {0 ,0},
{12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {6 ,0}, {19,0}, {21,0}, {23,0}, {0 ,0}, {0 ,0},
{0 ,0}, {12,0},
{0 ,0}, {19,0}, {20,0}, {21,0}, {22,0}, {23,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0},
{12,0}, {18,98}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {12,0},
{0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {12,0}, {12,0}, {12,0},
{12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0}, {12,0},
{12,0}, {12,0},
{12,0}, {12,0}, {12,0}, {12,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {12,0}, {12,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {34,99}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {12,0}, {12,0}, {12,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {36,99}, {0 ,0},
   {0 ,0}, {40,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99},
{29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {12,0}, {12,0}, {12,0},
{29,99}, {29,99},
{29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99}, {29,99},
{42,99}, {0 ,0},
   {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0},
{0 ,0}, {0 ,0},
{0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}, {0 ,0}
  };
  return maplist;
}
public static TiledLayer createTiledLayerByForeground(Image image)
{
  //生成地图
  int[][] maplist = Scene.getMap();
  TiledLayer tiledLayer = new TiledLayer(30, 32, image, 16, 16);
  for(int i = 0 ; i < maplist.length ; i++)
  {
   int col = i % 30;
   int row = (i - col) / 30;
   tiledLayer.setCell(col, row, maplist[i][0]);
  }
  return tiledLayer;
}
public static int getEvent(int x, int y)
{
  //根据地图单元格的x和y得到该单元格的事件,这里的30应该根据地图的实际大小来确定
  return Scene.getMap()[x + (y * 30)][1];
}
}

ok,地图完成!下面是检测上面定义的事件。

我们知道在Sprite类中已经有了一个检测Sprite(人物)和TiledLayer(地图)的方法:collidesWith()。可是该检测方法必须在Sprite和TiledLayer交错时才能检测出来,个人感觉使用不是很方便,所以我决定重新写一检测碰撞的方法,该方法可以检测出当前 Sprite在四个方向时下一个将要移动的地图单元是什么。

BraveCanvas类需要增加一个静态变量,这里为了篇幅,暂不给出原码,大家可以在下面看到。

运行后,只有左上角、中间的房子和栅栏,可以正常检测,这是因为其他的单元格为了方便都没有加上事件。

一点缺陷:
其实这里的地图并不是很完善,比如说人物如果在房子上面的话,屋顶应该会把人物遮住一部分,而人物在房子下面的话,人物应该把房子遮住一点。大家如果感兴趣的话可以用图层来解决这问题。

下面说说人物的对话实现。

需要用到的图片:



在实现人物对话的时候,我想实现对话的打字机效果,这就需要一个循环来实现它。循环结束后,打字机的效果结束,对话并没有结束。应该显示对话内容并进入等待状态直到用户再次按键,才真正的结束对话。

流程图如下:



代码如下:

Hero.java

package brave;

import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.Image;
import java.io.IOException;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.TiledLayer;

public class Hero extends Sprite
{
private int x;
private int y;

private BraveCanvas braveCanvas;
private BraveManager braveManager;

public Hero(Image image, int frameWidth, int frameHeight)
{
  super(image, frameWidth, frameHeight);
}

public void setBraveCanvas(BraveCanvas braveCanvas)
{
  this.braveCanvas = braveCanvas;
}

public void setBraveManager(BraveManager braveManager)
{
  this.braveManager = braveManager;
}

public void setManager(BraveCanvas braveCanvas)
{
  this.braveCanvas = braveCanvas;
}

public void init(int x, int y)
{
  this.x = x;
  this.y = y;
}

public void afresh()
{
  setPosition(this.x, this.y);
}

public void moveUp(Image image) throws IOException
{
  setImage(image, 17, 26);
  nextFrame();
  if(!eventActionExist(99) && !eventActionExist(98))
   this.y = Math.max(0, y - 1);
}

public void moveDown(Image image) throws IOException
{
  setImage(image, 17, 26);
  nextFrame();
  if(!eventActionExist(99) && !eventActionExist(98))
   this.y = Math.min(braveManager.getLayerAt(1).getHeight(), y + 1);
}

public void moveLeft(Image image) throws IOException
{
  setImage(image, 17, 26);
  nextFrame();
  if(!eventActionExist(99) && !eventActionExist(98))
   this.x = Math.max(0, x - 1);
}

public void moveRight(Image image) throws IOException
{
  setImage(image, 17, 26);
  nextFrame();
  if(!eventActionExist(99) && !eventActionExist(98))
   this.x = Math.min(braveManager.getLayerAt(1).getWidth(), x + 1);
}

//实现人物的对话
public void talk(String addressor,Image talkImage, String s, Graphics g)
{
  g.drawImage(talkImage, 0, 0, Graphics.TOP|Graphics.LEFT);
  g.drawString(addressor+":", 7, 6, Graphics.TOP|Graphics.LEFT);
  for(int i = 0 ; i < s.length() ; i++)
  {
   g.drawString(s.substring(i, i+1), (i*12)+12, 21,

Graphics.TOP|Graphics.LEFT);
   try
   {
    Thread.sleep(100);
   }
   catch(Exception e)
   {
    e.printStackTrace();
   }
   braveCanvas.flushGraphics();
  }
  //对话的文字形式结束,进入等待状态,直到
  //再次按下对话键
  while(BraveCanvas.isTalk)
  {
   try
   {
    int keystates = braveCanvas.getKeyStates();
    //再次按下对话键
    if(keystates == BraveCanvas.FIRE_PRESSED)
    {
     //是否对话标志位置为false
     BraveCanvas.isTalk = false;
     //是否可以重新开始对话标志位置为false
     //之所以这样做是保证在在下一次检测按键时,不重新开始对话
     BraveCanvas.isTalkSign = false;
     break;
    }
    Thread.sleep(50);
   }
   catch(Exception e)
   {
    e.printStackTrace();
   }
  }
}

public boolean eventActionExist(int eventID)
{
  …………
}

}

修改BraveCanvas.java 如下

BraveCanvas.java

package brave;

import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.Graphics;
import java.io.IOException;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.TiledLayer;


public class BraveCanvas extends GameCanvas implements Runnable
{
private boolean sign;
private Graphics g;
private Hero hero;
private Image upimage;
private Image downimage;
private Image leftimage;
private Image rightimage;
private Image talkImage;
private TiledLayer backgroundMap;
private TiledLayer foregroundMap;
private BraveManager braveManager;
//该标志位判断对话是否开始
public static boolean isTalk;
//该标志为判断对话是否可以重新开始,默认可以重新开始
public static boolean isTalkSign = true;
//当前的人物方向(碰撞检测用)
public static int way = 0;

public BraveCanvas()
{
  super(true);
  try
  {
   backgroundMap = Scene.createTiledLayerByBackground(
    Image.createImage("/background.png"));
   foregroundMap = Scene.createTiledLayerByForeground(
    Image.createImage("/foreground.png"));
   upimage = Image.createImage("/hero_up.png");
   downimage = Image.createImage("/hero_down.png");
   leftimage = Image.createImage("/hero_left.png");
   rightimage = Image.createImage("/hero_right.png");
   talkImage = Image.createImage("/talk.png");
   braveManager = new BraveManager();
   braveManager.setBraveCanvas(this);
   hero = new Hero(upimage, 17, 26);
   //hero.setFrameSequence(new int[]{1, 1, 0, 0, 1, 1, 2, 2});
   hero.setBraveCanvas(this);
   hero.setBraveManager(braveManager);
   hero.init(120, 120);
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
}

public void startup()
{
  this.sign = true;
  Thread thread = new Thread(this);
  thread.start();
}

public void run()
{
  g = getGraphics();
  braveManager.insert(hero, 0);
  braveManager.insert(foregroundMap, 1);
  braveManager.insert(backgroundMap, 2);
  while(sign)
  {
   try
   {
    input(g);
    BraveCanvas.isTalkSign = true;
    paint(g);
    Thread.sleep(15);
   }
   catch(Exception e)
   {
    e.printStackTrace();
   }
  }
}

public void input(Graphics g) throws IOException
{
  int keystates = getKeyStates();
  switch(keystates)
  {
   case UP_PRESSED:
    BraveCanvas.way = UP_PRESSED;
    hero.moveUp(upimage);
    break;
   case DOWN_PRESSED:
    BraveCanvas.way = DOWN_PRESSED;
    hero.moveDown(downimage);
    break;
   case LEFT_PRESSED:
    BraveCanvas.way = LEFT_PRESSED;
    hero.moveLeft(leftimage);
    break;
   case RIGHT_PRESSED:
    BraveCanvas.way = RIGHT_PRESSED;
    hero.moveRight(rightimage);
    break;
   case FIRE_PRESSED:
    //当用户按下对话键时候,首先判断对话是否可以重新开始
    //只有对话可以重新开始后才能再次开始对话
    if(hero.eventActionExist(98))
    {
     if(BraveCanvas.isTalkSign)
     {
      BraveCanvas.isTalk = true;
     }
    }
    break;
  }
  hero.afresh();
  braveManager.afresh();
}

public void paint(Graphics g)
{
  g.setColor(0x000000);
  g.fillRect(0, 0, getWidth(), getHeight());
  g.setColor(0x000000);
  braveManager.paint(g, 0, 0);
  if(BraveCanvas.isTalk)
  {
   //加入对话,这里只是简单做个例子。应该给对话规定编号,然后从文件中相应的编号中读取
   hero.talk("英雄", talkImage, "这是一个小镇", g);
  }
  else
  {
   flushGraphics();
  }
}

}

运行后,可以在小镇开始的小木牌处按[射击键]实现对话

J2ME RPG游戏边学边做(八)--使用j2meunit进行游戏测试

做一款精彩的j2me游戏是属不易呀。但是后面对游戏的测试更是麻烦,这也是一些手机游戏公司中测试人员与开发人员的比例相当的一个重要原因。。究竟有没有好的途径提高游戏测试的速度那??这也是摆在游戏开发team面前的老问题。。

不过现在就使用j2meunit这一利器,它可是好用的开源的东东呀!

j2meunit简介:
利用JUnit等单元测试框架进行单元测试对于Java程序员并不陌生,利用这些非常有效的工具,使得代码的质量得到有效的监控和维护。然而似乎一切在 J2ME的平台上,都显得略有些不同。由于J2ME环境不能提供反射(Reflection)API,因此很多基于反射的功能都无法使用,例如JUnit 中自动创建并运行test suite的功能。广大的J2ME程序员不能在J2ME平台上使用JUNIT进行单元测试,但谁都知道没有单元测试的程序是多么的脆弱!

J2MEUnit 是由Kent Beck和Erich Gamma设计开发的在J2ME平台上模仿JUnit的单元测试框架,大小17KB。它的运用为编写有保证的J2ME程序代码提供了基础性的支持。 J2MEUnit引入了一些新的机制来解决原有JUnit对反射的依赖。可能在使用中J2MEUnit明显的没有JUnit方便,但现阶段我们也只能利用它了,热烈的期盼着J2ME环境对反射的支持。现有的J2MEUnit的版本是1.1.1。如同JUnit一样,它也是开源的。你可以在sf.net上找到他的下载。相比较JUnit经常升级,J2MEUnit有一段时间没有升级了,一方面投入的力量较小,另外可能是考虑到J2ME环境的特殊性,要保证测试的LIB足够的小。


搭建测试平台:

我们以Eclipse配合EclipseME为例子说明如何使用J2MEUnit。

首先到sf下载J2MEUnit的最新版本:http://j2meunit.sourceforge.net,并解压缩到你的常用目录中。

新建一个Midlet Suite,选择Project…>properties…>Java Build Path…>Libraries…>Add External JARs…选择你需好下载的路径中的j2meunit.jar。




这样就可以使用了。

3。编写测试类:

让我们编写一个TestCase来学习如何使用这套工具。

编写TestCase类
编写测试的类要继承j2meunit.framework.TestCase。如同JUnit中一样,你可以覆写setUp() 和tearDown()方法,虽然这里没有反射机制,但还是推荐你把测试方法以test开头。这样一但J2ME有了反射机制,你也可以快速的移植。还有一点要注意的是,你需要为子类提供一个构造函数(假设你的类叫做TestOne):

public TestOne(String sTestName, TestMethod rTestMethod)
{
      super(sTestName, rTestMethod);
}
稍候解释这是为什么?
接下来编写两个个测试方法,这很熟悉:
public void testOne()
{
      System.out.println("TestOne.testOne()");
      assertTrue("Should be true", false);
}
public void testTwo()
{
      System.out.println("TestOne.testTwo()");
      throw new RuntimeException("Exception");
}

正是缺少反射机制,你需要手动编写suite方法,并一一调用你编写的测试方法,这个步骤多多少少有些烦闷。没办法了,这是理解J2MEUnit框架的关键了,咱连write once debug anywhere都忍了,还有什么困难不能克服呢?

suite 方法要求我们返回一个TestSuite对象,因此,首先建立一个新的TestSuite对象并调用addTest方法,为他添加Test对象。Test 是一个接口,TestSuite、TestCase都实现了他,因此既可以添加测试单元、又可以添加一个测试套件。

根据J2MEUnit的设计思想,一个TestCase在运行时,只能捆绑一个TestMethod对象。TestMethod是一个标准的回调接口,只含有一个回调run(TestCase tc)方法。这个run方法的任务是调用一个,注意,是一个测试方法,那么一旦这个方法出现问题,可以很好的捕捉它,并返回给用户。TestMethod 提供了一组set方法用于捆绑一个TestMethod对象,但实际我们不去使用它,因为效率太低了,为了更快捷的捆绑TestMethod对象,我们要利用构造函数和匿名类来捆绑TestMethod类的实例。这个匿名类很好编写,只要将传入的TestCase tc向上转型到你的TestCase子类,然后调用相关方法就可。我们不得不同时提供一个String作为名称给我们的构造函数(还记得吗?我们添加的那个构造函数,这下,明白她的用处了吧)。

看一下下面这个例子,希望能帮助你理解上面那段总觉得有些拗口的话。如果你理解了“一个TestCase在运行时,只能捆绑一个TestMethod对象” 这句话,那么就理解了J2MEUnit所谓的新机制。千万不要在一个TestMethod中连续调用多个test方法,这样一旦某个方法出了问题,那么整个方法会结束而后续的测试将不能执行。一定要老老实实做人,认认真真写suite(),似乎又回到了剪刀加浆糊的时代。。。[-_-"]


public Test suite()
{
      TestSuite aSuite = new TestSuite();       
      aSuite.addTest(new TestOne("testOne", new TestMethod()
      { public void run(TestCase tc) {((TestOne) tc).testOne(); } }));
      aSuite.addTest(new TestOne("testTwo", new TestMethod()
      { public void run(TestCase tc) {((TestOne) tc).testTwo(); } }));       
      return aSuite;
}
编写测试套件
接下来编写一个测试套件,其实你可能已经明白了,测试套件不过是一个特殊的TestCase,根据惯例,一般这样的类叫做TestAll,只需要将以前添加的TestCase中的suite添加给TestAll的suite就可以了。
public class TestAll extends TestCase
{
public Test suite()
{
      TestSuite suite = new TestSuite();
      suite.addTest(new TestOne().suite());
      suite.addTest(new TestTwo().suite());
      return suite;
}
}
4。调试:
有两个方法运行我们的测试。
使用textui
利用textui,这个大家都熟悉了,不做重点介绍。一般习惯上在TestAll方法中添加一个main方法:
public static void main(String[] args)
{
      String[] runnerArgs = new String[] { "j2meunit.examples.TestAll" };
      j2meunit.textui.TestRunner.main(runnerArgs);
}

要为TestRunner.main传入一个String数组,里面罗列所有要测试的TestCase的完整路径,因为我们编写了TestAll,所以只传入他就可以了。



使用midletui这才是这套框架迷人的地方,正是有了他我们可以在真机上进行Unit Test了,cool,这将节省多少的测试成本呀。所以之前所有的编写suite的工作就认了!

继承j2meunit.midletui.TestRunner,这是一个midlet父类。在startApp中调用如下方法:

protected void startApp()
{
  start(new String[] { "j2meunit.examples.TestAll" });
}

或者,更为灵活的,你可以在jad文件中编写一个J2MEUnitTestClasses属性,写入你要测试的若干个TestCase,这样也可以进行测试而不更改主类。

如下是在模拟上的结果:




在我的MIDP1.0,真机上运行这个例子得到同样的结果,用时401ms。如果你正在使用j2me开发项目,建议把单元测试引入到你的工作当中,正如我们看到单元测试对于别的java平台的影响一样,对于嵌入式开发,它也是大有用武之地的。