熱更新應用--熱補丁Hotfix學習筆記

movin2333 發表於 2021-04-07

一.熱補丁簡介

  熱補丁主要是用於將純C#工程在不重做的情況下通過打補丁的形式改造成具備lua熱更新功能工程,主要是讓原來指令碼中Start函式和Update函式等函式程式碼塊重定向到lua程式碼。

二.第一個熱補丁

  1.C#程式碼端:

    1)建立一個指令碼,並掛載到遊戲中的任意物體上(實際使用過程中一般修改已有指令碼,這裡測試隨意掛載就好)

    2)在指令碼中定義好測試用的方法,在Start函式中執行Lua檔案(LuaManager類及C#呼叫lua程式碼的方式詳見xlua學習筆記,LuaManager類在:四.C#呼叫lua-3.lua解析器管理器)

public class HotfixMain : MonoBehaviour
{
    void Start()
    {
        LuaManager.Instance.DoLuaFile("Main");

        //呼叫定義的方法,這些方法被lua中的熱補丁重新定義了
        Debug.Log(Add(1, 2));
        Speak("我很帥");
    }

    //預備給熱補丁覆蓋的成員方法
    public int Add(int a,int b)
    {
        return 0;
    }
    //預備給熱補丁覆蓋的靜態方法
    public static void Speak(string str)
    {
        Debug.Log("hahaha");
    }
}

  2.lua端程式碼

    1)將lua檔案放在LuaManager類能夠重定向到的資料夾中,或者新增LuaManager類中的重定向方法使lua檔案能被找到,這裡放在Assets目錄下的Lua資料夾下,LuaManager中已經新增了這個檔案的重定向方法。

    2).C#程式碼呼叫了Main,所以在資料夾中新增Main.lua這個lua檔案,這個檔案使lua的主入口檔案,相當於C#工程中的Main方法,主要用於執行其他lua檔案、定義一些通用全域性變數、初始化等。這裡Main檔案中執行Hotfix1檔案,程式碼就一句:

require("Hotfix1")

    3)Hotfix1.lua檔案中定義第一個熱補丁的程式碼,主要呼叫方法xlu.hotfix重寫C#中的方法:

--熱補丁

--lua中的熱補丁固定寫法
--通過xlua的hotfix函式進行熱補丁更新,引數是:類名、"函式名",lua函式

--成員方法將self作為第一個引數傳入
xlua.hotfix(CS.HotfixMain,"Add",function(self,a,b)
    return a + b
end)
--靜態方法不需要傳入self引數
xlua.hotfix(CS.HotfixMain,"Speak",function(a)
    print(a)
end)

--熱補丁還需要在Unity指令碼中作以下操作
--加特性、加巨集、生成程式碼、hotfix注入

--熱補丁缺陷:只要修改了熱補丁的程式碼,都需要重新做hotfix注入

  3.Unity中的操作

    1)加特性:在需要被熱補丁更新的C#類前面加上[Hotfix]特性,這裡給剛才1中建立的指令碼加上特性,其他非mono指令碼也是一樣的做法:

//加上特性以生成熱補丁程式碼
[Hotfix]
public class HotfixMain : MonoBehaviour
{
    void Start()
    {
        LuaManager.Instance.DoLuaFile("Main");

        //呼叫定義的方法,這些方法被lua中的熱補丁重新定義了
        Debug.Log(Add(1, 2));
        Speak("我很帥");
    }

    //預備給熱補丁覆蓋的成員方法
    public int Add(int a,int b)
    {
        return 0;
    }
    //預備給熱補丁覆蓋的靜態方法
    public static void Speak(string str)
    {
        Debug.Log("hahaha");
    }
}

    2)加巨集:開啟Unity中Edit->ProjectSetting

熱更新應用--熱補丁Hotfix學習筆記

      在Player->otherSetting->Scripting Define Symbols中輸入HOTFIX_ENABLE

熱更新應用--熱補丁Hotfix學習筆記

    3)加巨集之後生成程式碼,點選XLua選項下Generate Code選項生成程式碼。注意:加巨集成功後XLua選項下會出現Hotfix Inject In Editor選項,這個是hotfix注入使用的選項,如果沒有的話說明剛才的巨集沒有成功加上。

熱更新應用--熱補丁Hotfix學習筆記

    4)點選Hotfix Inject In Editor進行hotfix注入,如果報錯please install the Tools,將xlua工程原始檔中的Tools資料夾拷貝到自己的工程中。注意:不是拷貝到Assets目錄下,源工程資料夾中Tools資料夾和Assets資料夾同級,所以將Tools資料夾拷貝到自己的工程檔案中和Assets資料夾同級資料夾下而不是Assets目錄下(三張圖片分別是xlua原始檔夾中Tools資料夾所在位置、開啟工程所在資料夾、拷貝後在自己的工程中Tools資料夾所在位置):

熱更新應用--熱補丁Hotfix學習筆記熱更新應用--熱補丁Hotfix學習筆記熱更新應用--熱補丁Hotfix學習筆記

    5)執行工程

  4.最後:注意每次熱補丁的程式碼修改後,都需要重新生成程式碼和hotfix注入。

三.hotfix重定向其他內容

  1.多函式替換和構造解構函式熱補丁(建構函式和解構函式重定向後原始碼邏輯會先執行,再執行lua中重定向的程式碼邏輯,這一點和其他成員函式及靜態函式不同)

//加上特性以生成熱補丁程式碼
[Hotfix]
public class HotfixMain : MonoBehaviour
{
    void Start()
    {
        LuaManager.Instance.DoLuaFile("Main");

        //呼叫定義的方法,這些方法被lua中的熱補丁重新定義了
        Debug.Log(Add(1, 2));
        Speak("我很帥");
    }

