前言
早期為了實現對服務器的快速設計和實現,忽略了游戲架構上的設計。使用傳統的面向對象的方式對業務需求進行實現,導致了項目在中期的研發和擴展中遇到了各種數據對接不恰當的瓶頸。如果要強制實現會使系統之間的交叉絮亂。這樣開發下去后果可想而知。於是筆者在遇到了此問題后,細想就后怕。決定重構之。
介於筆者一直在用 Unity,對 ECS(實體組件系統) 一些思想也非常認可,而且堅信未來一定是 面向數據編程 的。所以毫不猶豫的選擇了用 ECS 來重構之。
什么是 ECS 呢?
ECS - 實體組件系統
E :Entity 實體 也就是游戲世界中的單位 (他可以擁有各種組件)
C : Component 組件 也就是實體上的很多個部件 (比如一個英雄 會存在:屬性組件 / 技能組件 / buff組件 / 皮膚組件 / 等等)
S : System 系統 也就是處理對應組件的一個中心 (比如:有 屬性組件 就會存在對應的 屬性系統 / 有 技能組件 就會 存在技能系統 一一對應)
ECS 基礎的內容大概就是以上。很簡單就能理解。
同步機制
如果單純以上的方式來考慮ECS的話其實是不夠的。
因為ECS的核心應該是在面向數據的編程方式上,起碼在游戲的應用中是這樣的。因為所有存在交互的 游戲實體 都是存在數據變化的。
所以在我們決定用ECS的方式來重構的時候,就決定了我們的服務器又或是客戶端一定是面向數據進行編程的。而不是消息,不是指令。
以此我們就能推敲出一套通用的底層機制。通過這套底層機制來進行游戲數據層的完全同步。而客戶端就面向本地的數據層進行邏輯處理。
於是有了如下圖所示的代碼:
Golang (服務器):
C# (客戶端):
Proto :
如上圖所示 服務器 / 客戶端 代碼都是通過解析 proto 文件 而 生成的 component 代碼組件。
這里的同步機制主要同步 實體 / 組件 的 添加 / 刪除 / 更新
增量更新
我們在生成的時候 處理了 服務器組件 進行 增量更新操作 因為這樣能節省網絡流量
也處理了 客戶端組件 進行 增量更新的操作
數據的處理
嚴格意義上來講 ECS 的 Component 上是不會存在任何邏輯操作的。他只是數據層。
而所有的數據邏輯操作都在對應的 System 中。
所以我們這里的 Component 只有數據查詢的 API
我們要實現的這套同步機制就是 無論什么情況 都讓客戶端先行同步 所有 Entity 的 Component 然后再通過 System 來處理 Component。 實現客戶端真正意義上的面向數據編程。這樣客戶端不用再去管緩存數據 或者 是傳遞數據的事務。
我們通過 System 將對應業務的代碼強內聚。這樣從排錯上來說也非常高效。因為只需要找到對應的 System 去排錯
如圖:
又因為 System 與 任何 Component 產生關聯 都能通過 Entity 交互。這樣又很好的剝離了 System 之間的強關聯。
客戶端 Entity 部分代碼:
Entity 的 Actor
因為我們的 Components 只是數據層。可能很多數據比如我們壓縮的數據,或者需要換算一次才能使用的數據。對於這種操作 我們將會對 Entity 引入 Actor的方式來處理。
這樣我們的Actor就會存在很多的原子函數。可以提供給 System 操作, 同時還能保證底層的安全性。
邏輯幀消息
邏輯幀上我們將消息分為單幀下發,也就是一幀只有一個消息下發的方式,而其中組件消息的部分:
因為我們的 Proto 還是老版本 還不支持 Map 或 any 的操作 所以這里略顯蛋疼。
不過辦法總是有的 我們的消息同步這塊也通過生成器來生成代碼。就省去了每次都需要去添加人工寫一次了。
如圖:
總結
總結下ECS就是 ECS其實本身並不復雜,但是在使用它解決業務場景的時候 需要嚴謹的定義業務中的規則,剝離組件的方式。
如何去定義ECS在面向業務的時候的場景很重要。如果用好了,那就是利器。否則很可能導致數據層淚崩。
另外 ECS 這種架構 在 配合上 代碼的自動化生成 真的能達到事倍功半的效果。所以在以后要多考慮讓機器幫我們制造。而不是靠手擼~
以上大致為本次我們用ECS方式重構的一些核心要素了 草草結尾~ ^_。