概述
ET框架的ECS架構是從ECS原生設計思想變形而來的(關於ECS架構的分析可以參考跳轉連結:《ECS架構分析》),其特點是:
- Entity:實體可以作為元件掛載到其他實體上,Entity之間可以有父子巢狀關係,和其他ECS架構一樣,Entity只允許是純資料的(除了基本介面)
- System:和其他ECS架構相比,一樣的是系統是純函式。不一樣的是ET的系統不是“自驅”的,而是響應式的。與其說是System,個人倒是覺得可以認為是EventHandle事件處理函式。
ET框架是基於U3D的,它的Entity和System的有點像GameObject的資料和函式的拆分:Entity有GameObject的元件式設計、父子巢狀、序列化等,而函式則被分成了多個系統(或者說事件響應函式),即被分成了Awake、Start、Update等等多個事件。和GameObject一樣,這套ECS是ET使用者開發者開發業務的“基石”。
Entity詳解
Entity繼承了IDisposable,在刪除的時候必須呼叫Dispose方法,以防非託管資源洩露
EntityStatus 狀態標籤標誌位
- IsFromPool:當使用物件池管理時,被置為true,這樣就會在Dispose時會被物件池回收
- IsRegister:這裡的註冊的語義可以被理解成是否全面被納入ET框架管理,有:
- 加入ET的Entity樹,可以被遍歷更新
- 在U3D環境下,會根據自身的ViewName,在U3D建立此Entity在U3D世界的GameObject的View對映(並對映父子關係)
- 丟擲Regigster訊息
- IsComponent:Entity是否作為元件,Dispose時若是元件,從其Entity(在ET是寫作parent,其語義就是被掛載到實體)中移除此元件,若不是就是從其父節點中移除此節點。(可以推論出,ET不允許Entity即作為子節點又作為元件)
- IsCreated:是否被創造出來的,目前看起來只是在設定Domain時可能剛反序列化出來,拋一個反序列化事件出去。
- IsNew:區分被建立還是被反序列化出來的,被建立出來被物件池管理
父子關係巢狀
- 和其他父子節點一樣,子節點關聯父節點的生命週期(建立、更新、銷燬、序列化等)
- parent 父節點或者自身作為元件所掛在在的Entity,取決於上述的IsComponent標誌位
- childern 子節點表
- childernDB 會被序列化的子節點表,在AddToChildren時會判斷這個子節點是不是要被序列化,是則進表
- 建立子節點時不要自己去new,使用Entity.AddChild的一系列介面
元件
- 作為元件時,與父子關係巢狀類似,元件關聯實體的生命週期(建立、更新、銷燬、序列化等)
- parent 父節點或者自身作為元件所掛在在的Entity,取決於上述的IsComponent標誌位(同上)
- components 元件表
- componentsDB 會被序列化的元件表,操作類似上述父子關係
- 同樣,建立元件時也不應自己去New,使用AddComponent的一系列介面
InstanceId 例項Id
- 由IdGenerator產生,每個程式每秒最多產生65535個例項ID,其包含時間、程式等資訊
- 由於由物件池,InstanceId可以用於判斷該示例是否有效
- 上述提到InstanceId帶有程式資訊,可以透過InstanceId鎖定物件位置發訊息,在Actor訊息中被用到
Scene
Scene是一個特殊的Entity,Entity是具有父子巢狀結構的,可以形成樹形結構,而Scene則被定義樹的根,它可以(注意是可以)沒有父節點,其他普通的(例如單例可能是例外)Entity必須有父節點或者作為元件掛載在Entity上。透過Scene來維護一棵Entity樹。
Domain
指向Entity所在的那棵樹的根節點,是指下述層次中的ZoneScene
Zone
Scene的Id,在伺服器端作為區服的索引id
層級
先來看看常見的客戶端模組生命週期管理分層:
- App(Game)層:進入App時被初始化,持續整個應用程式生命週期。有資源管理模組,定時器模組,...
- User(Player)層:跟隨玩家登入登出變化,登入被初始化,登出被清理掉。有揹包模組,技能模組,...
- Scene層:隨場景變化,切場景時初始化並清理上一個場景,並重新整理某些關聯模組,GC... 有地圖,玩家角色單位,怪物 ...
ET的客戶端和上述類似,有:
- Game + 單例:類似上述App層,有計數器、配置表、資源管理等單例元件
- ZoneScene:類似上述User層,有UI、技能、任務、揹包等元件
- CurrentScene:當前地圖(場景),有玩家、怪物、NPC等單位,還有場景相關的元件
在伺服器上,則不太一樣:
- GameScene:管理程式必備的基礎元件
- ZoneScene:當前ZoneScene業務相關的元件,比如Gate型別的ZoneScene包含GateSessionKeyCompontent,而Map型別的不用。
- CurrentScene:伺服器多數服務不需要,可能地圖伺服器或者戰鬥伺服器會用到,像聊天服務大多數都用不到。
System詳解
ET框架的ECS架構的System,其最明顯的特徵它是響應式的,說是System,感覺更像是平時用的EventHandle事件處理函式
事件機制EventSystem
引述官方文件的介紹:
ECS最重要的特性一是資料跟邏輯分離,二是資料驅動邏輯。什麼是資料驅動邏輯呢?不太好理解,我們舉個例子:
一個moba遊戲,英雄都有血條,血條會在人物頭上顯示,也會在左上方頭像UI上顯示。這時候服務端發來一個扣血訊息。我們怎麼處理這個訊息?第一種方法,在訊息處理函式中修改英雄的血數值,修改頭像上血條顯示,同時修改頭像UI的血條。這種方式很明顯造成了模組間的耦合。第二種方法,扣血訊息處理函式中只是改變血值,血值的改變丟擲一個hpchange的事件,人物頭像模組跟UI模組都訂閱血值改變事件,在訂閱的方法中分別處理自己的邏輯,這樣各個模組負責自己的邏輯,沒有耦合。
這裡的事件機制被賦予了更多的意義,也就是ECS的System的核心意義:使業務更加的內聚,感知不到多個元件聚合在Entity中帶來的耦合。
事件型別
關聯Entity的事件型別
正規化為:
public class AAABBBSystem: BBBSystem<AAA>
{
public override void BBB(AAA aaa)
{
}
}
/*
- AAA是Entity的派生類
- BBB是ET框架內建的一些事件名,如Awake、Start、Update...
- 事件的丟擲可以帶N個引數,透過泛型處理的, 類似上述片段變形BBBSystem<AAA, Param1>這樣
例如型別為Player的Entity的Awake事件訂閱處理:
*/
public class PlayerAwakeSystem: AwakeSystem<Player>
{
public override void Awake(Player self)
{
//DoSomething
}
}
ET框架自動丟擲
- AwakeSystem:元件工廠建立元件後丟擲,只丟擲一次
- StartSystem:Entity在UpdateSystem呼叫前丟擲
- UpdateSystem:Entity每幀丟擲
- DestroySystem:Entity被刪除丟擲
- DeserializeSystem:Entity反序列化時丟擲
- LoadSystem:EventSystem載入dll時丟擲,用於服務端熱更新,重新載入dll做一些處理,比如重新註冊handler
開發者手動丟擲
- ChangeSystem:元件內容改變時丟擲若需要開發者進行丟擲
開發者自定義事件
引述官方示例:
int oldhp = 10;
int newhp = 5;
// 丟擲hp改變事件
Game.EventSystem.Run("HpChange", oldhp, newhp);
// UI訂閱hp改變事件
[Event("HpChange")]
public class HpChange_ShowUI: AEvent<int, int>
{
public override void Run(int a, int b)
{
throw new NotImplementedException();
}
}
// 模型頭頂血條模組也訂閱hp改變事件
[Event("HpChange")]
public class HpChange_ModelHeadChange: AEvent<int, int>
{
public override void Run(int a, int b)
{
throw new NotImplementedException();
}
}
可以看到:
- 使用字串作為事件的key
- 使用C#特性(Attrbute)Event對響應事件的處理類標記,且該處理類繼承AEvent
- 可以帶若干引數,據官方文件最多三個,有需要可以自行擴充
訊息事件
引述官方示例:
除此之外還有很多事件,例如訊息事件。訊息事件使用MessageHandler來宣告,可以帶引數指定哪種伺服器需要訂閱,更具體的訊息事件可以參考訊息模組。
[MessageHandler(AppType.Gate)]
public class C2G_LoginGateHandler : AMRpcHandler<C2G_LoginGate, G2C_LoginGate>
{
protected override void Run(Session session, C2G_LoginGate message, Action<G2C_LoginGate> reply)
{
G2C_LoginGate response = new G2C_LoginGate();
reply(response);
}
}
ECS架構用例
元件的組裝可以封裝起來,比如工廠模式,這裡只是示意
// 首先得有一個父節點
Entity parent = XXX
Human human = parent.AddChild<Human>();
Head head = human.AddComponent<Head>();
head.AddComponent<Eye>();
head.AddComponent<Mouse>();
head.AddComponent<Nose>();
head.AddComponent<Ear>();
class Eye: Entity
{
public string Color { get; set; }
}
// 訂閱Eye的Awake事件處理(AddComponent時丟擲的)
public class EyeAwakeSystem: AwakeSystem<Eye>
{
public override void Awake(Eye self)
{
self.Color = "Black";
}
}
// ...