學習Unity 2019 ECS 框架(概念)

申明:該篇是學習筆記,內容多處複製引用。緩存

ECS(Entity,Component,System)架構其實已經不是新鮮事物,只是在GDC 2017守望先鋒講座後,才真正流行或者說是被大衆所知,我接觸已是很是晚的2019年,Unity 出了自帶ECS框架。網絡

守望先鋒使用ECS是用來下降不停增加的代碼庫的複雜度(譯註,代碼複雜度的概念須要讀者自行查閱)。爲了達到這個目的咱們遵循了一套嚴謹的架構。最後會經過討論網絡同步(netcode)這個本質很複雜的問題,來講明具體如何管理複雜性。架構

 

ECS架構簡述

ECS架構看起來就是這樣子的。先有個World,它是系統(譯註,這裏的系統指的是ECS中的S,不是通常意義上的系統,爲了方便閱讀,下文統稱System)和實體(Entity)的集合。而實體就是一個ID,這個ID對應了組件(Component)的集合。組件用來存儲遊戲狀態而且沒有任何的行爲(Behavior)。System有行爲可是沒有狀態。框架

- Entity是實例,做爲承載組件的載體,也是框架中維護對象的實體.
- Component只包含數據,具有這個組件便具備這個功能.
- System做爲邏輯維護,維護對應的組件執行相關操做.ide

 

System都是加入隊列中輪詢執行的,組件沒有處理邏輯,沒有數據,只包含狀態,而物體掛上組件即包含該功能,在Unity中是否能夠將系統組件,好比Image\Mesh\Render等認爲是組件ComponentA\B\C,這些子組件共同組成了上圖的Component,可是Component不可包含邏輯,因此一些Move\Jump\Run,是做爲C#的子組件掛載在物體上,和Image\Mesh\Render等系統組件一併合爲Component? 先留着這個問題,不着急慢慢看。工具

System隊列輪詢執行各類內部系統物理、網絡,這個系統不包含數據變量System不知道實體究竟是什麼,它只關心組件集合的小切片(slice,譯註:能夠理解爲特定子集合),而後在這個切片上執行一組行爲。有些實體有多達30個組件,而有些只有二、3個,System不關心數量,它只關心執行操做行爲的組件的子集。System也不用關係這些Component是什麼作了哪些事情,它只須要讓這些組件的子集執行而已。學習

System這種按時序進行的操做(輪詢),不用關心內部的執行,只須要關心狀態,拿MMO人物施法距離,我不須要知道待機,前搖,蓄能,施法,表現,結束的時間節點或者設置回調執行某個後置事件,我只須要知道當前技能進行到某個狀態,用狀態維護System的執行進度,只要狀態正確,就不用管當前的邏輯在當前幀或者下一幀執行。優化

 

處理數據

EntityAdmin是個World,存儲了一個全部System的集合,和一個全部實體的哈希表。表鍵是實體的ID。ID是個32位無符號整形數,用來在實體管理器(Entity Array)上惟一標識這個實體。另外一方面,每一個實體也都存了這個實體ID和資源句柄(resource handle),後者是個可選字段,指向了實體對應的Asset資源(譯註:這須要依賴暴雪的另外一套專門的Asset管理系統),資源定義了實體。this

 

 實體只是一個概念上的定義,指的是存在你遊戲世界中的一個獨特物體,是一系列組件的集合。爲了方便區分不一樣的實體,在代碼層面上通常用一個ID來進行表示。全部組成這個實體的組件將會被這個ID標記,從而明確哪些組件屬於該實體。因爲其是一系列組件的集合,所以徹底能夠在運行時動態地爲實體增長一個新的組件或是將組件從實體中移除。好比,玩家實體由於某些緣由(可能陷入昏迷)而喪失了移動能力,只需簡單地將移動組件從該實體身上移除,即可以達到沒法移動的效果了。spa

  • Player(Position, Sprite, Velocity, Health)
  • Enemy(Position, Sprite, Velocity, Health, AI)
  • Tree(Position, Sprite)

以Player爲例,Player在作什麼,是否處於某個位置,時間節點什麼,是由System來斷定/記錄,Entity沒有任何數據處理,單純只是保存這個物件執行所需的數值。

 

