j2me游戲開發(fā)案例講解
發(fā)表時間:2024-02-14 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]J2ME再現(xiàn)華容道一.序言 昨天在網(wǎng)上閑逛,發(fā)現(xiàn)一篇講解用delphi實現(xiàn)華容道游戲的文章,頗受啟發(fā).于是,產(chǎn)生了將華容道游戲移植到手機中去的沖動.現(xiàn)在手機游戲琳瑯滿目,不一而足,華容道的實現(xiàn)版本也很多.正巧不久前筆者對J2ME下了一番功夫,正想借這個機會小試牛刀.選用J2ME作為開發(fā)語言還有一...
J2ME再現(xiàn)華容道
一.序言
昨天在網(wǎng)上閑逛,發(fā)現(xiàn)一篇講解用delphi實現(xiàn)華容道游戲的文章,頗受啟發(fā).于是,產(chǎn)生了將華容道游戲移植到手機中去的沖動.現(xiàn)在手機游戲琳瑯滿目,不一而足,華容道的實現(xiàn)版本也很多.正巧不久前筆者對J2ME下了一番功夫,正想借這個機會小試牛刀.選用J2ME作為開發(fā)語言還有一個原因就是目前Java開發(fā)大行其到,無限增殖業(yè)務(wù)迅猛發(fā)展,J2ME的應(yīng)用日漸活躍起來,也希望我的這篇文章能夠為J2ME知識的普及和開發(fā)團隊的壯大推波助瀾.由于長期受ISO規(guī)范的影響,這次小試牛刀我也打算遵照軟件工程的要求,并采取瀑布式的開發(fā)模式來規(guī)劃項目,也希望借此機會向各位沒有機會參與正式項目開發(fā)的讀者介紹一下軟件開發(fā)的流程.
這里我們先定義項目組的人員體制(其實只有我一個人):技術(shù)調(diào)研、需求分析、概要設(shè)計、詳細設(shè)計、編碼、測試均有筆者一人擔(dān)任;美工這里我找了個捷徑,盜用網(wǎng)上現(xiàn)成的圖片,然后用ACDSee把它由BMP轉(zhuǎn)換成PNG格式(我出于講座的目的,未做商業(yè)應(yīng)用,應(yīng)該不算侵權(quán)吧);至于發(fā)布工作,由于缺少OTA服務(wù)器,此項工作不做(但是我會介紹這步如何做)。
接下來,我們規(guī)劃一下項目實現(xiàn)的時間表,以我個人經(jīng)驗,設(shè)想如下:技術(shù)調(diào)研用2天(這部分解決項目的可行性和重大技術(shù)問題,時間會長一些),需求分析用半天(畢竟有現(xiàn)成的東東可以參照,只要理清思路就行了,況且還有很多以前用過的設(shè)計模式和寫好的代碼),概要設(shè)計再用半天(有了需求,概要只不夠是照方抓藥),詳細設(shè)計要用2天(這一步要把所有的問題想清楚,還要盡可能的準(zhǔn)確描述出來),編碼用2天(其實1天就夠了,技術(shù)已經(jīng)不是問題,多計劃出一天來應(yīng)付突發(fā)事件),測試用2天(測試應(yīng)該至少占全部項目的四分之一,不過這個項目只是一個Demo,也太簡單了),發(fā)布也要用上半天(盡管我們不去實際發(fā)布它,但是還要花點時間搞清楚應(yīng)該如何做),最后就是項目總結(jié)和開慶功會(時間待定)。
二.利其器
。⒐破涫拢叵壤淦鳎,做項目之前第一步是前期調(diào)研.我們要做的華容道這個東東隨處可見,我們要調(diào)研的是兩個方面:
。保螒虻膬(nèi)容:游戲本身很簡單,就是有幾個格子,曹操占據(jù)其中一個較大的格子,然后被幾個格子包圍,這些格子形狀不一定相同,但是擋住了曹操移動的方向.游戲者需要挪動這些格子最終把曹操移動到一個指定的位置才算是過關(guān).更具體的分析我們放在后面需求分析和概要設(shè)計中討論.
2.技術(shù)儲備:談到技術(shù),這里簡單介紹一下J2ME.Java有三個版本,分別是J2ME(微型版).J2SE(標(biāo)準(zhǔn)版).J2EE(企業(yè)版).J2ME是一個標(biāo)準(zhǔn),采用3層結(jié)構(gòu)設(shè)計.最低層是配置層(Configuration)也就是設(shè)備層,其上是簡表層(Profile),再上是應(yīng)用層(Application).MIDP就是移動信息設(shè)備簡表,目前主流手機支持MIDP1.0,最新的是MIDP2.0,它比前一個版本增加了對游戲的支持,在javax.microedition.lcdui.game包中提供了一些類來處理游戲中的技術(shù),比如我們后面會用到的Sprite類,它是用來翻轉(zhuǎn)圖片的.權(quán)衡再三,筆者決定使用MIDP2.0來做開發(fā).首先需要安裝一個J2ME的模擬器,我們就用Sun公司的WTK2.0,我覺得Sun的東西最權(quán)威.當(dāng)然你也可以使用Nokia.Siemens或是Motolora等其他模擬器,但是他們的JDK不盡相同,寫出來的程序移植是比較麻煩的.Sun公司的WTK2.0可以到<A href="http://here/下">http://here/下</A>載,當(dāng)然要想成功下載的前提是你要先注冊成為Sun的會員(其實這樣對你是有好處的).當(dāng)下來之后就是按照提示一步一步的安裝.安裝好了之后,我們用一個"Hello World"程序開始你的J2ME之旅.我們啟動WTK2.0工具集中的KToolBar,然后點擊New Project按鈕,在彈出的輸入框中輸入Project Name為HelloWorld,MIDlet Class Name為Hello,然后點擊Create Project,開始生成項目,工具會彈出MIDP配置簡表,這里接受生成的默認(rèn)值(以后還可以修改)點擊OK,工具提示我們把寫好的Java源程序放到[WTK_HOME]\apps\HelloWorld\src目錄之下.我們編輯如下代碼,并保存在上述目錄之下,文件名為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按鈕,會彈出一個手機界面,剩下的就不用我教了吧(用鼠標(biāo)對手機按鍵一頓狂點).呵呵,你的第一個J2ME程序已經(jīng)OK了.什么?你還一點都沒懂呢(真是厲害,不懂都能寫出J2ME程序來,果然是高手).我這里主要是介紹WTK2.0工具的使用,程序并不是目的,不懂的話后面還會有詳細的解說,這里只是帶你上路.什么?你不懂Java!那也沒有關(guān)系,后面我再講得細一點.
跳過J2ME,我們先來講點游戲的理論.具體到華容道這個游戲,主要有三個方面,貼圖.游戲操作.邏輯判斷.這里講講貼圖,其他兩方面放在概要設(shè)計和詳細設(shè)計里講.所謂的貼圖,其實就是畫圖,就是在要顯示圖形的位置上輸出一副圖片,(要是牽扯到動畫就要麻煩一些,可以使用TimerTask.Thread或Rannable之類的技術(shù)),這副圖片可以是事先準(zhǔn)備好的也可以是臨時處理的.在J2ME中有一個Image類,專門用于管理圖片,它有createImage()方法,可以直接讀取圖片文件(J2ME只支持PNG格式的圖片),也可以截取已有的圖片的一部分(這樣我們可以把很多圖片放在一起,然后一張一張的截下來,好處是節(jié)省存儲空間和文件讀取時間,對于手機這兩者都是性能的瓶頸).J2ME還有一個Graphics類,專門用于繪圖,它有drawImage()方法,可以把一副圖片在指定的位置上顯示出來,它還有drawRect()方法和setColor()方法,這兩個方法在后面我們進行游戲操作時就會用到,這里先交代一下.有了圖片和繪圖的方法,還需要知道把圖畫到誰身上,J2ME提供了一個Canvas類,字面意思就是畫布,它有一個paint()方法用于刷新頁面,還有一個repaint()方法用于調(diào)用paint()方法.聽著有些糊涂是吧,不要緊,我來結(jié)合具體程序講解一下.為了今后編程的方便,我們創(chuàng)建兩個類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;//地圖的高度
//地圖標(biāo)記常量
public static final byte CAOCAO = (byte) 'a'; <A href="file://曹">file://曹</A>操的地圖標(biāo)記
public static final byte MACHAO = (byte) 'b';//馬超的地圖標(biāo)記
public static final byte HUANGZHONG = (byte) 'c';//黃忠的地圖標(biāo)記
public static final byte GUANYU = (byte) 'd';//關(guān)羽的地圖標(biāo)記
public static final byte ZHANGFEI = (byte) 'e';//張飛的地圖標(biāo)記
public static final byte ZHAOYUN = (byte) 'f';//趙云的地圖標(biāo)記
public static final byte ZU = (byte) 'g';//卒的地圖標(biāo)記
public static final byte BLANK = (byte) 'h';//空白的地圖標(biāo)記
public static final byte CURSOR = (byte) 'i';//光標(biāo)的地圖標(biāo)記
//地圖組合標(biāo)記常量
public static final byte DLEFT = (byte) '1'; <A href="file://組">file://組</A>合圖形左邊標(biāo)記
public static final byte DUP = (byte) '2'; <A href="file://組">file://組</A>合圖形上邊標(biāo)記
public static final byte DLEFTUP = (byte) '3'; <A href="file://組">file://組</A>合圖形左上標(biāo)記
//圖片常量
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;//關(guān)羽的圖片
public static Image image_Zhangfei;//張飛的圖片
public static Image image_Zu;//卒的圖片
public static Image image_Blank;//空白的圖片
public static Image image_Frame;//游戲框架的圖片
public Images() {//構(gòu)造函數(shù)
}
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類是用來翻轉(zhuǎn)圖片的,是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) {//構(gòu)造函數(shù)
}
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點轉(zhuǎn)化成畫布的絕對坐標(biāo),繪圖
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://畫曹操
//變成絕對坐標(biāo),并做調(diào)整
g.drawImage(Images.image_Caocao, Images.LEFT + x * unit,
Images.TOP + y * unit,
Graphics.TOP Graphics.LEFT);
break;
case Images.GUANYU://畫關(guān)羽
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://畫光標(biāo)
g.drawRect(Images.LEFT + x * unit,
Images.TOP + y * unit,Images.UNIT,Images.UNIT);
break;
}
return true;
}catch (Exception ex) {
return false;
}
}
}
其中Images類存的是繪圖位置常量(也就是在畫圖時每個格子的長度和相對坐標(biāo)原點位置要進行的調(diào)整)、地圖位置常量(地圖的長、寬),地圖標(biāo)記常量(人物對應(yīng)的記號),地圖組合標(biāo)記常量(后面會細說),圖片常量(存放人物的圖片);Draw類主要負責(zé)在制定的位置畫出人物圖片。下面我來說說Images類中的地圖標(biāo)記常量和地圖組合標(biāo)記常量。為了能夠靈活的安排各個關(guān)面的布局,我們決定把游戲布局的信息存儲在外部文件中,然后程序啟動后把它讀進來。這樣我們制定了一套存儲圖片的代碼,這就是地圖標(biāo)記常量,如上面Images類中定義的Caocao(曹操)用a字符來表示,當(dāng)程序讀到a字符時就能將它轉(zhuǎn)化成曹操對應(yīng)的圖片,并在讀到a字符的位置上進行顯示。但是從實際觀察中我們發(fā)現(xiàn)所有的圖片并不是統(tǒng)一大小的,有的占4個格子,有的占2個格子,還有的占1個格子,而且即便同是占兩個格子的圖片還有橫、豎之分。有鑒于此,我們引入了地圖組合標(biāo)記常量,就是說在遇到占有多個格子的時候,值1(也就是Images.LEFT)表示它的左邊是一個真正的地圖標(biāo)記,值2(也就是Images.UP)表示它的上邊是一個真正的地圖標(biāo)記,值1(也就是Images.LEFTUP)表示它的左上邊是一個真正的地圖標(biāo)記。地圖組合標(biāo)記常量其實就是用來占位置的,與實際顯示無關(guān),當(dāng)后面我們將到移動時還會再來分析組合標(biāo)記的使用。
Draw類主要是用來在畫布上畫出圖形,它有兩個paint方法,這是很常見的函數(shù)重載。但是程序中實際上只用到了4個參數(shù)的paint方法,它直接獲得要畫圖片的相對坐標(biāo)位置信息,然后調(diào)用5個參數(shù)的paint方法。5個參數(shù)的paint方法將相對坐標(biāo)位置信息轉(zhuǎn)換成絕對位置,并實際調(diào)用Graphics.drawImage()方法,將Images中的圖片畫了出來。這種實現(xiàn)方法的好處是靈活和便于擴展,但你需要畫圖的位置并不能夠?qū)?yīng)到格子中的相對坐標(biāo)位置時,你就可以直接調(diào)用5個參數(shù)的paint方法,而不必再去修改這各類;但你添加新的圖片時,只要在Images中增加對應(yīng)的常量,然后向Draw中5個參數(shù)的paint方法添加一條處理就可以了。
寫到這里,兩天的時間剛好用完。
三、需求分析
這部分叫做需求分析,聽起來挺嚇人的,其實就是搞清楚我們要做什么,做成什么樣,那些不做。下面我引領(lǐng)著大家共同來完成這一步驟。首先,我們要做一個華容道的游戲,華容道的故事這里不再贅述了,但其中的人物在這里限定一下,如上面Images類里的定義,我們這個版本只提供曹操(Caocao)、關(guān)羽(Guanyu)、張飛(Zhangfei)、趙云(Zhaoyun)、黃忠(Huangzhong)、馬超(Machao)和卒(Zu)。我們這里也限定一下游戲的操作方法:首先要通過方向鍵選擇一個要移動的區(qū)域(就是一張圖片),被選擇的區(qū)域用黑色方框框;選好后按Fire鍵(就是確定鍵)將這塊區(qū)域選中,被選中的區(qū)域用綠色方框框。蝗缓筮x擇要移動到的區(qū)域,此時用紅色方框框住被選擇的區(qū)域;選好要移動到的區(qū)域之后按Fire鍵將要移動的區(qū)域(圖片)移到要移動到的區(qū)域,并去掉綠色和紅色的方框。這里需要強調(diào)的概念有選擇的區(qū)域、選中的區(qū)域、要移動的區(qū)域和要移動到的區(qū)域,這四個概念請讀者注意區(qū)分,當(dāng)然也應(yīng)當(dāng)把這一部分記入數(shù)據(jù)字典之中。為了使文章的重點突出(介紹如何制作一個J2ME的收集游戲),我們這里限定一些與本主題無關(guān)的內(nèi)容暫不去實現(xiàn):過關(guān)之后的動畫(實現(xiàn)時要用到TimerTask或Thread類,后續(xù)的系列文章中我會詳細介紹動畫方面的知識)、關(guān)面之間的切換(其實很簡單,當(dāng)完成任務(wù)之后重新再做一邊)、暫停和保存等操作(這部分的內(nèi)容介紹的資料很多,我也寫不出什么新的東東來,難免抄襲,故此免掉)。
需求分析基本完成,離下午還有一段時間,馬上動手用ACDSee把從網(wǎng)上找來的BMP文件,調(diào)整其大小為271*177(我的這個圖片是兩個部分合在一起,所以比手機實際屏幕大了),另存為PNG格式。半天時間剛剛好,不但搞清楚了要做的東東,還把要用的圖片準(zhǔn)備好了。
四、概要設(shè)計
概要設(shè)計是從需求分析過渡到詳細設(shè)計的橋梁和紐帶,這一部分中我們確定項目的實現(xiàn)方法和模塊的劃分。我們決定將整個項目分成五個部分,分別是前面介紹的Images、Draw,還有Map和Displayable1和MIDlet1。Images和Draw類功能簡單、結(jié)構(gòu)固定,因此很多項目我們都使用這兩各類,這里直接拿來改改就能用了,前面已經(jīng)介紹過這里不再贅述。Map類是用來從外部文件讀入地圖,然后保存在一個數(shù)組之中,這部分的內(nèi)容是我們在本階段討論的重點。Displayable1是一個繼承了Canvas類的畫布,它用來處理程序的主要控制邏輯和一部分控制邏輯所需的輔助函數(shù),主要函數(shù)應(yīng)該包括用來繪圖的paint()函數(shù)、用來控制操作的keyPressed()函數(shù)、用來控制選擇區(qū)域的setRange()函數(shù)、用來控制選擇要移動到區(qū)域的setMoveRange()函數(shù)、用來移動選中區(qū)域的Move()函數(shù)和判斷是否完成任務(wù)的win()函數(shù),更具體的分析,我們放到詳細設(shè)計中去細化。MIDlet1實際上就是一個控制整個J2ME應(yīng)用的控制程序,其實也沒有什么可特別的,它和我們前面介紹的"Hello World"程序大同小異,這里就不展開來說了,后面會貼出它的全部代碼。
Map類主要應(yīng)該有一個Grid[][]的二維數(shù)組,用來存放華容道的地圖,還應(yīng)該有一個read_map()函數(shù)用來從外部文件讀取地圖內(nèi)容填充Grid數(shù)據(jù)結(jié)構(gòu),再就是要有一個draw_map()函數(shù)用來把Grid數(shù)據(jù)結(jié)構(gòu)中的地圖內(nèi)容轉(zhuǎn)換成圖片顯示出來(當(dāng)然要調(diào)用Draw類的paint方法)。說到讀取外部文件,筆者知道有兩種方法:一種是傳統(tǒng)的定義一個InputStream對象,然后用getClass().getResourceAsStream()方法取得輸入流,然后再從輸入流中取得外部文件的內(nèi)容,例如
InputStream is = getClass().getResourceAsStream("/filename");
if (is != null) {
byte a = (byte) is.read();
}
這里請注意文件名中的根路徑是相對于便以后的class文件放置的位置,而不是源文件(java)。第二種方法是使用onnector.openInputStream方法,然后打開的協(xié)議是Resource,但是這種方法筆者反復(fù)嘗試都沒能調(diào)通,報告的錯誤是缺少Resource協(xié)議,估計第二種方法用到J2ME的某些擴展類包,此處不再深究。由于以前已經(jīng)做過一些類似華容道這樣的地圖,這里直接給出Map類的代碼,后面就不再詳細解釋Map類了,以便于我們可以集中精力處理Displayable1中的邏輯。Map類的代碼如下:
package huarongroad;
import java.io.InputStream;
import javax.microedition.lcdui.*;
public class Map {
//處理游戲的地圖,負責(zé)從外部文件加載地圖數(shù)據(jù),存放地圖數(shù)據(jù),并按照地圖數(shù)據(jù)繪制地圖
public byte Grid[][];//存放地圖數(shù)據(jù)
public Map() {//構(gòu)造函數(shù),負責(zé)初始化地圖數(shù)據(jù)的存儲結(jié)構(gòu)
this.Grid = new byte[Images.HEIGHT][Images.WIDTH];
//用二維數(shù)組存放地圖數(shù)據(jù),注意第一維是豎直坐標(biāo),第二維是水平坐標(biāo)
}
public int[] read_map(int i) {
<A href="file://從">file://從</A>外部文件加載地圖數(shù)據(jù),并存放在存儲結(jié)構(gòu)中,返回值是光標(biāo)點的位置
//參數(shù)是加載地圖文件的等級
int[] a = new int[2];//光標(biāo)點的位置,0是水平位置,1是豎直位置
try {
InputStream is = getClass().getResourceAsStream(
"/huarongroad/level".concat(String.valueOf(i)));
if (is != null) {
for (int k = 0; k < Images.HEIGHT; k++) {
for (int j = 0; j < Images.WIDTH; j++) {
this.Grid[k][j] = (byte) is.read();
if ( this.Grid[k][j] == Images.CURSOR ) {
//判斷出光標(biāo)所在位置
a[0] = j;//光標(biāo)水平位置
a[1] = k;//光標(biāo)豎直位置
this.Grid[k][j] = Images.BLANK;//將光標(biāo)位置設(shè)成空白背景
}
}
is.read();//讀取回車(13),忽略掉
is.read();//讀取換行(10),忽略掉
}
is.close();
}else {
//讀取文件失敗
a[0] = -1;
a[1] = -1;
}
}catch (Exception ex) {
//打開文件失敗
a[0] = -1;
a[1] = -1;
}
return a;
}
public boolean draw_map(Graphics g) {
//調(diào)用Draw類的靜態(tài)方法,繪制地圖
try {
for (int i = 0; i < Images.HEIGHT; i++) {
for (int j = 0; j < Images.WIDTH; j++) {
Draw.paint(g, this.Grid[i][j], j, i);//繪制地圖
}
}
return true;
}catch (Exception ex) {
return false;
}
}
}
對于像華容道這樣的小型地圖可以直接用手工來繪制地圖的內(nèi)容,比如:
fa1c
2232
bd1e
2gg2
gihg
但是,如果遇到像坦克大戰(zhàn)或超級瑪莉那樣的地圖,就必須另外開發(fā)一個地圖編輯器了(我會在后續(xù)的文章中介紹用vb來開發(fā)一個地圖編輯器)。
看看時間,剛剛好有過了半天。休息,休息一下,明天再見。
五、詳細設(shè)計
詳細設(shè)計是程序開發(fā)過程中至關(guān)重要的一個環(huán)節(jié),好在我們在前面的各個階段中已經(jīng)搭建好了項目所需的一些工具,現(xiàn)在這個階段中我們只需集中精力設(shè)計好Displayable1中的邏輯。(兩天的時間當(dāng)然不只干這點活,還要把其他幾個類的設(shè)計修改一下)
Displayable1這個類負責(zé)處理程序的控制邏輯。首先,它需要有表示當(dāng)前關(guān)面的變量level、表示當(dāng)前光標(biāo)位置的變量loc、表示要移動區(qū)域的變量SelectArea、表示要移動到的區(qū)域的變量MoveArea、表示是否已有區(qū)域被選中而準(zhǔn)備移動的變量Selected和Map類的實例MyMap。然后,我們根據(jù)用戶按不同的鍵來處理不同的消息,我們要實現(xiàn)keyPressed()函數(shù),在函數(shù)中我們處理按鍵的上下左右和選中(Fire),這里的處理需要我展開來講一講,后面我很快會把這一部分詳細展開。接下來,是實現(xiàn)paint()函數(shù),我們打算在這一部分中反復(fù)的重畫背景、地圖和選擇區(qū)域,這個函數(shù)必須處理好區(qū)域被選中之后的畫筆顏色的切換,具體講就是在沒有選中任何區(qū)域時要用黑色畫筆,當(dāng)選重要移動的區(qū)域時使用綠色畫筆,當(dāng)選擇要移動到的區(qū)域時改用紅色畫筆(當(dāng)然附加一張流程圖是必不可少的)。再下面要實現(xiàn)的setRange()函數(shù)和setMoveRange()函數(shù),這兩個函數(shù)用來設(shè)置要移動的區(qū)域和要移動到的區(qū)域,我的思路就是利用前面在Images類中介紹過的地圖組合標(biāo)記常量,當(dāng)移動到地圖組合標(biāo)記常量時,根據(jù)該點地圖中的值做逆向變換找到相應(yīng)的地圖標(biāo)記常量,然后設(shè)置相應(yīng)的loc、SelectArea和MoveArea,其中setMoveRange()函數(shù)還用到了一個輔助函數(shù)isInRange(),isInRange()函數(shù)是用來判斷給定的點是否在已選中的要移動的區(qū)域之內(nèi),如果isInRange()的返回值是假并且該點處的值不是空白就表明要移動到的區(qū)域侵犯了其他以被占用的區(qū)域。有了setRange()和setMoveRange()函數(shù),Move()函數(shù)就水到渠成了,Move()函數(shù)將要移動的區(qū)域移動到要移動到的區(qū)域,在移動過程中分為三步進行:第一.復(fù)制要移動的區(qū)域,第二.將復(fù)制出的要移動區(qū)域復(fù)制到要移動到的區(qū)域(這兩步分開進行的目的是防止在復(fù)制過程中覆蓋掉要移動的區(qū)域),第三.用isInRange2()判斷給定的點是否在要移動到的區(qū)域內(nèi),將不在要移動到的區(qū)域內(nèi)的點設(shè)置成空白.
下面我們詳細的分析一下keyPressed()函數(shù)的實現(xiàn)方法:首先,keyPressed()函數(shù)要處理按鍵的上下左右和選中(Fire),在處理時需要用Canvas類的getGameAction函數(shù)來將按鍵的鍵值轉(zhuǎn)換成游戲的方向,這樣可以提高游戲的兼容性(因為不同的J2ME實現(xiàn),其方向鍵的鍵值不一定是相同的).接下來,分別處理四個方向和選中.當(dāng)按下向上時,先判斷是否已經(jīng)選定了要移動的區(qū)域(即this.selected是否為真),如果沒有選中要移動區(qū)域則讓光標(biāo)向上移動一格,然后調(diào)用setRange()函數(shù)設(shè)置選擇要移動的區(qū)域,再調(diào)用repaint()函數(shù)刷新屏幕,否則如果已經(jīng)選中了要移動的區(qū)域,就讓光標(biāo)向上移動一格,然后調(diào)用setMoveRange()函數(shù)判斷是否能夠向上移動已選中的區(qū)域,如果能移動就調(diào)用repaint()函數(shù)刷新屏幕,如果不能移動就讓光標(biāo)向下退回到原來的位置.當(dāng)按下向下時,先判斷是否已經(jīng)選定了要移動的區(qū)域,如果沒有選中要移動的區(qū)域則判斷當(dāng)前所處的區(qū)域是否為兩個格高,如果是兩個格高則向下移動兩格,如果是一個格高則向下移動一格,接著再調(diào)用setRange()函數(shù)設(shè)置選擇要移動的區(qū)域,而后調(diào)用repaint()函數(shù)刷新屏幕,否則如果已經(jīng)選中了要移動的區(qū)域,就讓光標(biāo)向下移動一格,然后調(diào)用setMoveRange()函數(shù)判斷是否能夠向下移動已選中的區(qū)域,如果能移動就調(diào)用repaint()函數(shù)刷新屏幕,如果不能移動就讓光標(biāo)向上退回到原來的位置.按下向左時情況完全類似向上的情況,按下向右時情況完全類似向下的情況,因此這里不再贅述,詳細情況請參見程序的源代碼.當(dāng)按下選中鍵時,先判斷是否已經(jīng)選中了要移動的區(qū)域,如果已經(jīng)選中了要移動的區(qū)域就調(diào)用Move()函數(shù)完成由要移動的區(qū)域到要移動到的區(qū)域的移動過程,接著調(diào)用repaint()函數(shù)刷新屏幕,然后將已選擇標(biāo)記置成false,繼續(xù)調(diào)用win()函數(shù)判斷是否完成了任務(wù),否則如果還沒有選定要移動的區(qū)域則再判斷當(dāng)前選中區(qū)域是否為空白,如果不是空白就將選中標(biāo)記置成true,然后刷新屏幕.這里介紹一個技巧,在開發(fā)程序遇到復(fù)雜的邏輯的時候,可以構(gòu)造一格打印函數(shù)來將所關(guān)心的數(shù)據(jù)結(jié)構(gòu)打印出來以利調(diào)試,這里我們就構(gòu)造一個PrintGrid()函數(shù),這個函數(shù)純粹是為了調(diào)試之用,效果這得不錯.至此我們完成了編碼前的全部工作.
看看時間還早,我在這里簡單介紹一下MIDlet1類,這個類和前面介紹的"Hello World"中的內(nèi)容大同小異,先看一下程序代碼:
其中startApp().pauseApp()和destroyApp()三個函數(shù)時MIDlet類的三個抽象方法必須要被重載一邊,他們控制著一個J2ME程序的開始.暫停和結(jié)束.而其中的Displayable1 就是我們前面提到的Displayable1的一個實例,用來負責(zé)顯示程序的用戶界面和控制程序邏輯.這個類一般都是寫成這樣.
兩天的時間稍稍還有些富裕,今天可以早放工.
六.編碼
整個項目共有五個類,有四個類的代碼前面已經(jīng)介紹過了,而且是在其他項目中使用過的相對成熟的代碼.現(xiàn)在只需全力去實現(xiàn)Displayable1類.Displayable1類的代碼如下:
package huarongroad;
import javax.microedition.lcdui.*;
public class Displayable1 extends Canvas implements CommandListener {
private int[] loc = new int[2]; <A href="file://光">file://光</A>標(biāo)的當(dāng)前位置,0是水平位置,1是豎直位置
private int[] SelectArea = new int[4];//被選定的區(qū)域,即要移動的區(qū)域
private int[] MoveArea = new int[4];//要移動到的區(qū)域
private Map MyMap = new Map();//地圖類
private boolean selected;//是否已經(jīng)選中要移動區(qū)域的標(biāo)志
private int level;//但前的關(guān)面
public Displayable1() {//構(gòu)造函數(shù)
try {
jbInit();//JBuilder定義的初始化函數(shù)
}catch (Exception e) {
e.printStackTrace();
}
}
private void Init_game(){
//初始化游戲,讀取地圖,設(shè)置選擇區(qū)域,清空要移動到的區(qū)域
this.loc = MyMap.read_map(this.level);//讀取地圖文件,并返回光標(biāo)的初始位置
//0為水平位置,1為豎直位置
this.SelectArea[0] = this.loc[0];//初始化選中的區(qū)域
this.SelectArea[1] = this.loc[1];
this.SelectArea[2] = 1;
this.SelectArea[3] = 1;
this.MoveArea[0] = -1;//初始化要移動到的區(qū)域
this.MoveArea[1] = -1;
this.MoveArea[2] = 0;
this.MoveArea[3] = 0;
}
private void jbInit() throws Exception {//JBuilder定義的初始化函數(shù)
<A href="file://初">file://初</A>始化實例變量
this.selected = false;//設(shè)置沒有被選中的要移動區(qū)域
this.level = 1;
Images.init();//初始化圖片常量
Init_game();//初始化游戲,讀取地圖,設(shè)置選擇區(qū)域,清空要移動到的區(qū)域
setCommandListener(this);//添加命令監(jiān)聽,這是Displayable的實例方法
addCommand(new Command("Exit", Command.EXIT, 1));//添加“退出”按鈕
}
public void commandAction(Command command, Displayable displayable) {
//命令處理函數(shù)
if (command.getCommandType() == Command.EXIT) {//處理“退出”
MIDlet1.quitApp();
}
}
protected void paint(Graphics g) {
//畫圖函數(shù),用于繪制用戶畫面,即顯示圖片,勾畫選中區(qū)域和要移動到的區(qū)域
try {
g.drawImage(Images.image_Frame, 0, 0,
Graphics.TOP Graphics.LEFT);//畫背景
MyMap.draw_map(g);//按照地圖內(nèi)容畫圖
if ( this.selected )
g.setColor(0,255,0);//如果被選中,改用綠色畫出被選中的區(qū)域
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);//畫出選擇區(qū)域,
<A href="file://如">file://如</A>果被選中,就用綠色
<A href="file://否">file://否</A>則,使用黑色
g.setColor(255,255,255);//恢復(fù)畫筆顏色
if (this.selected) {//已經(jīng)選中了要移動的區(qū)域
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);//畫出要移動到的區(qū)域
g.setColor(255, 255, 255);//恢復(fù)畫筆顏色
}
}catch (Exception ex) {
}
System.out.println(Runtime.getRuntime().freeMemory());
System.out.println(Runtime.getRuntime().totalMemory());
}
private void setRange() {
//設(shè)置移動后能夠選中的區(qū)域
//調(diào)整當(dāng)前光標(biāo)位置到地圖的主位置,即記錄人物信息的位置
if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFT) {
this.loc[0] -= 1;//向左調(diào)
}else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DUP) {
this.loc[1] -= 1;//向上調(diào)
}else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFTUP) {
this.loc[0] -= 1;//向左調(diào)
this.loc[1] -= 1;//向上調(diào)
}
this.SelectArea[0] = this.loc[0];//設(shè)置光標(biāo)的水平位置
this.SelectArea[1] = this.loc[1];//設(shè)置光標(biāo)的豎直位置
//設(shè)置光標(biāo)的寬度
if (this.loc[0] + 1 < Images.WIDTH) {
this.SelectArea[2] = this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] != (byte) '1' ?
1 : 2;
}else {
this.SelectArea[2] = 1;
}
//設(shè)置光標(biāo)的高度
if (this.loc[1] + 1 < Images.HEIGHT) {
this.SelectArea[3] = this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] != (byte) '2' ?
1 : 2;
}else {
this.SelectArea[3] = 1;
}
}
private boolean setMoveRange() {
//設(shè)置要移動到的區(qū)域,能夠移動返回true,否則返回false
for (int i = 0; i < this.SelectArea[2]; i++) {
for (int j = 0; j < this.SelectArea[3]; j++) {
if (this.loc[1] + j >= 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)點是否在選定區(qū)域之內(nèi),x是水平坐標(biāo),y是豎直坐標(biāo)
if (x >= this.SelectArea[0] &&
x < this.SelectArea[0] + this.SelectArea[2] &&
y >= this.SelectArea[1] &&
y < this.SelectArea[1] + this.SelectArea[3]) {
return true;
}else {
return false;
}
}
private boolean isInRange2(int x, int y) {
//判斷給定的(x,y)點是否在要移動到的區(qū)域之內(nèi),x是水平坐標(biāo),y是豎直坐標(biāo)
if (x >= this.MoveArea[0] &&
x < this.MoveArea[0] + this.MoveArea[2] &&
y >= this.MoveArea[1] &&
y < this.MoveArea[1] + this.MoveArea[3]) {
return true;
}else {
return false;
}
}
protected void keyPressed(int keyCode) {
//處理按下鍵盤的事件,這是Canvas的實例方法
switch (getGameAction(keyCode)) {//將按鍵的值轉(zhuǎn)化成方向常量
case Canvas.UP://向上
if (!this.selected) {//還沒有選定要移動的區(qū)域
if (this.loc[1] - 1 >= 0) {//向上還有移動空間
this.loc[1]--;//向上移動一下
setRange();//設(shè)置光標(biāo)移動的區(qū)域,該函數(shù)能將光標(biāo)移動到地圖主位置
repaint();//重新繪圖
}
}else {//已經(jīng)選定了要移動的區(qū)域
if (this.loc[1] - 1 >= 0) {//向上還有移動空間
this.loc[1]--;//向上移動一下
if (setMoveRange()) {//能夠移動,該函數(shù)能夠設(shè)置要移動到的區(qū)域
repaint();//重新繪圖
}else {//不能移動
this.loc[1]++;//退回來
}
}
}
break;
case Canvas.DOWN://向下
if (!this.selected) {//還沒有選定要移動的區(qū)域
if (this.loc[1] + 1 < Images.HEIGHT) {//向下還有移動空間
if (this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] ==
Images.DUP){//該圖片有兩個格高
this.loc[1]++;//向下移動一下
if (this.loc[1] + 1 < Images.HEIGHT) {//向下還有
<A href="file://移">file://移</A>動空間
this.loc[1]++;//向下移動一下
setRange();//設(shè)置光標(biāo)移動的區(qū)域,
<A href="file://該">file://該</A>函數(shù)能將光標(biāo)移動到地圖主位置
repaint();//重新繪圖
}else {//向下沒有移動空間
this.loc[1]--;//退回來
}
}else {//該圖片只有一個格高
this.loc[1]++;//向下移動一下
setRange();//設(shè)置光標(biāo)移動的區(qū)域,
<A href="file://該">file://該</A>函數(shù)能將光標(biāo)移動到地圖主位置
repaint();//重新繪圖
}
}else {
}
}else {//已經(jīng)選定了要移動的區(qū)域
if (this.loc[1] + 1 < Images.HEIGHT) {//向下還有移動空間
this.loc[1]++;//向下移動一下
if (setMoveRange()) {//能夠移動,該函數(shù)能夠設(shè)置要移動到的區(qū)域
repaint();//重新繪圖
}else {//不能移動
this.loc[1]--;//退回來
}
}
}
break;
case Canvas.LEFT://向左
if (!this.selected) {//還沒有選定要移動的區(qū)域
if (this.loc[0] - 1 >= 0) {//向左還有移動空間
this.loc[0]--;//向左移動一下
setRange();//設(shè)置光標(biāo)移動的區(qū)域,該函數(shù)能將光標(biāo)移動到地圖主位置
repaint();//重新繪圖
}
}else {//已經(jīng)選定了要移動的區(qū)域
if (this.loc[0] - 1 >= 0) {//向左還有移動空間
this.loc[0]--;//向左移動一下
if (setMoveRange()) {//能夠移動,該函數(shù)能夠設(shè)置要移動到的區(qū)域
repaint();//重新繪圖
}else {//不能移動
this.loc[0]++;//退回來
}
}
}
break;
case Canvas.RIGHT://向右
if (!this.selected) {//還沒有選定要移動的區(qū)域
if (this.loc[0] + 1 < Images.WIDTH) {//向右還有移動空間
if (this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] ==
Images.DLEFT) {//該圖片有兩個格寬
this.loc[0]++;//向右移動一下
if (this.loc[0] + 1 < Images.WIDTH) {//向右還有
<A href="file://移">file://移</A>動空間
this.loc[0]++;//向右移動一下
setRange();//設(shè)置光標(biāo)移動的區(qū)域,
<A href="file://該">file://該</A>函數(shù)能將光標(biāo)移動到地圖主位置
repaint();//重新繪圖
}else {//向右沒有移動空間
this.loc[0]--;//退回來
}
}else {//該圖片只有一個格寬
this.loc[0]++;//向右移動一下
setRange();//設(shè)置光標(biāo)移動的區(qū)域,
<A href="file://該">file://該</A>函數(shù)能將光標(biāo)移動到地圖主位置
repaint();//重新繪圖
}
}else {
}
}else {//已經(jīng)選定了要移動的區(qū)域
if (this.loc[0] + 1 < Images.WIDTH) {//向右還有移動空間
this.loc[0]++;//向右移動一下
if (setMoveRange()) {//能夠移動,該函數(shù)能夠設(shè)置要移動到的區(qū)域
repaint();//重新繪圖
}else {//不能移動
this.loc[0]--;//退回來
}
}
}
break;
case Canvas.FIRE:
if (this.selected) {//已經(jīng)選定了要移動的區(qū)域
Move();//將要移動的區(qū)域移動到剛選中的區(qū)域
repaint();//重新繪圖
this.selected = false;//清除已選定要移動區(qū)域的標(biāo)志
if ( win()) {
System.out.println("win");
}
}else {//還沒有選定要移動的區(qū)域
if (this.MyMap.Grid[this.loc[1]][this.loc[0]] ==
Images.BLANK) {//要移到的位置是一個空白
}else {//要移到的位置不是空白
this.selected = true;//設(shè)置已選定要移動區(qū)域的標(biāo)志
}
repaint();//重新繪圖
}
break;
}
}
private boolean win(){
<A href="file://判">file://判</A>斷是否已經(jīng)救出了曹操
if ( this.MyMap.Grid[Images.HEIGHT - 2 ][Images.WIDTH - 3 ] == Images.CAOCAO )
return true;
else
return false;
}
private void PrintGrid(String a) {
<A href="file://打">file://打</A>印當(dāng)前地圖的內(nèi)容,用于調(diào)試
System.out.println(a);
for (int i = 0; i < Images.HEIGHT; i++) {
for (int j = 0; j < Images.WIDTH; j++) {
System.out.print( (char)this.MyMap.Grid[i][j]);
}
System.out.println("");
}
}
private void Move() {
<A href="file://將">file://將</A>要移動的區(qū)域移動到剛選中的區(qū)域
if (this.MoveArea[0] == -1 this.MoveArea[1] == -1
this.SelectArea[0] == -1 this.SelectArea[1] == -1) {//沒有選中區(qū)域
}else {//已經(jīng)選中了要移動的區(qū)域和要移動到的區(qū)域
byte[][] temp = new byte[this.SelectArea[3]][this.SelectArea[2]];
<A href="file://復(fù)">file://復(fù)</A>制要移動的區(qū)域,因為這塊區(qū)域可能會被覆蓋掉
for (int i = 0; i < this.SelectArea[2]; i++) {
for (int j = 0; j < this.SelectArea[3]; j++) {
temp[j][i] =
this.MyMap.Grid[this.SelectArea[1] +j]
[this.SelectArea[0] + i];
}
}
<A href="file://PrintGrid">file://PrintGrid</A>("1"); // 調(diào)試信息
<A href="file://將">file://將</A>要移動的區(qū)域移動到剛選中的區(qū)域(即要移動到的區(qū)域)
for (int i = 0; i < this.SelectArea[2]; i++) {
for (int j = 0; j < this.SelectArea[3]; j++) {
this.MyMap.Grid[this.MoveArea[1] + j]
[this.MoveArea[0] + i] = temp[j][i];
}
}
<A href="file://PrintGrid">file://PrintGrid</A>("2");// 調(diào)試信息
<A href="file://將">file://將</A>要移動的區(qū)域中無用內(nèi)容置成空白
for (int i = 0; i < this.SelectArea[3]; i++) {
for (int j = 0; j < this.SelectArea[2]; j++) {
if (!isInRange2(this.SelectArea[0] + j,
this.SelectArea[1] + i)) {//該點是不在要移動到
<A href="file://的">file://的</A>區(qū)域之內(nèi),需置空
this.MyMap.Grid[this.SelectArea[1] + i]
[this.SelectArea[0] + j] = Images.BLANK;
}else {
}
}
}
<A href="file://PrintGrid">file://PrintGrid</A>("3");// 調(diào)試信息
this.SelectArea[0] = this.MoveArea[0];//重置選中位置的水平坐標(biāo)
this.SelectArea[1] = this.MoveArea[1];//重置選中位置的豎直坐標(biāo)
this.MoveArea[0] = -1;//清空要移動到的位置
this.MoveArea[1] = -1;//清空要移動到的位置
this.MoveArea[2] = 0;//清空要移動到的位置
this.MoveArea[3] = 0;//清空要移動到的位置
}
}
}
代碼的相關(guān)分析,在詳細設(shè)計階段已經(jīng)講過,代碼中有比較相近的注釋,請讀者自行研讀分析.將全部的代碼寫好,用wtk2.0自帶的Ktoolbar工具建立一個工程,接下來把去不源文件放到正確位置下,然后點擊build,再點run,就完成了程序的編寫.當(dāng)然如果有錯誤還要修改和調(diào)試.
經(jīng)過兩天的痛苦煎熬,程序終于有了眉目,編碼階段初戰(zhàn)告捷.
七、測試
作為一個真正的產(chǎn)品要經(jīng)過單體測試、結(jié)合測試和系統(tǒng)測試。由于項目本身簡單,而且大部分代碼已經(jīng)是相對成熟的,我們跳過單體測試;又由于筆者的實際環(huán)境所限,無法搞到Java手機,無法架設(shè)OTA服務(wù)器,因此我們也只能放棄系統(tǒng)測試。那么就讓我們開始結(jié)合測試吧。測試之前要先出一個測試式樣書,也就是測試的計劃。我們將它簡化一下,只測試如下幾種情況:第一、對各種形狀的區(qū)域的選擇和移動;第二、臨近邊界區(qū)域的選擇和移動;第三、同一區(qū)域的反復(fù)選擇和反復(fù)移動;第四、非法選擇和非法移動。有了測試的目標(biāo),接下來的工作就是用wtk2.0自帶的Run MIDP Application工具進行測試。打開這個工具,加載huarongRoad的jad文件,程序就會自動運行,選擇launch上MIDlet1這個程序,華容道游戲就會躍然屏幕之上,接下來的工作就是左三點.右三點,拇指扭扭,來做測試。測試過程中發(fā)現(xiàn)任何的問題,立刻發(fā)一個bug票給自己,然后就又是痛苦的調(diào)試和修正bug,如此如此。
兩日過后,程序日漸成熟起來,筆者也日漸消瘦下去,還好熬了過來。
八.發(fā)布
談到發(fā)布,其實是個關(guān)鍵,再好的產(chǎn)品不能很好的發(fā)布出去也只是個產(chǎn)品而已,變不成商品也就得不到回報.由于筆者的條件所限,這里只能是紙上談兵,不過還是希望能夠使讀者對這一過程有所了解(網(wǎng)上的資料也很多)。
J2ME的程序發(fā)布一般都是通過OTA(Over The Air),你只需要一臺有公網(wǎng)IP的主機和一個普通的web Server就可以了(盡管要求很低,但筆者還是沒有),這里我們以apache為例介紹一下OTA服務(wù)的配置,首先是安裝好了apache服務(wù)器,然后在conf目錄下找到mime.types文件,在該文件中加入如下兩行
application/java-archive jar
text/vnd.sun.j2me.app-descriptor jad
然后重起apache服務(wù)器就可以了。接下來的工作就是修改jad文件中MIDlet-Jar-URL:后面的參數(shù),將它改為URL的絕對路徑,即<A href="http://***/">http://***/</A>huarongroad.jar(其中***是你的域名或IP地址)。在下面就是用java手機下載jad文件,它會自動部署相應(yīng)的jar文件并加載它。剩下的工作就和在模擬器上操作是一樣的了。
九、項目總結(jié)
至此,我們已經(jīng)完成了一個J2ME游戲的全部開發(fā)過程,程序中涉及到了調(diào)研、分析、設(shè)計、編碼、測試和發(fā)布等方面的問題,其實在實際的工作中還有很多更為具體的問題,畢竟技術(shù)只在軟件開發(fā)過程中占據(jù)很有限的一部分,這里限于篇幅的限制無法一一具體展開。今后,筆者計劃再寫一篇使用J2ME開發(fā)手機屏保的文章,借此機會向讀者展示J2ME動畫技術(shù);然后再寫一篇J2ME網(wǎng)絡(luò)應(yīng)用的文章,做一個類似開心辭典那樣的知識問答游戲,以便向讀者展示J2ME的網(wǎng)絡(luò)技術(shù);待這兩方面的技術(shù)交待清楚之后,我將引領(lǐng)讀者制作一個稍大一些的游戲。
通過本文,筆者盡力向大家展示J2ME游戲開發(fā)的全貌,但限于水平和經(jīng)驗的問題,文中不當(dāng)之處還望廣大讀者不吝指教。我的聯(lián)系方式MSN:dong_peng_eng@eyou.com.