Unity 開源雙端框架 ET 中初嘗熱更新技術

ET 框架簡介

正所謂時勢造英雄,在 Web 開發領域或者傳統軟件開發領域中,人們把通過千錘百煉的代碼總結出一套開發框架,從而提升開發效率,讓開發者能更專一於業務自己。對於遊戲領域而言,不一樣遊戲需求的東西也不同:有的遊戲對性能有着苛刻要求,有的遊戲須要快速地迭代出來,有的遊戲須要聯網熱更新等等。所以不一樣的遊戲框架應運而生。ios

例如:git

  • Game Framework 是一個基於 Unity 引擎的遊戲框架,主要對遊戲開發過程當中經常使用模塊進行了封裝,很大程度地規範開發過程、加快開發速度並保證產品質量。
  • QFramework 一套漸進式的快速開發框架。框架內部積累了多個項目的在各個技術方向的解決方案。
  • Entitas 一套基於 C# 和 Unity 的實體組件系統。
  • Entities Unity 官方的實體組件系統實現,不過仍是 Beta 版本,詳細介紹能夠查看官網
  • StrangeIoC 一套基於 C# 和 Unity 的控制反轉 (Inversion-of-Control) 框架。

今天介紹的是 ET 框架。github

ET是一個開源的遊戲客戶端(基於unity3d)服務端雙端框架,服務端是使用C# .net core開發的分佈式遊戲服務端,其特色是開發效率高,性能強,雙端共享邏輯代碼,客戶端服務端熱更機制完善,同時支持可靠udp tcp websocket協議,支持服務端3D recast尋路等等

ET 框架能讓咱們只用 C# 就能搞定先後端,熱更新方面也採用了基於 C# 的 IL 運行時——ILRuntime, 貫徹了 "珍愛生命,遠離 Lua" 這句話。目前本身接觸的大可能是客戶端部分,所以服務器方面不作介紹。web

框架文件結構

ET 官網 自己給了不少介紹,咱們能夠克隆 Git 倉庫到本地。c#

下面來看看每一個文件夾的做用:後端

客戶端文件結構

本文主要來介紹客戶端,所以進入到 Unity 文件夾,文件夾結構以下:跨域

當前 Master 分支目前須要 Unity 2018.3 以上版本。使用以前須要參考下官方的 運行指南服務器

在 VS 中從新編譯,或者 Rider Rebuild 一下項目。Scene 選擇 ScenesInit.unity,點 Play 按鈕應該就能成功運行,看到登錄界面。websocket

組件設計

ET 框架使用了組件的設計,一切都是實體(Entity)和組件(Component),官方文檔 組件設計 介紹的很詳細。框架

看完文檔,咱們來看看項目代碼的啓動入口。

這個 Init.cs 文件,在 Model 文件夾下。可能有同窗注意到 Hotfix 文件夾下也有一個 Init.cs 文件,並且這兩個文件夾的結構大同小異,兩邊都有一些相同的文件,而它們只是命名空間不同。這是由於咱們用到 ILRuntime,而 ILRuntime 最好不要跨域繼承。

Model/Init.cs 文件中

private async ETVoid StartAsync()
{
    try
    {
        ...
        // 添加了組件,就賦予了各類功能。
        // 例如加了 Timer 組件,就有了計時功能
        Game.Scene.AddComponent<TimerComponent>();
        ...

        // 下載熱更用的 AssetBundle 包
        await BundleHelper.DownloadBundle();
        // 加載熱更用的dll等文件,調用 Hotfix/Init.cs
        Game.Hotfix.LoadHotfixAssembly();

        // 加載配置
        Game.Scene.GetComponent<ResourcesComponent>().LoadBundle("config.unity3d");
        Game.Scene.AddComponent<ConfigComponent>();
        // 加載後卸載相應的 AB 包
        Game.Scene.GetComponent<ResourcesComponent>().UnloadBundle("config.unity3d");
        Game.Scene.AddComponent<OpcodeTypeComponent>();
        Game.Scene.AddComponent<MessageDispatcherComponent>();

        Game.Hotfix.GotoHotfix();

        Game.EventSystem.Run(EventIdType.TestHotfixSubscribMonoEvent, "TestHotfixSubscribMonoEvent");
    }
    ...
}

經過組件設計,能夠輕易地加載組件和卸載組件,例如我能夠寫一個心跳包組件來每隔30秒發送一個心跳包到服務器,當我須要這個組件的時候,能夠直接 AddComponent,不須要的時候能夠 RemoveComponent 移除組件。

