0 初始化游戲 1 是否結束游戲(Yes:轉到6) 2 搜集玩家輸入信息 3 運行游戲邏輯 4 更新下一幀,顯示下一幀 5 回到1 6 清理,結束游戲 這是一個最基本的結構,特別對於比較簡單的J2ME游戲來說,這個結構更加有代表性。下面我們將分別講述專業手機游戲如何實現這個結構中的各個內容。
游戲循環的實現 我們需要一個進入後就一直循環下去直到游戲結束的結構。線程正好可以實現。最通常的做法是讓Canvas實現Runnable接口。於是我們就可以實現run方法。下面是一個run方法簡化版:public void run()
{
exitMidlet = false ;
long startTime = 0 ;
long timeCount = 0 ;
gameInit() ;
int curKey = 0 ;
while (!exitMidlet) {
startTime = System.currentTimeMillis();
//acquire key
acquireKey() ;
//call game loop
gameLoop() ;
//repaint the screen
repaint();
serviceRepaints();
frameCount++ ;
//lock fps
timeCount = MIN_DELAY - (System.currentTimeMillis() - startTime);
timeCount = (timeCount<1)?1:timeCount ;
try {
Thread.sleep(timeCount);
} catch (InterruptedException ex) {}
}
endMidlet() ;
}
看到我們的while循環了嗎?除非在程序邏輯中設定exitMidlet為true-那是當玩家選擇了退出游戲,我們的游戲將一直運行下去。在while循環之前,gameInit方法的作用是進行游戲初始化-比如初始化變量值,載入全局數據,生成全局對象等。在while循環中,我們先是調用了acquireKey方法,這個方法將鍵盤輸入信息進行緩沖以便邏輯中判斷按鍵狀態,下面講會講到鍵盤緩沖。gameLoop是我們游戲的主體,每幀中的邏輯運算,圖形處理都在這裡面進行。然後是repaint和serviceRepaints,刷新屏幕-新的一幀呈現在屏幕上。最後當跳出while之後,我們執行endMidlet結束這個Midlet。endMidlet的內容只是調用了destroyApp和notifyDestroyed方法。好了整個游戲循環就是這樣了,下面講分別講述鍵盤緩沖和gameLoop如何組織。不過再這之前先讓我解釋下lock fps。FPS就是Frame per second。為了防止游戲在不同的機器上速度變化太大,我們設定一個最大的FPS值,或者說設置一個每幀至少要花費的時間(這裡的MIN_DELAY)。比如我們設置MIN_DELAY=50,那麼max FPS = 1000/50 = 20 幀/秒。鎖定FPS有多種方法,這裡的方法是判斷如果一幀所有的時間還沒達到最大時間,那麼就讓線程sleep一會兒。順便在說一下FPS的計算,顧名思義用1000除以一幀所有時間即可,不過要注意的是,一般計算的FPS是平均FPS,所以FPS=累計幀數*1000/累計花費時間。
鍵盤緩沖
搜集玩家輸入信息是一個很重要的內容,我們知道J2ME中可以在keyPressed和keyReleased事件中處理按鍵內容,但這樣勢必將邏輯代碼分散與gameLoop和keyPressed中。如果你說將所有邏輯代碼放在keyPressed中,那可不行,因為keyPressed只有在按鍵的時候才產生,而即使沒有按鍵游戲也有很多邏輯運算要做。所以專業游戲開發中采用鍵盤緩沖將按鍵信息存起來,然後在gameLoop中就可以判斷這一幀按鍵的狀態,利用按鍵緩沖,除了可以判斷一個鍵是否按下松開,還可以判斷一個鍵是否一直被按住了,甚至可以判斷組合鍵。不過在這裡,我只介紹一種最簡單的按鍵緩沖。由於篇幅所限,只講述原理並不給出具體代碼。
首先我們需要一個數據結構存儲按鍵信息。你可以為每個鍵用一個bool值存儲它的狀態,不過專業一點的做法是用一個位表示一個鍵的狀態,一個int有32個位,足夠表示大多數手機的所有按鍵了。因為我們要判斷鍵是否按下或松開,為了方便,我們再用2個整數記錄這兩種狀態。所以現在我們一共有三個int了:
static int key , pressedKey, releasedKey ;
OK! 有了存儲的地方,我們還需要一些常量表示每個位,我們設定key中某個位為0表示某鍵沒有按下,為1表示按下。如果用第1位表示0鍵,第2位表示1鍵,那麼可以這樣設置常量:
final static int GKEY_0=1<<0, GKEY_1=1<<1 ;
明白了嗎?這些常量是用來指定某個位用的,比如GKEY_1其實就是第2為1其它位均為0的一個整數。如果還不明白,先看下面的。
keyPressed和keyReleased裡將分別將按下的鍵和松開的鍵進行記錄。
public void keyPressed(int keyCode)
{
int value = getKeyValue(keyCode) ;
key |= value ;
pressedKey |= value ;
}
public void keyReleased(int keyCode)
{
int value = getKeyValue(keyCode) ;
key ^= value ;
releasedKey |= value ;
}
在keyPressed裡面,我們先將keyCode轉換成我們的按鍵常量,就是上面的GKEY_0等。因為keyCode可不是像我們的常量那樣可以用某個位表示的,而且不同手機的keyCode是有可能不一樣的,所以我們必須用一個函數getKeyValue進行轉化。得到常量後key|=value的作用是將key裡面常量所代表的位置1,現在你應該明白常量的作用了吧!pressedKey|=value同理。不過keyReleased有些不同,由於releasedKey只記錄這一幀裡哪些鍵“被松開”了,所以仍然用或運算。但key是記錄整個按鍵的狀態,所以用異或。
接下來就是如何判斷按鍵狀態了:
private static void acquireKey()
{
frameKey = key ;
framePressedKey = pressedKey ;
frameReleasedKey = releasedKey ;
pressedKey = 0 ;
releasedKey = 0 ;
}
public static boolean keyHold(int gameKey)
{
return ((frameKey & gameKey)!=0) ;
}
public static boolean keyDown(int gameKey)
{
return ((framePressedKey & gameKey)!=0) ;
}
public static boolean keyUp(int gameKey)
{
return ((frameReleasedKey & gameKey)!=0) ;
}
還記得acquireKey吧,我們在while循環中首先要調用它,其作用就是記錄下這一幀的按鍵狀態,所有我們用了三個新int變量記錄他們,分別是frameKey,framePressedKey和frameReleasedKey。acquireKey所做的就是將按鍵狀態記錄在這3個變量中,其實framePressedKey和frameReleasedKey不是必須的,只是這樣看上去比較清楚。記錄完後我們將pressedKey和releasedKey清空,以便下次有鍵按下或松開時記錄新的信息。關鍵的三個函數登場了,keyDown判斷某個鍵是否在這一幀裡被按下,參數gameKey是我們定義的按鍵常量中的某個值。如果對位運算還算明白的話,很容易看懂這3個函數。唯一要說明的就是keyHold和keyDown的區別,keyHold表示某個鍵一直被按著,也就是至少從前一幀開始它就被按下了,而不是在這一幀裡被按下的。現在你應該明白我們為什麼要清空pressedKey和releasedKey了。
說到這裡也差不多了,有了這個按鍵緩沖我們就可以在gameLoop中調用keyDown等方法判斷按鍵的狀態了。不過我還是要說一句,這只是最簡單的按鍵緩沖,只能緩沖一次按鍵,如果一個鍵被多次按下就不行啦。更專業的內容需要你在實際工作中探索。