遊戲引擎架構-學習筆記

5 遊戲支持系統

每個遊戲都需要一些底層支持系統以管理一些例行但重要的任務,如啓動和終止引擎、存取文件系統、存取不同資產類型(網格、紋理、動畫、音頻),本章討論多數引擎都會出現的底層支持系統。

5.1 子系統的啓動和終止

當引擎啓動時必須一次配置並初始化子系統,子系統之間相互依賴,這決定了子系統的初始化順序。

5.1.1 c++的靜態初始化次序

遊戲中的子系統,最常用的設計模式是爲這些子系統定義單例類。很多遊戲引擎都是基於c++語言,所以首先需要考慮c++原生的啓動及終止語義能否做啓動(構造子系統對象)或終止引擎子系統(析構子系統對象)之用。在程序入口main()函數執行前所有全局和靜態對象被構建,在main()結束之後這些全局和靜態對象被析構,不幸的是這些對象的構造和析構過程是無序的,因此無法使用。
1. 按需構建
可以使用一個c++的小技巧來解決上述問題:如果在函數體內定義靜態變量,那麼該變量在首次調用函數時構造。

class RenderManager
{
    public:
         static RenderManager& get()
         {
             static RenderManager renderManager; // 首次調用時實例化
             return renderManager;
         }
         RenderManager()
         {
             VideoManager::get()
             // ... 構造時先創建其他存在依賴的子系統
         }
}

此種做法仍有問題:功能層面,該方法無法控制靜態對象的析構時間,有可能在析構某一子系統靜態對象時,其依賴的其他子系統已經提前被析構。設計層面:RenderManager對象在第一次使用get函數時創建,第一次調用何時發生?無人知曉。好的設計應該精確控制對象的創建時間。
2. 行之有效的簡單方法
避免上述問題的一個簡單辦法是:將對象的啓動過程從構造函數移至一個特定函數中,將對象的析構過程從析構函數也移至一個特定函數,然後重載構造析構函數,讓其不做任何事。

class RenderManager{
RenderManager(){}
~RenderManager(){}
void startUp() {...}
void shutDown(){...}
}

RenderManager renderManager;

int main()
{
    // .. 啓動其他子系統
    renderManager.startUp()
    // .. 啓動其他子系統

    g_Game.run() // 遊戲主循環
    // .. 關閉其他子系統
    renderManager.shutDown()
}

5.2 內存管理

內存對遊戲引擎的影響包含兩方面:1)動態分配內存:動態內存分配效率十分低,應儘量避免;2)內存訪問模式:將數據置於細小密集分佈的內存中相較於將數據分散至廣闊內存中,CPU對前者的操作效率會高很多。下面將針對上述兩點優化內存使用。

5.2.1 優化動態內存分佈

通過C++的new/delete運算符操作內存稱爲動態內存分佈,也成爲堆分配。這一操作效率很低,原因是:1)堆分配是通用分配器,它必須能夠處理任意大小的分配請求,這需要極大的管理開銷;2)在很多操作系統中,new/delete操作需要將用戶模式切換至內核模式,上下文環境的修改也十分耗時。因此遊戲中的一個經驗法則時:維持最低限度的堆分配,禁止在緊湊循環中使用堆分配。很多遊戲實現了定製的分配器以解決堆分配的缺陷,下面做個介紹。
1. 基於堆棧的分配器
堆棧分配器通過new或聲明一個全局字節數組分配一大塊連續內存,分配器用一個頂端指針指向該大塊內存的已分配內存的頂部,當分配新內存時指針繼續向上移動。堆棧分配器禁止以任意次序釋放內存,而必須嚴格按照內存分配時的順序逆序釋放。爲達到這一要求,每次分配內存時返回一個標誌Marker來標記當前內存的頂端位置,在後續釋放該內存的下一塊內存時將頂端指針與Marker直接的內存釋放,如圖所示。

