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;
                  }
       实际的按键处理的代码可以在线程中实现。