RPG

 
J2ME RPG游戏边学边做(一)

  笔者以前是做j2ee的,一个月前由于兴趣所致开始利用业余时间学习j2me游戏开发。在网上看了一通教程后,便准备动手一边学一边做一个简单的rpg游戏。虽然起点比较高,但感觉游戏难度越大,遇到的问题也就越多。这样待全部解决后,也就学的差不多了。另外只有做这种游戏才有学下去的兴趣。以后我会每次把写成的源码及思路发上来,供大家指正。希望各位高人可以无私的帮助我。

  下面是游戏中所需要的部分图片,还有的图片下次写到了再上传:
hero_left.png:
image
hero_right.png:
image
hero_up.png:
image
hero_down.png:
image

  整个程序的主体在BraveCanvas类中完成,该类继承于GameCanvas实现Runnable接口,由BraveMIDlet类启
动他,BraveMIDlet继承MIDlet实现CommandListener接口。BraveMIDlet类代码如下:(由于只是一个简单的rpg,开头画面和菜单全部省略了,改为由一个按扭启动游戏)

BraveMIDlet.java

package brave;

import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

public class BraveMIDlet extends MIDlet implements CommandListener
{
private Display d ;
private Command exitCommand ;
private Command startCommand ;

    private BraveCanvas braveCanvas;

public BraveMIDlet()
{
  d = Display.getDisplay(this);
  exitCommand = new Command("退出",Command.EXIT,1);
  startCommand = new Command("开始",Command.SCREEN,1);
}

public void startApp()
{
  //创建BraveCanvas
  braveCanvas = new BraveCanvas();
  braveCanvas.addCommand(exitCommand);
  braveCanvas.addCommand(startCommand);
  braveCanvas.setCommandListener(this);
  //装载BraveCanvas
  d.setCurrent(braveCanvas);
}

public void pauseApp()
{
}

public void destroyApp(boolean unconditional)
{
}

public void commandAction(Command c , Displayable dpa)
{

  String str_co = c.getLabel();
  if(str_co.equals("开始"))
  {
   //运行BraveCanvas中的线程(启动游戏)
   braveCanvas.startup();
  }
  else if(str_co.equals("退出"))
  {
   destroyApp(false);
   notifyDestroyed();
  }
}
}


BraveCanvas.java

package brave;

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

public class BraveCanvas extends GameCanvas implements Runnable
{
private boolean sign;
private Graphics g;
//设置@符号的当前位置
private int x,y;
public BraveCanvas()
{
  super(true);
  //初始化@位置
  x = getWidth()/2;
  y = getHeight()/2;
}

public void startup()
{
  this.sign = true;
  Thread thread = new Thread(this);
  //启动线程
  thread.start();
}

public void run()
{
  g = getGraphics();
  while(sign)
  {
   try
   {
    //@符号的移动
    input(g);
    //@符号的显示
    paint(g);
    //这里应该有详细的计算,方便为上,置为15
    Thread.sleep(15);
   }
   catch(Exception e)
   {
    System.out.println("2:"+e);
   }
  }
}

public void input(Graphics g) throws IOException
{
  int keystates = getKeyStates();
  switch(keystates)
  {
   case UP_PRESSED:
    y = Math.max(0, y - 1);
    break;
   case DOWN_PRESSED:
    y = Math.min(getHeight(), y + 1);
    break;
   case LEFT_PRESSED:
    x = Math.max(0, x - 1);
    break;
   case RIGHT_PRESSED:
    x = Math.min(getWidth(), x + 1);
    break;
  }
}

public void paint(Graphics g)
{
  //设置画布的背景色
  g.setColor(0x000000);
  //把背景色画满画布
  g.fillRect(0, 0, getWidth(), getHeight());
  //设置画布的前景色
  g.setColor(0xffffff);
  //在画布上写上@
  g.drawString("@", x, y, Graphics.TOP|Graphics.LEFT);
  //刷新画布
  flushGraphics();
}
}

上面代码运行后,画面上会出现一个"@"字符随着键盘的左右而移动。虽然这离rpg游戏相差很远,但这是一
个游戏的基本框架。下面我们要做的就是把"@"换成我们的英雄。为了程序清晰,我们还要设计一个英雄类
Hero,该类继承于Sprite,这个类里面要用到上面的图片。

Hero.java

package brave;

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

public class Hero extends Sprite
{
//设置英雄的当前位置
private int x, y;
private BraveCanvas braveCanvas;

public Hero(Image image, int frameWidth, int frameHeight)
{
  //英雄的初始化
  super(image, frameWidth, frameHeight);
}

public void setBraveCanvas(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() throws IOException
{
  //英雄上移,并改为相应的图片
  setImage(Image.createImage("/hero_up.png"), 17, 26);
  nextFrame();
  this.y = Math.max(0, y - 1);
}

public void moveDown() throws IOException
{
  //英雄下移,并改为相应的图片
  setImage(Image.createImage("/hero_down.png"), 17, 26);
  nextFrame();
  this.y = Math.min(braveCanvas.getHeight(), y + 1);
}

public void moveLeft() throws IOException
{
  //英雄左移,并改为相应的图片
  setImage(Image.createImage("/hero_left.png"), 17, 26);
  nextFrame();
  this.x = Math.max(0, x - 1);

}

public void moveRight() throws IOException
{
  //英雄右移,并改为相应的图片
  setImage(Image.createImage("/hero_right.png"), 17, 26);
  nextFrame();
  this.x = Math.min(braveCanvas.getWidth(), x + 1);
}

}

然后把BraveCanvas类稍做修改,把"@"换成英雄

BraveCanvas.java

package brave;

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

public class BraveCanvas extends GameCanvas implements Runnable
{
private boolean sign;
private Graphics g;
private Hero hero;
public BraveCanvas()
{
  super(true);
}

public void startup()
{
  this.sign = true;
  try
  {
   Image heroimage = Image.createImage("/hero_up.png");
   hero = new Hero(heroimage, 17, 26);
   hero.setBraveCanvas(this);
   hero.init(40,40);
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  Thread thread = new Thread(this);
  thread.start();
}

public void run()
{
  g = getGraphics();
  while(sign)
  {
   try
   {
    input(g);
    paint(g);
    Thread.sleep(15);
   }
   catch(Exception e)
   {
    System.out.println("2:"+e);
   }
  }
}

public void input(Graphics g) throws IOException
{
  int keystates = getKeyStates();
  switch(keystates)
  {
   case UP_PRESSED:
    hero.moveUp();
    break;
   case DOWN_PRESSED:
    hero.moveDown();
    break;
   case LEFT_PRESSED:
    hero.moveLeft();
    break;
   case RIGHT_PRESSED:
    hero.moveRight();
    break;
  }
  hero.afresh();
}

public void paint(Graphics g)
{
  g.setColor(0x000000);
  g.fillRect(0, 0, getWidth(), getHeight());
  g.setColor(0xffffff);
  hero.paint(g);
  flushGraphics();
}
}

这样,我们的英雄就可以在画面上移动了,不过英雄比较简单,没有hp,mp等等,只是一个图片。而且如果大
家感觉英雄的脚步过快,可以在BraveCanvas类中的Hero初始化时设置:
hero.setFrameSequence(new int[]{0, 0, 1, 1, 0, 0, 2, 2 });

暂时先写到这,在下次中我会把地图和碰撞检测加入。

思 想 的 自 由 才 是 真 正 的 自 由 ---Matrix.天路旅客
征求大家在matrix的用户体验

访问我的“出版物”,给我留言
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount119883", 3); DWRUtil.setValue("voteNum119883", 1); </script>

cleverpig     
user photo
  • Matrix排名:10
  • 排名变化:  --
  • 用户等级:8
  • 总发贴数:778
  • 总积分(Karma):5859
  • 参与分(经验):3469
  • 专家分(威望):2259
  • 可用分(财富):2345
  • 类型:副管理员
  • 昵称:Matrix.天路旅客
  • 来自:北京
  • 注册:2005-02-19
  • 在线状态: 在线
  • 进入cleverpig的部落格
[信息] [引用] [回复] 第 2 楼 BACK TOP

J2ME RPG游戏边学边做(二)
1、

public void moveDown() throws IOException
{
  //英雄下移,并改为相应的图片
  setImage(Image.createImage("/hero_down.png"), 17, 26);
  nextFrame();
  this.y = Math.min(braveCanvas.getHeight(), y + 1);
}

