unity-Entity-Component-System模式
所謂的ECS模式全稱就是Entity-Component-System模式。很早就聽說過unity等引擎中廣泛地使用了這樣的模式,沒有細查。今天看了幾篇文章之後有了些許瞭解,故記敘此文作為筆記。
一、問題提出
之前在寫STG框架的時候遇到了這樣的問題,以面向物件的思想對遊戲物件進行抽象,那麼可以實現一個基類GameObject。之後包括敵機、自機、子彈在內的所有物件都繼承這個基類並進行實現。
於是很容易設計出這樣一個基類:
- classGameObject
- {
- private:
- // === 基礎屬性 ===
-
fcyVec2 m_Position; // 位置
- fcyVec2 m_Rotation; // 方向
- fcyVec2 m_Scale; // 縮放
- // === 碰撞屬性 ===
- fcyVec2 m_Size; // 大小
- public:
- voidUpdate(floatdelta);
- voidRender(floatdelta);
- };
之後,繼承這個基類,分別實現GameBullet、GameEnemy……
這樣帶來的問題就是遊戲物件的邏輯往往在超類中被定死了,而一旦需要對邏輯作出修改,要麼重寫實現,要麼繼承基類進行覆蓋。此外,在C++中使用物件池優化時就會造成災難性的後果——一種型別一個池(儘管可以用通用的記憶體分配器,但是這樣還要考慮記憶體碎片等雜七雜八的問題)。
對於傳統的設計思路,在遊戲開發上就會導致“類災難”。於是ECS模式被提了出來,用於解決繼承帶來的問題。
二、ECS的解決之道
使用繼承去表述遊戲物件和邏輯會造成邏輯混雜、維護擴充套件困難的問題。
既然繼承出了問題,我們就用組合來解決吧。
於是在2002年的Game Dungeon Siege上,ECS模式被提了出來。
ECS全寫即“例項-元件-系統”的設計模式。簡言之,例項就是一個遊戲物件實體,一個實體擁有眾多的元件,而遊戲系統則負責依據元件對例項做出更新。
舉個例子,如果物件A需要實現碰撞和渲染,那麼我們就給它加一個碰撞元件和一個渲染元件;如果物件B只需要渲染不需要碰撞,那麼我們就給它加一個渲染元件即可。而在遊戲迴圈中,每一個系統都會遍歷一次物件,當渲染系統發現物件持有一個渲染元件時,就會根據渲染元件的資料來執行相應的渲染過程。同樣的碰撞系統也是如此。
也就是說遊戲物件需要什麼就會給自己加一個元件。而系統會依據遊戲物件增加了哪些元件來做出行為。換言之例項只需要持有必要的資料,由系統負責邏輯就行了。這也就是ECS模式能和資料驅動很好結合的一個原因。
於是在上述問題中所有的派生類都消失了,只留下了GameObject。
三、沒有什麼是完美的
雖然ECS模式可以讓維護和擴充套件的成本降低——必要的時候你只要給物件增加元件、為遊戲邏輯增加系統就可以擴充套件了。這種設計降低了耦合,也提高了可複用性。
但是很明顯的,ECS帶來了兩個缺陷:
1、資料共享
比如渲染元件需要物件的位置資訊、碰撞元件也要物件的位置資訊,那麼我們怎麼解決這裡的資料共享問題?
一種解決方法是把位置資訊作為元件抽象出來,但是這樣帶來的問題就是效率會降低,在處理渲染和碰撞的時候都會再去存取一次位置資訊。
另一種解決方法是使用引用(比如使用C++中的智慧指標)在構建物件的時候分配同一位置物件,然後傳遞引用給其他元件。
2、遍歷次數
當遊戲中數量巨大並且系統數量也增加時,很明顯整個演算法的複雜度將不低於O(n*m),遍歷物件的次數將變得巨大。
比如碰撞檢測系統若是兩兩物件進行邏輯處理那麼速度上的損失將是十分巨大的。
這裡的一種解決方法是分集合處理,不再贅述。
四、參考文件
【完】