天天看點

遊戲引擎剖析

為了解決“如何在IPHONE上建立一個遊戲”這個大問題,我們需要首先解決諸如“如何顯示圖像”與“如何播放聲音”等一系列小問題。這些問題關系到建立部分遊戲引擎。就像人類的身體一樣,遊戲引擎的每個部分雖然不同,但是卻都不可或缺。是以,首先從遊戲引擎剖析開始本章。我們将會讨論一個遊戲引擎的所有主要部分,包括應用程式架構、狀态機、圖像引擎、實體引擎、聲音引擎、玩家輸入和遊戲邏輯。

寫一個好玩的遊戲是一項牽扯到很多代碼的大任務。非常有必要從一開始就對項目進行良好的,有組織的設計,而不是随着進度的進行而到處雜亂添加代碼。就像建造房屋一樣,建築師為整幢房屋勾畫藍圖,建築勞工以此來建造。但是,許多對遊戲程式設計不熟悉的程式設計人員會從根據導讀建造出房屋的一部分,并随着學習的進行為其添加房間,這無疑将會導緻不好的結果。

遊戲引擎剖析

圖2-1 遊戲引擎的功能結構

圖2-1顯示了一個适用于大部分遊戲的遊戲引擎結構。為了了解一個遊戲引擎的所有部分和它們是如何工作在一起的,我們可以先為整個遊戲做設計,然後再建立我們的應用程式。在以下的幾個小節中,我們的講解内容将會涵蓋圖2-1的每個部分。

應用程式架構

遊戲狀态管理器

圖像引擎

應用程式架構包含使應用程式工作的必須代碼,包括建立一個應用程式執行個體和初期化其他子系統。當應用程式運作時,會首先建立一個架構類,并接管建立和銷毀狀态機、圖像引擎和聲音引擎。如果我們的遊戲足夠複雜以至于它需要一個實體引擎,架構也會管理它。

架構必須适應于我們所選擇的平台的獨特性,包括相應任何的系統事件(如關機與睡眠),以及管理載入與載出資源以使其他的代碼隻需要集中與遊戲。

架構會提供主循環,它是一切互動程式後的驅動力量。在循環中的每一次疊代過程中,程式會檢查和處理接受到的事件,運作遊戲邏輯中的更新并在必要時将内容描畫到螢幕上。(參見圖2-2)

遊戲引擎剖析

圖2-2 主循環序列

主循環如何實作依賴于你使用的系統。對于一個基本的控制台程式,它可能是一個簡單的while循環中調用各個函數:

while( !finished ) {

handle_events();

update();

render();

sleep(20);

}

注意到這裡的sleep函數。它使得代碼休眠一小段時間不緻于占用全部的CPU。

有些系統完全不想讓使用者代碼那些寫,它們使用了回調系統以強制程式員正常的釋放CPU。這樣,當應用程式執行後,程式員注冊一些函數給系統在每次循環中回調:

void main(void) {

OS_register_event_handler( myEventHandler );

OS_register_update_function( myUpdate );

OS_register_render_function( myRender );

一旦程式執行後,根據必要情況,那些函數會間隔性的被調用。IPHONE是最接近後面這個例子。你可以在下一章和IPHONE SDK中看到它。

一個好的視訊遊戲不僅有一組動作來維持遊戲:它會提供一個主菜單允許玩家來設定選項和開始一個新遊戲或者繼續上次的遊戲;制作群屏将會顯示所有辛勤制作這款遊戲的人員的名字;而且如果你的遊戲沒有使用者指南,應該一個幫助區域會給使用者一些提示告訴他們應該做什麼。

以上任何一種場合都是一種遊戲狀态,并且代表中一段獨立的應用程式代碼片段。例如,使用者在主菜單調用的函數與導航與使用者在制作群屏調用的是完全不同的,是以程式邏輯也是不同的。特别的是,在主菜單,你可能會放一張圖檔和一些菜單,并且等待使用者選擇哪個選項,而在制作群屏,你将會把遊戲制作人員的名字描繪在螢幕上,并且等待使用者輸入,将遊戲狀态從制作群屏改為主菜單。最後,在遊戲中狀态,将會渲染實際的遊戲并等待使用者的輸入以與遊戲邏輯進行互動。

以上的所有遊戲狀态都負責相應使用者輸入、将内容渲染到螢幕、并為該遊戲狀态提供相對應的應用程式邏輯的任務。你可能注意到了這些任務都來自于之前讨論的主循環中,這是因為它們就是同樣的任務。但是,每個狀态都會以它們自己的方式來實作這些任務,這也就是為什麼要保持他們獨立。你不必在主菜單代碼中尋找處理遊戲中的事件的代碼。

狀态管理器是一個狀态機,這意味着它跟蹤着現在的遊戲狀态。當應用程式執行後,狀态機會建立基本的狀态資訊。它接着建立各種狀态需要的資訊,并在離開每種狀态時銷毀暫時存儲的資訊。

狀态機維護着大量不同對象的狀态。一個明顯的狀态是使用者所在螢幕的狀态(主菜單、遊戲中等)。但是如果你有一個有着人工智能的對象在螢幕上時,狀态機也可以用來管理它的“睡眠”、“攻擊”、“死亡”狀态。

什麼是正确的遊戲狀态管理器結構?讓我們看看一些狀态機并決定哪種最适合我們。

有許多實作狀态機的方式,最基本的是一個簡單的switch語句:

class StateManager {

void main_loop() {

switch(myState) {

case STATE_01:

state01_handle_event();

state01_update();

state01_render;

break;

case STATE_02:

state02_handle_event();

state02_update();

state02_render;

case STATE_03:

state03_handle_event();

state03_update();

state03_render;

};

改變狀态時所有需要做的事情就是改變myState變量的值并傳回到循環的開始處。但是,正如你看到的,當我們加入越來越多的狀态時,代碼塊會變得越來越大。而且更糟的是,為了使程式按我們預期的執行,我們需要在程式進入或離開某個狀态時執行整個任務塊,初始化該狀态特定的變量,載入新的資源(比如圖檔)和釋放前一個狀态載入的資源。在這個簡單的switch語句中,我們需要加入更多的程式塊并保證不會漏掉任何一個。

以上是一些簡單重複的勞動,但是我們的狀态管理器需要更好的解決方案。下面一種更好的實作方式是使用函數指針:

//the function pointer:

void (*m_stateHandleEventFPTR) (void);

void (*m_stateUpdateFPTR)(void);

void (*m_stateRenderFPTR)(void);

stateHandleEventFPTR();

m_stateUpdateFPTR();

m_stateRenderFPTR();

void change_state( void (*newHandleEventFPTR)(void),

void (*newUpdateFPTR)(void),

void (*newRenderFPTR)(void)

) {

m_stateHandleEventFPTR = newHandleEventFPTR;

m_stateUpdateFPTR = newUpdateFPTR;

m_stateRenderFPTR = newRenderFPTR

現在,即使我們處理再多狀态,主循環也足夠小而且簡單。但是,這種解決方案依然不能幫助我們很好的解決初始化與釋放狀态。因為每種遊戲狀态不僅包含代碼,還有各自的資源,是以更恰當的做法是将遊戲狀态作為對象的屬性來考慮。是以,接下來,我們将會看看面向對象(OOP)的實作。

我們首先建立一個表示遊戲狀态的類:

class GameState

{

GameState(); //constructor

virtual ~GameState(); //destructor

virtual void Handle_Event();

virtual void Update();

virtual void Render();

接着,我們改變我們的狀态管理器以使用這個類:

GameState* m_state;

m_state->Handle_Event();

m_state->Update();

m_state->Render();

void change_state( GameState* newState ) {

delete m_state;

m_state = newState;

最後,我們建立一個指定具體遊戲狀态的類:

class State_MainMenu : public GameState

int m_currMenuOption;

State_MainMenu();

~State_MainMenu();

void Handle_Event();

void Update();

void Render();

當遊戲狀态以類來表示時,每個遊戲狀态都可以存儲它特有的變量在該類中。該類也可以它的構造函數中載入任何資源并在析構函數中釋放這些資源。

而且,這個系統保持着我們的代碼有很好的組織結構,因為我們需要将遊戲狀态代碼分别放在各個檔案中。如果你在查找主菜單代碼,你隻需要打開State_MainMenu類。而且OOP解決方案使得代碼更容易重用。

這個看起來是最适合我們需要的,是以我們決定使用它來作為我們的狀态管理器。

圖像引擎負責視覺輸出,包括使用者借以互動的圖形使用者界面(GUI)對象,2D精靈動畫或3D模型動畫,并渲染的背景與特效。

雖然渲染2D與3D圖檔的技術不盡相同,但他們都完成相同的一組圖形任務,包括紋理和動畫,它們的複雜度是遞增的。

對于顯示圖檔,紋理是中心。2D時,一個平面圖檔是以像素為機關顯示在螢幕上,而在3D時,一組三角行(或者被稱為網格)在數學魔法作用下産生平面圖檔并顯示在螢幕上。這以後,一切都變得複雜。

當進行螢幕描繪時,基本機關是像素。每個像素都可以被分解為紅、綠、藍色值和我們馬上要讨論的Alpha值。

紋理是一組關于渲染一組像素的資料。它包含每個像素的顔色資料。

圖檔是一個更高層的概念,它并非與一組特殊的像素與紋理相關聯。當一個人看到一組像素,他的大腦會将它們組合成一幅圖檔,例如,如果像素以正确的順序表示,他可能會看到一幅長頸鹿的畫像。

保持以上這些概念獨立是非常必要的。紋理可能包含構成長頸鹿圖檔的像素:它可能包含足夠的像素來構成一隻長頸鹿的多幅圖檔,或者僅包含構成一幅長頸鹿圖檔的像素。紋理本身隻是一組像素的集合,它并不固有的知道它包含的是一幅圖檔。

在任一刻,你的遊戲會有幾個或者多個物體渲染在螢幕上,其中一些會與另外一個重疊。問題是,如何知道哪個物體的哪個像素應該被渲染出來呢?

如果在最上層的紋理(在其他紋理之後被描畫)是完全不透明的,它的像素将會被顯示。但是,對于遊戲物體,可能是非矩形圖形和部分透明物體,結果會導緻兩種紋理的結合。

2D圖檔中最常用的混合方式是完全透明。假如我們想畫一幅考拉(圖2-3)在爬在桉樹頂上(圖2-4)的的圖檔。考拉的圖檔以矩形紋理的方式存儲在記憶體中,但是我們不想畫出整個矩形,我們隻想畫出考拉身體的像素。我們必須決定紋理中的每個像素是否應該顯示。

遊戲引擎剖析

圖2-3 考拉紋理

遊戲引擎剖析

圖2-4 桉樹紋理

有些圖檔系統通過添加一層遮罩來達到目的。想象我們在記憶體中有一幅與考拉紋理大小一樣的另外一份紋理,它隻包含白色和黑色的像素。遮罩中每個白色的像素代表考拉應該被描畫出來的一個像素,遮罩中的黑色像素則代表不應該被描畫的像素。如果當我們将考拉描畫到螢幕上時,有這樣的一個遮罩層,我們就可以檢查考拉對應的像素并僅将需要描畫的像素表示出來。如果每個像素有允許有一組範圍值而不是二進制黑/白值,那麼它還可以支援部分透明(參見圖2-5)。

遊戲引擎剖析

圖2-5 考拉遮罩紋理

為紋理而準備的存儲容量大到足以支援每個像素都有一個範圍值。典型的是,一個Alpha值占一個位元組,即允許0-255之間的值。通過合并兩個像素可以表現出有趣的視覺效果。這種效果通常用于部分透明化,例如部分或完全看透物體(圖2-6)。

遊戲引擎剖析

圖2-6 部分透明的綠色矩形

我們可以為每個像素來設定Alpha以決定它們如何被混合。如果一個像素允許的範圍值為0-255,Alpha的範圍值也同樣應當為0-255。盡管紅色值為0表示當描畫時不應該使用紅色,但Alpha值為0則表示該像素根本不應該被描畫。同樣,128的紅色值表示描畫時應該使用最大紅色值的一半,128的Alpha值表示當與另外一個像素混合時,應該使用該像素的一半顔色值。

當混合物體時,正确的排列物體順序是非常重要的。因為每個混合渲染動作都隻會渲染源物體與目标物體,首先被描畫的物體不會與後描畫的物體發生混合。盡管這在2D圖檔中很容易控制,但是在3D圖檔中變得非常複雜。

在2D圖檔中,大部分的紋理都會被直接渲染到目标上而不需要旋轉。這是因為标準硬體不具備旋轉功能,是以旋轉必須在軟體中計算完成。這是一個很慢的過程,而且容易産商低品質的圖檔。

通常,遊戲開發人員會通過預先渲染好物體在各個方向的圖檔,并當物體某個方向上時,為其在螢幕上描畫正确的圖檔來避免以上問題的發生。

在3D圖檔中,旋轉的計算方式與照明相同,是硬體渲染處理過程中的一部分。

由于某些在後面章節解釋的原因,紋理的另外一個重要方面是剪貼。盡管我們目前的例子都是将源紋理直接描畫到目标紋理上,但是經常會出現的情況是,需要将部分源紋理描畫到目标紋理的有限的一部分上。

例如,如果你的源紋理是在一個檔案中含有多幅圖檔,裁剪允許你僅将希望描畫的部分渲染出來。

剪貼同樣允許你進行描畫限制到目标紋理的一小部分上。它可以幫你通過紋理映射以3D方式渲染物體,将紋理鋪蓋到三角形組成的任意形狀的網格上。例如,一個紋理可以表示衣服或動物的毛皮,而且當3D角色穿着它移動的死後可能産生褶皺。這時候的紋理通常被稱作皮膚。

通過渲染連續的圖檔,我們可以確定玩家看到一個移動的物體,盡管他所做的僅僅是在同樣的像素上,但這些像素在快速的改變顔色。這就是動畫的基本概念。2D動畫很簡單,但3D動畫通常牽扯到更多的物體與動作,是以更複雜。

除了讨論動畫技巧,這一節還會讨論主要的優化類型可以使得我們的圖像引擎有效的和可靠的完成複雜的不可能以原始方式來完成的圖形任務。一些主要的優化技巧包括淘汰、紋理排序、使用智能紋理檔案、資源管理和細節級别渲染。

在2D圖像中,如果我們要渲染馬兒奔馳的完整場景,我們可以先建立出馬兒的奔馳各個姿态的圖檔。這種圖檔成為一幀。當一幀接一幀的渲染到螢幕上時,馬兒動起來了(見圖2-7)。這和電影建立動畫的方式非常相似,電影也是通過展示連續的幀來達到移動效果。

遊戲引擎剖析

圖2-7 斯坦福德的馬的動作

為了将這些幀儲存在一起,我們将它們放在同一個紋理中,稱為精靈。通過前面章節我們描述的裁剪方法,将隻包含目前幀内容的部分渲染到螢幕上。

你可以将每一幀渲染多次直到渲染該序列的下一幀。這取決于你希望你的動畫播放的多快,以及提供了多少幀圖檔。事實上,通過渲染的幀速和順序,你可以創造出多種特效。

與2D動畫中每次重畫時都維護一幅用來渲染的圖檔--精靈不同,3D動畫是通過實際的計算的計算運動的幾何效果。正如我們之前描述的,所有的3D物體都由包含一個或多個三角形構成,被稱作網格。有多種可以使網格動起來的方法,這些技術與遊戲發展與圖形硬體有關。這些技術後的基本概念都是:關鍵幀。

關鍵幀與我們之前讨論的2D動畫中的幀有些許不同。2維動畫的美術人員畫出每一幀并儲存在紋理中。但是在3D中,隻要我們儲存了最特殊的幾幀,我們就可以通過數學計算得到其他幀。

最開始的使用網格動畫的遊戲實際上存儲了網格的多個拷貝,每一個拷貝都是都在不同的關鍵幀方向上。例如,如果我們在3D中渲染馬兒,我們應該為上面精靈的每一個關鍵幀都建立網格。在time(1),第一幀被描畫出來,在time(2),第二針被描述出來。

在主要關鍵幀之間,使用一種叫做“插值”的技術方法。因為我們知道time(1)的關鍵幀和time(2)的關鍵幀有着相同數量的三角形,但是方向稍有差別,我們可以建立目前時間點的臨時的,融合了前面兩個網格的網格。是以在時間time(1.5),臨時網格看起來正好介于time(1)與time(2)之間,而在time(1.8),看起來更偏向于time(2)。

以上技術效率低下的原因是很明顯的。它僅在隻有少量的三角形和少量的關鍵幀時才是可接受的,但是現代圖像要求有高解析度與生動細節的動畫。幸運的是,有更好的存儲關鍵幀資料的方法。

這就技術叫做“骨骼動畫”(skeletal animation, or bone rigging)。還是以馬兒為例,你可能注意到了大多數的三角形都是成組的移動,比如頭部組、尾部組和四肢組。如果你将它們都看成是骨頭關聯的,那麼将這些骨頭組合起來就形成了骨骼。

骨骼是由一組可以适用于網格的骨頭組成的。當一組骨骼在不同方向連續的表示出來的時候,就形成了動畫。每一幀動畫都使用的是相同的網格,但是都會有骨頭從前一方位移動到下一個方位的細小的動作變化。

通過僅存儲在某一個方位的網格,然後在每一關鍵幀時都利用它,我們可以建立一個臨時的網格并将其渲染到螢幕上。通過在兩個關鍵幀之間插值,我們可以以更小的成本來建立相同的動畫。

動畫控制器對象在抽象低層次的任務非常有用,如選擇哪一幀來渲染,渲染多長時間,決定下一幀代替前一幀等。它也起到連接配接遊戲邏輯與圖像引擎等動畫相關部分的作用。

在頂層,遊戲邏輯隻關心将設某些東西,如播放跑動的動畫,和設定它的速度為可能應該每秒跑動數個機關距離。控制器對象知道哪個幀序列對應的跑動動畫以及這些幀播放的速度,是以,遊戲邏輯不必知道這些。

另外一個與動畫控制器相似的有用對象是粒子系統管理器。當需要描畫高度支離破碎的元素,如火焰、雲朵粒子、火苗尾巴等時可以使用粒子系統。雖然粒子系統中的每個對象都有有限的細節與動畫,它們組合起來卻能形成富有娛樂性的視覺效果。

最好的增加每秒鐘描畫到螢幕上的次數的方法是在每次疊代中都減少描畫在螢幕上的數目的總量。你的場景可能同時擁有成百上千的物體,但是如果你隻需要描述其中的一小部分,你仍然可以将螢幕渲染得很快。

淘汰是從描畫路徑上移除不必要的物體。你可以在多層次上同時進行淘汰。例如,在一個高層次,一個使用者在一間關閉了門的房間裡面是看不到隔壁房間的物體的,是以你不必描畫出隔壁其他物體。

在一個低層次,3D圖像引擎會經常移除部分你讓它們描畫的網格。例如,在任意合适的給定時間點,半數的網格幾何體在攝影機背面,你從錄影機中看不到這些網格,看到的隻是攝影機前方的網格,是以,當網格被渲染時,所有的在攝影機背後的網格都會被忽略。這叫做背面淘汰。

每次當一個物體被渲染到螢幕上時,圖形硬體都會将紋理源檔案載入到記憶體中。這是被稱作上下文交換(context switching)的一部分。

如果要将三幅圖檔描畫到螢幕上,而其中兩幅圖檔共用同一個紋理資源,有兩種辦法來處理紋理排序:高效的方法是連續的渲染兩幅共享資源的圖檔,這樣隻需要以此上下文交換,而低效的方法則需要兩次上下文交換。你不應該将第三幅圖檔放在共享紋理的兩幅圖檔之間描畫。

在渲染處理過程中,通過排列共享紋理的物體可以減少上下文交換的次數,進而提高渲染速度。

在一開始就計劃好紋理組織結構可以幫助你以最優化方式排列你的紋理。假設你準備在你的遊戲中描畫幾何體,一個主角和一些生物。

如果前兩個關卡是草地,接下來的關卡是沙漠,你可以将所有的樹木、草、灌木、岩石以及花兒的圖檔來放到一起來渲染前兩關,并将沙子圖檔放在另外一個紋理檔案中用來渲染第三關。同樣的,你可以将玩家虛拟人偶放到一個紋理中。如果所有的生物在所有關卡中都用到了,最優的方式可能是将它們放在一個紋理檔案中。但是,如果第一關有吼猴與鼯鼠,而第二關隻有森林鼠與蘇利南蛤蟆,你可以将第一次前兩種動物放在一個紋理中,将後兩種放在一個紋理中。

大部分的視訊遊戲在一個時間點隻會渲染它們所有圖檔内容的一小部分。将所有紋理同時載入記憶體是非常低效的。

幸運的是,遊戲設計通常決定了哪些資源在遊戲的各個章節是可見的。通過保留必須的紋理為載入狀态并解除安裝不使用的紋理,可以最有效的利用有限的記憶體資源。

還是使用前一節的例子,當遊戲引擎載入第一關時,資源管理代碼必須確定 吼猴與鼯鼠的紋理被載入到記憶體中。當程式進行到下一關時,資源管理代碼會解除安裝那些紋理,因為它已經知道它們不會在第二關被使用。

另外一個優化技巧,尤其是對3D圖像,叫做細節層次。考慮當一個物體遠離錄影機時,它看起來非常小,而且大部分細節都丢失了。你可以描畫一個同樣大小,卻僅擁有簡單網格的物體,或者甚至一張平面貼圖。

通過保留不同細節層次的物體的副本在記憶體中,圖像引擎可以根據與錄影機的距離決定使用哪個副本。

實體引擎是遊戲引擎中負責距離、移動與其它遊戲實體相關的部分。不是所有的遊戲引擎都需要實體引擎。但是所有的圖形遊戲都在某種程度上有實體相關代碼。

不相信嗎?用“井字遊戲”(tic-tac-toe)來舉 個例子。确實是一個非常簡單的遊戲,但是即使這個遊戲也有實體部分。當玩家選擇一個正方形用來标記的時候,我們必須檢查選擇的正方形是否有效。如果是,我們就将打上标記并判斷玩家是否獲勝。這就是實體引擎所完成的兩項基本任務的例子:偵測與解決。

在你腦海中保持這兩方面的獨立性非常重要。在遊戲代碼中,偵測是獨立于判定的。不是所有的物體與其它物體會以相同的方式發生碰撞,進而不是被偵測到的所有碰撞都會以相同的方式來解決。

例如,讓我們假想一個遊戲:O’Reilly野生冒險。假如玩家的虛拟人偶發現在自己無意間來到了O’Reilly野生保護區,它必須避免奇怪和危險的動物并回到安全的地方。

這個遊戲中會發生以下幾種實體互動:

1.玩家與地圖的碰撞

2.動物與地圖的碰撞

3.玩家與動物的碰撞

4.玩家與目的地的碰撞

第一種,玩家與地圖的碰撞,非常簡單。我們檢測玩家的身體區域邊界與關卡中的牆。如果玩家将與牆發生碰撞,我們稍微位移一下玩家,以使其不會與牆發生碰撞。

第二種互動稍微複雜一點。我們使用同樣的偵測方法:檢測動物的身體區域與關卡中的牆。但是我們的解決方法有一些不同,因為動物不是玩家控制,而是由電腦控制。我們解決情況1時,我們稍微位移一下玩家,以使其不會進入到牆裡面。這是用來提醒玩家他正在撞向牆而且必須改變方向。

如果我們對情況2做同樣的解決,AI不會認識到它正撞向牆,而且會繼續走向牆裡面。是以,我們分兩步解決這種情況,首先稍微位移一下動物以使其不會與牆發生碰撞,然後通知AI動物撞上了一面牆。這樣,遊戲邏輯會控制動物改變移動方向。

第三種情況,我們首先檢查玩家身體區域邊界與動物身體區域。一旦我們偵測到了他們将發生碰撞,可能會發生不同的結果。如果這個動物是眼鏡猴,他可能會逃跑;如果這個動物是毒蛇或者獅子,它可能會攻擊玩家;駱駝可能會忽略玩家;蝗蟲可能會被踩扁。

最後,第四種情況是一種新的情況。目的地與地圖、玩家與動物是不同的,因為它沒有圖形表示。它是一個隐含的觸發區域,當玩家踏入它

時,它會發出一個事件。幸運是,盡管它沒有圖形表示,它仍然具有實體表示。是以,我們依然可以偵測玩家的身體區域邊界與目的地的區域邊界。如果我們發現玩家到達了目标,就通知遊戲邏輯,使其進入“玩家勝利”的遊戲狀态。

二維碰撞偵測是一個相對簡單的處理過程。大部分都可以總結為如下的一段偵測正常:矩形對矩形、矩形包含點、圓對矩形、圓包含點與圓對圓(有可能也需要檢查線段,不過那通常可以被避免)。

由于這些正常可能被每秒鐘使用多次,是以確定它盡可能高效是非常重要的。為了達到這個目的,我們将會進行一系列低成本的測試來證明兩個物體碰撞之前是沒有碰撞在一起的:

bool cd_rectangleToRectangle( Rect r1, Rect r2)

//can't be colliding because R1 is too far left of R2

if( r1.x + r1.width < r2.x ) return FALSE;

//can't be colliding because R1 is too far right of R2

if( r1.x > r2.x + r2.width ) return FALSE;

//can't be colliding because R1 is too far below R2

if( r1.y + r1.width < r2.y ) return FALSE;

//can't be colliding because R1 is too far above R2

if( r1.y < r2.y + r2.width ) return FALSE;

//if we get here, the two rects MUST be colliding

return TRUE;

盡管這樣,當物體碰撞時,還是會有更多的代碼被執行。大部分時候,物體都不會互相碰撞,我們針對于此進行了優化,因為它更有效率。

在我們繼續之前,讓我們看一個非常重要的概念:遊戲對象的圖形表示獨立于它的實體表示。計算機每秒鐘隻能提供有限的計算總量,進行實時地實體模拟非常困難。正因為如此,遊戲實體代碼有一個悠長而驕傲的傳統,那就是隻要精确到讓玩家覺得遊戲正确即可。

比如,在我們上面提到的遊戲中,我們玩家的虛拟人偶即将撞上一頭犀牛(很明顯,要麼我們的玩家不太專心,要們就是被一隻生氣的母老虎追趕)。我們的玩家在跑動過程中,會有很多四肢動作,犀牛也是一樣,它伸出它頭上的牛角。

盡管玩家在跑動過程中,圖形表示需要出玩家的手和腳,但是在實體表示中,我們真的需要知道四肢的具體坐标嗎?我們真的需要去檢查玩家的雙手、雙腳以及頭是否與犀牛的頭、腳、及牛角發生了碰撞(還有别忘記了尾巴!)?當然不,因為我們的遊戲是按照我們的需要簡單的用矩形表示玩家和用矩形表示犀牛。

三維碰撞偵測要比二維困難很多。你必須很熟悉三維數學計算,比如線形代數。而且,即使數學要求不高,3D遊戲也擁有更複雜的遊戲場景。幸運的是,你可以依賴合适的技術來幫助減少計算次數。當然,還是要精确到讓玩家覺得遊戲正确即可。

就像我們之前讨論的,一個物體的圖像表示不同于它的實體表示。但是,有時我們需要確定他們之間的差别越小越好。想象一下第一人稱射擊遊戲,我們不僅要知道一個玩家知否射中了另外一個玩家,還要知道他是否取得了一個爆頭。很明顯,一個簡單的盒子邊界不能滿足需求,不過我們也無法提供對每一顆子彈都檢查路徑,判斷其穿過的每一個虛拟人偶的每個三角形的檢查。

我們使用同樣的3D偵測,就像我們在2D中使用的偵測一樣:在執行精确的與昂貴的測試之前,先執行一系列低成本的否定測試。以子彈為例,我們首先測試子彈有沒有劃過哪個玩家的邊界框。如果沒有擊中他們,我們退出碰撞檢查。對于擊中了的邊界框,找到起始擊中點,并對更個中的所有三角形都做更多詳細的測試。這是圖形優化中的細節層次的實體版本。如果在最近的玩家身上的檢查失敗了,我們将會在下個玩家身上做詳細檢查。用這種方式,我們可以高效并準确的更複雜的場景,比如一顆子彈從一個玩家的雙腿間飛過,擊中了他身後的另外一位玩家。

當碰撞被偵測到時,就要解決它。首先要考慮的是,什麼樣的低層次的或高層次的動作必須發生。低層次動作是指實體代碼可以解決的,如調整玩家的位置以使其在地面上而不會摔到地面下。高層次的動作是指發送到遊戲引擎的遊戲邏輯部分的信号。這些信号可以讓遊戲邏輯知道一個動物何時否跑進了一堵牆裡或者一個玩家是否到達了目的地。

有些碰撞響應需要同時多重高層次的和低層次的響應。例如,在彈鋼珠遊戲中,一個鋼珠打到了防撞杆上,它應該正确的彈離防撞杆(低層次),也會同時向遊戲邏輯發送一個信号,使得防撞杆産生動畫效果、聲音效果以及增加玩家的得分(高層次)。

當測試低層次的碰撞解決的代碼時,要特别注意細節。最影響遊戲感覺的重要元素之一就是實體代碼。虛拟人偶可以快速的響應玩家的輸入嗎?賽車遊戲能真實地模拟出懸挂系統與變速系統嗎?當玩家發生大炮的時候,螢幕會抖動嗎?

根本上,這是由設計人員的工作來讓遊戲感覺很好,但是這需要程式員的工作來實作它。不要害怕寫一些特殊的測試用例以得到正确的結果。

例如,測試時常用的一項技術是為對象設定“flag”。當一個對象與另外一個對象“ground”接觸時,你可以為這個對象設定一個“grounded” flag為true。當grounded flag為true時,可以認為這個物體在休息,不需要對其施加重力影響,或偵測它與地圖的碰撞。這不但可以幫助你防止在一大堆靜止物體上運作碰撞偵測,也可以避免在某些實體模拟鐘,物體在地面附近産生抖動現象。

聲音是遊戲開發中經常忽略的一的環節,但是當你知道聲音構成了人類玩視訊遊戲的1/3的感覺時,你肯定會很困窘。在合适的時機播放正确的聲音使得程式員僅作很小的工作就可以為遊戲增加分數。

基本的音效特征包括載入和解除安裝聲音樣本、播放聲音、停止播放、暫停播放以及循環播放。典型的,所有的聲音都可以以同樣的音量播放,但是你可能希望玩家調整以使所有的聲音都符合他們的喜好。

在播放音效之前,你需要從檔案或者緩存中載入聲音樣本。IPHONE API支援AAC與MP3格式的高品質的聲音,也支援PCM和IMA4的小樣品,以及其他一些格式。

一旦載入了聲音樣本,API提供了函數以開始和停止播放樣本。大多數API還提供了暫停和繼續功能,還有一些允許你從特定點開始播放。雖然還有更多進階特性,但是基本上以上就是程式員全部所需要的了。

因為聲音播放依賴于硬體,你在同一時間可以聽到的聲音是有限的。每種聲音播放時都使用一個聲道。在IPHONE中,所有的MP3和AAC樣本共用相同的硬體聲道,而且隻有一個是可用的,多聲道支援PCM和IMA4。

這意味着同一時間,隻有一個MP3/AAC格式的樣本可以被播放。典型的,但多個PCM/IMA4樣本可以同時播放多個樣本(同時與MP3/AAC播放)。

遊戲中的大部分聲音都可以分為兩大類:環境音(典型的,背景音樂)和音效(SFX)。有時,用環境音代替音樂,但他們都共享同樣的特點。

音樂一般是重複播放一個相對長的樣本或者引出下一段音樂樣本。通常情況下,隻有一首音樂在同一時間播放,使得MP3/AAC格式限制變成了一個不成問題的問題。

聲效要短的多,而且需要許多不同的樣本,在同一時間重疊播放。PCM/IMA4格式很好的滿足了這個需求。

由于PCM/IMA4也隻有有限的聲道數,是以如果你打算同時播放很多的SFX樣本,其中一些可能不能被播放。是以,為SFX音效設定優先級以確定某些音效一定能播放就顯得很重要了。

例如,讓我們讨論之前提到的我們的玩家虛拟人偶走進了一個滿是憤怒的犀牛的房間。每頭犀牛都會播放憤怒的鼻息聲,可能還會接着播放撞人的聲音,而玩家虛拟人偶則會發出害怕的哭叫聲。我們會給與玩家虛拟人偶聲效更高的優先級,以使其不會被犀牛的撞人聲效所淹沒。

幸運的是,目前的IPHONE支援至少32聲道,是以一般不太可能需要在這個平台上去刻意安排優先級。

IPHONE支援内置的對講機與聽筒作為輸出裝置。建議不要同時使用它們。當玩家将聽筒插入到IPHONE中時,音頻會自動改為從聽筒播放。

由于IPHONE是移動裝置,有可能玩家在路上在的時候,聽筒會掉落。一個好的設計選擇是當聽筒被移除的時候暫停遊戲以給玩家足夠的時間讓他重新接入耳機。此時,你也可以選擇同時提供停止播放聲音。

最重要的是,應該從玩家的使用者角度來考慮聲效。玩你的遊戲并不是使用者使用IPHONE的唯一标準,是以不要讓你的聲效的優先級打擾到使用者,否則玩家會将其關掉。

遊戲引擎的玩家輸入部分集中于來接收自于作業系統的低層次的事件,然後将其轉化為高層次的事件,這樣,遊戲邏輯代碼可以在PC遊戲中使用它。低層次的事件可以是滑鼠和鍵盤事件。對于控制台遊戲,他們可能産生于控制器的手、觸發器與按鈕。在我們的例子裡面,IPHONE會處理觸摸與旋轉事件。

觸摸屏接口的設計方式與其他大多數移動手機、控制台與PC平台的接口設計方式有着根本的差別。在使用者觸摸螢幕與應用程式接收到該事件之間有延遲(盡管IPHONE已經在将縮短延遲方面做的足夠好了),但是真正的問題是,不管何時使用者做出觸摸動作,他的手指都會遮蓋住部分螢幕,大大降低了遊戲畫面的可見性。

你可以通過提供一個圖形按鈕給使用者來點選(回到了按鈕點選系統,代價是螢幕空間),或者提供一個聰明的隐喻來解決這個問題。比如,如果玩家點選了螢幕上的一塊空間,你可以讓虛拟人偶朝那個方向走。這樣可以省去使用者的連續點選輸入。

盡管使用者接口設計是遊戲設計人員的責任,但是程式設計人員需要告訴設計人員這個平台可以做什麼和不能做什麼。IPHONE支援以下觸摸事件:

觸摸開始

觸摸移動

觸摸結束

觸摸取消

你可能會問什麼情況下會觸發“觸摸取消”事件。當某個事件将你的應用程式挂起在“觸摸開始”事件與“觸摸結束”事件之間時,程式會收到“觸摸取消”事件通知你不會收到其他的事件(如觸摸結束)。

為了處理多點觸摸,包含一個UITouch對象list的UIEvent對象被發送到你的應用程式中。如果隻有一個指頭觸摸到螢幕,你隻會接收到一個UITouch對象;如果兩個指頭觸摸到螢幕,你會接收到兩個UITouch對象等等。而且IPHONE可以追蹤正在發生或最近發生的連續的5次輕擊事件(一個觸摸開始事件後緊跟一個觸摸結束事件)。

不幸的是,通過以上事件來判斷使用者是單擊、輕按兩下、掃過或者壓縮動作會可能變得比較麻煩。雖然不是很困難,但是在一開始并不是很容易正确處理。看以下例子:

Time 0: TouchStart - numTaps(0) numTouches(1) Touches { (40,40) }

Time 100: TouchEnd - numTaps (1) numTouches(1) Touches { (40,40) }

Handle single-tap..

到目前為止,使用者觸摸了螢幕一次,你的代碼可以執行相應的單擊處理邏輯。但是稍等!

Handled single-tap.. INCORRECTLY

Time 200: TouchStart - numTaps (1) numTouches(1) Touches { (40,40) }

Time 300: TouchEnd - numTaps (2) numTouches(1) Touches { (40,40) }

FAILED to handle double-tap

使用者第二次輕擊了螢幕。如果你已經在收到觸摸結束的事件時進行了處理,可能錯誤的處理了使用者實際上的輕按兩下處理。

我們應該如何正确處理這種情況呢?解決方法是将第一次觸摸結束事件推遲為定時回調。當第一次接收到觸摸結束事件時,我們設定一個回調。如果我們在回調之前接收到了第二次觸摸結束事件,我們可以判定使用者進行了輕按兩下,并取消回調,執行輕按兩下處理。我們接收到了回調,我們認為使用者沒有進行輕按兩下并應該進行單擊處理。

這裡有表達兩種情況的例子:

Initiate callback timer

Handle double-tap, cancel callback

這次,玩家進行了輕按兩下而且代碼進行了正确的處理。

Time 500: Callback recieved

Handle single-tap

現在,玩家進行了單擊而且代碼也進行了正确的處理。

注意,你不必為那些僅期待單擊事件的接口加上這些處理。

偵測諸如掃過的動作會更麻煩一點,但也更容易正确處理。代碼中必須為每次觸摸儲存起始點與終點,并算出使用者劃的線的方向是向上、向下、向左、還是向右。還要判斷他的手劃過的是否足夠快。

一旦判定了使用者執行的實體動作,你的代碼必須能将它們轉換為遊戲邏輯元件可以使用的形式。具體怎麼做需要依賴于你的遊戲的上下文,但是這裡有幾種典型的形式:

如果玩家準備控制虛拟人偶,在玩家和遊戲之間通常會有連續的互動。經常需要存儲目前使用者輸入的表現形式。比如,如果輸入裝置為遙杆,你可能需要在主循環中記錄目前點的x軸坐标和y軸坐标,并修正虛拟人偶的動量。玩家和虛拟人偶之間的是緊密地耦合在一起的,是以控制器的實體狀态代表着虛拟人偶的高層次的狀态模型。當遙杆向前撥動時,虛拟人偶向前移動;當“跳躍”按鈕按下時,虛拟人偶跳起。

如果玩家正與遊戲地圖進行互動,那麼需要另外一種間接的方式。比如,玩家必須觸摸遊戲地圖中的一個物體,代碼必須将玩家在螢幕上的觸摸坐标轉化為遊戲地圖的坐标以判定使用者到底觸摸到了什麼。這可能隻是簡單的将y軸坐标減去2D錄影機坐标的偏移量,也可能是複雜到3D場景中的錄影機光線碰撞偵測。

最後,使用者可能進行一些間接影響到遊戲的動作,如暫停遊戲、與GUI互動等。這時,一個簡單的消息或者函數會被觸發,去通知遊戲邏輯應該做什麼。

遊戲邏輯是遊戲引擎中是你的遊戲獨一無二的部分。遊戲邏輯記錄着玩家狀态、AI狀态、判定什麼時候達到目的地、并生成所有的遊戲規則。給出兩個相似的遊戲,他們的圖像引擎與實體引擎可能隻有細微差别,但是它們的遊戲邏輯可能會有很大差異。

遊戲邏輯與實體引擎緊密配合,在一些沒有實體引擎的小遊戲中,遊戲邏輯負責處理所有實體相關内容。但是,當遊戲引擎中有遊戲引擎的時候,需要確定兩者的獨立。達到此目的的最好方式就是通過實體引擎向遊戲邏輯發送高層次的遊戲事件。

遊戲邏輯代碼應該盡可能僅處理高層次問題。它不應該處理當使用者觸摸螢幕時需要以什麼順序将什麼描畫到螢幕上,或者兩個矩形是否相交等問題。它應該處理玩家希望向前移動,什麼時候一個新的遊戲物體應當被建立/移除以及當兩個物體互相碰撞後應該做什麼。

為了維持概念上的距離,處理低層次概念(諸如使用者輸入與實體引擎等)的代碼應當建立高層次的消息并發送給遊戲邏輯代碼去處理。這不僅能保持代碼的獨立性與子產品化,還會對調試有所幫助。通過檢視高層次消息傳遞的日志,你可以判定是沒有正确處理消息(遊戲邏輯代碼的問題),還是沒有在正确的時機傳送消息(低層次代碼問題)。

一個非常基本的傳遞高層次消息的技術是寫一個String并傳遞它。假如玩家按下了上箭頭鍵,它的虛拟人偶必須向上移動。

void onPlayerInput( Input inputEvt ) {

if(inputEvt.type == IE_KEY && inputEvt.value == KEY_UP ) {

g_myApp->sendGameLogicMessage( "player move forward" );

雖然上面的代碼對程式員來說通俗易懂,但對于電腦來說卻并不高效。它需要更多的記憶體與處理,遠比實際需要的多。我們應該用提示來替代使用者輸入方法。比起一個字元串,它使用一個"type"和"value"。由于可能的事件都是結構化的和有限的,是以我們可以使用整數和枚舉類型來我們消息中的事件資訊。

首先,我們定義一個枚舉類型來辨別事件類型:

enumeration eGameLogicMessage_Types {

GLMT_PLAYER_INPUT,

GLMT_PROJECTILE_WEAPON,

GLMT_GOAL_REACHED,

接着我們再建立一個枚舉類型來辨別事件的值:

enumeration eGameLogicMesage_Values {

GLMV_PLAYER_FORWARD,

GLMV_PLAYER_BACKWARD,

GLMV_PLAYER_LEFT,

GLMV_PLAYER_RIGHT,

GLMV_ROCKET_FIRED,

GLMV_ROCKET_HIT,

現在我們定義一個結構體來存儲我們的消息資料:

struct sGameLogicMessage {

short type;

short value;

} Message;

現在,我們就可以像上一個例子代碼一樣,用一個對象來傳遞我們的消息:

Message msg;

msg.type = GLMT_PLAYER_INPUT;

msg.value = GLMV_PLAYER_FORWARD;

g_myApp->sendGameLogicMessage( msg );

這看起來作了更多的工作,但它運作起來會更有效率。前一個(壞的)例子用了20個位元組來傳遞消息(20個字元各占一個位元組,别忘了終止符)。第二個例子隻用了4個位元組來傳遞同樣的消息。但是更要的是,當sendGameLogicMessage()處理方法的時候,它隻需要分析兩個switch語句就可以找到正确的響應,而前一個例子則組要從字元串進行解析,速度很慢。

遊戲邏輯的另外一個職責就是管理AI代理。兩類典型的遊戲需要用到AI系統:一種是玩家與電腦競賽;另外一種是在遊戲世界中有半自主系統的敵人。在這兩種情況下,AI代理為遊戲世界中的物體的動作接受輸入并提供輸出。

在第一種類型遊戲裡,AI被稱作專家系統。它被期待用來模拟了解遊戲規則的人的行為動作,并可以采取具有不同難度的政策來挑戰玩家。AI具有與玩家類似的輸入與輸出,可以近似的模拟玩家的行為。由于人類比現在的AI代理更擅長處理複雜資訊,有時為專家系統提供的輸入資訊要多于給玩家的,以使AI系統看起來更智能。

例如,在即時戰略遊戲(RTS)中,戰争迷霧用來限制玩家的視野,但AI敵人可以看見地圖上所有的機關。盡管這樣提高AI對抗更高智慧玩家的能力,但是如果優勢變的太大,會讓人覺得AI在作弊。記住,遊戲的重要點是讓玩家獲得樂趣,而不是讓AI擊敗他們。

在第二種類型的遊戲中,可能有許多AI代理。每一個都獨立,其不是非常智能。在某些情況下,AI代理會直接面對玩家,而有些可能是中立狀态,甚至還有一些是前面兩種狀态的結合。

有些代理可能是完全愚笨的,提供特定的、有限的行為而且并不關心遊戲世界中發生的事情。在走廊裡面來來回回走動的敵人就是一個例子。有些可能是稍微有些愚笨,隻有一個輸入和一個輸出,比如玩家可以打開和關閉的門。還有一些可能非常複雜,甚至懂得将它們的行為組合在一起。為AI代理選擇恰當的輸入允許你模仿“意識”和增加現實性。

不論AI代理有多麼簡單,一般都會它們使用狀态機。例如,第一個例子中的完全愚笨的物體必須記錄它在朝哪個方向走動;稍微愚笨的物體需要記錄它是開的狀态還是關的狀态。更複雜的物體需要記錄“中立”與“進攻性之間的”動作狀态,如巡邏、對抗與攻擊。

将遊戲視作具有主要遊戲狀态的模拟是非常重要的。不要将現實世界時間與遊戲時間混淆。如果玩家決定休息會兒,遊戲必須可以暫停。之後,遊戲必須可以平滑的繼續,就像任何事情都沒有發生一樣。由于IPHONE是移動裝置,儲存與繼續遊戲狀态變得尤其重要。

IPHONE上,在一個時間點隻允許一個應用程式運作,使用者也希望這些應用程式能夠很快載入。同時,他們希望能夠繼續他們在切換應用程式之前所做的事情。這意味着我們需要具有在裝置上儲存遊戲狀态,并盡可能快的繼續遊戲狀态的能力。對于開發遊戲,一項任務是要求保持現在的關卡并可以重新載入它使玩家即使在重新啟動應用程式後也可以繼續遊戲。你需要選擇儲存哪些資料,并以一種小巧的、穩定的格式将其寫到磁盤上。這種結構化的資料存儲被稱為序列化。

根據遊戲類型的不同,這可能比聽起來要困難的多。對于一個解謎遊戲,你将僅需要記錄玩家在哪個關卡、以及現在記分闆看起來是什麼樣的。但是在動作類遊戲中,除了記錄玩家虛拟人偶之外,你可能還需要記錄關卡中的每個物體的位置。在一個特定時間點,這可能變得難以管理,特别是當希望它能夠很快完成。對于這種情況,你可以在遊戲設計階段采取一些措施以確定成功。

首先,你必須決定什麼東西是在儲存遊戲狀态時必須儲存的。火焰粒子系統中的每根小火苗的位置并不重要,但是在粒子系統的位置在大型遊戲中可能很重要。如果它們能從關卡資料中獲得,那麼遊戲中每個敵人的狀态可能并不重要。用這種方式進一步考慮,如果你可以簡單的讓玩家的虛拟人偶從check point開始的話,那玩家虛拟人偶的确切狀态與位置也可能不需要儲存。

基于幀的邏輯是指基于單獨的幀的改變來更新遊戲物體。基于時間的邏輯雖然更複雜但卻與實際遊戲狀态更緊密,是随着時間的流逝而更新遊戲物體。

不熟悉遊戲開發的程式員總是犯了将基于幀的邏輯與基于時間的邏輯混合的錯誤。 它們在定義上的差別是微妙的,不過如果處理不得當,會造成非常明顯的BUG。

比如,讓我們以玩家移動為例。新手程式員可能寫出這樣的代碼:

void onPlayerInput( Input inputEvent ) {

if(inputEvt.type == IE_KEY && inputEvt.value == KEY_UP) {

//apply movement based on the user input

playerAvatar.y += movementSpeed;

每當玩家按下按鍵,虛拟人偶像前移動一點。這是基于幀的邏輯,因為每次移動的變化都會潛在的伴随着新的幀。事實上,在這個的例子中,每次玩家輸入事件都會發生移動。這或多或少有點像主循環的疊代。移動的可視化影響隻有在主循環的下次疊代中才會反映,是以任何疊代中間的虛拟人偶移動都會浪費計算。讓我們做一下改進:

//save the input state, but don't apply it

playerAvatar.joystick = KEY_UP;

if(inputEvt.type == IE_KEY_RELEASE) {

playerAvatar.joystick = 0;

void Update() {

//update the player avatar

if( playerAvatar.joystick == KEY_UP ) {

現在我們知道,在鍵被按下的過程中,每次遊戲循環中都隻會被賦予一次速度。但是,這仍然是基于幀的邏輯。

基于幀的邏輯的問題是,幀變化不會總是以相同的時間間隔發生。如果在遊戲循環中,渲染或者遊戲邏輯會比通常耗費更多的時間,它可能會被推遲到下一次循環中。是以,有時你需要有60幀每秒(fps),有時,你隻需要30fps。由于移動是适用于幀的,有時你隻會以通常的一半速度來移動。

你可以用基于時間的邏輯來準确的表達移動。通過記錄自從上次幀更新的時間,你可以适用部分移動速度。用這種方式,你可以以每秒為機關來辨別移動速度,而不必關心目前幀速率是多少,玩家虛拟人偶的速度是一緻的:

void Update( long currTime ) {

long updateDT = currTime - lastUpdateTime;

//since currTime is in milliseconds, we have to divide by 1000

// to get the correct speed in seconds.

playerAvatar.y += (movementSpeed * updateDT)/1000;

lastUpdateTime = currTime;

在這個例子中,移動速度的總量将會是相同的,不管是2fps還是60fps。基于時間的邏輯需要一點額外的代碼,但是它可以使程式更精确而不必在乎暫時的延遲。

當然可以用基于幀的邏輯來開發遊戲。重要的是,不要混合它們。比如,如果你的圖形代碼使用基于時間的邏輯來渲染玩家虛拟人偶的移動動畫,但是遊戲邏輯代碼卻使用基于幀的邏輯在遊戲世界中來移動它,這樣移動的動畫将不能玩玩家移動的距離完全同步。

如果可能的話,請盡量移除基于幀的邏輯。基于時間的邏輯将會對你有更大的幫助。

遊戲邏輯代碼的核心功能就是管理遊戲狀态的規則與進度。根據你的遊戲設計,這可能意味着任何事情。但是,還是有一些基本模式基于制作的遊戲的類型。

遊戲邏輯不與任何一個特定的類相關聯,它遊戲狀态對象中表現出來。當主遊戲狀态被初始化後,它将會為關卡載入與初始化必要的資源。例如猜謎遊戲中的一組提示與單詞、玩家虛拟人偶的圖檔資料以及玩家目前所在區域的圖檔資料。在遊戲循環中,遊戲邏輯将會接受使用者輸入,運作實體模拟,并負責處理所有的碰撞結局消息,模拟AI動作,執行遊戲規則。最後,當應用程式需要終止主遊戲狀态,它會釋放釋放所有的遊戲資源,并可能将遊戲狀态儲存到硬碟驅動器上。

根據遊戲的複雜度,你可能會發現很友善進一步分解遊戲邏輯。比如,如果你在開發一款冒險遊戲,你可能有一個充滿環境資料(地面、建築、河流、樹等)、可以移動、與玩家互動的實體(玩家虛拟人偶、敵人、非玩家角色、開關、障礙物等),各種GUI使玩家作出特殊動作和顯示重要資訊的遊戲世界。每種遊戲特征都必須有大量的代碼。雖然它們合在一起才能組成完整的遊戲,但是你還是可以保持它們的工作子產品化。

你可以建立一個Level Manager類來處理遊戲關鍵,包括載入和解除安裝顯示在遊戲世界中的實體與圖像資料與調用遊戲引擎來偵測實體與遊戲世界的碰撞。你還可以建立另外一個類或者一些類來處理遊戲世界中存在的實體。每個類都載入和解除安裝渲染那些物體的必要的實體和圖檔資料,以及包括控制它們的AI。

最後,你可能建立另外一個單獨的類來處理遊戲中使用者互動,以保持代碼與三大概念獨立。

這個體系結構适用于任何類型的遊戲。首先評估遊戲設計的主要特性,接着以某種方式組合,将相近的功能與資料組合在一起。

你應該對創造一個遊戲引擎時必須完成的任務有了一個基本的了解。這将會幫助我們在下一節建立這些元素,為我們的遊戲做準備。

繼續閱讀