   在io包中的读取是非常耗内存的,所以Image.createImage("/hero_down.png")放在线程的循环中确实不好,
现在已经改成图象在BraveCanvas类中创建(下面的代码已更改),然后由moveDown(Image image)方法接收传递
进来的图象。

2、RPG是个大工程,一个人的力量是很难完成的,我只想实现基本的RPG功能,比如人物的对话,场景的
转换,战斗等等,希望大家多多给予帮助。

  这一篇我将给游戏加入地图,以下是该篇所需要的图片:
    background.png
image
      foreground.png
image
  一个RPG中的游戏地图是非常大而且多的,为了方便以后的维护,我创建了一个Scene类,该类主要是产
生游戏所需要的地图。

Scene.java

package brave;

import javax.microedition.lcdui.game.TiledLayer;
import javax.microedition.lcdui.Image;

public class Scene
{
public static TiledLayer createTiledLayerByBackground(Image image)
{
  //使用TiledLayer创建地图
  TiledLayer tiledLayer = new TiledLayer(10, 8, image, 48, 64);
  tiledLayer.fillCells(0, 0, 10, 8, 2);
  return tiledLayer;
}
}



修改BraveCanvas类,加入地图

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 TiledLayer backgroundMap;

public BraveCanvas()
{
  super(true);
}

public void startup()
{
  this.sign = true;
  try
  {
   //产生地图
   backgroundMap = Scene.createTiledLayerByBackground(
    Image.createImage("/background.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");

   hero = new Hero(upimage, 17, 26);
   hero.setFrameSequence(new int[]{1, 1, 0, 0, 1, 1, 2, 2});
   hero.setBraveCanvas(this);
   hero.init(40,40);
  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  Thread thread = new Thread(this);
  thread.start();
}

public void run()
{
  g = getGraphics();
  while(sign)
  {
   try
   {
    input(g);
    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:
    //由这里传入需要改变的图片
    hero.moveUp(upimage);
    break;
   case DOWN_PRESSED:
    hero.moveDown(downimage);
    break;
   case LEFT_PRESSED:
    hero.moveLeft(leftimage);
    break;
   case RIGHT_PRESSED:
    hero.moveRight(rightimage);
    break;
  }
  hero.afresh();
}

public void paint(Graphics g)
{
  g.setColor(0x000000);
  g.fillRect(0, 0, getWidth(), getHeight());
  g.setColor(0xffffff);
  //显示地图
  backgroundMap.paint(g);
  hero.paint(g);
  flushGraphics();
}
}


  现在我们的英雄虽然能在草地上行走了,但感觉给他移动的空间太小了。而且我们在创建背景地图的时
候,地图大小明明是512*480的。好,接下来我们要做的就是让英雄可以在更大的天地中活动。这里就需要一
个BraveManager类来管理这些屏幕上的Sprite和TiledLayer,该类继承LayerManager。

BraveManager.java

package brave;

import javax.microedition.lcdui.game.LayerManager;

public class BraveManager extends LayerManager
{
private BraveCanvas braveCanvas;

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

public void afresh()
{
  //确定当前试图的坐标
  //这里用一个比较简单的算法来使英雄永远在屏幕的中央
  int viewX = Math.max(0, getLayerAt(0).getX() - braveCanvas.getWidth()/2);
  int viewY = Math.max(0, getLayerAt(0).getY() - braveCanvas.getHeight()/2);
  viewX = Math.min(viewX, getLayerAt(1).getWidth() - braveCanvas.getWidth());
  viewY = Math.min(viewY, getLayerAt(1).getHeight() - braveCanvas.getHeight());
  setViewWindow(viewX, viewY, braveCanvas.getWidth(), braveCanvas.getHeight());
}
}


修改BraveCanvas类

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 TiledLayer backgroundMap;
//创建Layer管理视图类
private BraveManager braveManager;

public BraveCanvas()
{
  super(true);
}

public void startup()
{
  this.sign = true;
  try
  {
   backgroundMap = Scene.createTiledLayerByBackground(
    Image.createImage("/background.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");
   //创建Layer管理视图类
   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(0,0);

  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  Thread thread = new Thread(this);
  thread.start();
}

public void run()
{
  g = getGraphics();
  //插入图层
  braveManager.insert(hero, 0);
  braveManager.insert(backgroundMap, 1);
  while(sign)
  {
   try
   {
    input(g);
    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:
    hero.moveUp(upimage);
    break;
   case DOWN_PRESSED:
    hero.moveDown(downimage);
    break;
   case LEFT_PRESSED:
    hero.moveLeft(leftimage);
    break;
   case RIGHT_PRESSED:
    hero.moveRight(rightimage);
    break;
  }
  hero.afresh();
  //刷新视图的位置
  braveManager.afresh();
}

public void paint(Graphics g)
{
  g.setColor(0x000000);
  g.fillRect(0, 0, getWidth(), getHeight());
  g.setColor(0xffffff);
  //显示视图
  braveManager.paint(g, 0, 0);
  flushGraphics();
}
}


这样英雄就可以在地图上任何地方行动了,不过还得改一个小地方:
不知道大家还记的不,在Hero类中,我们定义英雄移动的范围最大为屏幕的尺寸。
这显然是不行的,最大的移动范围应改成地图的大小:

this.y = Math.min(braveManager.getLayerAt(1).getHeight(), y + 1);
this.x = Math.min(braveManager.getLayerAt(1).getWidth(), x + 1);

代码如下:

Hero.java

package brave;

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

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();
  this.y = Math.max(0, y - 1);
}

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

public void moveLeft(Image image) throws IOException
{
  setImage(image, 17, 26);
  nextFrame();
  this.x = Math.max(0, x - 1);

}

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

现在英雄的移动范围大了,但只英雄一人,也太孤单了,我们给他创造一个小镇吧。
修改Scene类如下:

Scene.java

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 TiledLayer createTiledLayerByForeground(Image image)
{
  TiledLayer tiledLayer = new TiledLayer(30, 32, image, 16, 16);
  // 30 * 32
  int[] maplist =
  {
    //0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

26 27 28 29
   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,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,30,0 ,//1
   0 ,34,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,0 ,//2
   0 ,34,0 ,1 ,2 ,3 ,4 ,5 ,0 ,0 ,0 ,1 ,2 ,3 ,3 ,26,3 ,3 ,4 ,5 ,0 ,0 ,1 ,2 ,3

,4 ,5 ,0 ,36,0 ,//3
   0 ,34,0 ,7 ,8 ,46,10,11,0 ,0 ,0 ,7 ,8 ,47,31,32,33,47,10,11,0 ,0 ,7 ,8

,46,10,11,0 ,36,0 ,//4
   0 ,34,0 ,13,14,15,16,17,0 ,0 ,0 ,13,14,14,37,38,39,14,16,17,0 ,0

,13,14,15,16,17,0 ,36,0 ,//5
   0 ,34,0 ,19,20,21,22,23,6 ,0 ,0 ,19,20,20,43,44,45,20,20,23,0 ,0

,19,20,21,22,23,0 ,36,0 ,//6
   0 ,34,0 ,0 ,0 ,12,0 ,0 ,0 ,0 ,0 ,24,24,24,13,15,17,24,24,24,0 ,0 ,0 ,0

,12,0 ,0 ,0 ,36,0 ,//7
   0 ,34,0 ,0 ,0 ,12,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,19,21,23,0 ,0 ,0 ,0 ,0 ,0 ,0

,12,0 ,0 ,0 ,36,0 ,//8
   0 ,34,0 ,0 ,0 ,12,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,12,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0

,12,0 ,0 ,0 ,36,0 ,//9
   0 ,34,0 ,0 ,0

,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0 ,0 ,0 ,36,0 ,//10
   0 ,34,0 ,0 ,0 ,0 ,0 ,0 ,0 ,12,25,25,25,25,25,25,25,25,25,25,12,0 ,0 ,0 ,0

,0 ,0 ,0 ,36,0 ,//11
   0 ,34,0 ,1 ,2 ,3 ,4 ,5 ,0 ,12,25,25,25,25,25,25,25,25,25,25,12,0 ,1 ,2 ,3

,4 ,5 ,0 ,36,0 ,//12
   0 ,34,0 ,7 ,8 ,46,10,11,0 ,12,25,25,25,25,25,25,25,25,25,25,12,0 ,7 ,8

,46,10,11,0 ,36,0 ,//13
   0 ,34,0 ,13,14,15,16,17,0 ,12,25,25,25,25,25,25,25,25,25,25,12,0

,13,14,15,16,17,0 ,36,0 ,//14
   0 ,34,0 ,19,20,21,22,23,0 ,12,25,25,25,25,25,25,25,25,25,25,12,0

,19,20,21,22,23,0 ,36,0 ,//15
   0 ,34,0 ,0 ,0 ,12,0 ,0 ,0 ,12,25,25,25,25,25,25,25,25,25,25,12,0 ,0 ,0

,12,0 ,0 ,0 ,36,0 ,//16
   0 ,34,0 ,0 ,0 ,12,0 ,0 ,0 ,12,25,25,25,25,25,25,25,25,25,25,12,0 ,0 ,0

,12,0 ,0 ,0 ,36,0 ,//17
   0 ,34,0 ,0 ,0 ,12,0 ,0 ,0 ,12,25,25,25,25,25,25,25,25,25,25,12,0 ,0 ,0

,12,0 ,0 ,0 ,36,0 ,//18
   0 ,34,0 ,0 ,0

,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0 ,0 ,0 ,36,0 ,//19
   0 ,34,0 ,0 ,0 ,0 ,0 ,0 ,0 ,12,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,12,0 ,0 ,0 ,0

,0 ,0 ,0 ,36,0 ,//20
   0 ,34,0 ,0 ,0 ,0 ,0 ,0 ,0 ,12,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,12,0 ,0 ,0 ,0

,0 ,0 ,0 ,36,0 ,//21
   0 ,34,0 ,1 ,2 ,3 ,4 ,5 ,0 ,12,0 ,0 ,0 ,0 ,1 ,3 ,5 ,0 ,0 ,0 ,12,0 ,1 ,2 ,3

,4 ,5 ,0 ,36,0 ,//22
   0 ,34,0 ,7 ,8 ,46,10,11,0 ,12,0 ,0 ,0 ,0 ,7 ,48,11,0 ,0 ,0 ,12,0 ,7 ,8

,46,10,11,0 ,36,0 ,//23
   0 ,34,0 ,13,14,15,16,17,0 ,12,0 ,0 ,0 ,6 ,13,15,17,0 ,0 ,0 ,12,0

,13,14,15,16,17,0 ,36,0 ,//24
   0 ,34,0 ,19,20,21,22,23,0 ,12,0 ,0 ,0 ,6 ,19,21,23,0 ,0 ,0 ,12,0

,19,20,21,22,23,0 ,36,0 ,//25
   0 ,34,0 ,0 ,0 ,12,0 ,0 ,0 ,12,18,0 ,0 ,0 ,0 ,12,0 ,0 ,0 ,0 ,12,0 ,0 ,0

,12,0 ,0 ,0 ,36,0 ,//26
   0 ,34,0 ,0 ,0

,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0 ,0 ,0 ,36,0 ,//27
   0 ,34,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,12,12,12,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0

,0 ,0 ,0 ,36,0 ,//28
   0 ,34,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,12,12,12,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0

,0 ,0 ,0 ,36,0 ,//29
   0

,40,29,29,29,29,29,29,29,29,29,29,29,29,12,12,12,29,29,29,29,29,29,29,29,29,29,29,42,0 ,//30
   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  //31
  };
  for(int i = 0 ; i < maplist.length ; i++)
  {
   int col = i % 30;
   int row = (i - col) / 30;
   tiledLayer.setCell(col, row, maplist[i]);
  }
  return tiledLayer;
}
}


修改BraveCanvas类如下:

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 TiledLayer backgroundMap;
private TiledLayer foregroundMap;
private BraveManager braveManager;

public BraveCanvas()
{
  super(true);
}

public void startup()
{
  this.sign = 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");
   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(0,0);

  }
  catch(Exception e)
  {
   e.printStackTrace();
  }
  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);
    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:
    hero.moveUp(upimage);
    break;
   case DOWN_PRESSED:
    hero.moveDown(downimage);
    break;
   case LEFT_PRESSED:
    hero.moveLeft(leftimage);
    break;
   case RIGHT_PRESSED:
    hero.moveRight(rightimage);
    break;
  }
  hero.afresh();
  braveManager.afresh();
}

public void paint(Graphics g)
{
  g.setColor(0x000000);
  g.fillRect(0, 0, getWidth(), getHeight());
  g.setColor(0xffffff);
  braveManager.paint(g, 0, 0);
  flushGraphics();
}
}