登錄界面的熱更新啓動過程

接下來看到 Hotfix/Init.cs 文件中

public static void Start()
{
    try
    {
        // 註冊熱更層回調
        ETModel.Game.Hotfix.Update = () => { Update(); };
        ETModel.Game.Hotfix.LateUpdate = () => { LateUpdate(); };
        ETModel.Game.Hotfix.OnApplicationQuit = () => { OnApplicationQuit(); };
        ...
        // 加載熱更配置
        ETModel.Game.Scene.GetComponent<ResourcesComponent>().LoadBundle("config.unity3d");
        Game.Scene.AddComponent<ConfigComponent>();
        ETModel.Game.Scene.GetComponent<ResourcesComponent>().UnloadBundle("config.unity3d");

        UnitConfig unitConfig = (UnitConfig)Game.Scene.GetComponent<ConfigComponent>().Get(typeof(UnitConfig), 1001);
        Log.Debug($"config {JsonHelper.ToJson(unitConfig)}");
        // 發送事件來啓動界面
        Game.EventSystem.Run(EventIdType.InitSceneStart);
    }
    ...
}

來看看發送的事件,代碼在
Hotfix\Module\Demo\UI\UILogin\System\InitSceneStart_CreateLoginUI.cs

namespace ETHotfix
{
    // 用 Attribute 來註冊事件
    [Event(EventIdType.InitSceneStart)]
    public class InitSceneStart_CreateLoginUI: AEvent
    {
        public override void Run()
        {
            UI ui = UILoginFactory.Create();
            // 這裏就是啓動登錄界面的地方,界面能夠直接 add 或者 remove
            Game.Scene.GetComponent<UIComponent>().Add(ui);
        }
    }
}

再來看看一個界面是怎麼生成的,代碼在 Hotfix\Module\Demo\UI\UILogin\System\UILoginFactory.cs

public static UI Create()
{
         ...
    ResourcesComponent resourcesComponent = ETModel.Game.Scene.GetComponent<ResourcesComponent>();
        // 讓資源組件讀取登錄界面的 AB 包
    resourcesComponent.LoadBundle(UIType.UILogin.StringToAB());
        // 從 AB 包拿到登錄界面的 GameObject
    GameObject bundleGameObject = (GameObject) resourcesComponent.GetAsset(UIType.UILogin.StringToAB(), UIType.UILogin);
    GameObject gameObject = UnityEngine.Object.Instantiate(bundleGameObject);

    UI ui = ComponentFactory.Create<UI, string, GameObject>(UIType.UILogin, gameObject, false);
        // 添加登錄界面組件
    ui.AddComponent<UILoginComponent>();
    return ui;
        ...
}

來看看登錄界面組件,代碼在 Hotfix\Module\Demo\UI\UILogin\Component\UILoginComponent.cs

public class UILoginComponent: Component
{
    private GameObject account;
    private GameObject loginBtn;

    public void Awake()
    {
        // 經過引用來獲取 UI 組件,再爲其添加點擊事件
        ReferenceCollector rc = this.GetParent<UI>().GameObject.GetComponent<ReferenceCollector>();
        loginBtn = rc.Get<GameObject>("LoginBtn");
        loginBtn.GetComponent<Button>().onClick.Add(OnLogin);
        this.account = rc.Get<GameObject>("Account");
    }

    public void OnLogin()
    { // 有興趣能夠再進去看看 OnLoginAsync,其中登錄的 Session 鏈接了服務器地址 127.0.0.1:10002
        LoginHelper.OnLoginAsync(this.account.GetComponent<InputField>().text).Coroutine();
    }
}

服務器地址存在了 Tools 菜單中的全局配置,上面的資源路徑則是熱更新服務器的地址。

遊戲運行後,在 Hierarchy 界面中也能夠看到組件的結構,其中 uilogin.unity3d 就是登錄界面的 AB 包引用:

這就是經過熱更新邏輯生成的界面,也就是說,上面的代碼讓咱們能夠經過熱更新來給應用加載各類界面和改寫頁面跳轉邏輯,固然還能夠經過熱更來增長修改遊戲邏輯和功能。

若是不喜歡這種頁面加載方式,能夠考慮不使用 Hotfix/Init.cs 中的 Game.Scene.AddComponent<UIComponent>(); 這個 UIComponent,而使用其餘 UI 組件,例如主倉庫中的 FairyGUI 分支,讓 FairyGUI 來單獨負責 UI 界面。這裏也能夠看出基於組件的框架的靈活性,我之後也會出文章單獨介紹 FairyGUI。