2. 池分配器
遊戲引擎有時需要分配大量同等尺寸的小內存塊,如可能要分配或釋放矩陣、迭代器、鏈表中的節點及可渲染的網格實例等,這類情況可使用池分配器。如需要分配網格實例,網格大小4*4,每個網格元素佔4KB,池分配器可分配一塊4*16kb的內存,內存分爲16個單元,每個單元存放到一個鏈表當中,當需要分配一個網格實例元素時,池分配器將鏈表下一個單元取出並傳回,實例元素將存放於該單元中,釋放實例元素時,將其對應的單元插入至鏈表中。簡單來說,池分配器將大內存塊切分成很多小塊並用鏈表將這些內存塊在邏輯層面連接起來以保證內存塊在物理位置上的順序,分配和釋放內存實則時鏈表的刪除插入操作,效率很高。
3. 單幀與雙緩衝內存分配器
很多時候遊戲需要在主循環中使用一些臨時數據,這些變量在本次主循環結束時或下次主循環結束時即刻被銷燬,此時會用到單幀或雙緩衝分配器來分配這些臨時變量的內存。

  • 單幀分配器:預先一大塊內存並使用上文所述的堆棧分配器管理,在每次引擎進入主循環時先將單幀分配器管理的內存清空(棧頂指針指向桟底),然後在循環體內利用單幀分配器爲臨時變量分配內存。單幀分配器的優勢是:我們不需要關心分配了的內存何時回收,因爲分配器會統一回收。但是該分配器的缺陷也很明顯:用它分配的變量僅能使用在當前幀中,因爲下一幀該變量內存就被釋放。
while(true)
{
    g_singleFrameAllo.clear() // 先全部清空內存
    // ...
    g_singleFrameAllo.allo(nbyte) // 分配內存 無需關注何時釋放掉該內存
}
  • 雙緩衝分配器:這一分配器能保證當前幀分配的內存延遲到下一幀結束時被是釋放。
while(true)
{
    g_doubleFrameAllo.swapBuffers() // 交換現行(上一幀)與無效(當前幀)緩衝區
    g_doubleFrameAllo.clearCurrentBuffer() // 清空新的現行緩衝區
    g_doubleFrameAllo.allo(nbyte) // 在新的現行緩衝區中分配內存
}

在多核遊戲機上緩存非同步處理的數據,這樣的分配器十分有用。如在第i幀將某任務數據寫進緩存,在第i+1幀兩個緩衝互換,任務數據緩衝處於非活動狀態,不用擔心其數據被覆蓋,因此在第i+1幀結束前可放心使用這些任務數據。

5.2.2 內存碎片

動態堆分配以隨機方式分配與釋放內存,在多次分配與釋放之後會內存自由塊與使用塊相間排布,我們稱使用塊之間的自由塊爲洞,當洞變得多而小的時候,這個狀態稱爲內存碎片狀態。內存碎片的問題在於:即使自由內存足夠的大,分配請求仍會失敗,因爲對於一次分配請求,內存必須連續。

  1. 以堆棧或池分配器避免內存碎片
    堆棧分配器能完全避免內存碎片,因爲它以連續方式分配內存,並嚴格按照逆序方式釋放內存;池分配器也能完全避免內存碎片,雖然池分配器也會產生自由塊與使用塊相間分佈的狀態,但它的內存塊大小完全一致,且是提前基於分配需求劃定的,因此它能夠相應後續的內存分配請求。
  2. 碎片整理與重定向
    若需要分配大小不同的內存(無法使用池分配器)且必須按照隨機次序進行(無法使用堆棧分配器),對付這種情況,我們可以使用碎片整理。碎片整理將洞(自由內存區)冒泡至內存高地址區,相對應的,使用區被移至內存低地址區域,從而使得自由區連續。這個操作不難,但困難的是:使用區的地址發生了變化,那麼指向這些使用區的指針也將失效。因此我們必須採用某些手段維護這些指針,使其在內存地址發生變化後指向新的內存,這一過程稱爲重定位。但是在C++中無法搜索指向某塊內存的指針,即在移動已使用內存時我們無法得知那些指針指向了該內存,因此這使得指針的更新很難進行(只能通過程序員自己實現方法手動維護)。一種更好的方式是捨棄使用普通指針而使用智能指針或句柄。
    • 智能指針:智能指針使用起來與普通指針幾乎完全一樣,它實則是一個細小的類,類包含一個普通指針。智能指針之所以能夠解決重定向,是因爲我們可以在這個類裏新增代碼:當智能指針指向一個內存時,將該智能指針存儲到一個全局鏈表裏,當移動某塊內存時在全局鏈表裏搜索並更新相關的智能指針裏的普通指針信息。
    • 句柄:句柄通常實現爲句柄表的索引,句柄指向的表內元素存放着某塊內存的地址。當內存發生移動時自動遍歷句柄表並更新相關內存地址,這樣所有使用該句柄的程序總能夠得到正確的內存位置。
  3. 分攤碎片整理成本
    碎片整理涉及到內存複製,其操作可能十分耗時,如果放在一幀內處理很有可能對遊戲幀率產生影響。我們可以將碎片整理操作放在多幀裏完成。假設一幀執行時間爲1/30秒,則1秒包含30幀,我們將整理操作分攤到這30幀裏完成。只要分配及釋放次數低於碎片整理移動次數,堆內存塊就能基本保持完全整理狀態。