运行结果如图:
image

思 想 的 自 由 才 是 真 正 的 自 由 ---Matrix.天路旅客
征求大家在matrix的用户体验

访问我的“出版物”,给我留言
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount119885", 3); DWRUtil.setValue("voteNum119885", 1); </script>

dreampig     
user face
  • Matrix排名:148
  • 排名变化:  --
  • 用户等级:5
  • 总发贴数:224
  • 总积分(Karma):1070
  • 参与分(经验):409
  • 专家分(威望):399
  • 可用分(财富):770
  • 类型:普通用户
  • 昵称:许愿猪
  • 来自:上海
  • 注册:2005-08-17
  • 在线状态: 离线
[信息] [引用] [回复] 第 3 楼 BACK TOP

我最近学了点J2ME也做了个RPG人物能在地图上行走的部分,
不过我还很菜,程序和图片都比较... ....呵呵,不说了(不过我还是挺开心的^_^).

感谢cleverpig的RPG游戏FollowME系列哦,有空正好研习一下... ...

顺便问一下:

类似的人物场景的图片素材有比较好的下载站点吗?

Taking an intern job.
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount119906", 0); DWRUtil.setValue("voteNum119906", 0); </script>

dreampig     
user face
  • Matrix排名:148
  • 排名变化:  --
  • 用户等级:5
  • 总发贴数:224
  • 总积分(Karma):1070
  • 参与分(经验):409
  • 专家分(威望):399
  • 可用分(财富):770
  • 类型:普通用户
  • 昵称:许愿猪
  • 来自:上海
  • 注册:2005-08-17
  • 在线状态: 离线
