一個J2ME的2D游戲技術DEMO????????????????????
源碼計劃了很久了,準備作為J2ME的Game APIs的例子貼出來,無奈一直不得空,直到最近才簡單地整理了一下,把它共享出來。
嚴格地講,這個不能算作一個完整的游戲,沒有自己的創意,只是簡單地模仿了一個Flash的小游戲,當時是為了自己練習用的,Game Play也很簡單,但包含了J2ME的Game包中所有的東西,作為一個Demo來講,內容還是很充實的。
在介紹之前,先簡單地提一下Gaming API中的幾個類,包括Sprite,TiledLayer,LayerManager,Media.Player等。
Sprite:偶爾看到過有些人把它翻譯成精靈,我偷懶一下,就不翻譯了,這個類用來表達游戲中的一個活動的角色,包括玩家控制的Player角色和非玩家控制的角色(NPC)。
TiledLayer:這個是用來表達背景的類,其實從繪圖的角度來看,Sprite和TiledLayer沒有本質差別,只是將要畫在屏幕上的一幅圖像而已,因此在Game包中它們都是Layer類的子類。并且都能夠從一幅圖像方便地構造。
LayerManager:這個是用來管理所有圖像對象的類,通過把Sprite和TiledLayer加入其中,J2ME設備就知道如何來繪制它們了。
GameCanvas:最后要提到的是畫布,這是所有可視對象最終要表演的舞臺。其實跟以前的Canvas沒有本質的不同,同樣是提供了一個Graphics接口來供把一些內容畫上去而已,但增加了對玩家輸入的處理,能夠通過按鍵狀態來直接讀入玩家按鍵操作,相對更加簡便了。
還利用到了Media包中的Player和ToneControl來播放音樂,沒有音樂和聲音的游戲是不可能出現的,呵呵。
先簡單地介紹一下這個游戲,玩家只能控制角色(一個端著網兜的小人)水平地移動,接住自然下落的小球就得分,積分到一定程度后,小球下落速度將加快,直到最高速為止;如果沒接到小球,也有相應的懲罰,最終游戲會Game Over。
為了避免過于單調,玩家角色不是簡單地平移,而是利用了Sprite的簡單幀動畫來讓角色看上去有些動作。其實很方便的,只是在移動位置時更換一下圖像就行了,Sprite提供有幾個方法NextFrame(),PrevFrame()用來切換。
聲音部分就更簡單了,只是重復地播放一段預先寫進去的音樂,來自Sun的WTK中的一個例子。
不過既然提到了它,就還是先簡單地說一下吧,免得后面介紹其他部分時有些疑問。
J2ME中的聲音部分非常簡單,當然效果也不太好,所需的基本元素只有如下幾個,一個內容部分的Byte系列;一個是播放器Player;還有一個是控制部分的ToneControl。代碼示例如下:
????????????? tonePlayer = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR);
????????????? tonePlayer.setLoopCount(-1);
????????????? tonePlayer.realize();
????????????? ToneControl tc = (ToneControl)tonePlayer.getControl("javax.microedition.media.control.ToneControl");
????????????? tc.setSequence(mySequence);
當然還需要些初始化工作和異常處理,然后就可以通過tonePlayer.start()/close()方法來控制聲音的播放和停止了。
具體請參見源碼中CanvasGetBall.java中的createTonePlayer()方法。
鑼鼓響了半天,主角也該出場了。這個游戲里的主角只有一個接球小人,不過角色還有一個跟它演對手戲的NPC,就是那只從天而降的小球了。這兩個類都是Sprite的子類,小球因為有些自己的動作,同時實現了Runnable接口,能自主活動。不過也很簡單,NPC嘛,一般來說都是相對弱智些,不然也沒法玩了,誰的反應速度跟得上機器啊,再說了,NPC知道的信息也要多些:)。
先來看看主角吧,BallPlayer類就是我們的主角,其實非常簡單,在所有的6個類中,除了記分用的Score外就數它最小了。提供一幅圖像把它實例化后,就只能通過左右移動來控制了,額外的幾個方法都是跟記分有關系的,先略過不提。
先來看看它的構造方法:
? public BallPlayer(Image img, int fw, int fh) {
??? super(img, fw, fh);
??? this.step = fw / 2;
? }
主要工作都由它的父類Sprite做了,給出一幅圖像,這個圖像是用PNG格式提供的,大家可能留意到不是一幅單一的圖像,而是有點象幀動畫中的幾個關鍵幀,不錯,的確如此,構造方法中的后兩個參數就是告訴Sprite如何分割這幅圖像的。這里整個Player共有6個關鍵幀,比較粗糙,呵呵,自己動手截屏做的:)
Field step是用來控制主角的移動步伐的,為了快一點,取了它身寬的一半。
接下來我們看看如何移動它,就是通過這樣兩個方法來左移和右移。
? public void left() {
??? prevFrame();
??? if( (getX() - step) >= -12 ) {
????? move( -1 * step, 0);
??? }
? }
? public void right () {
??? nextFrame();
??? if((getX() + step) < canvas.getWidth()) {
????? move(step, 0);
??? }
? }
留意一下,這里只管相對位移,主角的開始位置通過setPosition來設定,在運動過程中最好就不要直接設置位置了,增大計算量,要不就看起來動作不自然了。
接下來簡單說一下配角--球。球的構造跟主角類似,只是為了節省構造銷毀對象帶來的開銷,這個對象是一直存在的,也就是讓它掉下去了又自己起來,并根據記分來確定下落速度,簡單地用線程實現的,沒怎么仔細設計,大家看看代碼就清楚了。
再來看看CanvasGetBall這個類,它從GameCanvas繼承,并實現了CommandListener和Runnable兩個接口,是整個游戲中最復雜的一個類了,主要工作有如下幾個部分,實例化主角,配角對象,還有背景對象,音樂等,并在適當的時候畫出這些對象,在頂部畫出些狀態信息,并根據玩家操作開始和暫停游戲,并顯示相應畫面。并通過進行碰撞檢測來判定玩家是否接到了小球。
檢測方法如下:
? private boolean notMiss( ) {
//??? return player.collidesWith(ball,false);
??? int ballCX = ball.getX() + ball.getWidth()/2;
??? int ballCY = ball.getY() + ball.getHeight()/2;
??? int playerCX = player.getX() + player.getWidth()/2;
??? int playerCY = player.getY() + player.getHeight()/2;
??? return ((Math.abs(playerCX - ballCX)< ball.getWidth()/2) &&
??????????? (Math.abs(ballCY - playerCY) < 5));
? }
被注釋掉的一行是直接用Sprite的碰撞檢測,下面的部分是自己計算兩幅圖像有沒有重疊,效果差不多。其中collidesWith()的第二個參數是告訴內部方法是否要用像素級別的檢測,通常答案是千萬不要,這很慢的,而且沒有必要這么精確。
為了說明整個游戲的控制邏輯,我們先來看看MIDletGetBall這個類,跟通常的MIDlet略有不同,因為我把主線程放在了CanvasGetBall中,MIDletGetBall只是簡單地控制主線程就行了。
? public void startMainThread() {
??? Display.getDisplay(this).setCurrent(displayable);
??? if(mainThread != null) {
????? mainThread = null;
????? Runtime.getRuntime().gc();
??? }
??? mainThread = new Thread(displayable);
??? mainThread.start();
? }
其中第一行就是設置當前顯示頁面;也就是顯示CanvasGetBall。
回到CanvasGetBall,整個游戲分幾個階段,相應有不同的畫面和命令接口,詳細說明如下:
1. 等待開始,對應在方法ready():
???? public void ready() {
??????? cover.setTitle(TIPS[2]);
??????? cover.addCommand(playCommand);
??????? Display.getDisplay(MIDletGetBall.instance).setCurrent(cover);
???? }
??? 為了繪制方便,這里單獨用了個GameCanvas來繪制提示信息和響應命令,并根據玩家操作在CanvasCover和CanvasGetBall兩個畫面之間來回切換。
2. 游戲畫面,包括啟動和結束兩個方法:
?public void start() {
??? if(!playing) {
????? strTip = TIPS[0];
????? playing = true;
????? MIDletGetBall.instance.startMainThread();
????? removeCommand(playCommand);
????? removeCommand(resumeCommand);
????? addCommand(pauseCommand);
????? ball.start();
????? try {
??????? if(tonePlayer != null) {
????????? tonePlayer.start();
??????? }
????? }
????? catch (MediaException ex) {
??????? tonePlayer.close();
??????? tonePlayer = null;
????? }
??? }
? }
? public void stop () {
??? if(playing) {
????? ball.stop();
????? strTip = TIPS[1];
????? try {
??????? Thread.sleep(300);
????? }
????? catch (InterruptedException ex) {
????? }
????? playing = false;
????? removeCommand(pauseCommand);
????? addCommand(resumeCommand);
????? try {
??????? tonePlayer.stop();
????? }
????? catch (MediaException ex1) {
??????? tonePlayer.close();
??????? tonePlayer = null;
????? }
??? }
? }
?并對應設置相應的命令來讓玩家能夠繼續下去,構成一個簡單的封閉控制環路。
3.游戲結束,對應方法gameover()
? public void gameover() {
??? this.stop();
??? cover.setTitle(TIPS[3]);
??? cover.removeCommand(playCommand);
??? cover.addCommand(restartCommand);
??? Display.getDisplay(MIDletGetBall.instance).setCurrent(cover);
? }
說到這里,基本上也就把它講完了,具體內容請詳細研究源碼,其實沒必要看太多書,深入地研究一個問題并根據自己的理解來改進或者是修正它,實踐才是最好的老師,希望大家能夠有所收獲。
總結一下,這個游戲存在的問題有如下幾個:
1. 沒有好的Game Play,畫面很差;
2. 可玩性不強,控制比較單調;
3. 游戲聲音過于單調;
4. 運行速度有些慢。
但作為一個技術Demo,它涵蓋了Game包中的所有內容,并提供了一個利用線程方式實現簡單游戲的方法,很簡單,但不適合真實的游戲,比較費時。
背景處理很差,可以通過一個Map來分割組合處理背景小片,能讓游戲場景變得生動些,可以實現類似于卷軸游戲的效果,自己試試吧!
附:源代碼和工程,在JBuilderX下編譯,同時需要WTK2.0或以上版本。好久沒有用Jbuilder了,買不起正版:),現在主要開發工具是Eclipse和EclipseME,感覺非常爽,免費的也有好貨。