5.2.3 緩存一致性

讀寫系統內存通常需要幾千個處理器週期,而讀寫CPU寄存器只需要幾個處理器週期,爲降低系統內存的平均讀寫時間,現代處理器一般採用告訴的內存緩存。緩存是特殊的內存,CPU讀寫緩存要快於主內存。當首次讀主內存時,該內存小塊會載入高速緩存,這個內存塊單位稱爲緩存線。若後來在讀取內存而其數據已經在緩存中,則直接讀取緩存中的數據,如果不再緩存中,則緩存命中失敗,此時程序被逼停直到緩存先被更新後才能繼續執行。我們無法完全避免緩存命中失敗,但可以採用一些方法儘量減少命中失敗。

  1. 避免緩存命中失敗:避免緩存命中失敗最佳的方式是把數據編排進連續的內存塊中,尺寸越小越好,且順序訪問這些數據。這種做法之所以有效,是因爲當發生命中失敗時,會儘可能多的將相關的數據載入至緩存線中,如果數據量小,很有可能載入至單個緩存線中,當順序存取數據時(不用在內存直接跳來跳去),就可以最大程度的減少緩存命中失敗。

7 遊戲循環及實時模擬

遊戲中包含不同種類的時間概念:實時、遊戲時間、動畫自身的時間線、函數實際執行的CPU時間週期等。下面的部分介紹了實時、動態模擬軟件如何運作,並探討這類軟件中時間的常見運用方法。

7.1 渲染循環

圖形用戶界面的畫面大部分的內存是靜止不動的,在某一時刻只有少部分視窗會主動更新其外貌,傳統上會利用一種稱爲矩陣失效的技術讓屏幕中有改動的內容重繪。較老的遊戲引擎也會採用類似的技術以儘量降低需重回的像素數目。
實時三維計算機圖形以另一種方式實現,當攝像機移動時屏幕和視窗上的一切內容都不會變,而是在視窗上快速的顯示一連串靜止的影像。在實時渲染應用中,用一個循環體來實現這種效果。

while(!quit)
{
    // 基於預設路徑更新相機
    updateCamera();
    // 更新場景中所有動態元素的定向、位置等信息
    updateSceneElement();
    // 把靜止的場景渲染至場景外的幀緩衝中
    renderScene();
    // 交換背景緩衝和前景緩衝,將最近渲染的影像顯示在屏幕上
    swapBuffers();
} 

7.2 遊戲循環

遊戲由許多子系統構成,輸入/輸出設備、渲染、動畫、碰撞檢測、音頻等。遊戲子系統需要週期性的爲遊戲提供服務,其週期頻率可能各不相同,如動畫頻率需要和渲染頻率保持一致,如30Hz,而動力學模擬系統需要更頻繁的更新,如120Hz。可通過使用遊戲循環來實現子系統的更新。

7.2.1 遊戲循環的架構風格

有許多方法實現子系統的週期性更新,但其核心通常包含一個或多個循環。

1. 視窗消息泵

對於window平臺上的遊戲,即要處理遊戲的邏輯,還要處理window的消息,因此這類遊戲都會有一個消息泵,基本原則是先處理window消息,在處理遊戲邏輯。

 while(true)
 {
         // 處理window消息
         Msg msg;
         while(PeekMessage(&msg))
         {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
         }
         // 開始處理遊戲邏輯
         RunGameLoop();
 }

2. 回調驅動框架

多數遊戲引擎中的子系統都是以程序庫的方式構建,程序員通過調用這些程序庫來是實現相關行爲,這種方式的缺陷是:很多程序庫比較難用,程序員必須瞭解各個函數和類的具體使用方式。還有些遊戲引擎以框架的方式構建,框架是半成品的應用軟件,程序員需要完成框架中的空缺的自定義實現,但幾乎無法修改該軟件的核心控制流程,因爲這些都由框架層面控制。