[信息] [引用] [回复] 第 4 楼 BACK TOP

我按照说明的顺序从上到下试了一下,终于看到效果了,
我也是才学的J2ME~冒昧的提出点问题:
就是


最后一个BraveCanvas类的
public void paint(Graphics g) {}方法中
的   braveManager.paint(g, 0, 0);语句
在第一次运行到paint()的时候会抛出
java.lang.NullPointerException
不过改成
if(sign){braveManager.paint(g, 0, 0);}
就好了~
( 前面的几个BraveCanvas类也是同样的问题)


最后的一个Hero.java中
public void setManager(BraveCanvas braveCanvas)
{
     this.braveCanvas = braveCanvas;
}
这个方法好像是多余的?


这个RPG和我心目中想像已久(就是作不出)的样子已经差不多啦(感动...),
有时间再慢慢研习一下这个程序,谢谢!
可以学到很多东西~

Taking an intern job.
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount120043", 0); DWRUtil.setValue("voteNum120043", 0); </script>

dreampig     
user face
  • Matrix排名:148
  • 排名变化:  --
  • 用户等级:5
  • 总发贴数:224
  • 总积分(Karma):1070
  • 参与分(经验):409
  • 专家分(威望):399
  • 可用分(财富):770
  • 类型:普通用户
  • 昵称:许愿猪
  • 来自:上海
  • 注册:2005-08-17
  • 在线状态: 离线
[信息] [引用] [回复] 第 5 楼 BACK TOP

我忽然想起玩过的一个PC的RPG游戏"星月奇缘"记不清人物了,场景挺像的哈.

Taking an intern job.
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount120115", 0); DWRUtil.setValue("voteNum120115", 0); </script>

编辑推荐 (新闻) Eclipse 3.3 M3 发布
Norther     
user photo
  • Matrix排名:32
  • 排名变化:  --
  • 用户等级:6
  • 总发贴数:477
  • 总积分(Karma):2565
  • 参与分(经验):1973
  • 专家分(威望):165
  • 可用分(财富):2039
  • 类型:普通用户
  • 昵称:草原上奔跑的烈马
  • 来自:Norther
  • 注册:2005-08-23
  • 在线状态: 离线
  • 进入Norther的部落格
[信息] [引用] [回复] 第 6 楼 BACK TOP




很不错的样子类,期待完全版!

From Norther I Came!
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount120313", 0); DWRUtil.setValue("voteNum120313", 0); </script>

cleverpig     
user photo
  • Matrix排名:10
  • 排名变化:  --
  • 用户等级:8
  • 总发贴数:778
  • 总积分(Karma):5859
  • 参与分(经验):3469
  • 专家分(威望):2259
  • 可用分(财富):2345
  • 类型:副管理员
  • 昵称:Matrix.天路旅客
  • 来自:北京
  • 注册:2005-02-19
  • 在线状态: 在线
  • 进入cleverpig的部落格
[信息] [引用] [回复] 第 7 楼 BACK TOP

多谢dreampig的悉心支持,发现了我所忽略的问题!在此特此感谢dreampig对FollowME系列的大力支持!cleverpig在此有礼了!

