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; } 實際的按鍵處理的代碼可以在線程中實現。