因?yàn)橛X得本文還是可以起到拋磚引玉的作用的,就照著英文版自己翻譯了,由于晚上時(shí)間傖俗所以難免有所紕漏,僅供參考。
如何去編寫自己的手機(jī)游戲模擬器呢?
對(duì)于一個(gè)程序員來說,修改幾行代碼之后,等待它運(yùn)行起來看效果時(shí)的編譯等待時(shí)間顯得特別漫長(zhǎng),而來回修改運(yùn)行調(diào)試的無(wú)盡的等待應(yīng)該算最操蛋事情了。而開發(fā)j2me游戲那頻繁的編譯混淆以及導(dǎo)出jar包令之前說的操蛋事更加突出。而操蛋加操蛋的就是那既啟動(dòng)速度很慢而按鍵響應(yīng)很爛且調(diào)試功能很差的種多不同機(jī)型的模擬器。通過用j2se代碼去模擬實(shí)現(xiàn)j2me的API函數(shù)也許我們可以使得手機(jī)上的java程序得到加速甚至不再操蛋。想要一個(gè)加速的、方便調(diào)試的、生成版本快速的、與機(jī)型無(wú)關(guān)的獨(dú)立的模擬器?那么這篇文章將賜予你這個(gè)模擬器然后把你從繁瑣的編譯導(dǎo)出中解救出來~
@模擬器有什么問題么?
我不打算將我對(duì)那些手機(jī)生產(chǎn)商所提供的模擬器的厭惡一一列出。我確信如果你有j2me的開發(fā)經(jīng)驗(yàn)?zāi)悄憧隙ㄓX得他們的并不是最理想的方案。首先,很明顯安裝、維護(hù)、學(xué)習(xí)然后把那么多廠商的不同的SDK加入你的項(xiàng)目里是一件很操蛋的費(fèi)力不討好的事情。這些廠商的模擬器本應(yīng)該讓你省去在真機(jī)測(cè)試的麻煩,但是他們運(yùn)行緩慢,而且對(duì)于程序錯(cuò)誤基本沒有什么提示,對(duì)于調(diào)試的支持甚少而且與真機(jī)的性能相差甚遠(yuǎn)。
@那么,你通常如何做呢?
一個(gè)好的模擬器必不可少的要能得到你的游戲現(xiàn)在使用了多少內(nèi)存。這讓你能確信你的游戲如果能在諾基亞的7210模擬器上跑起來,那一定能在7210的真機(jī)上跑。然而這個(gè)內(nèi)存數(shù)字在每個(gè)模擬器上是不一樣的,這個(gè)問題就讓我們陷入了操蛋的內(nèi)存溢出的異常里。
如果你是一個(gè)知道如何編寫結(jié)構(gòu)清晰的代碼、能有效利用內(nèi)存的有經(jīng)驗(yàn)的j2me程序員的話,我建議你先做到讓你的一套游戲程序能夠在任何尺寸的屏幕下跑,能夠在一個(gè)僅僅支持你用到的API函數(shù)的虛擬機(jī)里跑。你可以在一個(gè)簡(jiǎn)單的工具里做到這些,這個(gè)工具在眨眼的功夫就可以運(yùn)行項(xiàng)目而且方便調(diào)試。
@那么,你打算怎么做呢?
我們之前說的操蛋事,也許你已經(jīng)在j2me開發(fā)里經(jīng)歷了成千上萬(wàn)次。怎么辦呢?
@在桌面的java中模擬j2me
用最簡(jiǎn)單有效的代碼去實(shí)現(xiàn)j2me必須的API函數(shù)其實(shí)是很簡(jiǎn)單的。在CLDC1.0和1.1中,就是java.io, java.lang 和 java.util
這幾個(gè)包里面的類都是由j2se為基礎(chǔ)編寫的,所以將庫(kù)函數(shù)重寫成單純簡(jiǎn)單的j2se代碼是必須的。javax.microedition.midlet包是很容易實(shí)現(xiàn)的。javax.microedition.lcdui包里面包含的主要的類在j2se的AWT里都有對(duì)應(yīng)的類似的,所以你根本不用費(fèi)心去在j2se里實(shí)現(xiàn)。雖然javax.microedition.rms你覺得肯定有用,但它不過是文件的保存讀取而已,很簡(jiǎn)單就可以實(shí)現(xiàn)了。
@模擬器的類概述
我們下面將概述一下模擬器所有的內(nèi)容除了MIDletStateChangeException這個(gè)類,因?yàn)檫@個(gè)類的實(shí)現(xiàn)需要建立一個(gè)手機(jī)j2me環(huán)境。j2meAPI的類MIDlet、Display、Displayable、Canvas對(duì)應(yīng)我們模擬器里的Main、FrameAWT、CanvasAWT,處理了主要的框架、入口點(diǎn)、程序顯示、鍵盤輸入。而j2me里的類Image、Graphics、Font在AWT里的實(shí)現(xiàn)分別是AWT Image、AWT Graphics、AWT Font。
@那么,開始編寫代碼吧
在包javax.microedition.midlet里的MIDlet類是所有midlet程序的啟動(dòng)入口,顯而易見的我們先來看看MIDP程序的運(yùn)行環(huán)境。所有的midlet程序都要有一個(gè)繼承了MIDlet并且重寫了MIDlet的3個(gè)抽象方法的類,MIDlet的3個(gè)抽象方法的功能正如其名:startApp、pauseApp、destroyApp。
為了順利開始,我們的MIDP模擬器需要通過幾個(gè)public方法實(shí)現(xiàn)類MIDlet來調(diào)用他控制生命周期的抽象方法。由于這些并不能實(shí)現(xiàn)MIDletStateChangeException類,那么我們也要考慮如何實(shí)現(xiàn)MIDletStateChangeException。
那么我們的模擬器需要一個(gè)“main”類來用幾句代碼加載midlet類然后用public方法去調(diào)用他的startApp。下面的代碼將加載一個(gè)midlet類。
// 創(chuàng)建我們的AWT主窗口 (后面會(huì)改的多幾行)
frame = new FrameAWT (midlet_class_name);
// 加載并且實(shí)例midlet
midlet = (MIDlet) Class.forName(midlet_class_name).newInstance();
// 調(diào)用midlet的startApp方法
midlet.amsStartApp ();
主類可能在一個(gè)包中也可能在缺省包里,并且有一個(gè)名字。我選擇“caodan”(操蛋)作為我的j2me模擬器的名字。這是個(gè)言簡(jiǎn)意賅的名字。僅僅在正確的地方用上面這三句我們就可以使用“操蛋”去運(yùn)行一個(gè)midlet程序。如果你僅僅想運(yùn)行一個(gè)簡(jiǎn)單的midlet那么你可以通過下面代碼去調(diào)用我們的“操蛋”:
java -classpath bin;BasicMidlet_v1.0.0.jar com.longsteve.caodan.Main BasicMIDlet
@資源加載
j2me使用Class.getResourceAsStream()方法來加載資源,這點(diǎn)和j2se是一樣的。如果你正在運(yùn)行jar包的里一個(gè)midlet,通過在classpath路徑上的jar(classpath:設(shè)置Classpath的目的,在于告訴Java執(zhí)行環(huán)境,在哪些目錄下可以找到您所要執(zhí)行的Java程序),任何midlet程序調(diào)用getResourceAsStream()方法都會(huì)運(yùn)行在虛擬機(jī)環(huán)境下。不一定你就非要從一個(gè)jar包去運(yùn)行你的游戲代碼。一個(gè)很快速的方法是用一個(gè)模擬器去跳過打包這步。你的classpath路徑可以包含你編譯好的類的路徑,你的游戲資源的路徑(比如png圖片)。你調(diào)試的過程也簡(jiǎn)化了,不需要一次次的打包了。
@添加一個(gè)display
現(xiàn)在可以簡(jiǎn)單運(yùn)行一個(gè)midlet了,但是我們還需要添加一個(gè)display作為顯示器。我們現(xiàn)在在用j2se寫模擬器,那么我們來用一個(gè)j2se的桌面開發(fā)工具包試試。我們可以選擇Swing的JFrame以及它的相關(guān)類,但是我總是在使用Swing時(shí)因線程問題困擾不已。用了它我們的midlet模擬器也會(huì)變得很困擾,我們只需要一個(gè)窗口(window)一個(gè)畫布(canvas),我們不需要任何奢侈的Swing控件,所以我會(huì)讓它簡(jiǎn)單又干凈,我們使用AWT。
在j2me中,midlet使用javax.microedition.lcdui包里的類來實(shí)現(xiàn)display。Display類提供靜態(tài)方法setCurrent()來讓midlet將一個(gè)可顯示的對(duì)象顯示在屏幕上。在游戲中常用的Displayable的子類主要是Canvas類。midlet控制一個(gè)繼承了Canvas的類去調(diào)用它的繪圖方法(paint(Graphics graphics))來繪制游戲內(nèi)容。
我們要?jiǎng)?chuàng)建javax.microedition.lcdui包然后添加Displayable、Canvas、Graphics到我們的模擬器“操蛋”。這意味著我們要添加AWT的Frame和Canvas類,并且讓它們和MIDP的類聯(lián)系起來。稍后我們會(huì)把MIDP里面的Image和Font類也加入到我們的測(cè)試midlet里,不久它會(huì)看起來像是一個(gè)游戲。
@主窗口
FrameAWT繼承AWT里的Frame類并且提供顯示midlet的display的主要窗口。為了獲知窗口大小,我們暫時(shí)寫一行代碼定義模擬器要顯示的midlet畫布尺寸,因?yàn)槌藦氖謾C(jī)設(shè)備的詳細(xì)資料或者真機(jī)調(diào)試數(shù)據(jù),我們無(wú)從獲知不同手機(jī)的屏幕尺寸。將屏幕尺寸做成自定義,恩暫時(shí)這樣吧。
@畫布
CanvasAWT繼承自AWT的Canvas類處理屏幕的繪制以及鍵盤和鼠標(biāo)的輸入。CanvasAWT與一般的midlet的Displayable對(duì)象差不多,就是參考Displayable類的。在Displayable類的方法invokePaint()實(shí)際上被用來調(diào)用midlet的Canvas的paint()方法。CanvasAWT類包含一個(gè)MIDP的圖片(Image)對(duì)象,用在midlet的屏幕上。每次CanvasAWT里的繪制方法被調(diào)用,它會(huì)從屏幕圖片獲取一只畫筆以此去調(diào)用midlet的Canvas的paint()方法。當(dāng)MIDP的Canvas的paint()方法返回時(shí),屏幕圖片會(huì)通過CanvasAWT的paint()方法被以AWT的畫筆重繪。
下面的代碼寫在CanvasAWT.paint()方法里。invokePaint()方法是我們實(shí)現(xiàn)MIDP的Displayable類的一部分。當(dāng)一個(gè)midlet調(diào)用Display.setCurrent()方法時(shí),我們的Display類實(shí)際上將現(xiàn)在的顯示對(duì)象設(shè)置到CanvasAWT上,然后窗口開始繪制來自midlet的paint請(qǐng)求。
public javax.microedition.lcdui.Displayable current;
public javax.microedition.lcdui.Image midp_screen;
public void paint (java.awt.Graphics g)
{
javax.microedition.lcdui.Graphics midp_graphics =
midp_screen.getGraphics ();
// 用j2me的畫筆去調(diào)用midlet的paint方法
current.invokePaint (midp_graphics);
// 將midlet的屏幕畫到我們的canvas上 縮放
g.setClip(0,0,awt_canvas_width,awt_canvas_height);
g.drawImage(midp_screen._image,
0,0,getWidth(),getHeight(),
0,0,midp_screen.getWidth(),midp_screen.getHeight(),
this);
}
AWT的drawImage()方法也可以很輕松的實(shí)現(xiàn)縮放,所以我們可以隨意調(diào)整想把midlet顯示為多大尺寸。我討厭瞇著眼看我桌面上的模擬器,有些小屏幕的設(shè)備甚至放大一倍還是顯得不爽,所以我將“操蛋”的默認(rèn)大小設(shè)置為midlet的尺寸的3倍。不過最好你將他做成自定義的,以后想設(shè)置為多大都可以。
@鍵盤輸入
CanvasAWT提供的鍵盤輸入類似Displayable類。CanvasAWT實(shí)現(xiàn)了AWT的KeyListener接口,并且提供鍵盤事件,通過j2se的鍵值直接映射到Displayable的invokeKeyPressed()方法。
我們的Displayable類包含了j2se的鍵值表并且映射到了MIDP的鍵值以及游戲動(dòng)作(game actions)。在這里使用j2se代碼比使用j2me的代碼要簡(jiǎn)單的多。然而j2me的Canvas類定義實(shí)際上已經(jīng)指定了鍵值為不變的,比如KEY_NUM0 = 48,所以使用相同的鍵值使得模擬器方便的結(jié)合于midlet。
@圖片和畫筆
MIDP的Image對(duì)象可以用java.awt.BufferedImage很簡(jiǎn)單的實(shí)現(xiàn)。BufferedImage對(duì)象可以以寬度和高度創(chuàng)建,就像MIDP的Image對(duì)象一樣也可以以一個(gè)輸入流(inputStream)或者任何MIDP的Image所支持的參數(shù)創(chuàng)建。在java5的ImageIO提供了對(duì)PNG圖片的支持,超級(jí)Image包(javax.media.jai),但是我發(fā)現(xiàn)現(xiàn)在這些讀PNG數(shù)據(jù)的方法還不靠譜。我至今用過的最快捷的最強(qiáng)大的PNG處理函數(shù)庫(kù)是sixlegs.com。庫(kù)的jar包在50k以下,他在許可GPL協(xié)議(GPL,自己去google查是啥)之外,相比java在png圖片的優(yōu)化和壓縮技術(shù)上獲得了成功。
MIDP的Graphics畫筆是從Image圖片對(duì)象直接創(chuàng)建的,我們使用從java.awt.Image對(duì)象獲取的java.awt.Graphics對(duì)象可以輕松實(shí)現(xiàn)。大多數(shù)的j2me的功能就是通過方法簡(jiǎn)單的調(diào)用了AWT的Graphics對(duì)象,比如drawLine()。不過還是有一些不同的,j2me的方法比如drawImage()和drawString()是需要錨點(diǎn)的。越來越多的強(qiáng)大復(fù)雜的功能已經(jīng)實(shí)現(xiàn),如果你開始使用MIDP2.0的話你一定接觸過drawRegion(),他可以實(shí)現(xiàn)圖片旋轉(zhuǎn)啊鏡像啊,這些絕不是不可能做到的,以后想到的都可以實(shí)現(xiàn)。
public void drawImage(Image img, int x, int y, int anchor)
{
// 默認(rèn)錨點(diǎn)
if (anchor == 0)
{
anchor = TOP | LEFT;
}
// 計(jì)算x和y的偏移根據(jù)給出的錨點(diǎn)
switch (anchor & (TOP|BOTTOM|BASELINE|VCENTER))
{
case BASELINE:
case BOTTOM:
y -= img.getHeight();
break;
case VCENTER:
y -= img.getHeight() >> 1;
break;
case TOP:
default:
break;
}
switch (anchor & (LEFT|RIGHT|HCENTER))
{
case RIGHT:
x -= img.getWidth();
break;
case HCENTER:
x -= img.getWidth() >> 1;
break;
case LEFT:
default:
break;
}
// 用MIDP的圖片畫到我們AWT
_graphics.drawImage(img._image,x,y,null);
}
@字體
跟MIDP的Font類非常相似,如果沒有它那么將不會(huì)完成對(duì)畫筆的任何操作。通過使用java.awt.Font和java.awt.FontMetrics對(duì)話,所有的功能都可輕松實(shí)現(xiàn)。有些并不是很完美,比如對(duì)下劃線的支持,但是如果你的游戲使用點(diǎn)陣字體,你完全不必操心字體的問題。
@多余的話
實(shí)現(xiàn)8個(gè)j2me的類以及3個(gè)應(yīng)用程序類足夠可以使你的模擬器得以啟動(dòng)開發(fā)的游戲。有了你需要的所有功能,你或許也可以用它來玩一些你有的j2me游戲。不過,如果你嘗試運(yùn)行一些復(fù)雜的游戲,你會(huì)發(fā)現(xiàn)它會(huì)提示類或者方法沒有找到的異常(class and method not found exceptions)。沒啥大不了的,你可以馬上根據(jù)需要將缺少的東西添加到你的模擬器里。
如果你在使用MIDP1.0開發(fā)游戲你一定會(huì)發(fā)現(xiàn)它需要javax.microedition.rms來給游戲存檔。用一個(gè)模擬的代碼就可以馬上解決這個(gè)問題,你會(huì)驚訝的發(fā)現(xiàn)它馬上又可以跑了。使用java的優(yōu)秀的文件類和IO流來實(shí)現(xiàn)實(shí)際的文件存儲(chǔ)器完全沒有困難。
然后,或許在javax.microedition.lcdui里的一些類也是需要添加進(jìn)去的。Command和CommandListener類應(yīng)該是首先被需求的。與form相關(guān)的類你也許根本不必考慮,因?yàn)榇蠖鄶?shù)游戲根本沒有用到它。
在MIDP1.0的功能基礎(chǔ)上,MIDP2.0填補(bǔ)了很多空白,比如我們之前說到的繪圖的變化。如果你想在現(xiàn)在的游戲創(chuàng)作上做出較大的改變,那么也可以使用諾基亞的界面(nokiaUI),如果你在開發(fā)MIDP2.0,那么諾基亞的界面將會(huì)是你的首選。
@音頻
javax.microedition.media包對(duì)音頻做了支持。如果你使用的是java5或者更高的版本你可以使用javax.sound.midi和javax.sound.sampled的j2se包。通過調(diào)試你要運(yùn)行的項(xiàng)目你可以實(shí)現(xiàn)正確的播放方法。
@藍(lán)牙
在沒有j2me模擬器可以支持真正的藍(lán)牙傳輸前開發(fā)藍(lán)牙的midlet程序是很麻煩的。它們只能通過傳遞實(shí)例去模擬藍(lán)牙。真正的藍(lán)牙設(shè)備使用程序都需要到真機(jī)上進(jìn)行測(cè)試,雖然開發(fā)和調(diào)試都會(huì)很慢。現(xiàn)在在j2se里有了javax.bluetooth (JSR-82)包。GNU LGPL執(zhí)行藍(lán)牙通信并且可以被添加到classpath便于開發(fā)。jsr-82可以免費(fèi)試用,它的購(gòu)買也是很便宜的。
@3D
當(dāng)你體驗(yàn)到使用j2se的模擬器開發(fā)之后,你會(huì)希望將你要到的所有API都添加進(jìn)去。手機(jī)3D API(JSR-184)現(xiàn)在被越來越多的用在手機(jī)游戲中。這個(gè)復(fù)雜的畫筆有一個(gè)叫做Rasteroid的產(chǎn)品是基于j2se實(shí)現(xiàn)的JSR-184。使用這個(gè)并不能完全讓你在你的AWT模擬器里實(shí)現(xiàn)3D,但是他可以讓你開發(fā)3D的midlet項(xiàng)目只需要短短幾行代碼。
@好處
你也許會(huì)思考放棄現(xiàn)有的各個(gè)廠商的設(shè)備模擬器而自己去努力制作一個(gè)模擬器是否值得。我自己的模擬器現(xiàn)在已經(jīng)是我開發(fā)游戲的工具了。我希望我的java游戲引擎能夠快速的反饋給我信息。最簡(jiǎn)單的方法就是在我的工具里面包含j2me必要的類和方法使得游戲引擎可以在源碼絲毫不變的情況下隨時(shí)運(yùn)行。在編寫工具的時(shí)候,java開發(fā)的立即運(yùn)行的速度(編譯運(yùn)行)讓我覺得它也適合游戲之外的別的j2me代碼。
當(dāng)我開始思考我的工具的設(shè)計(jì)需求時(shí),我已經(jīng)知道Mpowerplayer。我與其使用這個(gè)不咋地的工具,何不自己寫一個(gè)呢。Mpowerplayer 是個(gè)比較強(qiáng)大的工具但是丫挺的是收費(fèi)的。有你自己的工具是有好處的,雖然很多時(shí)候這個(gè)好處不是馬上就顯現(xiàn)出來。
@真正的敏捷開發(fā)
一個(gè)ant build程序,編譯混淆打包等步驟也許要花費(fèi)一點(diǎn)時(shí)間。你可以在你的IDE里創(chuàng)建一個(gè)項(xiàng)目,然后加入你自己的模擬器,游戲的類和資源路徑。然后點(diǎn)擊“運(yùn)行”可以馬上看到你的代碼在運(yùn)行。如果你使用的是Eclipse那你甚至開始都不用寫,它類似開發(fā)java的桌面項(xiàng)目。你可以修改我們之前說的模擬器屏幕尺寸,不需要選擇模擬器就可以讓一個(gè)游戲在不同尺寸的屏幕下運(yùn)行。
@調(diào)試
用j2se的模擬器實(shí)際上你就是在一個(gè)純凈的java環(huán)境下編寫你的midlet代碼。我前面已經(jīng)提過,你可以點(diǎn)擊“運(yùn)行”以運(yùn)行它,也可以點(diǎn)擊“調(diào)試”然后步進(jìn)源代碼。你開發(fā)j2me的游戲有多久沒有進(jìn)行源碼調(diào)試了呢?這個(gè)特性也許是拋棄傳統(tǒng)模擬器的最大理由。我知道它們應(yīng)該支持調(diào)試,并且一些新的做的也不差,但是在IDE以及調(diào)試器里對(duì)java程序的調(diào)試還需要更加成熟。你也可以嘗試更多的java調(diào)試器。
@目的
有了我們的強(qiáng)大的工具,你可以快速的開發(fā)出你的產(chǎn)品。而且我們的工具可以給策劃、測(cè)試等等在電腦上玩手機(jī)游戲的人。他們只需要在電腦上安裝一個(gè)java虛擬機(jī)即可。不需要多個(gè)模擬器,這一個(gè)模擬器就可以滿足我們的所有需求了。
使用自己的模擬器運(yùn)行你的游戲是什么感覺的?策劃可以拿這個(gè)編寫關(guān)卡,美術(shù)可以換了圖馬上就可以看到效果。你可以添加另一個(gè)AWT窗口調(diào)整游戲引擎,這樣測(cè)試可以實(shí)時(shí)調(diào)整不同的參數(shù)。美術(shù)可以自己將png圖片放到資源目錄里就可以實(shí)時(shí)的查看變化,總好過重新打包吧。
@開發(fā)
掌握你的游戲運(yùn)行環(huán)境在游戲完成后好處才顯現(xiàn)出來。很多項(xiàng)目在真機(jī)要得到設(shè)備的許可(比如發(fā)短信,錄制視頻保存圖片),保存截圖是很簡(jiǎn)單的只需要把MIDP的屏幕緩沖寫成一個(gè)png圖片就可以了。
java.io.File f = new File ("screenshot.png");
javax.imageio.ImageIO.write((BufferedImage)midp_screen._image,
"png", f);
有時(shí)將游戲錄制成視頻保存下來也是個(gè)不錯(cuò)的功能,這樣對(duì)于質(zhì)檢是很有幫助的,測(cè)試可以將一個(gè)bug以視頻的方式錄制下來。java的Media框架包含了寫AVI文件的功能,并且能夠和CanvasAWT的paint方法掛鉤輸出,就像截屏的代碼那樣簡(jiǎn)單。
通過修改自己的模擬器的設(shè)置可以實(shí)現(xiàn)所有的可能。你可以在任何平臺(tái)比如筆記本電腦全屏玩你的手機(jī)游戲,這比拿著手機(jī)瞇著眼玩要強(qiáng)得多。你可以很輕松的為VGA手機(jī)開發(fā)游戲僅僅需要將你的模擬器設(shè)置為640x480的屏幕尺寸。你可以將你的游戲在網(wǎng)頁(yè)上玩,在微型電腦上玩,在PDA上等等等等的設(shè)備。java的本機(jī)編譯也可以實(shí)現(xiàn)讓你的游戲在沒有java虛擬機(jī)的環(huán)境下運(yùn)行。(只需要將用到的庫(kù)文件一并發(fā)布,用到的其實(shí)是很小的)
@尾聲
我希望這篇文章可以說明用純凈的j2se寫一個(gè)你自己的j2me模擬器是件多么簡(jiǎn)單的事情。通過拋棄各個(gè)廠商的設(shè)備模擬器,你的游戲開發(fā)流程將會(huì)得到極大的簡(jiǎn)化。穩(wěn)定舒服的調(diào)試可以讓j2me的程序員飛奔起來!~這個(gè)“操蛋”模擬器也許會(huì)令你的手機(jī)游戲開發(fā)之路豁然開朗~
下面是英文原文
How to Code Your Own J2ME Simulator
There are few things worse for a programmer than the delay between altering some lines of code and seeing those changes working in game. Developing J2ME games is especially prone to build process delay, with many steps required between compilation and final jar file output. Add to this the complication of many different device emulators with their slow start up times, poor keyboard response and limited debugging support. Mobile Java programming can be sped up and even made a pleasurable experience with the help of some J2SE code which simulates the J2ME APIs. This article will lead programmers through the creation of a deceptively simple J2ME simulator which speeds up programming, improves debugging, facilitates rapid prototyping, aids the creation of device independent code and frees you from build processes that may take minutes to complete.
What's wrong with emulators?
I'm not going to list point by point all the things I personally dislike about using device manufacturer's emulators for J2ME development. I'm sure if you've spent any time developing J2ME applications you've felt there must be something better. Firstly, there are just so many SDKs and emulators available. Installing, maintaining, learning and integrating them into your development process is a time consuming and cumbersome task. Secondly, actually using them is all too often frustrating and slow, when they should be saving you from the even slower task of testing your game on a real device. They are slow to invoke, offer little or no feedback when errors occur, and debugging support is usually unreliable or functionally limited.
Figure 1. What you usually do.
The one area where a good emulator is essential is gauging the memory usage of your game. You can be pretty much assured that if your game runs in the Nokia 7210 MIDP SDK v1.0, it will work on a real 7210. However, you can't say the same for all emulators, and quite often you're left in the dark regarding memory errors.
If you are an experienced J2ME developer who knows how to write memory efficient, jar size and performance conscious code, I would suggest that your primary need for the majority of the programming work on a project is simply a J2ME environment that supports arbitrary screen sizes and all the relevant APIs your code utilises. You can get this in a tool that can be invoked in the blink of an eye both for normal execution and source level debugging, much more simply than you might think.
Figure 2. What you could do.
Compare the steps in figures 1 and 2, which you would potentially perform thousands of times during the programming of a J2ME game. I'd take the three steps over five any day.
Simulating J2ME with Desktop Java
Implementing the necessary J2ME APIs using pure Java turns out to be quite simple. The classes in CLDC 1.0 and 1.1 (java.io, java.lang and java.util) are all derived from their J2SE counterparts, so literally no wrapping or re-writing is required for the base standard library. The javax.microedition.midlet package is simple, well defined and easy to implement. The javax.microedition.lcdui package contains classes that (for the main part) have AWT counterparts, and those that don't, you probably never use anyway, so there's no need to implement them! You can probably do without the rest too, although javax.microedition.rms comes in handy, it's just file storage and again, easy to implement.
Figure 3. Simulator Class Overview
Figure 3 shows all but one class (MIDletStateChangeException) that needs to be implemented to create a functioning J2ME environment. The boxes in red are the main framework of the application and provide the entry point, window to display the midlet upon and keyboard input. The boxes in grey are the J2ME API classes that midlets use. These hook into each other and the simulator classes. Image, Graphics and Font are implemented using their AWT equivalent classes, shown in blue.
Start Coding
The MIDlet class contained within the javax.microedition.midlet package is the starting point of all midlet development and is the obvious place to look first when creating a MIDP execution environment. All midlets have to provide a class that extends MIDlet, and the 3 abstract methods that need to be overridden are the almost completely self explanatory startApp, pauseApp and destroyApp.
To get off the ground, our MIDP simulator needs an implementation of the MIDlet class with some public methods that call the abstract midlet lifecycle methods. Since these refer to the trivial MIDletStateChangeException, we should add this too.
Then we'll need a ‘main' application class for command line invocation that loads the concrete midlet class and invokes the public method that calls startApp. The following shows the code needed to load and invoke a midlet.
// Create our AWT main frame (more on this later)
frame = new FrameAWT (midlet_class_name);
// Load and instantiate the midlet
midlet = (MIDlet) Class.forName(midlet_class_name).newInstance();
// Call the midlet startApp method
midlet.amsStartApp ();
The main class should probably be in a package of its own, with a suitable name. I've chosen Jammy as the name of my J2ME simulator. I prefer single words over compound words or complex names that aren't very catchy. With just these 3 classes in place we are able to compile a midlet and execute it by invoking it with Jammy. If you have simple midlet built into a jar, you can invoke the simulator using a command line like Figure 5.
java -classpath bin;BasicMidlet_v1.0.0.jar com.longsteve.jammy.Main BasicMIDlet
Resource loading
J2ME uses the same mechanism to load resources as J2SE, the Class.getResourceAsStream() method. If you are running a midlet from a jar file, with the jar on the classpath, any midlet calls to getResourceAsStream will simply work in the simulated environment. You don't need to run your game code from a jar file however. One of the speed improvements of using a simulator comes from having to avoid the jar packaging step. Your classpath can simply include the directory containing your compiled classes, and your game resource directory (with PNG images in for example). Your compile - run cycle is then reduced to simply that, without the need for preverification and jar packaging.
Adding a display
Just running a basic midlet is ok, but we need to add a display. Since we're writing our simulator in Java, we can use one of the windowing toolkits available with J2SE. We could choose Swing and use JFrame and its companion classes, but I've often had trouble with threads when using Swing. As far as our midlets are concerned too, all we need is a window and a canvas, we don't need all the fancy widgets of Swing, so I'm going to keep it simple and use the AWT.
In J2ME, midlets use classes in the javax.microedition.lcdui package to interface with a display. The Display class provides the static setCurrent method, which a midlet uses to set a Displayable object visible on the screen. Canvas is the primary Displayable subclass a game will use. The midlet is coded with its own subclass of Canvas, which draws the game frames in its paint method, using a Graphics context.
We're going to need to create the javax.microedition.lcdui package and add Displayable, Canvas and Graphics to the simulator. This will mean adding AWT Frame and Canvas classes, and interfacing these with the MIDP classes. Later we'll add the MIDP Image and Font classes and expand the basic midlet even further into something resembling a game.
Main window
FrameAWT extends the AWT Frame class and provides the main window for the midlet display. In order to know how big the window should be, there's a command line argument to the main class that specifies the dimensions for the midlet canvas size. There's nothing to stop these dimensions from being cleverly worked out by referring to known device details, or by inferring the size from some metadata left over from a build process. Command line arguments are convenient for now though.
The canvas
CanvasAWT extends the AWT Canvas class and handles the screen drawing and keyboard/mouse input. CanvasAWT is linked to the current midlet Displayable object by containing a reference to it. A public method on Displayable called invokePaint() is used to actually invoke the midlets Canvas.paint() method. CanvasAWT maintains a MIDP Image object, which is used as the midlet screen. Each time the AWT paint method in CanvasAWT is called, it calls the midlet Canvas paint() method with a MIDP Graphics object derived from the screen Image. When the MIDP Canvas paint() method returns, the screen image is painted to the AWT Graphics context passed to the CanvasAWT paint() method.
The following summarises the code needed in the CanvasAWT.paint() method. The invokePaint() method is part of our implementation of the MIDP Displayable class. When a midlet calls Display.setCurrent(), our Display class actually sets the ‘current' object in CanvasAWT, so all windows paint requests get forwarded to the midlet.
public javax.microedition.lcdui.Displayable current;
public javax.microedition.lcdui.Image midp_screen;
public void paint (java.awt.Graphics g)
{
javax.microedition.lcdui.Graphics midp_graphics =
midp_screen.getGraphics ();
// Call the midlet paint method with a J2ME graphics context
current.invokePaint (midp_graphics);
// Draw the midlet screen image to this canvas, scaled up
g.setClip(0,0,awt_canvas_width,awt_canvas_height);
g.drawImage(midp_screen._image,
0,0,getWidth(),getHeight(),
0,0,midp_screen.getWidth(),midp_screen.getHeight(),
this);
}
The AWT drawImage() method also handily scales up the MIDP screen image to the required size, so you can resize the main window and see a zoomed in view of the midlet. I hate squinting at tiny emulator windows on my desktop, even the 2x zoom offered by some device emulators isn't enough. By default, Jammy creates its canvas 3x the required size, but a command line parameter could be used to set this as you like.
Keyboard input
Keyboard input is handled by CanvasAWT in combination with the Displayable class. CanvasAWT implements the AWT KeyListener interface, and handles keyboard events, passing the J2SE key code straight to the invokeKeyPressed() method of the current Displayable.
Our Displayable class contains a table of J2SE key codes and their mappings to MIDP key codes and game actions. It would have been easy to simply re-use the J2SE key codes here, instead of inventing (or borrowing) J2ME codes. However, the J2ME Canvas class definition actually specifies the constant values for the codes (e.g. KEY_NUM0 is 48), so using the same ones will make the simulator directly compatible with existing midlets in this respect.
Image and Graphics
MIDP Image objects are implemented fairly simply by using a java.awt.BufferedImage. BufferedImage objects can be created with a width and height, like MIDP Image objects, or created from input streams using one of a number of image support methods or libraries. The Java 5 ImageIO class contains support for PNG images, as does the Advanced Imaging (javax.media.jai) package. I've found these methods of reading PNG data unreliable though. By far the fastest and most robust PNG library for Java (that I've used) is the library from sixlegs.com. The library jar is under 50k, licensed with a library exception to the GPL and succeeds in reading optimised and crushed PNGs where the built in Java support fails.
MIDP Graphics objects are created directly from Image objects and are implemented using a reference to a java.awt.Graphics object, created from the java.awt.Image. Most of the J2ME functionality is simply forwarding on method calls to the underlying AWT Graphics object, e.g. drawLine(). There are some differences though, which include the image anchors commonly used with J2ME for the drawImage() and drawString() methods, see below for an example. Things do get more complex if you start implementing any enhanced MIDP 2.0 methods like drawRegion(), which includes image transformations like rotation and mirroring. These are by no means impossible though, and some investigation into the AWT Graphics2D and AffineTransform classes should provide all the methods required.
public void drawImage(Image img, int x, int y, int anchor)
{
// default anchor
if (anchor == 0)
{
anchor = TOP | LEFT;
}
// Work out the x and y offsets given specific anchors
switch (anchor & (TOP|BOTTOM|BASELINE|VCENTER))
{
case BASELINE:
case BOTTOM:
y -= img.getHeight();
break;
case VCENTER:
y -= img.getHeight() >> 1;
break;
case TOP:
default:
break;
}
switch (anchor & (LEFT|RIGHT|HCENTER))
{
case RIGHT:
x -= img.getWidth();
break;
case HCENTER:
x -= img.getWidth() >> 1;
break;
case LEFT:
default:
break;
}
// Draw the AWT image within the MIDP image to our AWT
// graphics context
_graphics.drawImage(img._image,x,y,null);
}
Font
Closely related to the Graphics class is the MIDP Font class, and any implementation of Graphics cannot be completed without it. By using java.awt.Font and java.awt.FontMetrics objects, all of the functionality is fairly simple to code. Some elements aren't quite perfect, like underlined text support, but if your game uses a bitmap font, you're not going to worry too about any text limitations.
Extras
Implementing just eight J2ME classes and three application classes is enough to start developing games using your simulator. You have all the components you need, and you may even find that you can run some existing games you already have. However, if you try running anything complex, you're likely to run into class and method not found exceptions. What's great though, is you can immediately see any components you need to add in order to flesh out the simulator.
Assuming you try a MIDP 1.0 compliant game you'll almost certainly find it needs an implementation of the javax.microedition.rms record store package. Stub this out with some dummy code and you'll be surprised at how much will now run. Adding actual file storage isn't hard either using Java's excellent file and stream IO classes.
Following that, it will probably be the additional classes in the javax.microedition.lcdui package that need adding. Command, and CommandListener should be the first, allowing access to device ‘softkeys'. You'll probably not need any of the forms classes, since most games don't use these. Figure 4 shows Jammy running our old X-Change game.
Figure 4. Jammy running X-Change
Following on from the basic MIDP 1.0 functionality, MIDP 2.0 additions slot in fairly obviously, I already mentioned the graphics transformations. If you really want to get a wide range of existing games working, then adding the Nokia UI would be good too. If you do MIDP 2.0 first, then the Nokia UI can be implemented completely on top of it.
Audio
The javax.microedition.media package can be added for audio support. You can use the javax.sound.midi and javax.sound.sampled J2SE packages if you're using Java 5 or above. Again, stub out the classes first so that any midlet you are attempting to run actually works without crashing, then you can implement the functions and get real sound playing.
Bluetooth
Developing midlets with Bluetooth can be awkward since none of the manufactures J2ME emulators support actual wireless transmission. They all simulate it between running instances of emulators. Any actual integration with a real Bluetooth device is going to need testing on a phone, which slows the development and debug cycle immensely. There are javax.bluetooth (JSR-82) packages for J2SE available today. One GNU LGPL implementation is called BlueCove and can be dropped into your classpath for development. Another JSR-82 implementation is called avetana and is available for free trial, with a very reasonable fee for continued use.
3D
Once you've experienced the benefits of developing with a J2SE simulator, you'll want it to support all the common APIs your games utilize. The Mobile 3D Graphics API (JSR-184) is increasingly important to J2ME games. Hybrid Graphics have a development product called Rasteroid which is a J2SE implementation of JSR-184. Adding this to your simulator won't quite provide binary compatibility with midlets due to it's direct dependency on AWT classes, but it will allow you to develop 3D midlets with only a couple of minor source code tweaks.
The Benefits
You might wonder if programming your own J2ME simulator is worth the effort, in the face of existing emulators from device manufacturers. My own simulator application grew out of a level design tool for a J2ME game. I wanted to use my game engine within the Java based design tool to provide rapid feedback to the designer. The simplest way to do this was to include the necessary J2ME support classes and methods within my design tool so the game engine class could be dropped in without any source code changes. While coding the design tool, the speed of standard Java development (compile and run) along with instant source code debugging made me wish I could carry on doing it for the rest of the game code.
In the end, I re-factored the J2ME support code into a stand alone application that ran my complete midlet. Then, over new projects, I've gradually implementing more supporting J2ME code as it was needed.
When I first thought about my design tool requirements, had I known of Mpowerplayer I probably would have used it, rather than write my own tool. Mpowerplayer is a great development tool and the company have built a quality service offering around it. There are additional benefits to owning your own in house tool however, many of which weren't immediately obvious.
Really Rapid Development
An ant build process, with its compile, obfuscate, preverify and jar steps will take several seconds. You can set up a project in your IDE that includes your simulator classes, game classes and resource path, hit the ‘Run' button and see you code running immediately. If you use Eclipse you won't even need to compile first, it's like developing any desktop Java application. If your standard studio project environment includes any sort of meta data about your current device target, you can build this into the simulator and have it start automatically at the correct screen dimensions. No need to switch emulators to perform the bulk of the porting work, retargeting a game at a different screen size.
Debugging
With a J2SE simulator you're essentially coding your midlets in a pure Java development environment. As well as hitting the ‘Run' button in your IDE (did I mention that already?), you can hit the ‘Debug' button, and step through the source code. How long have you been developing J2ME games without source code debugging? This feature alone is probably the single best reason to ditch traditional emulators. I know they are supposed to support debugging, and some of the modern ones don't do a bad job, but source level Java debugging in your IDE and debugger of choice is much more mature. You can also take advantage of numerous Java profiler tools.
Game Design/Prototyping
Having nailed the rapid development cycle, you're going to be able to knock up prototypes faster. These builds can be given to designers, testers and anyone else to play with on their PCs, and all they need is a Java Virtual Machine installed. No need for numerous emulators, all to run different builds of the game.
How about a design tool that incorporates a build of your game using your simulator as the runtime? Designers can code levels, or artists can change tile sets and instantly see the results. You could add another AWT window to a special build with some sliders or checkboxes that allow game engine adjustments, so testers can tune difficulty parameters in real time. Artists can drop PNG images directly into the resource directory to see real time changes in the game, rather than fiddling with zip files and updating the JAD files.
Post Development
Having control of the environment your games run in brings further benefits after a game is complete. A lot of post development work is involved in obtaining submission material for operators, screenshots and video footage. Adding a screenshot facility is simply a matter of taking the MIDP screen buffer and writing it to a PNG file (inside CanvasAWT):
java.io.File f = new File ("screenshot.png");
javax.imageio.ImageIO.write((BufferedImage)midp_screen._image,
"png", f);
Another potential time saver would be the ability to output video files of a game session. This would benefit QA too, with testers being able to record details of a bug. The Java Media Framework contains functionality to write AVI files, and could be hooked into the paint output of CanvasAWT, like the screenshot code.
Having a set of code not constrained to a J2ME execution environment opens up countless possibilities for running your games. You can demonstrate J2ME games to potential clients using a laptop and a projector, rather than hand them a phone to squint at, they get a full screen presentation. You can plan for future developments like VGA screen size phones simply by setting your simulator display to 640x480. Running your games as applets on the web is another obvious possibility, as are Pocket PC and PDA type devices. Java native compilers might also offer a quick route to alternative executable versions of your games for non-Java platforms.
Conclusion
I hope this article has shown how simple it is to write your own J2ME simulator using pure J2SE Java. By becoming (mostly) free of manufacturers device emulators, your full game development cycle can be positively enhanced. Stable source level debugging alone should have J2ME programmers running! Taking control of your midlet execution environment might also open up surprising new avenues for your games.