思 想 的 自 由 才 是 真 正 的 自 由 ---Matrix.天路旅客
征求大家在matrix的用户体验

访问我的“出版物”,给我留言
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount122112", 0); DWRUtil.setValue("voteNum122112", 0); </script>

cleverpig     
user photo
  • Matrix排名:10
  • 排名变化:  --
  • 用户等级:8
  • 总发贴数:778
  • 总积分(Karma):5859
  • 参与分(经验):3469
  • 专家分(威望):2259
  • 可用分(财富):2345
  • 类型:副管理员
  • 昵称:Matrix.天路旅客
  • 来自:北京
  • 注册:2005-02-19
  • 在线状态: 在线
  • 进入cleverpig的部落格
[信息] [引用] [回复] 第 8 楼 BACK TOP

J2ME RPG游戏边学边做(三)--游戏基本元素类

    虽然我们有了midp2.0的支持,但是有时还是需要一些辅助工具,方便我们使用。这怕是在进行真正的游戏设计之前最有趣的了。

    1,首先是一个ImageTools工具类,提供一个方法帮助调用Image

public class ImageTools {

  protected ImageTools() {

  }

  public static Image getImage(String str){

    Image img=null;

    try {

      img = Image.createImage(str);

    }

    catch (Exception ex) {

      System.out.println(ex);

    }

    finally{

      return img;

    }

  }

}

2.GameObject,提供一个通用的游戏对象。

       有了Sprite类,为什么还要GameObject呢?其实我们一般是将Sprite,看作成一个高级的Image,往往一个Sprite要被多个游戏对象调用,GameObject其实就是Sprite的状态类。GameObject提供简单的生命周期概念,动画更新速度;

public class GameObject {

  public Sprite sprite;//内置的Sprite

  public boolean alive;//存活标记

  private int lifecount=0;//生命周期计数器

  public int lifetime=0;//生命周期,以桢为单位

  public int speed=0;//动画桢更新速度,(0至无穷,0代表每一桢跟新一个画面)

  private int animcount=0;// /动画桢更新计数器

   public GameObject(Image img,int width,int height){

    sprite=new Sprite(img,width,height);

    reset();

  }

   public void move(int dx,int dy){//相对移动

    sprite.move(dx,dy);

  }

   public void moveto(int x,int y){//绝对移动

    sprite.setPosition(x,y);

  }

   public void update(){//更新状态,动画桢更新,生命周期更新

    if(!alive)

      return;

    if(++animcount>speed){

      animcount=0;

      sprite.nextFrame();

      if(lifetime!=0 && ++lifecount>lifetime)

        alive=false;

    }

  }

   public void paint(Graphics g){//Paint

    if(!alive)

      return;

    sprite.paint(g);

  }

  public void reset(){//复位

    alive=true;

    lifecount=0;

    animcount=0;

    sprite.setFrame(0);

  }

}

     3.封装字体类,你需要漂亮的字体吗?

    我们经常需要用图片来输出文字,一个方便的字体类是必须的。我们希望仅仅提供一个图片,一个图片所描述的字符的数组,来初始化一个字体类。字体类提供一个类似Textout的方法,方便得在一个位置输出信息。先封装一个简单的版本,只支持英文和数字,并且输出不能自动换行。可能你有一个简单的思路,就是简单的保存字符数组,当打印的时候遍历数组,来查找每个字符在sprite的frameseq中的index,但当我们打印一个字符串的时候就会发现,太多的遍历操作耽误了宝贵时间,这里我们使用一个小技巧用容量换取速度,我们知道Character. hashCode()可以返回字符的ascii编码,常用字符将返回1-127;利用这一点,我们开辟一个128的数组charhash,将输入的字符c所在图片index存入charhash[c. hashCode()]中。以后也用这种映射方法来读取字符。charhash的元素初值为-1,以后只要数值大于0就是有效值。

public class Font {

  Sprite sprite;       //Sprite

  int width,height;  //每个char的尺寸

  int[] charhash;    //储存1-127个常见字符在sprite的frameseq中的位置

  Graphics g;

   public Font(Graphics g,Image img, int width,  int height, char[] chars) {

    this.g=g;

    sprite=new Sprite(img,width,height);

    this.width=width;

    this.height=height;

    charhash=new int[128];

    for (int i = 0; i < charhash.length; i++) {

      charhash[i]=-1;//没有代表此字符的图片

    }

    Character c;

    for (int i = 0; i < chars.length; i++) {

      c=new Character(chars[i]);

      charhash[c.hashCode()]=i;

    }

  }

   public void drawChar(char ch, int x, int y){

    Character c=new Character(ch);

    int hashcode=c.hashCode();

    sprite.setPosition(x,y);

    if(hashcode>=0){

      sprite.setFrame(charhash[hashcode]);

      sprite.paint(g);

    }

  }

   public void drawString(String str, int x, int y){

    int length=str.length();

    for (int i = 0; i < length; i++) {

      drawChar(str.charAt(i),x+width*i,y);

    }

  }

}


这样只要有一个实例font,就可以调用font.drawString(“hello”,0,0);

在0,0位置输出漂亮的图片字符串。怎么样还挺好使的吧:)


思 想 的 自 由 才 是 真 正 的 自 由 ---Matrix.天路旅客
征求大家在matrix的用户体验

访问我的“出版物”,给我留言
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount122115", 0); DWRUtil.setValue("voteNum122115", 0); </script>

cleverpig     
user photo
  • Matrix排名:10
  • 排名变化:  --
  • 用户等级:8
  • 总发贴数:778
  • 总积分(Karma):5859
  • 参与分(经验):3469
  • 专家分(威望):2259
  • 可用分(财富):2345
  • 类型:副管理员
  • 昵称:Matrix.天路旅客
  • 来自:北京
  • 注册:2005-02-19
  • 在线状态: 在线
  • 进入cleverpig的部落格
[信息] [引用] [回复] 第 9 楼 BACK TOP