while(true)
{
    for(each frameListener)
        frameListener.frameStarted(); // 自己實現具體的frameStarted方法

    renderCurrentScene();

    for(each frameListener)
        frameListener.frameEnded();

    finializeSceneAndSwapBuffer();
}

程序員自己要實現一個frameListener以繼承引擎的基類FrameListener, 並重寫兩個虛函數frameStarted,frameEnded。

3. 基於事件的更新

在遊戲中,事件是指遊戲狀態或遊戲環境發生的某種變化。多數遊戲引擎都會提供自己的事件系統,讓各個子系統登記其關注的相關事件類型,當該類事件發生時,子系統便可以一一響應。
有些引擎使用事件系統對子系統進行週期性更新。實現的方式是:子系統在實現週期性更新時,只需要簡單的加入事件類型,在事件處理器裏,代碼便能以任何所需要的週期進行更新,接着,該代碼發送一個新事件,並設定該事件在1/60s之後生效,當事件生效後,執行關注了該事件類型的子系統,以此完成一次子系統的週期性更新。

7.3 抽象時間線

7.3.1 真實時間線

我們可以使用CPU高分辨率計時寄存器來度量時間,這個時間在真實時間線上,此時間線的原點爲遊戲本次啓動時間。

7.3.2 遊戲時間線

遊戲時間線在正常情況下與真實時間線保持一直,但可通過修改遊戲時鐘實現遊戲暫停或減慢。暫停遊戲並非停止遊戲的主循環,而是修改該循環體內對遊戲時鐘的操作方式,比如註釋掉遊戲時鐘的更新代碼,則與遊戲時鐘相關的功能將暫停。

7.3.3 局部和全局時間線

播放一個動畫,實則是動畫在局部時間線上的一次動作,局部時間線的起點對應着動畫開始播放時間點。當動畫按照正常速率播放時,局部時間線可以看作在全局時間線上的簡單映射,兩者的長度一致,當動畫的播放速率降低一倍,可以看作將局部時間線拉長一倍然後映射至全局時間線上。

10 渲染引擎

10.2 渲染管道

高級的渲染步驟由名爲管道的軟件架構所實現,管道只是一連串順序計算階段,每個階段對輸入流進行相關處理並對輸出流產生數據。管道中的每個階段可以獨立與其他階段,因此每個階段可以實現並行,如階段1在處理一個數據元素,階段2可以同步處理階段1已經產生的相關結果。管道的吞吐量量度整體每秒產生的數據量,單個階段的吞吐量衡量該階段需要多長時間處理單個數據,潛伏期量度單個數據需要花費多長時間才能走完整個管道。管道由多個階段順序相連,因此最低吞吐量的階段將成爲整個管道的上限。對於優良設計的管道,所有階段同步執行,沒有階段需要長期閒置等待其他階段的數據。

10.2.1 渲染管道概觀

管道中,最高級的階段包括:

  • 工具階段(脫機):定義幾何與表面特性(材質);
  • 資產調節階段(脫機):資產調節管道處理幾何與材質數據,生成引擎能處理的格式;
  • 應用程序階段(CPU):識別出潛在可是的網格實例,並把它們及其材質遞交至圖形硬件以供渲染;
  • 幾何階段(GPU):對頂點實施變換、照明,接着投影至齊次裁剪空間。可選用幾何着色器處理三角形,並用平截頭體裁剪三角形;
  • 光柵化處理(GPU):將三角形轉換爲片段,並對片段着色。片段經過多種測試(深度測試、alpha測試、模板測試等)後,最終與幀緩衝混合。

1 渲染管道如何變換數據

工具和資產調節階段負責處理網格和材質,應用程序階段負責處理網格實例與子網格,每個子網格關聯一個材質。在幾何階段,每個子網格分解成幾個頂點,頂點獲大規模並行處理,在這一階段的結尾,完全變換、着色後的頂點重新構成三角形。光柵化階段,每個三角形分解爲片段,如果片段沒被裁剪丟棄,其顏色最終會寫入緩衝區。

10.2.2 工具階段

在工具階段,三維建模式可以藉助大量工具創建三維模型,這些模型可以由任何方便的表面描述方式所表達,比如四邊形、三角形等各種類型的網格。然而在管道的運行時渲染前,總需要鑲嵌成三角形。
工具階段,美術人員也需要基於材質編輯器確定材質(表面屬性),併爲材質確定着色器、着色器所需紋理及設置着色器配置參數等。材質可由個別網格存儲及管理,但如果這麼做會導致大量重複性數據,因爲在許多遊戲中,少量材質會應用至許多物體當中。爲解決這個問題,很多遊戲會建立材質庫以統一存儲管理材質,從中爲每個網格挑選合適的材質,以此讓網格和材質保持鬆散的耦合。

