iPhone 遊戲開發教程 遊戲引擎 6)是本我要介紹的內容,繼續上一章開始介紹,本節主要介紹了事件的相關內容,先來看本文詳解。
解決高層次事件
一旦判定了使用者執行的物理動作,你的代碼必須能將它們轉換為遊戲邏輯組件可以使用的形式。具體怎麼做需要依賴於你的遊戲的上下文,但是這裡有幾種典型的形式:
如果玩家準備控制虛擬人偶,在玩家和遊戲之間通常會有連續的互動。經常需要儲存目前使用者輸入的表現形式。比如,如果輸入裝置為遙杆,你可能需要在主迴圈中記錄當前點的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,
- };
現在我們定義一個結構體來儲存我們的訊息資料:
- view plaincopy to clipboardprint?struct sGameLogicMessage {
- short type;
- short value;
- } Message;
現在,我們就可以像上一個例子代碼一樣,用一個對象來傳遞我們的訊息:
- void onPlayerInput( Input inputEvt ) {
- if(inputEvt.type == IE_KEY && inputEvt.value == KEY_UP ) {
- 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;
- }
- }
每當玩家按下按鍵,虛擬人偶像前移動一點。這是基於幀的邏輯,因為每次移動的變化都會潛在的伴隨著新的幀。事實上,在這個的例子中,每次玩家輸入事件都會發生移動。這或多或少有點像主迴圈的迭代。移動的可視化影響只有在主迴圈的下次迭代中才會反映,所以任何迭代中間的虛擬人偶移動都會浪費計算。讓我們做一下改進:
- void onPlayerInput( Input inputEvent ) {
- if(inputEvt.type == IE_KEY && inputEvt.value == KEY_UP) {
- //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 ) {
- playerAvatar.y += movementSpeed;
- }
- }
現在我們知道,在鍵被按下的過程中,每次遊戲迴圈中都只會被賦予一次速度。但是,這仍然是基於幀的邏輯。
基於幀的邏輯的問題是,幀變化不會總是以相同的時間間隔發生。如果在遊戲迴圈中,渲染或者遊戲邏輯會比通常耗費更多的時間,它可能會被延遲到下一次迴圈中。所以,有時你需要有60幀每秒fps),有時,你只需要30fps。由於移動是適用於幀的,有時你只會以通常的一半速度來移動。
你可以用基於時間的邏輯來準確的表達移動。通過記錄自從上次幀更新的時間,你可以適用部分移動速度。用這種方式,你可以以每秒為單位來標識移動速度,而不必關心當前畫面播放速率是多少,玩家虛擬人偶的速度是一致的:
- void Update( long currTime ) {
- long updateDT = currTime - lastUpdateTime;
- //update the player avatar
- if( playerAvatar.joystick == KEY_UP ) {
- //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。
最後,你可能建立另外一個單獨的類來處理遊戲中使用者互動,以保持代碼與三大概念獨立。
這個體繫結構適用於任何類型的遊戲。首先評估遊戲設計的主要特性,接著以某種方式組合,將相近的功能與資料群組合在一起。
小結:
小結:iPhone 遊戲開發教程 遊戲引擎 6)的內容介紹完了,希望本文對你有所協助!你應該對創造一個遊戲引擎時必須完成的任務有了一個基本的理解。這將會協助我們在下一節建立這些元素,為我們的遊戲做準備。 想要深入瞭解iPhone 遊戲引擎的更多內容,請參考以下幾篇文章:
iPhone 遊戲開發教程 遊戲引擎 1)
iPhone 遊戲開發教程 遊戲引擎 2)
iPhone 遊戲開發教程 遊戲引擎 3)
iPhone 遊戲開發教程 遊戲引擎 4)
iPhone 遊戲開發教程 遊戲引擎 5)