熱更新切換

首先看看做者的介紹:

7.客戶端熱更新一鍵切換
由於ios的限制,以前unity熱更新通常使用lua,致使unity3d開發人員要寫兩種代碼,麻煩的要死。以後幸虧出了ILRuntime庫,利用ILRuntime庫,unity3d能夠利用C#語言加載熱更新dll進行熱更新。ILRuntime一個缺陷就是開發時候不支持VS debug,這有點不爽。ET框架使用了一個預編譯指令ILRuntime,能夠無縫切換。日常開發的時候不使用ILRuntime,而是使用Assembly.Load加載熱更新動態庫,這樣能夠方便用VS單步調試。在發佈的時候,定義預編譯指令ILRuntime就能夠無縫切換成使用ILRuntime加載熱更新動態庫。這樣開發起來及其方便,不再用使用狗屎lua了
8.客戶端全熱更新
客戶端能夠實現全部邏輯熱更新,包括協議,config,ui等等

預編譯指令指的就是在 Player Setting 中,上圖右下角箭頭指着的地方。當前有兩個預編譯指令,一般在開發中,能夠只填寫 NET452,這樣能夠獲得完整的堆棧信息來調試程序。還有一個預編譯指令 ASYNC,加上後,應用就會從前面填寫的熱更新服務器下載熱更包,該指令在後文會提到。

在國內環境下,手機遊戲熱更新的需求較強烈。市場上手機系統廣泛分紅 Android 和 iOS 陣營,其中 iOS 不支持 JIT 熱更,所以 ET 框架給了兩種選擇:ILRuntime 熱更新和 Mono 熱更新。

二者概念能夠參考文末的參考連接,在這裏很少說。

體驗熱更新

體驗熱更新以前,先把項目切到 Android 平臺。

按照下圖配置 Mono 熱更新:

確保 Scripting Backend 爲 Mono,下面預編譯宏去掉 ILRuntime,加上 ASYNC,按下回車鍵執行變動。ASYNC 說明咱們如今的熱更新資源從資源服務器中獲取,這裏的熱更新資源包括 Res 文件夾、Bundles 文件夾、Hotfix 文件夾中的代碼等。在這個例子中,登錄界面的代碼就已經寫在熱更新文件夾中了,咱們將嘗試經過熱更新來展現登錄界面。

點擊 Play 按鈕,會有兩個報錯:

第二個 Log 信息展現了應用想要獲取資源的熱更資源服務器地址,這個地址能夠在 Tools 菜單的全局配置中找到。報錯信息提示找不到終端主機。報錯是理所固然的,由於咱們尚未啓動本地服務器。

首先要生成熱更新文件,在 Tools 菜單中點擊打包工具,以下圖所示:

平臺選擇當前的 Android 平臺,目前不須要打包應用,因此無視第一個單選按鈕。

前面的思惟導圖提到了 ET 根目錄的 Release 文件夾存的就是熱更新資源文件。打包工具也會把打包後的資源放在 Release 文件夾下。而第二個按鈕指的是是否把打包的熱更新資源也放在應用中,目前也不須要選擇。開啓熱更新後,應用會比較服務器和本地應用的 Version 文件,計算文件差別後纔會下載相關的熱更新資源文件。

若是勾選了第二個按鈕,打包工具將會把資源也複製到 Assets/StreamingAssets 文件夾下,同時更新 Version 文件,這樣咱們將不能測試下載熱更包的過程。

點擊開始打包後,熱更文件就生成了:

再點擊 Tools 菜單中的 web 資源服務器開啓映射了 Release 文件夾的本地文件服務器。

點擊 Play 按鈕,應用經過下載熱更新資源,生成了登錄界面,也把熱更資源下載到了應用中,也就是 Assets/StreamingAssets 文件夾。

從新啓動 web 資源服務器清除 log 信息,再次運行應用,會發現沒有再次下載熱更新資源。由於對比了 Version 文件後,應用本地的文件已經不須要更新了。

至此,咱們完成了一次完整的熱更新。

總結

ET 框架給了咱們一種統一的開發體驗,提供了方便的熱更新切換和調試方案,這足以支撐起一些小遊戲的開發需求,有須要的同窗能夠了解下 ET 框架~

2019 年立了個 Flag:周更技術博客,歡迎督促和交流,也歡迎常來我博客 螢火之森 逛!

參考

相關文章
相關標籤/搜索