J2ME RPG游戏边学边做(四)--爆炸效果

 大多数游戏都有着丰富的效果类,在精灵移动类游戏中曾一度以此为一个重要的卖点。光光是一些丰富的特效是不能够产生一个好的游戏的,但是一个好的游戏是万万不能缺少好的效果的。

  很多人认为游戏的效果层有时和跟游戏逻辑本身并没有太大的关系,往往就是在最终屏幕上再画上一层效果层。但是游戏逻辑和效果层之间的通信是很重要的。这种通信往往体现在延时与等待上。比如飞机爆炸时,不接受任何用户输入,并且爆炸效果还要继续跟随飞机坠落,甚至爆炸的范围会影响周围的物体,要等待爆炸结果结束了才继续进行游戏。游戏逻辑和效果层之间的通信是很复杂的问题。在这里我突然有了罪恶感,我们没有对游戏进行任何的分析就起步了,游戏完全是基于硬编码的,我想到那儿,大家跟着看到那儿。飞机类仅仅是一个sprite,没有设计成一个状态机,这也就使得我们的效果层和逻辑层的通信有些卡通了。也许本文给了你编写自己第一个游戏的喜悦,也带给了你对游戏扩展性与复杂性的一丝担忧。或许这比便一个硬编码的游戏更有意义呢?谁说得好呢,现还是以为那些扩展性良好的游戏是伟大游戏构架师的杰作吧,相信你有了一两个好的想法后会重新设计这个游戏的,使之稍微有一些像个“系统”。然而好的技术不一定产生好的游戏。

  有扯远了,会到现实吧,boys and girls!goon.

  描述一下我们的爆炸效果,在子弹击中飞机后,子弹要迅速消失,飞机图像保持不变,此时将爆炸效果至于飞机图像之上,然后开始显示boom动画,在此期间,飞机不接受任何移动指示,因为他lose control。在爆炸效果后飞机消失。

image
我们的爆炸效果类:

GameObject explosion;


  初始化once:

img=ImageTools.getImage("/pic/explosion.png");
explosion=new GameObject(img,32,32);


  初始化:

explosion.reset();
explosion.lifetime=3;//生命周期定位三桢


  逻辑处理:

if (gameover) {//如果游戏结束,显示效果类
 explosion.paint(g);
 explosion.update();
 if(!explosion.alive){//当生命周期结束了
  plane.alive=false;//关闭plane
  g.setColor(255,255,255);
  g.drawString(StringTools.timeOpinion(gametime),5,22,g.LEFT|g.TOP);
  g.drawString("fly 0.1 ver by favo yang",2,100,g.LEFT|g.TOP);
  g.drawString("E-mail : favoyang@yahoo.com",2,115,g.LEFT|g.TOP);
  g.drawString("simulate from:",2,130,g.LEFT|g.TOP);
  g.drawString("Mr.tony 's <hold on 20sec 1.20> ",2,145,g.LEFT|g.TOP);
  g.drawString("hello tony, just funny.",2,160,g.LEFT|g.TOP);
 }
}


  现在你看我是如何解决效果层与逻辑层之间的通信的,我使用的是全局变量gameover,在简单游戏中使用大量的全局状态变量也是一种常见的方法,可以避免动脑劲。不过缺点明显,游戏硬编码,结构既不清晰也不漂亮,几乎没有扩展性。所以说最好还是将飞机基于状态机设计,并将效果类设计成含有回调函数的抽象类,然后继承效果类实现回调函数来实现通信。至于总体层次上可以用堆栈将绘画单元串起来。还有分层处理等等…给你个思考的起点…

  导弹的是实现,是不是你已经有个想法了呢,其实就是利用Bullets.killbullets。

image
逻辑处理

if(bomb.alive){
 bomb.moveto(plane.sprite.getX()-20,plane.sprite.getY()-20);
 bomb.paint(g);
 bomb.update();
 bullets.killbullets(plane.sprite,32);
}

  在这里我不得不提一句,将生命概念封装在GameObject中是很好的(其实我们只是将其用作显示关键字),但将生命周期安排在GameObject中有欠妥当,生命周期也不一定就是基于桢的,有时基于时间,有时还有别的什么。我是说她足够复杂到交给另一个独立类处理,在这里实际需要的是一个足够强大的显示方法,其支持以桢数为参数显示罢了。

思 想 的 自 由 才 是 真 正 的 自 由 ---Matrix.天路旅客
征求大家在matrix的用户体验

访问我的“出版物”,给我留言
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount122120", 0); DWRUtil.setValue("voteNum122120", 0); </script>

cleverpig     
user photo
  • Matrix排名:10
  • 排名变化:  --
  • 用户等级:8
  • 总发贴数:778
  • 总积分(Karma):5859
  • 参与分(经验):3469
  • 专家分(威望):2259
  • 可用分(财富):2345
  • 类型:副管理员
  • 昵称:Matrix.天路旅客
  • 来自:北京
  • 注册:2005-02-19
  • 在线状态: 在线
  • 进入cleverpig的部落格
[信息] [引用] [回复] 第 10 楼 BACK TOP

J2ME RPG游戏边学边做(五)--游戏灵魂(计时器和奖惩与评价)

    我们该加入我们的计时器了,我要从游戏开始时刻开始计时,并不断的更新到屏幕上,在游戏结束后计时器要停止工作。


首先在初始化的时候将当前时刻记录下来:

gametime=0;

gametimeoffset=System.currentTimeMillis();



以后只要游戏不gameover就在每个更新周期都进行一次计算:

gametime=(System.currentTimeMillis()-gametimeoffset)/1000;

//转换为秒



下面要做的就是将它显示出来,还记得我们曾经实现的字体类了吗,这下子有用了,首先是实例化一个字体类:

img=ImageTools.getImage("/pic/b_number.png");

fontbig=new   Font(g,img,10,15,

new char[]{'0','1','2','3','4','5','6','7','8','9'});

然后就是显示:

fontbig.drawString(String.valueOf(gametime),screenwidth/2-15,10);

还真是方便yeah!





接着是奖励系统,我们规定每过20s就加一个bomb给玩家。

int awardindex=(int)gametime/20;//计算奖励时间

    if(awardindex>bombawardtop)

      awardindex=bombawardtop;

    if(bombaward[awardindex]!=0){//如果本20s没有奖励

      bombnum+=bombaward[awardindex];

      bombaward[awardindex]=0;//奖励过了

}



尽可能简单和给出些提示是我写本文的原则, 所以评价系统,很easy。

我们建立一个辅助类,提供一个方法,输入游戏时间,返回一个String评语。

public class StringTools {

  protected StringTools() {

  }



  public static String timeOpinion(long gametime){

    if(gametime<10){

      return "Do you play with your foot?";

      //return "i can't belive,your are a game master";

    }else if(gametime<16){

      return "come boy, you can do it!";

    }else if(gametime<20){

      return "what a pity! try again.";

    }else if(gametime<25){

      return "very well, you are a real man.";

    }else if(gametime<30){

      return "i know you have talent of this game.";

    }else if(gametime<40){

      return "i can't belive, your are a game master.";

    }else{

      return "oh my god, are you a human?";

    }

  }

}

