j2me遊戲開發例項講解 (轉)
j2me遊戲開發例項講解 (轉)[@more@]再現華容道
一.序言
昨天在網上閒逛,發現一篇講解用實現華容道遊戲的文章,頗受啟發.於是,產生了將華容道遊戲移植到中去的衝動.現在手機遊戲琳琅滿目,不一而足,華容道的實現版本也很多.正巧不久前筆者對J2ME下了一番功夫,正想借這個機會小試牛刀.選用J2ME作為開發語言還有一個原因就是目前開發大行其到,無限增殖業務迅猛發展,J2ME的應用日漸活躍起來,也希望我的這篇文章能夠為J2ME知識的普及和開發團隊的壯大推波助瀾.由於長期受ISO規範的影響,這次小試牛刀我也打算遵照工程的要求,並採取瀑布式的開發來規劃專案,也希望藉此機會向各位沒有機會參與正式專案開發的讀者介紹一下的流程.
這裡我們先定義專案組的人員體制(其實只有我一個人):技術調研、需求分析、概要設計、詳細設計、編碼、測試均有筆者一人擔任;美工這裡我找了個捷徑,網上現成的圖片,然後用ACDSee把它由BMP轉換成PNG格式(我出於講座的目的,未做商業應用,應該不算侵權吧);至於釋出工作,由於缺少OTA,此項工作不做(但是我會介紹這步如何做)。
接下來,我們規劃一下專案實現的時間表,以我個人,設想如下:技術調研用2天(這部分解決專案的可行性和重大技術問題,時間會長一些),需求分析用半天(畢竟有現成的東東可以參照,只要理清思路就行了,況且還有很多以前用過的設計模式和寫好的程式碼),概要設計再用半天(有了需求,概要只不夠是照方抓藥),詳細設計要用2天(這一步要把所有的問題想清楚,還要儘可能的準確描述出來),編碼用2天(其實1天就夠了,技術已經不是問題,多計劃出一天來應付突發事件),測試用2天(測試應該至少佔全部專案的四分之一,不過這個專案只是一個Demo,也太簡單了),釋出也要用上半天(儘管我們不去實際釋出它,但是還要花點時間搞清楚應該如何做),最後就是專案總結和開慶功會(時間待定)。
二.利其器
"公欲善其事,必先利其器",做專案之前第一步是前期調研.我們要做的華容道這個東東隨處可見,我們要調研的是兩個方面:
1.遊戲的內容:遊戲本身很簡單,就是有幾個格子,曹操佔據其中一個較大的格子,然後被幾個格子包圍,這些格子形狀不一定相同,但是擋住了曹操移動的方向.遊戲者需要挪動這些格子最終把曹操移動到一個指定的位置才算是過關.更具體的分析我們放在後面需求分析和概要設計中討論.
2.技術儲備:談到技術,這裡簡單介紹一下J2ME.Java有三個版本,分別是J2ME(微型版).(標準版).(企業版).J2ME是一個標準,採用3層結構設計.最低層是層(Configuration)也就是裝置層,其上是簡表層(Profile),再上是應用層(Application).MIDP就是移動資訊裝置簡表,目前主流手機支援MIDP1.0,最新的是MIDP2.0,它比前一個版本增加了對遊戲的支援,在javax.microedition.lcdui.game包中提供了一些類來處理遊戲中的技術,比如我們後面會用到的Sprite類,它是用來翻轉圖片的.權衡再三,筆者決定使用MIDP2.0來做開發.首先需要一個J2ME的模擬器,我們就用Sun公司的2.0,我覺得Sun的東西最權威.當然你也可以使用Nokia.Siemens或是Motolora等其他模擬器,但是他們的不盡相同,寫出來的移植是比較麻煩的.Sun公司的WTK2.0可以到載,當然要想成功的前提是你要先註冊成為Sun的會員(其實這樣對你是有好處的).當下來之後就是按照提示一步一步的安裝.安裝好了之後,我們用一個"Hello World"程式開始你的J2ME之旅.我們啟動WTK2.0工具集中的KToolBar,然後點選New Project按鈕,在彈出的輸入框中輸入Project Name為HelloWorld,MIDlet Class Name為Hello,然後點選Create Project,開始生成專案,工具會彈出MIDP配置簡表,這裡接受生成的預設值(以後還可以修改)點選OK,工具提示我們把寫好的Java源程式放到[WTK_HOME]appsHelloWorldsrc目錄之下.我們編輯如下程式碼,並儲存在上述目錄之下,名為Hello.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class Hello extends MIDlet
{
private Display display;
public Hello(){
display =Display.getDisplay(this);
}
public void startApp(){
TextBox t = new TextBox("Hello","Hello",256,0);
display.setCurrent(t);
}
public void pauseApp(){
}
public void destroyApp(boolean unconditional){
}
}
儲存好了之後,點選Build按鈕,工具會為你編譯程式,如無意外再點選Run按鈕,會彈出一個手機介面,剩下的就不用我教了吧(用滑鼠對手機按鍵一頓狂點).呵呵,你的第一個J2ME程式已經OK了.什麼?你還一點都沒懂呢(真是厲害,不懂都能寫出J2ME程式來,果然是高手).我這裡主要是介紹WTK2.0工具的使用,程式並不是目的,不懂的話後面還會有詳細的解說,這裡只是帶你上路.什麼?你不懂Java!那也沒有關係,後面我再講得細一點.
跳過J2ME,我們先來講點遊戲的理論.具體到華容道這個遊戲,主要有三個方面,貼圖.遊戲操作.邏輯判斷.這裡講講貼圖,其他兩方面放在概要設計和詳細設計裡講.所謂的貼圖,其實就是畫圖,就是在要顯示圖形的位置上輸出一副圖片,(要是牽扯到動畫就要麻煩一些,可以使用TimerTask.Thread或Rannable之類的技術),這副圖片可以是事先準備好的也可以是臨時處理的.在J2ME中有一個Image類,專門用於管理圖片,它有createImage()方法,可以直接讀取圖片檔案(J2ME只支援PNG格式的圖片),也可以擷取已有的圖片的一部分(這樣我們可以把很多圖片放在一起,然後一張一張的截下來,好處是節省空間和檔案讀取時間,對於手機這兩者都是的瓶頸).J2ME還有一個Graphics類,專門用於繪圖,它有drawImage()方法,可以把一副圖片在指定的位置上顯示出來,它還有drawRect()方法和setColor()方法,這兩個方法在後面我們進行遊戲操作時就會用到,這裡先交代一下.有了圖片和繪圖的方法,還需要知道把圖畫到誰身上,J2ME提供了一個Canvas類,字面意思就是畫布,它有一個paint()方法用於重新整理頁面,還有一個repaint()方法用於paint()方法.聽著有些糊塗是吧,不要緊,我來結合具體程式講解一下.為了今後的方便,我們建立兩個類Images和Draw,Images用於儲存一些常量值和圖片,Draw主要是用於畫圖,這兩個類的如下。
Images類的原始碼如下:
package huarongroad;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
public class Images {//儲存常量
//繪圖位置常量
public static final int UNIT = 32;//方塊的單位長度
public static final int LEFT = 10;//畫圖的左邊界頂點
public static final int TOP = 9;//畫圖的上邊界頂點
//地圖位置常量
public static final int WIDTH = 4;//地圖的寬度
public static final int HEIGHT = 5;//地圖的高度
//地圖示記常量
public static final byte CAOCAO = (byte) 'a'; 操的地圖示記
public static final byte MACHAO = (byte) 'b';//馬超的地圖示記
public static final byte HUANGZHONG = (byte) 'c';//黃忠的地圖示記
public static final byte GUANYU = (byte) 'd';//關羽的地圖示記
public static final byte ZHANGFEI = (byte) 'e';//張飛的地圖示記
public static final byte ZHAOYUN = (byte) 'f';//趙雲的地圖示記
public static final byte ZU = (byte) 'g';//卒的地圖示記
public static final byte BLANK = (byte) 'h';//空白的地圖示記
public static final byte CURSOR = (byte) 'i';//游標的地圖示記
//地圖組合標記常量
public static final byte DLEFT = (byte) '1'; 合圖形左邊標記
public static final byte DUP = (byte) '2'; 合圖形上邊標記
public static final byte DLEFTUP = (byte) '3'; 合圖形左上標記
//圖片常量
public static Image image_base;//基本圖片
public static Image image_Zhaoyun;//趙雲的圖片
public static Image image_Caocao;//曹操的圖片
public static Image image_Huangzhong;//黃忠的圖片
public static Image image_Machao;//馬超的圖片
public static Image image_Guanyu;//關羽的圖片
public static Image image_Zhangfei;//張飛的圖片
public static Image image_Zu;//卒的圖片
public static Image image_Blank;//空白的圖片
public static Image image_Frame;//遊戲的圖片
public Images() {//構造
}
public static boolean init() {//初始化遊戲中用到的圖片
try {
image_base = Image.createImage("/huarongroad/BITBACK.png");
image_Frame = Image.createImage(image_base, 126, 0, 145, 177,
Sprite.TRANS_NONE);
//Sprite類是用來翻轉圖片的,是MIDP2.0新新增加的支援遊戲的特性
image_Zhaoyun = Image.createImage(image_base, 0, 0, UNIT, 2 * UNIT,
Sprite.TRANS_NONE);
image_Caocao = Image.createImage(image_base, UNIT, 0, 2 * UNIT,
2 * UNIT, Sprite.TRANS_NONE);
image_Huangzhong = Image.createImage(image_base, 3 * UNIT, 0, UNIT,
2 * UNIT,
Sprite.TRANS_NONE);
image_Machao = Image.createImage(image_base, 0, 2 * UNIT, UNIT,
2 * UNIT,
Sprite.TRANS_NONE);
image_Guanyu = Image.createImage(image_base, UNIT, 2 * UNIT,
2 * UNIT, UNIT,
Sprite.TRANS_NONE);
image_Zhangfei = Image.createImage(image_base, 3 * UNIT, 2 * UNIT,
UNIT, 2 * UNIT,
Sprite.TRANS_NONE);
image_Zu = Image.createImage(image_base, 0, 4 * UNIT, UNIT, UNIT,
Sprite.TRANS_NONE);
image_Blank = Image.createImage(image_base, 1 * UNIT, 4 * UNIT,UNIT,
UNIT,
Sprite.TRANS_NONE);
return true;
}catch (Exception ex) {
return false;
}
}
}
Draw類的原始碼如下:
package huarongroad;
import javax.microedition.lcdui.*;
public class Draw {
//繪製遊戲中的圖片
public Draw(Canvas canvas) {//建構函式
}
public static boolean paint(Graphics g, byte img, int x, int y) {
//在地圖的x,y點繪製img指定的圖片
try {
paint(g, img, x, y, Images.UNIT);//把地圖x,y點轉化成畫布的絕對座標,繪圖
return true;
}
catch (Exception ex) {
return false;
}
}
public static boolean paint(Graphics g, byte img, int x, int y, int unit) {
try {
switch (img) {
case Images.CAOCAO://畫曹操
//變成絕對座標,並做調整
g.drawImage(Images.image_Caocao, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.GUANYU://畫關羽
g.drawImage(Images.image_Guanyu, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.HUANGZHONG://畫黃忠
g.drawImage(Images.image_Huangzhong, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.MACHAO://畫馬超
g.drawImage(Images.image_Machao, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.ZHANGFEI://畫張飛
g.drawImage(Images.image_Zhangfei, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.ZHAOYUN://畫趙雲
g.drawImage(Images.image_Zhaoyun, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.ZU://畫卒
g.drawImage(Images.image_Zu, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.BLANK://畫空白
g.drawImage(Images.image_Blank, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP | Graphics.LEFT);
break;
case Images.CURSOR://畫游標
g.drawRect(Images.LEFT + x * unit,
Images.TOP + y * unit,Images.UNIT,Images.UNIT);
break;
}
return true;
}catch (Exception ex) {
return false;
}
}
}
其中Images類存的是繪圖位置常量(也就是在畫圖時每個格子的長度和相對座標原點位置要進行的調整)、地圖位置常量(地圖的長、寬),地圖示記常量(人物對應的記號),地圖組合標記常量(後面會細說),圖片常量(存放人物的圖片);Draw類主要負責在制定的位置畫出人物圖片。下面我來說說Images類中的地圖示記常量和地圖組合標記常量。為了能夠靈活的安排各個關面的佈局,我們決定把遊戲佈局的資訊儲存在外部檔案中,然後程式啟動後把它讀進來。這樣我們制定了一套儲存圖片的程式碼,這就是地圖示記常量,如上面Images類中定義的Caocao(曹操)用a字元來表示,當程式讀到a字元時就能將它轉化成曹操對應的圖片,並在讀到a字元的位置上進行顯示。但是從實際觀察中我們發現所有的圖片並不是統一大小的,有的佔4個格子,有的佔2個格子,還有的佔1個格子,而且即便同是佔兩個格子的圖片還有橫、豎之分。有鑑於此,我們引入了地圖組合標記常量,就是說在遇到佔有多個格子的時候,值1(也就是Images.LEFT)表示它的左邊是一個真正的地圖示記,值2(也就是Images.UP)表示它的上邊是一個真正的地圖示記,值1(也就是Images.LEFTUP)表示它的左上邊是一個真正的地圖示記。地圖組合標記常量其實就是用來佔位置的,與實際顯示無關,當後面我們將到移動時還會再來分析組合標記的使用。
Draw類主要是用來在畫布上畫出圖形,它有兩個paint方法,這是很常見的函式過載。但是程式中實際上只用到了4個引數的paint方法,它直接獲得要畫圖片的相對座標位置資訊,然後呼叫5個引數的paint方法。5個引數的paint方法將相對座標位置資訊轉換成絕對位置,並實際呼叫Graphics.drawImage()方法,將Images中的圖片畫了出來。這種實現方法的好處是靈活和便於擴充套件,但你需要畫圖的位置並不能夠對應到格子中的相對座標位置時,你就可以直接呼叫5個引數的paint方法,而不必再去修改這各類;但你新增新的圖片時,只要在Images中增加對應的常量,然後向Draw中5個引數的paint方法新增一條處理就可以了。
寫到這裡,兩天的時間剛好用完。
三、需求分析
這部分叫做需求分析,聽起來挺嚇人的,其實就是搞清楚我們要做什麼,做成什麼樣,那些不做。下面我引領著大家共同來完成這一步驟。首先,我們要做一個華容道的遊戲,華容道的故事這裡不再贅述了,但其中的人物在這裡限定一下,如上面Images類裡的定義,我們這個版本只提供曹操(Caocao)、關羽(Guanyu)、張飛(Zhangfei)、趙雲(Zhaoyun)、黃忠(Huangzhong)、馬超(Machao)和卒(Zu)。我們這裡也限定一下游戲的操作方法:首先要透過方向鍵選擇一個要移動的區域(就是一張圖片),被選擇的區域用黑色方框框住;選好後按Fire鍵(就是確定鍵)將這塊區域選中,被選中的區域用綠色方框框住;然後選擇要移動到的區域,此時用紅色方框框住被選擇的區域;選好要移動到的區域之後按Fire鍵將要移動的區域(圖片)移到要移動到的區域,並去掉綠色和紅色的方框。這裡需要強調的概念有選擇的區域、選中的區域、要移動的區域和要移動到的區域,這四個概念請讀者注意區分,當然也應當把這一部分記入資料字典之中。為了使文章的重點突出(介紹如何製作一個J2ME的收集遊戲),我們這裡限定一些與本主題無關的內容暫不去實現:過關之後的動畫(實現時要用到TimerTask或Thread類,後續的系列文章中我會詳細介紹動畫方面的知識)、關面之間的切換(其實很簡單,當完成任務之後重新再做一邊)、暫停和儲存等操作(這部分的內容介紹的資料很多,我也寫不出什麼新的東東來,難免抄襲,故此免掉)。
需求分析基本完成,離下午還有一段時間,馬上動手用ACDSee把從網上找來的BMP檔案,調整其大小為271*177(我的這個圖片是兩個部分合在一起,所以比手機實際螢幕大了),另存為PNG格式。半天時間剛剛好,不但搞清楚了要做的東東,還把要用的圖片準備好了。
四、概要設計
概要設計是從需求分析過渡到詳細設計的橋樑和紐帶,這一部分中我們確定專案的實現方法和模組的劃分。我們決定將整個專案分成五個部分,分別是前面介紹的Images、Draw,還有Map和Displayable1和MIDlet1。Images和Draw類功能簡單、結構固定,因此很多專案我們都使用這兩各類,這裡直接拿來改改就能用了,前面已經介紹過這裡不再贅述。Map類是用來從外部檔案讀入地圖,然後儲存在一個陣列之中,這部分的內容是我們在本階段討論的重點。Displayable1是一個繼承了Canvas類的畫布,它用來處理程式的主要控制邏輯和一部分控制邏輯所需的輔助函式,主要函式應該包括用來繪圖的paint()函式、用來控制操作的keyPressed()函式、用來控制選擇區域的setRange()函式、用來控制選擇要移動到區域的setMoveRange()函式、用來移動選中區域的Move()函式和判斷是否完成任務的win()函式,更具體的分析,我們放到詳細設計中去細化。MIDlet1實際上就是一個控制整個J2ME應用的控制程式,其實也沒有什麼可特別的,它和我們前面介紹的"Hello World"程式大同小異,這裡就不展開來說了,後面會貼出它的全部程式碼。
Map類主要應該有一個Grid[][]的二維陣列,用來存放華容道的地圖,還應該有一個read_map()函式用來從外部檔案讀取地圖內容填充Grid資料結構,再就是要有一個draw_map()函式用來把Grid資料結構中的地圖內容轉換成圖片顯示出來(當然要呼叫Draw類的paint方法)。說到讀取外部檔案,筆者知道有兩種方法:一種是傳統的定義一個InputStream,然後用getClass().getReAsStream()方法取得輸入流,然後再從輸入流中取得外部檔案的內容,例如
InputStream is = getClass().getResourceAsStream("/filename");
if (is != null) {
byte a = (byte) is.read();
}
這裡請注意檔名中的根路徑是相對於便以後的class檔案放置的位置,而不是原始檔(java)。第二種方法是使用onnector.openInputStream方法,然後開啟的是Resource,但是這種方法筆者反覆嘗試都沒能調通,報告的錯誤是缺少Resource協議,估計第二種方法用到J2ME的某些擴充套件類包,此處不再深究。由於以前已經做過一些類似華容道這樣的地圖,這裡直接給出Map類的程式碼,後面就不再詳細解釋Map類了,以便於我們可以集中精力處理Displayable1中的邏輯。Map類的程式碼如下:
package huarongroad;
import java.io.InputStream;
import javax.microedition.lcdui.*;
public class Map {
//處理遊戲的地圖,負責從外部檔案載入地圖資料,存放地圖資料,並按照地圖資料繪製地圖
public byte Grid[][];//存放地圖資料
public Map() {//建構函式,負責初始化地圖資料的儲存結構
this.Grid = new byte[Images.HEIGHT][Images.WIDTH];
//用二維陣列存放地圖資料,注意第一維是豎直座標,第二維是水平座標
}
public int[] read_map(int i) {
外部檔案載入地圖資料,並存放在儲存結構中,返回值是游標點的位置
//引數是載入地件的等級
int[] a = new int[2];//游標點的位置,0是水平位置,1是豎直位置
try {
InputStream is = getClass().getResourceAsStream(
"/huarongroad/level".concat(String.valueOf(i)));
if (is != null) {
for (int k = 0; k selectArea、表示要移動到的區域的變數MoveArea、表示是否已有區域被選中而準備移動的變數Selected和Map類的例項MyMap。然後,我們根據按不同的鍵來處理不同的訊息,我們要實現keyPressed()函式,在函式中我們處理按鍵的上下左右和選中(Fire),這裡的處理需要我展開來講一講,後面我很快會把這一部分詳細展開。接下來,是實現paint()函式,我們打算在這一部分中反覆的重畫背景、地圖和選擇區域,這個函式必須處理好區域被選中之後的畫筆顏色的切換,具體講就是在沒有選中任何區域時要用黑色畫筆,當選重要移動的區域時使用綠色畫筆,當選擇要移動到的區域時改用紅色畫筆(當然附加一張流程圖是必不可少的)。再下面要實現的setRange()函式和setMoveRange()函式,這兩個函式用來設定要移動的區域和要移動到的區域,我的思路就是利用前面在Images類中介紹過的地圖組合標記常量,當移動到地圖組合標記常量時,根據該點地圖中的值做逆向變換找到相應的地圖示記常量,然後設定相應的loc、SelectArea和MoveArea,其中setMoveRange()函式還用到了一個輔助函式isInRange(),isInRange()函式是用來判斷給定的點是否在已選中的要移動的區域之內,如果isInRange()的返回值是假並且該點處的值不是空白就表明要移動到的區域侵犯了其他以被佔用的區域。有了setRange()和setMoveRange()函式,Move()函式就水到渠成了,Move()函式將要移動的區域移動到要移動到的區域,在移動過程中分為三步進行:第一.複製要移動的區域,第二.將複製出的要移動區域複製到要移動到的區域(這兩步分開進行的目的是防止在複製過程中覆蓋掉要移動的區域),第三.用isInRange2()判斷給定的點是否在要移動到的區域內,將不在要移動到的區域內的點設定成空白.
下面我們詳細的分析一下keyPressed()函式的實現方法:首先,keyPressed()函式要處理按鍵的上下左右和選中(Fire),在處理時需要用Canvas類的getGameAction函式來將按鍵的鍵值轉換成遊戲的方向,這樣可以提高遊戲的相容性(因為不同的J2ME實現,其方向鍵的鍵值不一定是相同的).接下來,分別處理四個方向和選中.當按下向上時,先判斷是否已經選定了要移動的區域(即this.selected是否為真),如果沒有選中要移動區域則讓游標向上移動一格,然後呼叫setRange()函式設定選擇要移動的區域,再呼叫repaint()函式重新整理螢幕,否則如果已經選中了要移動的區域,就讓游標向上移動一格,然後呼叫setMoveRange()函式判斷是否能夠向上移動已選中的區域,如果能移動就呼叫repaint()函式重新整理螢幕,如果不能移動就讓游標向下退回到原來的位置.當按下向下時,先判斷是否已經選定了要移動的區域,如果沒有選中要移動的區域則判斷當前所處的區域是否為兩個格高,如果是兩個格高則向下移動兩格,如果是一個格高則向下移動一格,接著再呼叫setRange()函式設定選擇要移動的區域,而後呼叫repaint()函式重新整理螢幕,否則如果已經選中了要移動的區域,就讓游標向下移動一格,然後呼叫setMoveRange()函式判斷是否能夠向下移動已選中的區域,如果能移動就呼叫repaint()函式重新整理螢幕,如果不能移動就讓游標向上退回到原來的位置.按下向左時情況完全類似向上的情況,按下向右時情況完全類似向下的情況,因此這裡不再贅述,詳細情況請參見程式的原始碼.當按下選中鍵時,先判斷是否已經選中了要移動的區域,如果已經選中了要移動的區域就呼叫Move()函式完成由要移動的區域到要移動到的區域的移動過程,接著呼叫repaint()函式重新整理螢幕,然後將已選擇標記置成false,繼續呼叫win()函式判斷是否完成了任務,否則如果還沒有選定要移動的區域則再判斷當前選中區域是否為空白,如果不是空白就將選中標記置成true,然後重新整理螢幕.這裡介紹一個技巧,在開發程式遇到複雜的邏輯的時候,可以構造一格列印函式來將所關心的資料結構列印出來以利,這裡我們就構造一個PrintGrid()函式,這個函式純粹是為了除錯之用,效果這得不錯.至此我們完成了編碼前的全部工作.
看看時間還早,我在這裡簡單介紹一下MIDlet1類,這個類和前面介紹的"Hello World"中的內容大同小異,先看一下程式程式碼:
其中startApp().pauseApp()和destroyApp()三個函式時MIDlet類的三個抽象方法必須要被過載一邊,他們控制著一個J2ME程式的開始.暫停和結束.而其中的Displayable1 就是我們前面提到的Displayable1的一個例項,用來負責顯示程式的使用者介面和控制程式邏輯.這個類一般都是寫成這樣.
兩天的時間稍稍還有些富裕,今天可以早放工.
六.編碼
整個專案共有五個類,有四個類的程式碼前面已經介紹過了,而且是在其他專案中使用過的相對成熟的程式碼.現在只需全力去實現Displayable1類.Displayable1類的程式碼如下:
package huarongroad;
import javax.microedition.lcdui.*;
public class Displayable1 extends Canvas implements CommandListener {
private int[] loc = new int[2]; 標的當前位置,0是水平位置,1是豎直位置
private int[] SelectArea = new int[4];//被選定的區域,即要移動的區域
private int[] MoveArea = new int[4];//要移動到的區域
private Map MyMap = new Map();//地圖類
private boolean selected;//是否已經選中要移動區域的標誌
private int level;//但前的關面
public Displayable1() {//建構函式
try {
jbInit();//JBuilder定義的初始化函式
}catch (Exception e) {
e.printStackTrace();
}
}
private void Init_game(){
//初始化遊戲,讀取地圖,設定選擇區域,清空要移動到的區域
this.loc = MyMap.read_map(this.level);//讀取地圖檔案,並返回游標的初始位置
//0為水平位置,1為豎直位置
this.SelectArea[0] = this.loc[0];//初始化選中的區域
this.SelectArea[1] = this.loc[1];
this.SelectArea[2] = 1;
this.SelectArea[3] = 1;
this.MoveArea[0] = -1;//初始化要移動到的區域
this.MoveArea[1] = -1;
this.MoveArea[2] = 0;
this.MoveArea[3] = 0;
}
private void jbInit() throws Exception {//JBuilder定義的初始化函式
始化例項變數
this.selected = false;//設定沒有被選中的要移動區域
this.level = 1;
Images.init();//初始化圖片常量
Init_game();//初始化遊戲,讀取地圖,設定選擇區域,清空要移動到的區域
setCommandListener(this);//新增命令,這是Displayable的例項方法
addCommand(new Command("Exit", Command.EXIT, 1));//新增“退出”按鈕
}
public void commandAction(Command command, Displayable displayable) {
//命令處理函式
if (command.getCommandType() == Command.EXIT) {//處理“退出”
MIDlet1.quitApp();
}
}
protected void paint(Graphics g) {
//畫圖函式,用於繪製使用者畫面,即顯示圖片,勾畫選中區域和要移動到的區域
try {
g.drawImage(Images.image_Frame, 0, 0,
Graphics.TOP | Graphics.LEFT);//畫背景
MyMap.draw_map(g);//按照地圖內容畫圖
if ( this.selected )
g.setColor(0,255,0);//如果被選中,改用綠色畫出被選中的區域
g.drawRect(this.SelectArea[0] * Images.UNIT + Images.LEFT,
this.SelectArea[1] * Images.UNIT + Images.TOP,
this.SelectArea[2] * Images.UNIT,
this.SelectArea[3] * Images.UNIT);//畫出選擇區域,
果被選中,就用綠色
則,使用黑色
g.setColor(255,255,255);//恢復畫筆顏色
if (this.selected) {//已經選中了要移動的區域
g.setColor(255, 0, 255);//改用紅色
g.drawRect(this.MoveArea[0] * Images.UNIT + Images.LEFT,
this.MoveArea[1] * Images.UNIT + Images.TOP,
this.MoveArea[2] * Images.UNIT,
this.MoveArea[3] * Images.UNIT);//畫出要移動到的區域
g.setColor(255, 255, 255);//恢復畫筆顏色
}
}catch (Exception ex) {
}
System.out.println(Runtime.getRuntime().freeMemory());
System.out.println(Runtime.getRuntime().totalMemory());
}
private void setRange() {
//設定移動後能夠選中的區域
//調整當前游標位置到地圖的主位置,即記錄人物資訊的位置
if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFT) {
this.loc[0] -= 1;//向左調
}else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DUP) {
this.loc[1] -= 1;//向上調
}else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFTUP) {
this.loc[0] -= 1;//向左調
this.loc[1] -= 1;//向上調
}
this.SelectArea[0] = this.loc[0];//設定游標的水平位置
this.SelectArea[1] = this.loc[1];//設定游標的豎直位置
//設定游標的寬度
if (this.loc[0] + 1 = Images.HEIGHT ||
this.loc[0] + i >= Images.WIDTH ||
(!isInRange(this.loc[0] + i, this.loc[1] + j) &&
this.MyMap.Grid[this.loc[1] + j][this.loc[0] + i] !=
Images.BLANK)) {
return false;
}
}
}
this.MoveArea[0] = this.loc[0];
this.MoveArea[1] = this.loc[1];
this.MoveArea[2] = this.SelectArea[2];
this.MoveArea[3] = this.SelectArea[3];
return true;
}
private boolean isInRange(int x, int y) {
//判斷給定的(x,y)點是否在選定區域之內,x是水平座標,y是豎直座標
if (x >= this.SelectArea[0] &&
x = this.SelectArea[1] &&
y = this.MoveArea[0] &&
x = this.MoveArea[1] &&
y = 0) {//向上還有移動空間
this.loc[1]--;//向上移動一下
setRange();//設定游標移動的區域,該函式能將游標移動到地圖主位置
repaint();//重新繪圖
}
}else {//已經選定了要移動的區域
if (this.loc[1] - 1 >= 0) {//向上還有移動空間
this.loc[1]--;//向上移動一下
if (setMoveRange()) {//能夠移動,該函式能夠設定要移動到的區域
repaint();//重新繪圖
}else {//不能移動
this.loc[1]++;//退回來
}
}
}
break;
case Canvas.DOWN://向下
if (!this.selected) {//還沒有選定要移動的區域
if (this.loc[1] + 1 file://移動空間
this.loc[1]++;//向下移動一下
setRange();//設定游標移動的區域,
函式能將游標移動到地圖主位置
repaint();//重新繪圖
}else {//向下沒有移動空間
this.loc[1]--;//退回來
}
}else {//該圖片只有一個格高
this.loc[1]++;//向下移動一下
setRange();//設定游標移動的區域,
函式能將游標移動到地圖主位置
repaint();//重新繪圖
}
}else {
}
}else {//已經選定了要移動的區域
if (this.loc[1] + 1 = 0) {//向左還有移動空間
this.loc[0]--;//向左移動一下
setRange();//設定游標移動的區域,該函式能將游標移動到地圖主位置
repaint();//重新繪圖
}
}else {//已經選定了要移動的區域
if (this.loc[0] - 1 >= 0) {//向左還有移動空間
this.loc[0]--;//向左移動一下
if (setMoveRange()) {//能夠移動,該函式能夠設定要移動到的區域
repaint();//重新繪圖
}else {//不能移動
this.loc[0]++;//退回來
}
}
}
break;
case Canvas.RIGHT://向右
if (!this.selected) {//還沒有選定要移動的區域
if (this.loc[0] + 1 file://移動空間
this.loc[0]++;//向右移動一下
setRange();//設定游標移動的區域,
函式能將游標移動到地圖主位置
repaint();//重新繪圖
}else {//向右沒有移動空間
this.loc[0]--;//退回來
}
}else {//該圖片只有一個格寬
this.loc[0]++;//向右移動一下
setRange();//設定游標移動的區域,
函式能將游標移動到地圖主位置
repaint();//重新繪圖
}
}else {
}
}else {//已經選定了要移動的區域
if (this.loc[0] + 1 file://判斷是否已經救出了曹操
if ( this.MyMap.Grid[Images.HEIGHT - 2 ][Images.WIDTH - 3 ] == Images.CAOCAO )
return true;
else
return false;
}
private void PrintGrid(String a) {
印當前地圖的內容,用於除錯
System.out.println(a);
for (int i = 0; i file://將要移動的區域移動到剛選中的區域
if (this.MoveArea[0] == -1 || this.MoveArea[1] == -1 ||
this.SelectArea[0] == -1 || this.SelectArea[1] == -1) {//沒有選中區域
}else {//已經選中了要移動的區域和要移動到的區域
byte[][] temp = new byte[this.SelectArea[3]][this.SelectArea[2]];
制要移動的區域,因為這塊區域可能會被覆蓋掉
for (int i = 0; i file://PrintGrid("1"); // 除錯資訊
要移動的區域移動到剛選中的區域(即要移動到的區域)
for (int i = 0; i file://PrintGrid("2");// 除錯資訊
要移動的區域中無用內容置成空白
for (int i = 0; i file://的區域之內,需置空
this.MyMap.Grid[this.SelectArea[1] + i]
[this.SelectArea[0] + j] = Images.BLANK;
}else {
}
}
}
("3");// 除錯資訊
this.SelectArea[0] = this.MoveArea[0];//重置選中位置的水平座標
this.SelectArea[1] = this.MoveArea[1];//重置選中位置的豎直座標
this.MoveArea[0] = -1;//清空要移動到的位置
this.MoveArea[1] = -1;//清空要移動到的位置
this.MoveArea[2] = 0;//清空要移動到的位置
this.MoveArea[3] = 0;//清空要移動到的位置
}
}
}
程式碼的相關分析,在詳細設計階段已經講過,程式碼中有比較相近的註釋,請讀者自行研讀分析.將全部的程式碼寫好,用wtk2.0自帶的Ktoolbar工具建立一個工程,接下來把去不原始檔放到正確位置下,然後點選build,再點run,就完成了程式的編寫.當然如果有錯誤還要修改和除錯.
經過兩天的痛苦煎熬,程式終於有了眉目,編碼階段初戰告捷.
七、測試
作為一個真正的產品要經過單體測試、結合測試和測試。由於專案本身簡單,而且大部分程式碼已經是相對成熟的,我們跳過單體測試;又由於筆者的實際環境所限,無法搞到Java手機,無法架設OTA伺服器,因此我們也只能放棄系統測試。那麼就讓我們開始結合測試吧。測試之前要先出一個測試式樣書,也就是測試的計劃。我們將它簡化一下,只測試如下幾種情況:第一、對各種形狀的區域的選擇和移動;第二、臨近邊界區域的選擇和移動;第三、同一區域的反覆選擇和反覆移動;第四、選擇和非法移動。有了測試的目標,接下來的工作就是用wtk2.0自帶的Run MIDP Application工具進行測試。開啟這個工具,載入huarongRoad的jad檔案,程式就會自動執行,選擇launch上MIDlet1這個程式,華容道遊戲就會躍然螢幕之上,接下來的工作就是左三點.右三點,拇指扭扭,來做測試。測試過程中發現任何的問題,立刻發一個票給自己,然後就又是痛苦的除錯和修正bug,如此如此。
兩日過後,程式日漸成熟起來,筆者也日漸消瘦下去,還好熬了過來。
八.釋出
談到釋出,其實是個關鍵,再好的產品不能很好的釋出出去也只是個產品而已,變不成商品也就得不到回報.由於筆者的條件所限,這裡只能是紙上談兵,不過還是希望能夠使讀者對這一過程有所瞭解(網上的資料也很多)。
J2ME的程式釋出一般都是透過OTA(Over The Air),你只需要一臺有公網IP的主機和一個普通的 Server就可以了(儘管要求很低,但筆者還是沒有),這裡我們以為例介紹一下OTA服務的配置,首先是安裝好了apache伺服器,然後在conf目錄下找到mime.types檔案,在該檔案中加入如下兩行
application/java-archive jar
text/vnd.sun.j2me.app-descriptor jad
然後重起apache伺服器就可以了。接下來的工作就是修改jad檔案中MIDlet-Jar-URL:後面的引數,將它改為URL的絕對路徑,即huarongroad.jar(其中***是你的域名或)。在下面就是用java手機下載jad檔案,它會自動部署相應的jar檔案並載入它。剩下的工作就和在模擬器上操作是一樣的了。
九、專案總結
至此,我們已經完成了一個J2ME遊戲的全部開發過程,程式中涉及到了調研、分析、設計、編碼、測試和釋出等方面的問題,其實在實際的工作中還有很多更為具體的問題,畢竟技術只在軟體開發過程中佔據很有限的一部分,這裡限於篇幅的限制無法一一具體展開。今後,筆者計劃再寫一篇使用J2ME開發手機屏保的文章,藉此機會向讀者展示J2ME動畫技術;然後再寫一篇J2ME應用的文章,做一個類似開心辭典那樣的知識問答遊戲,以便向讀者展示J2ME的;待這兩方面的技術交待清楚之後,我將引領讀者製作一個稍大一些的遊戲。
透過本文,筆者盡力向大家展示J2ME遊戲開發的全貌,但限於水平和經驗的問題,文中不當之處還望廣大讀者不吝指教。我的聯絡方式to::dong_peng_eng@eyou.com">MSN:dong_peng_eng@eyou.com。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-963236/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- SQL觸發器例項講解SQL觸發器
- 設計模式例項講解 - 開放封閉設計模式
- python開發俄羅斯方塊小遊戲程式碼例項Python遊戲
- Python裝飾器例項講解(三)Python
- 防抖和節流(例項講解)
- Unity射擊例項講解—主角建立Unity
- 設計模式 - 原則及例項講解設計模式
- 設計模式例項講解 - 里氏替換設計模式
- 設計模式例項講解 - 介面隔離設計模式
- 設計模式例項講解 - 依賴倒置設計模式
- Unity射擊例項講解—子彈建立Unity
- Android:Retrofit 2.0 使用攻略(含例項講解)Android
- 例項詳解不同VLAN間通訊(轉發過程)
- Macro / Micro / Weighted AUC 如何計算例項講解Mac
- C#中的虛方法(virtual)例項講解C#
- Android探索之旅 | AIDL原理和例項講解AndroidAI
- Retrofit2與服務端例項講解服務端
- python開發例項-python開發案例Python
- python專案開發例項-Python專案案例開發從入門到實戰——爬蟲、遊戲Python爬蟲遊戲
- C#開發例項大全C#
- Maven例項講解教程,從零開始學Maven,帶你快速入門!Maven
- 效能測試-例項講解VU、RPS、RT公式換算公式
- 如何讀懂火焰圖?+ 例項講解程式效能優化優化
- python3+requests+unittest介面自動化例項講解Python
- 例項講解:我的強化學習初體驗!強化學習
- Java開發中的事件驅動模型例項詳解Java事件模型
- 遊戲立項與開發中的6種開發導向遊戲
- Hash雜湊遊戲原始碼丨雜湊競猜遊戲系統技術開發丨Hash雜湊遊戲講解遊戲原始碼
- 初元星球農場遊戲開發玩法模式講解功能定製詳情遊戲開發模式
- vue之父子元件間通訊例項講解(props、$ref、$emit)Vue元件MIT
- Vue.js 2.0中$on與$emit如何使用之例項講解Vue.jsMIT
- Spring程式設計式和宣告式事務例項講解Spring程式設計
- Js 的事件迴圈(Event Loop)機制以及例項講解JS事件OOP
- Laravel 服務容器、服務提供器、契約例項講解Laravel
- 對pandas進行資料預處理的例項講解
- 把遊戲推向死亡的5項開發者原罪遊戲
- Unity射擊遊戲例項—物理碰撞的實現Unity遊戲
- pyqt5例項——pycharm實現猜數遊戲QTPyCharm遊戲
- 一個pyspark 開發練習例項Spark