系統中的邏輯

一個系統就是對擁有一個或多個相同組件的實體集合進行操做的工具,它只有行爲,沒有狀態,即不該該存聽任何數據。舉個例子,遊戲中玩家要操做對應的角色進行移動,由上面兩部分可知,角色是一個實體,其擁有位置和速度組件,那麼怎麼根據實體擁有的速度去刷新其位置呢,MoveSystem(移動系統)登場,它能夠獲得全部擁有位置和速度組件的實體集合,遍歷這個集合,根據每個實體擁有的速度值和物理引擎去計算該實體應該所處的位置,並刷新該實體位置組件的值,至此,完成了玩家操控的角色移動了。

 

System從Entity中讀取數據,交由Component處理,System本身維護這些數據存取/Component處理執行的狀態,若是是一幀內執行完畢的行爲,System甚至不須要緩存它的狀態就已經設爲完成狀態從System隊列中移除。

 

Singleton Component (單例組件) ,明白了系統的概念更容易說明,仍是玩家操做角色的例子,該實體速度組件的值從何而來,通常狀況下是根據玩家的操做輸入去賦予對應的數值。這裏就涉及到一個新組件InputComponent(輸入組件)和一個新系統ChangePlayerVelocitySystem(改變玩家速度系統),改變玩家速度系統會根據輸入組件的值去改變玩家速度,假設還有一個系統FireSystem(開火系統),它會根據玩家是否輸入開火鍵進行開火操做,那麼就有 2 個系統同時依賴輸入組件,真實遊戲狀況可能比這還要複雜,有無數個系統都要依賴於輸入組件,同時擁有輸入組件的實體在遊戲中僅僅須要有一個,每幀去刷新它的值就能夠了,這時很容易讓人想到單例模式(便捷地訪問、只有一個引用),一樣的,單例組件也是指整個遊戲世界中有且只有一個實體擁有該組件,而且但願各系統可以便捷的訪問到它,通過一些處理,在任何系統中都能經過相似world->GetSingletonInput()的方法來得到該組件引用。

 

 

用組件實現行爲

 ECS中的組件更加像是一堆數據集合,它的目的是協助真實的遊戲引擎component實現各類行爲功能,也就是說

Component(組件)只包含數據

ComponentSystem 則包含行爲,一個 ComponentSystem 更新全部與之組件類型匹配的GameObject。

維基上對component的解釋:

Component: the raw data for one aspect of the object, and how it interacts with the world. "Labels the Entity as possessing this particular aspect". Implementations typically use structs, classes, or associative arrays.

舉個例子:

using Unity.Entities; using UnityEngine; // 數據:能夠在Inspector窗口中編輯的旋轉速度值
class Rotator : MonoBehaviour { public float Speed; } // 行爲:繼承自ComponentSystem來處理旋轉操做
class RotatorSystem : ComponentSystem { struct Group { // 定義該ComponentSystem須要獲取哪些components
        public Transform Transform; public Rotator Rotator; } override protected void OnUpdate() { // 這裏能夠看第一個優化點: // 咱們知道全部Rotator所通過的deltaTime是同樣的, // 所以能夠將deltaTime先保存至一個局部變量中供後續使用, // 這樣避免了每次調用Time.deltaTime的開銷。
        float deltaTime = Time.deltaTime; // ComponentSystem.GetEntities<Group>能夠高效的遍歷全部符合匹配條件的GameObject // 匹配條件:即包含Transform又包含Rotator組件(在上面struct Group中定義)
        foreach (var e in GetEntities<Group>()) { e.Transform.rotation *= Quaternion.AngleAxis(e.Rotator.Speed * deltaTime, Vector3.up); } } }

上面的代碼實現了一個包含game component的組件,而ComponentSystem則是system對衆多組件的處理,System對每個ComponentSysten都有單獨的OnUpdate()方法,不須要再像傳統MonoBehaviour那樣順序執行各類邏輯大雜燴,也不需維護OnUpdate()內的各類變量數據的使用順序。

相關文章
相關標籤/搜索