10.2.3 資產調節階段

資產調節階段本身也是一個管道,其工作時導出、鏈接、處理多個種類的資產,生成內聚的整體。例如三維模型由幾何(頂點和索引緩衝)、材質、紋理、骨骼所組成,該階段確保三維模型所有涉及到的個別資產均可用,且都已準備好供引擎使用。
資產調節階段也會計算高級的場景圖數據結構,如爲靜態關卡幾何建立BSF樹,以加速渲染引擎判斷哪些物體需要渲染。
耗時的光照計算也在該階段完成,這種計算稱爲靜態光照,靜態光照可以計算網格頂點上的光照顏色,也可以把每像素的光照信息存於紋理中,這種紋理稱爲光照貼圖。

10.2.4 GPU簡史

在遊戲開發的早起,所有渲染都在CPU上進行,後來硬件廠商開始開發圖形硬件。早起的圖形加速器只能處理管道中最耗時的階段:光柵化,後來這些硬件也能負責一些幾何的計算。最初,這些圖形硬件只提供硬接線但可配置的的管道實現,這種管道稱爲固定功能管道,這項技術稱爲硬件變換及光照。之後該管道內的數個子階段變成了可編程的,如工程師能編寫着色器程序處理頂點及片段。
圖形硬件已進化成一種專門的微處理器,稱爲圖形處理器GPU,GPU爲最大化管道吞吐量而設計,其使用了大量的並行化處理。即使GPU在完全可編程的形式下,也不能作爲通用處理器使用-也不應該如此,因爲GPU之所以能達到極高的處理速度,在於其仔細的控制了管道的數據流,有些管道是完全固定功能的,有些時可配置但不可編程的。內存只能在控制範圍內存儲,且採用了緩存存儲了那些不需要重複計算的數據。

10.2.5 GPU管道

幾乎所有的GPU都會將管道拆分成下圖所述的各個子階段。
這裏寫圖片描述

1. 頂點着色器

此階段是完全可編程的,頂點着色器負責變換及着色|光照頂點,此階段的輸入是單個節點(雖然實際上會並行處理多個節點)。頂點位置和法矢量以模型空間或世界空間表達。此階段也會進行透視投影、每頂點光照及紋理計算,頂點着色器也可以通過改變頂點位置來產生程序式動畫,如模擬風吹草動和水波等。此階段的輸出時變化和光照後的頂點,其空間爲齊次裁剪空間。

2. 幾何着色器

可選的幾何着色器也是完全可編程的,該着色器用以處理以齊次裁剪空間表示的整個圖元(三角形、線、點),他能剔除和修改輸入的圖元,又能生成新的圖元,其典型應用包括:陰影體積拉伸、在網格的輪廓邊拉伸毛髮的鰭、幾何動態鑲嵌、把線段以分形細分以模擬閃電特效等。

3. 流輸出

現在的GPU容許將達致管道階段的數據回寫進內粗,數據能從那裏回到管道之初做進一步處理,這一功能稱爲流輸出。有了流輸出功能,許多迷人的視覺效果可以不經CPU完成,如頭髮渲染,以前在實現這一效果時在CPU上進行物理模擬得到三次樣條數據,之後繼續在CPU上將樣條鑲嵌爲線段。流輸出可將該實現移至GPU上實現:通過頂點着色器完成物理模擬,通過幾何着色器完成鑲嵌過程。之後將處理後的數據輸出至管道初始位置進行後續渲染。

4. 裁剪

裁剪階段是將齊次裁剪空間以外的三角形部分裁減掉,其原理是:首先計算落在裁剪空間以外的頂點,之後求出這些頂點對應的三角形的棱與平截頭體面之間的交點,這些交點將會作爲裁減後新三角形的頂點。此階段是固定功能,但提供有限度配額。如除了平截頭體平面以外,還可定義其他截面。

5. 屏幕映射

屏幕映射時簡單的平移或縮放頂點,是指從裁剪空間轉換至屏幕空間,此階段是固定且完全不可配的。

6. 三角形建立