之后显示出来就好了,我手头没有合适大小的字体图片,我直接使用

g.drawString(StringTools.timeOpinion(gametime),5,22,g.LEFT|g.TOP);

想在手机小小的屏幕容下那么东西是挺费劲的,其实这也是我对手机上玩游戏没什么兴趣,不过我对手机网络应用冲满了信心。

思 想 的 自 由 才 是 真 正 的 自 由 ---Matrix.天路旅客
征求大家在matrix的用户体验

访问我的“出版物”,给我留言
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount122121", 0); DWRUtil.setValue("voteNum122121", 0); </script>

编辑推荐 (新闻) 帮助Matrix获得"公平"的排名
cleverpig     
user photo
  • Matrix排名:10
  • 排名变化:  --
  • 用户等级:8
  • 总发贴数:778
  • 总积分(Karma):5859
  • 参与分(经验):3469
  • 专家分(威望):2259
  • 可用分(财富):2345
  • 类型:副管理员
  • 昵称:Matrix.天路旅客
  • 来自:北京
  • 注册:2005-02-19
  • 在线状态: 在线
  • 进入cleverpig的部落格
[信息] [引用] [回复] 第 11 楼 BACK TOP

J2ME RPG游戏边学边做(六)--莱克兄弟的第一架飞机
实在是等不及了吧?加把劲,让我们加入游戏的主角飞机吧。

你可以将这个游戏定位在动作游戏,一个动作游戏需要不断的从用户端获得输入,基本上不间断,所以plane需要不断的改变位置。那么这个基于桢的游戏应该定在多少桢合适呢?这需要在手机中测试,在模拟器上,如果定在15-18桢左右,是比较合适的。听csdn的几位xd说有的游戏仅仅定在12桢,所以不同的游戏是不一定的。基本上动作游戏对机子的要求是要高一点。我们的游戏框架是线性的,即输出显示和接受输入是在同一个线程中,15桢以下的游戏处理输入会显得比较苯,高于20桢如果处理器速度较慢,很容易出现输入卡在缓冲区,处理不过来的情况。现阶段尽量让桢数降下来吧,你不能和pc上用dx编程时,统一30fps的情况相提并论。

在这个以控制精灵移动为主要内容的游戏中,我们不封装任何的游戏事件,我们的程序可能有些稚嫩,没关系,一步一步来吧。



1.       飞机

plane是一个标准的plane。由三桢画面组成,首先在ps中处理,让其透明:
image


在构造函数中,我们加入对飞机的初始化对象语句:

Image img=ImageTools.getImage("/pic/MyPlaneFrames.png");

plane=new GameObject(img,24,24);

      

   在gameInit()中初始化状态,位置居中:

plane.reset();

plane.moveto((screenwidth-plane.sprite.getWidth())/2,(screenheight-plane.sprite.getHeight())/2);



   在gameMain中加入:

plane.paint(g);



   接下来,让飞机在控制下移动,主要是在gameMain中加入输入处理,如果飞机左移就更新画面到飞机左倾的画面,如果飞机右倾反之,没有输入的时候让飞机处于正常。

if (gameover) {

}else{

       if (keyevent) {

        if(key_up){

          plane.move(0, -3);

          plane.sprite.setFrame(0);

        }

        if(key_down){

          plane.move(0, 3);

            plane.sprite.setFrame(0);

        }

        if(key_left){

          plane.move( -3, 0);

          plane.sprite.setFrame(1);

        }

        if(key_right){

          plane.move(3, 0);

          plane.sprite.setFrame(2);

        }

        if(key_fire){

          }

        }

      }

      else {

        plane.sprite.setFrame(0);

      }

}

   尽管很简单,但这是控制精灵移动的主要方式。可以想象一下,如果往左飞不是简单的一桢画面而是播放动画,该怎么实现呢?我们的GameObject功能有限,没有对spriite的动画序列进行增强,今后我们可以增强GameObject,使其支持多个动画序列,弥补spriite的不足。现在逐渐体会到一个好的游戏引擎是多么重要了吧…

      

2.       背景

   让飞机在海面飞行吧,我们用一个蓝色的背景图片代表海面,我们需要他铺满整个背景。我们使用TiledLayer来绘画背景。
image


初始化:

img=ImageTools.getImage("/pic/back_water.png");

int backcolumns=screenwidth/img.getWidth()+1;//计算横向

int backrows=screenheight/img.getHeight()+1;

background=new TiledLayer(backcolumns,backrows,img,img.getWidth(),img.getHeight());

int x,y;

for (int i = 0; i < backcolumns*backrows; i++) {

   x=i%backcolumns;

   y=i/backcolumns;

   System.out.println("x="+x+" y="+y);

   background.setCell(x,y,1);

}



接下来在gameMain中加入绘制语句。

background.paint(g);//draw background

注意,要保证背景的绘制在飞机的绘画之前。如果使用Layermanager可以很方便的的控制绘制层,但是我们的接下来的子弹要求多个子弹对象共用一个sprite的图像,但是Layermanager绘画的时候以Layer为单位,由Layermanager帮助我们调用各个layer的paint,so一次只支持将一个sprite画在一个地方,所以我觉得Layermanager有些鸡肋。一般时候我们还是自己paint吧。当然,层是一个很重要的概念,利用层,屏幕校准将非常方便,震动整个屏幕等特效将很实现。也许你有个好法,可以让我不在Layermanager层与sprite状态对象组之间矛盾…

飞行战机源代码:

思 想 的 自 由 才 是 真 正 的 自 由 ---Matrix.天路旅客
征求大家在matrix的用户体验

访问我的“出版物”,给我留言
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount122444", 0); DWRUtil.setValue("voteNum122444", 0); </script>

cleverpig     
user photo
  • Matrix排名:10
  • 排名变化:  --
  • 用户等级:8
  • 总发贴数:778
  • 总积分(Karma):5859
  • 参与分(经验):3469
  • 专家分(威望):2259
  • 可用分(财富):2345
  • 类型:副管理员
  • 昵称:Matrix.天路旅客
  • 来自:北京
  • 注册:2005-02-19
  • 在线状态: 在线
  • 进入cleverpig的部落格
[信息] [引用] [回复] 第 12 楼 BACK TOP

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在四个方向时下一个将要移动的地图单元是什么。

基本如下:

