狀態機是編譯原理的內容,看上去挺復雜的,不過說白了就是選擇分支結構。但我為什麼要提狀態機呢?其實它是一個簡化問題的好工具。再復雜的問題都可以被分解成若干小問題去解決。雖然一個游戲很復雜,但我們把它分解成若干塊,分而治之,就簡單多了。分類的依據就是狀態。我們可以將一個游戲劃分成很多狀態。比如主菜單狀態,控制主角狀態,暫停狀態等。在狀態中可以再細分子狀態。一直分下去,直到問題變簡單。下面看看某個游戲的gameLoop片斷。
public void gameLoop()
{
switch(gameState){
case GS_Logo:
logic_Logo() ;
break ;
case GS_MainMenu:
logic_MainMenu() ;
break ;
case GS_PlayerControl:
logic_PlayerControl() ;
break ;
case GS_PauseMenu:
logic_PauseMenu() ;
break ;
case GS_GameOver:
endGame() ;
break ;
}
}
針對不同的狀態進入不同的邏輯處理函數,問題就簡單多了。從某個角度來說,游戲就是狀態的集合,我們要處理好狀態之間的轉換,寫好每個狀態的代碼。在寫狀態機之前,最好先畫出狀態轉換圖。這樣條理清楚,而且便於觀看整個游戲的結構。舉個例子,下面是一個ARPG游戲中敵人的AI狀態圖:(圖略)
從狀態轉換圖很容易得到AI代碼,而且容易檢查錯誤。常犯的錯誤是沒有處理所有可能的狀態。
畫出狀態轉換圖並仔細檢查是個不錯的方法.當然對於簡單的AI就不必要麻煩了。中等復雜的AI畫一個簡單的小圖就可以解決問題。
渲染
首先解釋一下渲染這個詞,英文是render,用在這裡只是繪制的意思,這麼用只是一個習慣。上面講的run函數中並沒有任何渲染的代碼。這是由J2ME的結構決定的,所有的繪制代碼被放在了paint裡面。當然如果你使用一個Muttable Image緩沖屏幕,也可以在gameLoop裡面對這個Muttable Image進行繪制,然後在paint裡一次性的將這個Image貼到屏幕上。不過由於大多數手機硬件上支持雙緩沖-paint的參數來自一個後台緩沖,這麼做並沒有太大的意義,反而白白浪費一塊內存。所以J2ME游戲中的渲染往往放在paint中。
在paint裡面我們進行繪制工作也是分狀態進行的,比如logo狀態,我們在gameLoop裡面根據時間(可以用run裡面的那個frameCount代替真實時間)設置下一幀將要顯示的logo圖片,然後在paint裡面將當前的圖片畫出來。其實也有一種寫法是將gameLoop的代碼寫在paint中,因為paint是通過repaint和serviceRepaints每幀強制調用的,所以這樣做完全可行。如果這樣paint就是gameLoop,不需要另外一個gameLoop了。
整個游戲的渲染可以按層次分幾塊進行。首先是場景的渲染,然後是場景中的所有物體,我稱之為Sprite。這些Sprite需要根據位置進行排序後按順序渲染,這樣才能顯示出正確的遮擋關系。場景有可能是分層的,比如分兩層,第一層是地面,第二層是比較高的物體如大樹。這樣所有的Sprite就要放在這兩層之間。當然不排除有些Sprite能飛到樹上面,這需要單獨處理。我這裡說的Sprite並不是MIDP Game API裡面的Sprite類,它只是一個概念而以。其實專業手機游戲開發中很少用到Game API,往往是自己開發一套類似於Game API但功能強很多的引擎。對於初學者,可以利用Game API進行比較方便的開發,但一定不能依賴於Game API,畢竟有能力開發出比Game API更好的引擎才是專業游戲開發者應該具有的素質。而且不依賴於某某API對於移植也很有好處。所有的渲染中,最上層的一般是游戲界面(GUI)的繪制。專業的說法是HUD(Headup Display)。這些GUI包括菜單、狀態條、數據信息等等。
渲染除了繪制的簡單意思,往往還有一層圖形效果的意思。在PC游戲開發中,常常使用Alpha混合、飽和運算、光影等各種效果。這些效果都是通過像素操作實現的。在手機上由於設備能力的限制,這些操作往往速度非常慢,不過隨著手機硬件的發展,終究會被用上。除此之外,粒子系統是一種常常使用的效果,在手機游戲開發中也經常運用。有興趣的讀者可以搜索相關的文章學習。
場景與角色
場景與角色是構成游戲的兩大要素。從程序角度說,他們是兩個重要的數據結構。本節將從數據結構、渲染方法、物理作用等方面分別講解。
1 場景管理
場景是什麼呢?場景就是游戲角色所存在的世界。在RPG、SLG、RTS等類型的游戲中,場景的概念比較明顯,從視覺上看就是游戲的地圖。不過從程序的角度看,場景是一種數據結構,不但包含了地圖顯示的圖形信息也包含了角色在場景中活動所需要的物理信息和事件信息。比如地圖上有些地方是不可以通過的,有些地方主角走過去會觸發一個事件等等,這些信息往往包含在場景中。廣義的場景還包含地圖上的NPC信息,而狹義的場景僅包含地圖和物理層。這裡討論狹義的場景,即不包含NPC信息的場景。
首先討論怎樣組織圖片顯示場景。最常用的方法是拼Tile,俗稱貼瓷磚。整個場景用有限的圖塊拼接而成。Game API中的TiledLayer就是這種方式。使用Tile方式,一般用一個二維數組表示一張地圖。實際使用中,往往使用一維數組代替二維數組,這裡為了講解方便使用二維數組描述。二維數組很好的對應了二維坐標。二維數組的每一項對應地圖上某格使用那個瓷磚,整個二維數組就表示整張地圖。為了方便的編輯地圖,往往需要開發地圖編輯器,所見即所得的編輯地圖,將生成的數據存儲為文件,在游戲中通過讀取文件獲取數據。如果場景有多層,比如比較高的建築物,就要編輯多層地圖了。數據組織好了,渲染的時候只要按順序將二維數組中的每一項對應的Tile在指定位置畫出來。實際操作中,地圖往往遠遠超出屏幕大小,這就涉及到卷軸的概念。在卷軸式地圖中,屏幕可以看成是一個攝像機,每次只顯示地圖的一部分。隨著主角的移動,屏幕所顯示的部分跟著變化,形成卷軸。
卷軸式地圖顯示的時候,從屏幕(攝像機)所覆蓋的第一個Tile開始繪制。需要注意的是屏幕所能覆蓋的Tile包括部分覆蓋的Tile。上圖中第一個Tile就是一個部分覆蓋的Tile,盡管只是部分覆蓋,它也是需要繪制的第一個Tile,否則屏幕上就會出現沒有圖像的部分了。造成部分覆蓋的原因是屏幕的卷動速度並不是Tile寬度的整數倍。在實際開發中,卷軸速度往往可能是變化的,這樣的卷軸顯得比較自然。按照一定的順序將屏幕所能覆蓋到的所有Tile都繪制一遍,場景的繪制工作就完成了。這些Tile繪制的時候要采用相對於屏幕的坐標,而不是相對於地圖的坐標。在2D游戲中經常用到屏幕坐標和地圖坐標的轉換。我的原則是所有的邏輯計算都統一在地圖坐標上,只在最後渲染時才將邏輯坐標轉換成屏幕坐標,對於場景和角色都一樣。轉換的方法很簡單。設一個Tile(或角色)在地圖上的坐標為(x,y),屏幕相對於地圖的坐標為(sx,sy),那麼Tile在屏幕上的坐標就是(x-sx,y-sy)。注意單位要統一,往往Tile的單位是格,所以要轉換成像素單位,即格子坐標乘上Tile寬度或高度。將到這兒你會發現,其實我們已經實現了TiledLayer的主要功能,再加上管理Tile圖片的部分一個簡單的Tile引擎就成形了。不過你可千萬不要滿足,真正的游戲開發中場景引擎比這復雜的多,除了這種2D引擎還有2.5D的斜視角引擎,在渲染速度方面更是做了多方面的優化。
最後談談場景中的物理作用和事件檢測。在場景中有些地方是無法通過的,有些地方走上去會觸發某個機關,這些信息往往也存儲在場景信息中,用另一個數組表示,一般稱為物理層、地形層或事件層等等。最常用的信息就是碰撞信息,它用來處理角色和地圖的碰撞。碰撞檢測在游戲開發中是一個永恆的話題,而2D地圖碰撞是其中最簡單的一種。由於使用了物理層,我們只要計算出下一幀角色所要到達的位置是哪一格,如果這一格的物理層信息是不可通過,則組織角色前進。這種碰撞檢測不同於物體間的碰撞檢測,不必和所有的物體進行遍歷判斷,缺點是不夠精確。減小物理層格子的大小可以提高精確度,但這會使數據變多。所以物理層的碰撞檢測一般只用在地圖上,如果有比較特殊的物體,就讓它作為一個角色存在,利用角色間的碰撞檢測。
2 角色管理
角色就是存在於場景中的一切東西,它可以是活動的如RPG中的NPC,也可以是靜止的,如一個箱子。角色的數據結構視作用而不同,但基本的都有坐標、速度、使用到的圖片等數據。具體到不同的游戲會增加各種數據,比如RPG中,會增加很多角色屬性。這裡我們只談最根本的一些東西,主要講角色的繪制和相互作用。
繪制角色簡單的只是繪制一個幀序列的動畫。不同於動畫片,角色的動畫根據AI變化。不同的AI對應不同的動作和動畫。復雜些的角色是由很多部分組合而來,可稱之為組合精靈技術,或者稱為紙娃娃系統(Avatar)。這種技術可以實現換裝效果。組合精靈的復雜之處就在於要組織好每個小部分之間的關系,這需要組合精靈編輯器完成。在專業游戲開發中,會大量運用到各種工具,編寫工具也是游戲程序員的工作之一。無論什麼樣的繪制系統,在最終繪制到屏幕時都要注意兩個基本點 - 角色的坐標和層次。角色的坐標是AI運算的結果,繪制時要將地圖坐標轉換成屏幕坐標。角色的層次需要排序得到,最後根據從遠到近的順序繪制各個角色,這樣才能顯示正確的遮擋關系。
角色的物理作用分為和場景之間的物理作用和角色間的物理作用。前者已經在場景管理中講過。角色間的碰撞檢測的時機是某個角色運動時。對於RPG這種類型的游戲,一個矩形碰撞框就可以表示角色的輪廓。對於格斗等類型的游戲,需要用多個碰撞框表示角色。碰撞框的檢測非常簡單,只要進行矩形相交測試。也有用圓形表示碰撞框的,利用圓心距離和半徑的關系就可以判斷。在2D飛機類游戲中,三角型的碰撞框也常常使用到。總之用一些幾何形狀粗略的描述角色的輪廓是2D游戲碰撞檢測的通用方法。
場景中的所有角色往往用一個數組存儲起來,這樣方便遍歷操作。主角做為一個例外,是需要玩家操縱的角色,而其他角色都是用AI控制的。AI雖然是人工智能的縮寫,但這裡借用來表示操縱角色的代碼。
場景和角色是游戲中兩個重要的基礎組成部分,而開發一個游戲的工作主要部分就是設計各種場景和角色,按照設計編寫角色的AI。