    private void Update()
    {
        
    }

    //預備給熱補丁覆蓋的成員方法
    public int Add(int a,int b)
    {
        return 0;
    }
    //預備給熱補丁覆蓋的靜態方法
    public static void Speak(string str)
    {
        Debug.Log("hahaha");
    }
}

[Hotfix]
public class HotfixTest
{
    public HotfixTest()
    {
        Debug.Log("HotfixTest建構函式");
    }
    public void Speak(string str)
    {
        Debug.Log(str);
    }
    ~HotfixTest()
    {

    }
}
--多函式替換
--將多個函式寫成一個表作為引數傳入
xlua.hotfix(CS.HotfixMain,{
    Update = function(self)
        print(os.time())
    end,
    Add = function(self,a,b)
        return a + b
    end,
    Speak = function(a)
        print(a)
    end
})

--建構函式熱補丁
xlua.hotfix(CS.HotfixTest,{
    --建構函式的熱補丁固定寫法
    [".ctor"] = function()
        print("Lua熱補丁建構函式")
    end,
    Speak = function(self,a)
        print("Lua熱補丁Speak函式")
    end,
    --解構函式的熱補丁固定寫法
    Finalize = function()
        print("Lua熱補丁解構函式")
    end
})

  2.協程函式替換

[Hotfix]
public class HotfixMain : MonoBehaviour
{
    void Start()
    {
        LuaManager.Instance.DoLuaFile("Main");

        StartCoroutine(TestCoroutine());
    }

    IEnumerator TestCoroutine()
    {
        while (true)
        {
            yield return new WaitForSeconds(1f);
            Debug.Log("c#協程列印一次");
        }
    }
}
--協程函式替換

--使用協程必須引入xlua.util
util = require("xlua.util")


xlua.hotfix(CS.HotfixMain,{
    TestCoroutine = function(self)
        return util.cs_generator(function()
            while true do
                coroutine.yield(CS.UnityEngine.WaitForSeconds(1))
                print("lua熱補丁協程函式")
            end
        end)
    end
})

  3.索引器和屬性替換

[Hotfix]
public class HotfixMain : MonoBehaviour
{
    private int[] array = new int[] { 5, 4, 3, 2, 1 };
    void Start()
    {
        LuaManager.Instance.DoLuaFile("Main");

        Debug.Log(this.Age);
        this.Age = 200;
        Debug.Log(this[0]);
        this[2] = 10000;
    }

    //定義屬性
    public int Age
    {
        get
        {
            return 0;
        }
        set
        {
            Debug.Log(value);
        }
    }
    //定義索引器
    public int this[int index]
    {
        get
        {
            if (index >= 0 && index < 5)
                return array[index];
            return 0;
        }
        set
        {
            if (index >= 0 && index < 5)
                array[index] = value;
        }

    }
}
xlua.hotfix(CS.HotfixMain,{
    --屬性熱補丁的固定寫法
    --使用set_屬性名替換設定屬性的方法,使用get_屬性名替換獲取屬性值的方法
    set_Age = function(self,v)
        print("Lua熱補丁設定屬性")
    end,
    get_Age = function(self)
        return 10
    end,
    --索引器在類中是唯一的,固定寫法和屬性類似
    --使用set_Item替換索引器的set方法,使用get_Item替換索引器的set方法
    get_Item = function(self,index)
        print("Lua熱補丁重定向索引器get")
        return 1000;
    end,
    set_Item = function(self,index,v)
        print("Lua熱補丁重定向索引器set")
    end
})

  4.事件替換

[Hotfix]
public class HotfixMain : MonoBehaviour
{
    event UnityAction customEvent;
    void Start()
    {
        LuaManager.Instance.DoLuaFile("Main");

        StartCoroutine(EventAddRemove());
    }
    private void Update()
    {
        if (customEvent != null)
            customEvent();
    }
    /// <summary>
    /// 新增到委託中的函式
    /// </summary>
    private void Test()
    {
        Debug.Log("event test running");
    }
    /// <summary>
    /// 使用協程新增和刪除委託函式
    /// </summary>
    /// <returns></returns>
    private IEnumerator EventAddRemove()
    {
        customEvent += Test;
        yield return new WaitForSeconds(5f);
        customEvent -= Test;
    }
}
--事件熱補丁

xlua.hotfix(CS.HotfixMain,{
    --add_事件名 代表新增事件
    --remove_事件名 代表移除事件
    add_customEvent = function(self,del)
        print(del)
        print("新增事件函式")
        --在新增事件時,不要把傳入的委託往事件中存,否則會死迴圈
        --self:customEvent("+",del)
    end,
    remove_customEvent = function(self,del)
        print(del)
        print("移除事件函式")
    end
})

  5.泛型類替換

[Hotfix]
public class HotfixTest2<T>
{
    public void Test(T str)
    {
        Debug.Log(str);
    }
}
    void Start()
    {
        LuaManager.Instance.DoLuaFile("Main");

        new HotfixTest2<string>().Test("movin");
        new HotfixTest2<int>().Test(10000);
    }
--泛型類中泛型T可以變化,所以要一個型別一個型別地替換
--在第一個引數後面加上括號,括號中書寫一個型別,代表如果泛型是這個型別時地替換方法
xlua.hotfix(CS.HotfixTest2(CS.System.String),{
    Test = function(self,str)
        print("泛型為string時的熱補丁,引數是"..str)
    end
})
xlua.hotfix(CS.HotfixTest2(CS.System.Int32),{
    Test = function(self,i)
        print("泛型為int時的熱補丁,引數是"..i)
    end
})