游戲是什麼呢?在游戲中,往往是顯示各種各樣的畫面,玩家可以做一些設定好的控制,畫面根據玩家的控制有所變化。從這兒可以看出,游戲至少需要三個功能-顯示畫面,接受玩家輸入和對輸入產生反饋。這就是常說的渲染,輸入輸出和邏輯三個模塊。將這三個模塊組合在一起有很多方法。比如事件驅動-畫面保持不變直到接受到輸入事件,程序進行邏輯運算然後改變畫面。而游戲往往不是這樣,游戲是時間驅動的。也就是說無論有沒有輸入,游戲都在不停的循環-檢查是否有輸入,運行邏輯,渲染畫面。這裡我多說一句,其實什麼樣的結構並不能區別一個程序是不是游戲,其實只要可以互動娛樂的程序就可以稱為游戲,也不一定要用時間驅動,但采用時間驅動是專業游戲的普遍做法,也是很容易接受的方法-因為從一定程度上講,游戲很像電影,隨著時間流逝畫面在改變。
既然是時間驅動,游戲中就會有幀的概念。所謂幀就是某個時刻顯示在屏幕上的畫面。從整體上看,游戲就是一系列的幀不斷播放著,像動畫片一樣,不過玩家可以通過交互改變播放的內容。而我們開發游戲的主要任務就是安排每一幀的內容。在每一次游戲循環中,我們需要搜集玩家的輸入、運行邏輯以更新游戲的數據、根據更新後的數據安排下一幀顯示的內容。 所以一個最簡單的游戲結構就是:
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等方法判斷按鍵的狀態了。不過我還是要說一句,這只是最簡單的按鍵緩沖,只能緩沖一次按鍵,如果一個鍵被多次按下就不行啦。更專業的內容需要你在實際工作中探索。