每個遊戲都需要一些底層支援,以管理一些例行卻關鍵的任務。例如啟動及終止引擎、訪問(多個)檔案系統、訪問各種不同的資產型別(網格、紋理、動畫、音訊等),以及為遊戲團隊提供除錯工具。
5.1 子系統的啟動和終止
遊戲引擎是複雜軟體,由多個互相合作的子系統結合而成。當引擎啟動時,必須一次配置及初始化每個子系統。各子系統間相互依賴關係,隱含地定義了每個子系統所需要的啟動次序。例如子系統b依賴子系統a,那麼在啟動b之前,必須先啟動a。各子系統的終止通常採取反向次序,即先終止b,再終止a。
5.1.1 c++的靜態初始化次序(是不可用的)
由於多數新式遊戲引擎皆採用c++為程式語言,我們應該考慮一下,c++原生的啟動和終止語意是否可作為啟動及終止引擎子系統之用。c++中,在呼叫程式進入主函式或者windows的winmain())之前,全域性及靜態物件已被構建。然而,我們完全不可預知這些建構函式的呼叫次序。在main或winmain結束之後,會呼叫全域性及靜態物件的析構函式,而這些函式的呼叫次序也是無法預知的。顯而易見,此c++行為並不適合用來初始化及終止遊戲引擎的子系統,實際上,對任何含互相依賴全域性物件的軟體都不適合。
這很令人失望,因為要實現各主要子系統,例如遊戲引擎中的子系統,常見的設計模式是為每個子系統定義單例類(singleton class)(通常稱作管理器/manager)。若c++能給予我們更多的控制能力,指明全域性或靜態例項的建構、析構次序,那麼我們就可以把單例定義為全域性變數,而不必使用動態記憶體分配。例如,各子系統可寫為以下形式:
calss rendermanager
public:
rendermanager()
//啟動管理器....
~rendermanager()
//終止管理器.....
static rendermanager grendermanager;
可惜,由於沒法直接控制建構和析構次序,此方法行不通。
5.1.1.1 按需架構
對付此問題,可使出乙個c++的花招:函式內宣告的靜態變數並不會於main()之前建構,而是在第一次呼叫該函式時才建構。因此,若把全域性單例改為靜態變數,我們就可以控制全域性單例的建構次序。
class rendermanager
rendermanager()
~rendermanager()
;你會發現,許多的工程教科書都會建議此方法,或以下這種含動態分配單例的變種:
static rendermanager& get()
assert(gpsingleton);
return *gpsingleton;
}但是,此方法不可控制析構的次序。例如,在rendermanager析構之前,其依賴的單例可能已被析構。而且,很難預計rendermanager單例的確切析構時間,因為第一次呼叫rendermanager::get()時,單例就會進行建構,天知道那是什麼時候!此外,使用該類的程式設計師可能不會預期,貌似無傷大雅的get()函式可能會有很高的開銷,例如,分配及初始化乙個重量級的例項。此方法是難以預計且危險的設計。這促使我們訴諸更直接、更大控制權的方法。
5.1.2行之有效的簡單方法
假設我們對於子系統繼續採用單例管理器的概念。最簡單的「蠻力」方法就是,明確地為各個單例管理類定義啟動和終止函式。這些函式取代構造和析構函式,實際上,我們會讓構造和析構函式完全不做任何事情。這樣的話,就可以在main()中(或某個管理整個引擎的例項中),按所需的明確次序呼叫各種啟動和終止函式。例如:
class rendermanager
~rendermanager()
void startup()
void shutdown()
};class physicsmanager;
class animationmanager;
class memorymanager;
class filesystemmanager;
//......
rendermanager grendermanager;
physicsmanager gphysicsmanager;
animationmanager ganimationmanger;
texturemanager gtexturemanager;
videomanager gvideomanager;
filesystemmanager gfilesystemmanager;
//.....
int main(int argc,const char*argv)
{//以正確次序啟動各引擎系統
gmemorymanager.startup();
gfilesystemmanager.startup();
gvideomanager.startup();
gtexturemanager.startup();
grendermanager.startup();
ganimationmanager.startup();
gphysicmanager.startup();
//......
//執行遊戲
gsimulationmanage.run();
//以反向次序終止各引擎系統
//......
gphysicsmanager.shutdown();
ganimationmanager.shutdown();
grendermanager.shutdown();
gtexturemanager.shutdown();
gvideomanager.shutdown();
gfilesystemmanager.shutdown();
gmemorymanager.shutdown();
return 0;
此法還有「更優雅的」實現方式。例如,可以讓各管理器把自己登記在乙個全域性的優先佇列中,之後再恰當次序逐一啟動所有管理器。此外,也可以通過每個管理器列舉其依賴的管理器,定義乙個管理器間的依賴圖(dependency graph),然後按互相依賴關係計算最優的啟動次序。(以上兩種建議確實比較有用,不過需要你的邏輯清晰)。根據《遊戲引擎架構》作者本人的認為,蠻力方法總是優於其它方法。原因如下:
此方法既簡單又容易實現。
此方法明確**的執行次序(強制自定義次序)
此方法容易除錯和維護。若某子系統啟動時機不夠早或者過早,只需要移動一行**。
當然,用蠻力方法手動啟動及終止子系統,還有各小缺點,就是程式設計師有可能意外地終止一些子系統,而非按啟動的相反次序。只要能夠成功啟動及終止引擎的各子系統,你的任務就完成了。
書中譯註說:要解決按需建構方式的析構次序問題,可以在單例建構時,把自己登記在乙個全域性堆疊中,在main()結束之前,逐一 把堆疊彈出並呼叫其終止函式。此方法假設單例的終止次序可以為啟動次序的相反,但理論上不能解決所有情況。
以上內容來自《遊戲引擎架構》p185-p189
架構之美第五章 架構概述
建築師 家 作家 計算機設計師 網路設計師和軟體開發者都在使用 架構 這個術語,其他人也用 你有沒有聽說過 食物架構 然而不同的用法其結果也不同。建築與交響樂完全不同,但都有架構。而且,所有的架構師都在談論他們工作中的美,以及因此而導致的結果。建築師可能會說,一座建築應該提供適合工作或生活的環境,而...
第五章 家境之困
屋中的老人不知何時又睜開了眼,怔怔地望著東邊的山。老人姓張,名冬生。是草廟村的村民。膝下無兒無女。本來是自在一人,習慣了獨身,雖然有時不免傷神 膝下無人,晚來沒有照料。但實際情況便是這樣,他也不可能再找個老太太,一起制定生育計畫。自在的改變是在十四年前,那時的旅遊不如現在興盛,星星點點有一些遊人。放...
第五章 容器之字典
知識點 字典 dictionary 用鍵值對 key value 來表示,可以用鍵 key 來查詢值 value 反之不行。字典用來儲存成對出現的資料。字典的建立 鍵值對的新增及查詢 刪除。可以用 in 或者not in 來查詢鍵 key 是否在字典裡,但不可以用來查值 x dict a 1,b 2...