RPG(角色扮演游戏)是手机游戏中的一类主要类型,也是相对来说比较麻烦的一类游戏,下面通过一系列的文章来介绍如何使用J2ME技术来开发RPG游戏。


首先让我们来看一下游戏的骨架——程序框架的实现。程序框架主要包含三个方面:绘制结构、事件处理结构以及线程结构。在整个框架中,采用当前游戏编程中的通用的状态控制机制,为每个界面,如菜单、帮助、游戏对话、商店界面设置一个唯一的状态值,使用该状态值控制界面的绘制、事件的处理以及线程处理。


在程序的实现上为了通用,以MIDP1.0为基础来进行制作,这个要比使用MIDP2.0的Game API实现起来要复杂一些。


在类结构的划分上,为了节约减小jar文件大小,把这个程序代码划分为两个类,一个MIDlet类,一个界面类,所有逻辑代码以及线程实现均放置在界面类中。


下面是MIDlet类的代码,主要实现显示界面、处理手机来电、释放资源以及退出功能,线程启动放在界面类中实现。源代码如下:


package myrpg; 

 

  import javax.microedition.midlet.*; 

 

  import javax.microedition.lcdui.*; 

 

  /** 

 

  * RPG结构的MIDlet类 

 

  * 包含如下功能: 

 

  * 
  1、显示界面 

 

  * 
  2、手机来电处理 

 

  * 
  3、释放资源 

 

  * 
  4、退出方法 

 

  */ 

 

  public class MyRPGMIDlet extends MIDlet { 

 
 
  /**MyRPGMIDlet对象,用于实现退出功能*/ 

 
 
  static MyRPGMIDlet instance; 

 
 
  /**界面类对象*/ 

 
 
  MyRPGCanvas mainScreen = new MyRPGCanvas(); 

 
 
  public MyRPGMIDlet() { 

 
 
  //初始化 

 
 
  instance = this; 

 
 
  //显示界面 

 
 
  Display.getDisplay(this).setCurrent(mainScreen); 

 
 
  } 

 
 
  public void startApp() { 

 
 
  //开始或继续游戏 

 
 
  if (mainScreen != null) { 

 
 
  mainScreen.startGame(); 

 
 
  } 

 
 
  } 

 
 
  public void pauseApp() { 

 
 
  //暂停游戏 

 
 
  if (mainScreen != null) { 

 
 
  mainScreen.pauseGame(); 

 
 
  } 

 
 
  } 

 
 
  public void destroyApp(boolean unconditional) { 

 
 
  //释放资源 

 
 
  if (mainScreen != null) { 

 
 
  mainScreen.destroyGame(); 

 
 
  mainScreen = null; 

 
 
  } 

 
 
  } 

 
 
  /** 

 
 
  * 退出方法 

 
 
  */ 

 
 
  public static void quitApp() { 

 
 
  instance.destroyApp(true); 

 
 
  instance.notifyDestroyed(); 

 
 
  instance = null; 

 
 
  } 

 

  } 

 
 
  游戏逻辑和界面绘制以及控制都放在一个类MyRPGCanvas中,这样实现没有使用面向对象容易修改和扩展,但是通过结构化代码,还是可以保证较高的可读性以及维护性。在MyRPGCanvas中,通过状态变量status控制界面的绘制以及线程逻辑,为了清晰,把每个处理逻辑都封装成一个方法,如果方法比较复杂还可以继续拆分为多个方法。 

 
 
  关于绘制部分,如果每个界面都具有一张不透明的背景图片的话,可以省略清屏功能,这样可以提高程序的执行效率。 

 
 
  关于线程部分主要实现了暂停控制,通过isPaused变量来控制逻辑是否执行,从而实现暂停功能,并实现精确的延时。 

 
 
  关于资源加载和销毁,如果机器的内存不是很紧张的话,可以一次加载,如果内存比较紧张的话,需要编写专门的代码控制资源的加载和销毁。 

 
 
  具体的实现代码如下: 

 

  package myrpg; 

 

  import javax.microedition.lcdui.*; 

 

  /** 

 

  * 游戏界面,包含所有游戏界面、逻辑以及事件处理 

 

  */ 

 

  public class MyRPGCanvas extends Canvas implements Runnable { 

 
 
  /**游戏是否处于运行状态,true代表处于运行状态*/ 

 
 
  private boolean isRunning = true; 

 
 
  /**游戏是否处于暂停状态,true代表处于暂停状态*/ 

 
 
  private boolean isPaused = false; 

 
 
  /**屏幕宽度*/ 

 
 
  private int width; 

 
 
  /**屏幕高度*/ 

 
 
  private int height; 

 
 
  /**时间间隔*/ 

 
 
  private final int INTERVAL_TIME = 100; 

 
 
  /**游戏状态,使用该变量标示游戏的界面和逻辑*/ 

 
 
  private int status; 

 
 
  //各个界面状态常量 

 
 
  /**Logo界面状态*/ 

 
 
  private final int LOGO_STATUS = 0; 

 
 
  /**菜单界面状态*/ 

 
 
  private final int MENU_STATUS = 1; 

 
 
  /**帮助界面状态*/ 

 
 
  private final int HELP_STATUS = 2; 

 
 
  /**关于界面状态*/ 

 
 
  private final int ABOUT_STATUS = 3; 

 
 
  //游戏中各个状态常量 

 
 
  /**地图1状态*/ 

 
 
  private final int GAME_MAP1_STATUS = 4; 

 
 
  /**武器店1状态*/ 

 
 
  private final int GAME_WEAPONSHOP1_STATUS = 5; 

 
 
  /**对话1状态*/ 

 
 
  private final int GAME_DIALOG1_STATUS = 6; 

 
 
  public MyRPGCanvas() { 

 
 
  //初始化 

 
 
  init(); 

 
 
  //启动线程 

 
 
  Thread thread = new Thread(this); 

 
 
  thread.start(); 

 
 
  } 

 
 
  /** 

 
 
  * 初始化游戏 

 
 
  * 导入资源和初始化游戏状态 

 
 
  */ 

 
 
  private final void init() { 

 
 
  //获得屏幕尺寸 

 
 
  width = this.getWidth(); 

 
 
  height = this.getHeight(); 

 
 
  //初始化游戏状态,默认显示LOGO界面 

 
 
  status = LOGO_STATUS; 

 
 
  //导入图片和其他资源 

 
 
  } 

 
 
  protected void paint(Graphics g) { 

 
 
  //清屏 

 
 
  clearScreen(g); 

 
 
  //绘制 

 
 
  switch (status) { 

 
 
  case LOGO_STATUS: 

 
 
  paintLogo(g); 

 
 
  break; 

 
 
  case MENU_STATUS: 

 
 
  paintMenu(g); 

 
 
  break; 

 
 
  case HELP_STATUS: 

 
 
  paintHelp(g); 

 
 
  break; 

 
 
  case ABOUT_STATUS: 

 
 
  paintAbout(g); 

 
 
  break; 

 
 
  case GAME_MAP1_STATUS: 

 
 
  paintGame_Map1(g); 

 
 
  break; 

 
 
  case GAME_WEAPONSHOP1_STATUS: 

 
 
  paintGame_WeaponShop1(g); 

 
 
  break; 

 
 
  case GAME_DIALOG1_STATUS: 

 
 
  paintDialog1(g); 

 
 
  break; 

 
 
  } 

 
 
  } 

 
 
  /** 

 
 
  * 绘制LOGO界面 

 
 
  * @param g Graphics 画笔 

 
 
  */ 

 
 
  private final void paintLogo(Graphics g) { 

 
 
  } 

 
 
  /** 

 
 
  * 绘制菜单界面 

 
 
  * @param g Graphics 画笔 

 
 
  */ 

 
 
  private final void paintMenu(Graphics g) { 

 
 
  } 

 
 
  /** 

 
 
  * 绘制帮助界面 

 
 
  * @param g Graphics 画笔 

 
 
  */ 

 
 
  private final void paintHelp(Graphics g) { 

 
 
  } 

 
 
  /** 

 
 
  * 绘制关于界面 

 
 
  * @param g Graphics 画笔 

 
 
  */ 

 
 
  private final void paintAbout(Graphics g) { 

 
 
  } 

 
 
  /** 

 
 
  * 绘制游戏地图1界面 

 
 
  * @param g Graphics 画笔 

 
 
  */ 

 
 
  private final void paintGame_Map1(Graphics g) { 

 
 
  } 

 
 
  /** 

 
 
  * 绘制游戏武器店1界面 

 
 
  * @param g Graphics 画笔 

 
 
  */ 

 
 
  private final void paintGame_WeaponShop1(Graphics g) { 

 
 
  } 

 
 
  /** 

 
 
  * 绘制游戏对话1界面 

 
 
  * @param g Graphics 画笔 

 
 
  */ 

 
 
  private final void paintDialog1(Graphics g) { 

 
 
  } 

 
 
  /** 

 
 
  * 清屏 

 
 
  * @param g Graphics 画笔 

 
 
  */ 

 
 
  private final void clearScreen(Graphics g) { 

 
 
  g.setColor(0xffffff); 

 
 
  g.fillRect(0, 0, width, height); 

 
 
  } 

 
 
  /** 

 
 
  * 开始和继续游戏 

 
 
  */ 

 
 
  public void startGame() { 

 
 
  isPaused = false; 

 
 
  } 

 
 
  /** 

 
 
  * 暂停游戏 

 
 
  */ 

 
 
  public void pauseGame() { 

 
 
  isPaused = true; 

 
 
  } 

 
 
  /** 

 
 
  * 释放资源 

 
 
  * 包括图片、声音等资源 

 
 
  */ 

 
 
  public void destroyGame() { 

 
 
  } 

 
 
  /** 

 
 
  * logo界面线程逻辑 

 
 
  */ 

 
 
  private final void doLogo() { 

 
 
  } 

 
 
  /** 

 
 
  * 帮助界面线程逻辑 

 
 
  */ 

 
 
  private final void doHelp() { 

 
 
  } 

 
 
  /** 

 
 
  * 关于界面线程逻辑 

 
 
  */ 

 
 
  private final void doAbout() { 

 
 
  } 

 
 
  /** 

 
 
  * 菜单界面线程逻辑 

 
 
  */ 

 
 
  private final void doMenu() { 

 
 
  } 

 
 
  /** 

 
 
  * 游戏地图1界面线程逻辑 

 
 
  */ 

 
 
  private final void doGame_Map1() { 

 
 
  } 

 
 
  /** 

 
 
  * 游戏武器店1界面线程逻辑 

 
 
  */ 

 
 
  private final void doGame_WeaponShop1() { 

 
 
  } 

 
 
  /** 

 
 
  * 游戏对话1界面线程逻辑 

 
 
  */ 

 
 
  private final void doDialog1() { 

 
 
  } 

 
 
  public void run() { 

 
 
  try { 

 
 
  while (isRunning) { 

 
 
  //精确延时 

 
 
  long start = System.currentTimeMillis(); 

 
 
  //逻辑处理 

 
 
  if (!isPaused) { 

 
 
  switch (status) { 

 
 
  case LOGO_STATUS: 

 
 
  doLogo(); 

 
 
  break; 

 
 
  case MENU_STATUS: 

 
 
  doMenu(); 

 
 
  break; 

 
 
  case HELP_STATUS: 

 
 
  doHelp(); 

 
 
  break; 

 
 
  case ABOUT_STATUS: 

 
 
  doAbout(); 

 
 
  break; 

 
 
  case GAME_MAP1_STATUS: 

 
 
  doGame_Map1(); 

 
 
  break; 

 
 
  case GAME_WEAPONSHOP1_STATUS: 

 
 
  doGame_WeaponShop1(); 

 
 
  break; 

 
 
  case GAME_DIALOG1_STATUS: 

 
 
  doDialog1(); 

 
 
  break; 

 
 
  } 

 
 
  } 

 
 
  //重绘 

 
 
  repaint(); 

 
 
  serviceRepaints(); 

 
 
  long end = System.currentTimeMillis(); 

 
 
  //延时 

 
 
  if ((end - start) < INTERVAL_TIME) { 

 
 
 Thread.sleep(INTERVAL_TIME - (end - start)); 

 
 
  } 

 
 
  } 

 
 
  } catch (Exception e) {} 

 
 
  } 

 

  } 

 
 
  这些只是一个简单的框架,包含了有些开发中的常见功能的实现,但是尚不包含按键处理方面的代码,如果大家有什么建议和意见也可以积极提出。 

 
 
   在实际的游戏中,一般为了按键灵敏,我们一般不会直接在keyPressed或keyReleased方法内部书写逻辑的代码,而只是在这些方法内部记录或清除按键的记录,而把实际的处理放在线程中进行。这个是本机制中采用的方式。 
 
 
 
 
   而且不同手机的按键键值存在不同,为了方便移植,我们把按键转换成自己定义的数值,然后在程序中使用自定义的值进行处理。 
 
 
 
 
   该机制中最核心的变量为; 
 
 
 
 
   private int keyStates; 
 
 
 
 
   用该变量中的一个二进制位来代表一种按键是否按下,如果按下为1,否则为0。每个按键自己进行了定义,定义的代码如下: 
 
 
 
 
   /**向上*/ 
 
 
 
 
   private final int KEY_UP = 1; 
 
 
 
 
   /**向下*/ 
 
 
 
 
   private final int KEY_DOWN = 1 << 1; 
 
 
 
 
   /**向右*/ 
 
 
 
 
   private final int KEY_RIGHT = 1 << 2; 
 
 
 
 
   /**向左*/ 
 
 
 
 
   private final int KEY_LEFT = 1 << 3; 
 
 
 
 
   /**5键*/ 
 
 
 
 
   private final int KEY_FIRE = 1 << 4; 
 
 
 
 
   /**左软键*/ 
 
 
 
 
   private final int KEY_LEFT_SOFT = 1 << 5; 
 
 
 
 
   /**右软键*/ 
 
 
 
 
   private final int UP = 1 << 6; 
 
 
 
 
   /**特殊用途按键,例如0键*/ 
 
 
 
 
   private final int KEY_ZERO = 1 << 7; 
 
 
 
 
   转换按键键值的方法根据手机型号不同,也存在很多的不同,下面是WTK模拟器的实现代码: 
 
 
 
 
   /** 
 
 
 
 
   * 将物理键值转换为自定义键值 
 
 
 
 
   * 说明:该方法和机型相关,下面是WTK的实现 
 
 
 
 
   * @param keyCode 物理键值 
 
 
 
 
   * @return 自定义键值 
 
 
 
 
   */ 
 
 
 
 
   private int convertKey(int keyCode) { 
 
 
 
 
   switch (keyCode) { 
 
 
 
 
   case -6: 
 
 
 
 
   return KEY_LEFT_SOFT; 
 
 
 
 
   case -7: 
 
 
 
 
   return KEY_RIGHT_SOFT; 
 
 
 
 
   case Canvas.KEY_NUM2: 
 
 
 
 
   case -1: 
 
 
 
 
   return KEY_UP; 
 
 
 
 
   case Canvas.KEY_NUM4: 
 
 
 
 
   case -3: 
 
 
 
 
   return KEY_LEFT; 
 
 
 
 
   case Canvas.KEY_NUM6: 
 
 
 
 
   case -4: 
 
 
 
 
   return KEY_RIGHT; 
 
 
 
 
   case Canvas.KEY_NUM8: 
 
 
 
 
   case -2: 
 
 
 
 
   return KEY_DOWN; 
 
 
 
 
   case Canvas.KEY_NUM0: 
 
 
 
 
   return KEY_ZERO; 
 
 
 
 
   } 
 
 
 
 
   return 0; 
 
 
 
 
   } 
 
 
 
 
   按键按下时,首先把物理按键的键值转换为自定义的键值,然后把按键信息保存到按键状态变量keyStates中,保存时采用的是位运算符位或实现的。实现代码如下: 
 
 
 
 
   public void keyPressed(int keyCode) { 
 
 
 
 
   //转换按键 
 
 
 
 
   int key = convertKey(keyCode); 
 
 
 
 
   //保存按键 
 
 
 
 
   keyStates |= key; 
 
 
 
 
   } 
 
 
 
 
   按键释放时,和按键按下类似,首先转换键值,然后清除按键信息。清除时把按键状态取反,然后与keyStates位与即可。实现代码如下: 
 
 
 
 
   public void keyReleased(int keyCode) { 
 
 
 
 
   //转换按键 
 
 
 
 
   int key = convertKey(keyCode); 
 
 
 
 
   //清除按键 
 
 
 
 
   keyStates &= ~key; 
 
 
 
 
   } 
 
 
 
 
   在界面切换时,需要把按键状态清空,这样只需要把keyStates清零即可。实现代码如下: 
 
 
 
 
   /** 
 
 
 
 
   * 清除按键 
 
 
 
 
   */ 
 
 
 
 
   private void clearKey(){ 
 
 
 
 
   keyStates = 0; 
 
 
 
 
   }


实际的按键处理的代码可以在线程中实现。