ET框架6.0分析一、ECS架構

2023-05-14 18:00:54

概述

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";
	}
}

// ...