int xmax = (getX() + getWidth()) / 16;
int ymax = (getY() + getHeight()) / 16;
int xmin = getX() / 16;
int ymin = getY() / 16;
//如果当前的人物方向等于上,并且在地图单元之间的交错处,开始判断他的下一个将要移动的
//地图单元是什么
if(BraveCanvas.way == BraveCanvas.UP_PRESSED && getY() % 16 == 0)
{
if((getX() + getWidth()) % 16 == 0)
  xmax -= 1;
for(int i = xmin ; i <= xmax ; i++)
{
  //这里的i,ymin-1即表示人物将要移动的地图单元。而且并不是一个,是由他的
  //getX()和getWidth()来决定
  System.out.print(i+","+ymin-1)
}
}

其他的几个方向实现原理是一样的。

该方法置于Hero类中,代码如下:

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();
  //如果事件不为99,98才允许移动
  //(随着事件的增加,这里的的条件也会增加,如果采取3维数组则不会出现该问题)
  if(!eventActionExist(99) && !eventActionExist(98))
   this.y = Math.max(0, y - 1);
}

public void moveDown(Image image) throws IOException
{
  setImage(image, 17, 26);
  nextFrame();
  //如果事件不为99,98才允许移动
  //(随着事件的增加,这里的的条件也会增加,如果采取3维数组则不会出现该问题)
  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();
  //如果事件不为99,98才允许移动
  //(随着事件的增加,这里的的条件也会增加,如果采取3维数组则不会出现该问题)
  if(!eventActionExist(99) && !eventActionExist(98))
   this.x = Math.max(0, x - 1);
}

public void moveRight(Image image) throws IOException
{
  setImage(image, 17, 26);
  nextFrame();
  //如果事件不为99,98才允许移动
  //(随着事件的增加,这里的的条件也会增加,如果采取3维数组则不会出现该问题)
  if(!eventActionExist(99) && !eventActionExist(98))
   this.x = Math.min(braveManager.getLayerAt(1).getWidth(), x + 1);
}
/**
* 根据事件id得到当前人物将要移动的位置能否触发该事件
* @param eventID 时间id
* @return 存在:true 不存在:false
*/
public boolean eventActionExist(int eventID)
{
  int xmax = (getX() + getWidth()) / 16;
  int ymax = (getY() + getHeight()) / 16;
  int xmin = getX() / 16;
  int ymin = getY() / 16;
  if(BraveCanvas.way == BraveCanvas.UP_PRESSED && getY() % 16 == 0)
  {
   if((getX() + getWidth()) % 16 == 0)
    xmax -= 1;
   for(int i = xmin ; i <= xmax ; i++)
   {
    if(Scene.getEvent(i, ymin-1) == eventID)
     return true;
   }
  }
  else if(BraveCanvas.way == BraveCanvas.DOWN_PRESSED && (getY()+getHeight()) % 16

== 0)
  {
   if((getX() + getWidth()) % 16 == 0)
    xmax -= 1;
   for(int i = xmin ; i <= xmax ; i++)
   {
    if(Scene.getEvent(i, ymax) == eventID)
     return true;
   }
  }
  else if(BraveCanvas.way == BraveCanvas.LEFT_PRESSED && getX() % 16 == 0)
  {
   if((getY() + getHeight()) % 16 == 0)
    ymax -= 1;
   for(int i = ymin ; i <= ymax ; i++)
   {
    if(Scene.getEvent(xmin-1, i) == eventID)
     return true;
   }
  }
  else if(BraveCanvas.way == BraveCanvas.RIGHT_PRESSED && (getX()+getWidth()) % 16

== 0)
  {
   if((getY() + getHeight()) % 16 == 0)
    ymax -= 1;
   for(int i = ymin ; i <= ymax ; i++)
   {
    if(Scene.getEvent(xmax, i) == eventID)
     return true;
   }
  }
  return false;
}
}

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

运行后,只有左上角、中间的房子和栅栏,可以正常检测,这是因为其他的单元格为了方便都没有加上事
件。
一点缺陷:
其实这里的地图并不是很完善,比如说人物如果在房子上面的话,屋顶应该会把人物遮住一部分,而人物在
房子下面的话,人物应该把房子遮住一点。大家如果感兴趣的话可以用图层来解决这问题。

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

需要用到的图片:

image

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

image
代码如下:

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();
  }
}

}

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

思 想 的 自 由 才 是 真 正 的 自 由 ---Matrix.天路旅客
征求大家在matrix的用户体验

访问我的“出版物”,给我留言
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount122447", 0); DWRUtil.setValue("voteNum122447", 0); </script>

编辑推荐 (文章) 裸奔的“Mashup”
cleverpig     
user photo
  • Matrix排名:10
  • 排名变化:  --
  • 用户等级:8
  • 总发贴数:778
  • 总积分(Karma):5859
  • 参与分(经验):3469
  • 专家分(威望):2259
  • 可用分(财富):2345
  • 类型:副管理员
  • 昵称:Matrix.天路旅客
  • 来自:北京
  • 注册:2005-02-19
  • 在线状态: 在线
  • 进入cleverpig的部落格
[信息] [引用] [回复] 第 13 楼 BACK TOP

特别公告:
    观者、参者可以将高见或者希望以文字的形式留在本贴。希望大家积极回复,说出自己的观点。或者对斑竹提意见,也可以点播要学习的内容!斑竹将尽量满足众人的愿望!。。


思 想 的 自 由 才 是 真 正 的 自 由 ---Matrix.天路旅客
征求大家在matrix的用户体验

访问我的“出版物”,给我留言
    
        
    

非常好 还行 一般 扔鸡蛋 总得分: 投票人次: <script type="text/javascript"> DWRUtil.setValue("voteCount122451", 0); DWRUtil.setValue("voteNum122451", 0); </script>

cleverpig     
user photo
  • Matrix排名:10
  • 排名变化:  --
  • 用户等级:8
  • 总发贴数:778
  • 总积分(Karma):5859
  • 参与分(经验):3469
  • 专家分(威望):2259
  • 可用分(财富):2345
  • 类型:副管理员
  • 昵称:Matrix.天路旅客
  • 来自:北京
  • 注册:2005-02-19
  • 在线状态: 在线
  • 进入cleverpig的部落格
[信息] [引用] [回复] 第 14 楼 BACK TOP

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足够的小。


image

搭建测试平台:

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

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

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


image

这样就可以使用了。

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,所以只传入他就可以了。



image

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

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

protected void startApp()

       {

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

       }

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

如下是在模拟上的结果:


image

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

参考资料:

J2MEUnit Documentation ---- http://j2meunit.sourceforge.net/doc.html
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值