自三角形建立階段開始,光柵化硬件迅速的將三家形轉換成片段。此階段是不可配置的。

7. 三角形遍歷

三角形遍歷將三角形分解成片段(即光柵化),每個片段對應一個像素,通常基於頂點插值獲得片段屬性值,以供像素着色器使用,該階段是不可配置的。

8. 提前深度測試

許多顯卡能夠在此時間點對片段的深度進行測試,若發現片段被幀緩衝中的像素遮擋,這將丟棄該片段,避免後續像素着色器的處理,但並非所有着色器都支持在這一階段進行深度檢測,因爲以前的深度檢測和alpha檢測都是在像素着色器之後進行的,所以這裏的測試稱爲提前深度測試。

9. 像素着色器

像素着色器是完全可編程的,該階段可對像素進行着色(包括光照和其他處理)。像素着色器可對多個紋理採樣、計算每像素光照以及任何影響片段顏色的計算。

10. 合併或光柵運算階段

管道的最終階段爲合併階段或光柵運算階段,該階段不可編程但是可高度配置,該片段負責執行多個測試,包括:深度測試、alpha測試以及模板測試。若通過測試,就會和幀緩衝中的顏色值混合,混合方式由alpha混合函數決定,該函數時固定的,但可以通過配置其運算符及參數因子實現不同的混合方式。

14 運行時遊戲性基礎系統

14.1 遊戲性基礎系統的組件

多數引擎都會以某種形式提供以下的子系統:

  • 運行時遊戲對象模型:抽象遊戲對象模型的實現,供遊戲設計師在世界編輯器中使用;
  • 關卡管理及串流:此係統負責載入及釋放遊戲用到的數據內容,許多引擎會在遊戲運行時把數據串流至內存中;
  • 更新實時對象模型:負責實時更新遊戲中的對象;
  • 消息及事件處理:大多數遊戲對象需要和其他對象通信,對象間的消息很多時候是用來發出遊戲中各種狀態改變的信號的,此時稱這種消息爲事件。
  • 腳本:很多時候用C\C++等語言編寫較爲上層的遊戲邏輯會過於累贅,此時通常需要使用腳本;
  • 目標及遊戲流程管理:此子系統管理玩家的目標及遊戲的整體流程。

在這些子系統當中,運行時遊戲對象模型最複雜,它基本上提供了以下大部分功能:

  • 動態的產生與銷燬遊戲對象:許多引擎會提供一個系統,爲動態產生的遊戲對象管理內存及相關資源;
  • 聯繫底層引擎系統:多數遊戲對象在視覺上以可渲染的三角形網格表示,有些對象有粒子效果,有些有聲音,有些有動畫,因此需要確保遊戲對象能夠訪問所需要的底層的引擎系統;
  • 實時模擬對象行爲:遊戲引擎需要隨時間動態更新遊戲對象的各種行爲及狀態,對象可能需要以某種特定次序進行更新;
  • 遊戲對象查詢:遊戲性基礎系統必須提供一些方法去搜索遊戲世界中的對象;
  • 遊戲對象引用:當找到了所需的對象,我們需要以某種形式保留對象的引用。
  • 有限狀態機:許多遊戲對象類型的最佳建模方式是使用有限狀態機,有些遊戲引擎可以令遊戲對象處於多個狀態之一,而每個狀態下尤其屬性及行爲特徵。

14.2 各種運行時對象模型架構

運行時對象模型多數會採用兩種架構風格:

  • 以對象爲中心:遊戲對象在運行時以單個類實例或數個相連的實例組成,每個對象所包含的屬性及行爲都會封裝在那些對象實例的類中,而遊戲世界則是衆多遊戲對象的集合;
  • 以屬性爲中心:遊戲對象僅以唯一標識符表示,每個對象的屬性分佈於多個數據表,每個屬性類型對應一張表,表以對象的唯一標識符爲鍵,而遊戲對象的行爲,有對象擁有的屬性決定,比如對象定義了血量屬性,則該對象能夠被攻擊、扣血及死亡等,若對象含有網格實例屬性,則該對象能被渲染三角網格實例。

14.2.1 以對象爲中心的各種架構

1. 單一龐大的類層次結構

當遊戲對象模型中所有的類都繼承至單個共同的基類時,此類的層次結構就表現的單一且龐大。

2. 深寬層次結構

15 遊戲中常用的機制實現方法

15.1 場景數據的下發

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章