引言
現在的手機作為一種娛樂性的電子通訊裝置,早已超出原先的通話、簡訊息等基本通訊功能,越來越多的娛樂、休閑性軟體如手機遊戲、電子書、音樂編輯、拍照與圖象處理等也都流行於當今各種品牌的手機。其中,遊戲軟體佔有相當大的比重。既然我們已經掌握了手機軟體的開發過程,為什麼不自己開發一個個人化的手機遊戲呢。本文將介紹一個簡單的圖象化手機遊戲--"花皮貓大戰流氓兔"的製作過程。花皮貓自然是筆者養的愛貓了,感興趣的讀者也完全可以讓自己喜歡的阿貓阿狗擔當遊戲中的主角,充分展示DIY的魅力。
遊戲的設計
手機遊戲的開發首先需要規劃好整體流程和具體的遊戲規則(或遊戲劇本),然後才能根據此劇本進行具體的編碼實現。受文章篇幅限制,本遊戲劇本設計不能太複雜。首先將遊戲定位為人機對弈類遊戲,程式運行開始首先顯示本遊戲的封面畫面,停留幾秒後自動轉入角色選擇畫面,在玩家選擇某一角色後開始遊戲。遊戲開始時隨機決定哪一方先行。人機分別在3乘3大小的棋盤網格中交替落子(雙方棋子圖案是有區別的),而且只允許在沒有落過子的空網格中下子。只要有一方所落棋子在橫、豎、斜任何一方向上的總數達到三顆即獲勝。如棋盤被填滿時雙方均未在上述方向達到三顆棋子則該局為平局。無論結果如何,在每局結束後均顯示當局勝負結果與總比分。玩家可以選擇退出或是重新開始新的一局。以上便是本遊戲的主體架構和基本遊戲規則,隨後進行的編碼工作便以此為依據。
遊戲架構的搭建
首先建立項目並建立一Midlet TicTacToe加入其中,繼續添加ChoosePieceScreen、GameScreen和Game三個類到項目。作為一款遊戲,如果仍拿文字來作軟體封面顯得也太不專業了。如果要在J2ME程式中使用圖片,必須預先將其轉換為png格式圖片然後在項目上點擊滑鼠右鍵,從建立菜單下選擇檔案功能表項目將彈出如上所示對話方塊。剛開始下半部分是隱藏的,需要通過點擊進階按鈕將其顯示出來。選中連結至檔案系統中的檔案並通過瀏覽對話方塊指定要添加的圖片路徑。最後在檔案名稱一欄輸入圖片的檔案名稱並點擊完成,將圖片添加到項目。下面只須在startApp()中通過如下代碼裝載圖片並通過資訊框將其顯示出來即可:
Image logo = null; try { logo = Image.createImage("/logo.png"); }catch (IOException e) {} Alert splashScreen = new Alert(null, "郎銳2004年作/n著作權(c)/n2004--2005", logo, AlertType.INFO); splashScreen.setTimeout(4000); // 延遲4秒 |
在持續顯示圖2四秒後進入角色選擇介面(圖3):
choosePieceScreen = new ChoosePieceScreen(this); Display.getDisplay(this).setCurrent(splashScreen, choosePieceScreen); |
此任務在ChoosePieceScreen類中實現,主要的功能有對角色表徵圖的裝載顯示、對選定角色的確認等。在其建構函式中首先指定當前介面為列表選擇方式,然後通過append()將裝載的圖象與相應的列表文字建立關聯。最後,為了響應使用者的輸入選擇還必須調用setCommandListener()來檢測按鍵事件的發生,並在commandAction()方法中實現對選定角色的確認:
super("請選擇:", List.IMPLICIT); // 設定列表選擇 this.midlet = midlet; append(CAT_TEXT, loadImage("/cat.png")); // 添加圖象選項到列表 append(RABBIT_TEXT, loadImage("/rabbit.png")); setCommandListener(this); // 偵聽按鍵響應 …… public void commandAction(Command arg0, Displayable arg1) { if (arg0 == List.SELECT_COMMAND){ // 檢測是否為列表按鍵響應 // 檢測使用者選中的選項 boolean isPlayerCat = getString(getSelectedIndex()).equals(CAT_TEXT); midlet.choosePieceScreenDone(isPlayerCat); // 進入遊戲畫面 } } |
這裡是通過檢測使用者選擇的清單項目文字來判斷玩家選擇的是花皮貓還是流氓兔並通過變數isPlayerCat來標識,在choosePieceScreenDone()方法中建立一個GameScreen對象並將其作為當前顯示介面來開始一局新的遊戲。GameScreen類負責遊戲介面的繪製,如對棋盤和雙方棋子的繪製以及對游標移動的處理等工作。
遊戲介面編程
對弈遊戲最主要的介面就是棋盤與棋子的繪製。這首先要根據螢幕大小計算棋盤網格間距和棋子的大小:
screenWidth = getWidth();// 擷取螢幕大小 screenHeight = getHeight(); if (screenWidth > screenHeight) {// 計算網格大小 boardCellSize = (screenHeight - 2) / 3; boardLeft = (screenWidth - (boardCellSize * 3)) / 2; boardTop = 1; }else{ boardCellSize = (screenWidth - 2) / 3; boardLeft = 1; boardTop = (screenHeight - boardCellSize * 3) / 2; } |
繪製棋盤時,首先用背景色清空整個畫布然後再分別按行列繪製出黑色網格即可:
g.setColor(WHITE); g.fillRect(0, 0, screenWidth, screenHeight); g.setColor(BLACK); for (int i = 0; i < 4;i++) { g.fillRect(boardLeft, boardCellSize*i+boardTop,(boardCellSize*3)+2,2); g.fillRect(boardCellSize * i + boardLeft, boardTop, 2, boardCellSize * 3); } |
棋子的繪製可以通過在指定位置顯示裝載的圖象來實現。例如,對於花皮貓棋子的繪製可按如下代碼先裝載預先準備好的圖象(大小須與網格相匹配)然後再調用drawImage方法在指定位置繪製。對於流氓兔棋子的繪製只需更改待裝載的圖象即可:
private void drawCat(Graphics g, int x, int y) { Image image = null; try {// 裝載圖象 image = Image.createImage("/cat.png"); }catch (Exception e) {} g.drawImage(image, x + 1, y + 1, 0); // 在指定位置繪製圖象 } |
至於對移動游標的處理,可以先在將要移動到的網格內側繪製一個新的、四邊與棋盤網格緊密相連的黑色矩形框,然後再在原網格位置用原網格背景進行重繪以擦除上次繪製的游標痕迹。在擦除舊游標痕迹時首先需要判斷該位置是空白還是繪製有棋子圖案,並根據判斷結果繪製白色矩形或是重新裝載當前顯示的棋子圖象。圖4給出了幾個回合後的遊戲截圖,只要遊戲沒有結束,上述繪製模組將會多次反覆調用執行。如果程式的智能控制部分判斷出遊戲已經結束並給出勝負結果,則不再顯示棋盤介面而是通過下面這段代碼以特定的字型在白色畫布上繪製出當前戰績(如圖5所示)。
Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_MEDIUM); // 設定字型 int strHeight = font.getHeight(); int statusMsgWidth = font.stringWidth(statusMsg); int tallyMsgWidth = font.stringWidth(tallyMsg); int strWidth = tallyMsgWidth; if (statusMsgWidth > tallyMsgWidth) strWidth = statusMsgWidth; int x = (screenWidth - strWidth) / 2; // 計算字元繪製位置 x = x < 0 ? 0 : x; int y = (screenHeight - 2 * strHeight) / 2; y = y < 0 ? 0 : y; g.setColor(WHITE); // 白色清空畫布 g.fillRect(0, 0, screenWidth, screenHeight); g.setColor(BLACK); // 黑色顯示資訊 g.drawString(statusMsg, x, y, (Graphics.TOP | Graphics.LEFT)); g.drawString(tallyMsg, x, (y + 1 + strHeight), (Graphics.TOP | Graphics.LEFT)); |
在顯示此介面時,如果使用者按下退出或開始鍵,則在commandAction方法中將通過如下代碼分別執行程式退出處理或是重新開始下一局新的遊戲:
if (arg0 == exitCommand) // 退出 midlet.quit(); else if (arg0 == newGameCommand) // 開始遊戲 initialize(); |
人工智慧的實現
如果說前面介紹的架構是骨骼,介面是皮肉的話,那麼接下來將要介紹的人工智慧部分則可以說是整個程式的靈魂了。它將進行對弈雙方落子的合法性檢測、電腦行棋的智能計算、遊戲結束檢測以及對勝負結果的判定等工作。以上這些都需要有合理的設計才能實現較高的遊戲運行效率。考慮到遊戲規則始終是圍繞雙方棋子的排列形狀來進行的,因此可以把棋盤網格作為主要因素進行設計。按從左至右,從上到下的次序從0開始依次對棋盤的9個網格進行編號,可以得出如下幾組獲勝條件:0,1,2;3,4,5;6,7,8;0,3,6;1,4,7;2,5,8;0,4,8;2,4,6。只要有一方有三顆棋子的位置符合其中任何一組即可認定該方獲勝(讀者可以在紙上驗證一下)。在程式實現過程中以WINS數組記錄上述幾種獲勝條件,並在每一次行棋完畢後進行比對,以判斷遊戲是否有獲勝方產生。限於篇幅,下面主要對電腦行棋思路的人工智慧設計進行介紹。
首先明確電腦的對弈目的:獲勝,如果暫時無法獲勝則阻止選手獲勝,如果雙方都暫時無法獲勝則可以下一些"隨手棋"。在計算出合適的下子位置後將其添加到己方的行棋記錄(打譜)以備後用。由此可以寫出如下代碼:
int move = getWinningComputerMove();//如能立即獲勝則在獲勝位置下子 if (move == -1) { //如選手即將獲勝則在選手將獲勝的位置下子 move = getRequiredBlockingComputerMove(); if (move == -1) // 如雙方均暫時無法獲勝則下隨手棋 move = getRandomComputerMove(); } computerState |= bit(move); // 當前電腦佔用的所有位置 |
其中,getWinningComputerMove方法通過對所有可能下子位置(即尚未落子的網格)的枚舉,智能判斷電腦下一步走到哪裡才能獲勝:
int move = -1; for (int i = 0; i < 9;++i) { if (isFree(i) && isWin(computerState | bit(i))) { move = i; // 找到獲勝位置時中斷 break; } } |
如果迴圈完畢仍沒有找到獲勝位置則表示目前己方暫無法獲勝,需要進一步調用getRequiredBlockingComputerMove方法來計算下一回合對方有無獲勝的可能,其實現代碼與getWinningComputerMove完全類似,只是以選手的行棋記錄playerState替代電腦的行棋記錄computerState而已。以上寥寥數行代碼即構成了電腦對弈演算法的人工智慧核心部分,顯然人工智慧在實現上並沒有想象的那麼複雜與困難。
小結
通過本系列文章的介紹,陸續將J2ME手機應用程式的一般開發過程向讀者作了一個較為系統和全面的介紹。尤其是本篇對圖形化手機遊戲的介紹相信一定對讀者有不同程度的啟發作用,而且本文所述程式架構完全是通用的,讀者只需在此基礎之上重新設計遊戲劇本即可實作類別似的手機遊戲如"華容道"、"俄羅斯方塊"等。本系列文章開發環境為:
Windows 2000 Professional + SP4; Java2SDK 1.5.0; J2ME Wireless ToolKits 2.1; SonyErisson J2ME SDK(WTK 1.0.4); SonyErisson T628; Eclipse 3.0.1-win32; EclipseMe 0.5.5; NLpack-eclipse-SDK-3.0.x-win32
|
From: http://www.yesky.com/SoftChannel/72342371878043648/20050409/1933394_1.shtml