引言
現在的手機作為一種娛樂性的電子通信設備,早已超出原先的通話、短信息等基本通信功能,越來越多的娛樂、休閑性軟件如手機游戲、電子書、音樂編輯、拍照與圖象處理等也都流行于當今各種品牌的手機。其中,游戲軟件占有相當大的比重。既然我們已經掌握了手機軟件的開發過程,為什么不自己開發一個個性化的手機游戲呢?本文將介紹一個簡單的圖象化手機游戲--"花皮貓大戰流氓兔"的制作過程?;ㄆへ堊匀皇枪P者養的愛貓了,感興趣的讀者也完全可以讓自己喜歡的阿貓阿狗擔當游戲中的主角,充分展示DIY的魅力!
游戲的設計 手機游戲的開發首先需要規劃好整體流程和具體的游戲規則(或游戲劇本),然后才能根據此劇本進行具體的編碼實現。受文章篇幅限制,本游戲劇本設計不能太復雜。首先將游戲定位為人機對弈類游戲,程序運行開始首先顯示本游戲的封面畫面,停留幾秒后自動轉入角色選擇畫面,在玩家選擇某一角色后開始游戲。游戲開始時隨機決定哪一方先行。人機分別在3乘3大小的棋盤網格中交替落子(雙方棋子圖案是有區別的),而且只允許在沒有落過子的空網格中下子。只要有一方所落棋子在橫、豎、斜任何一方向上的總數達到三顆即獲勝。如棋盤被填滿時雙方均未在上述方向達到三顆棋子則該局為平局。無論結果如何,在每局結束后均顯示當局勝負結果與總比分。玩家可以選擇退出或是重新開始新的一局。以上便是本游戲的主體框架和基本游戲規則,隨后進行的編碼工作便以此為依據。
游戲框架的搭建
 圖1 |
首先建立項目并新建一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數組記錄上述幾種獲勝條件,并在每一次行棋完畢后進行比對,以判斷游戲是否有獲勝方產生。限于篇幅,下面主要對計算機行棋思路的人工智能設計進行介紹。
首先明確計算機的對弈目的:獲勝,如果暫時無法獲勝則阻止選手獲勝,如果雙方都暫時無法獲勝則可以下一些"隨手棋"。在計算出合適的下子位置后將其添加到己方的行棋記錄(打譜)以備后用。由此可以寫出如下代耄?BR>
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
凡是有該標志的文章,都是該blog博主Caoer(草兒)原創,凡是索引、收藏
、轉載請注明來處和原文作者。非常感謝。