ILRuntime Unity熱更新


https://blog.csdn.net/wangjiangrong/article/details/90294366?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

 

在新的項目中,使用到了ILRuntime的熱更新方式,不同於XLua等,這種方式的熱更新是由純C#實現的,所以就不需要客戶端懂Lua的代碼。更詳細的介紹可以看官方的文檔。

官方的介紹及文檔為:http://ourpalm.github.io/ILRuntime/public/v1/guide/index.html

目前大致的理解為:將游戲分為兩個部分,Unity和Hotfix。其中主要的游戲邏輯部分也就是可能需要熱更的部分都寫在Hotfix當中,然后將其導出為Hotfix.dll文件供Unity使用。游戲上線后若需要更新代碼,只需要修改Hotfix中的代碼,然后生成新的Hotfix.dll文件熱更上去即可。

接下來就用一個簡單的Demo來實現這么一個過程。思路為,把原本UI邏輯的類放在Hotfix當中,由於沒有繼承MonoBehaviour,所以通過實現一個帶有Start,Update等方法的接口,然后在Unity部分相應調用,來實現Hotfix中的生命周期。同時通過GameObject.Find的方法找到對應的組件,進行操作。

 
創建Unity工程環境
首先我們創建一個新的Unity工程,然后將ILRuntime需要的部分導入工程當中,即官方Demo中的Mono.Cecil.20,Mono.Cecil.Pdb,ILRuntime三個文件夾(刪除ILRuntime/Adapters/Editor)

勾選 player settings -> Other Settings - > Allow 'unsafe' Code選項

 

創建Hotfix工程環境
打開我們的VS,文件->新建->項目,創建一個C#類庫,命名為Hotfix,如圖

 

然后在解決方案->引用處右鍵,添加引用,如圖

 

其中UnityEngine.dll,UnityEngine.UI.dll 和 UnityEngine.CoreModule.dll三個文件在Unity的安裝目錄下,Assembly-CSharp.dll在上面創建的Unity工程的Library/ScriptAssemblies目錄下

注:UnityEngine.CoreModule.dll是在Unity2017.2之后的版本才有,之前的版本可以不用添加,若在官方demo中UnityEngine.dll報找不到的錯誤的話,重新引用下正確目錄下的dll文件即可。

 

創建接口與適配器,並實現接口
首先我們可以在Unity中創建一個簡單的接口,用於處理Hotfix中的生命周期

public interface IUI
{
void Start();
void Update();
}
然后在Hotfix工程中,新建一個類Main.cs,實現IUI接口

namespace Hotfix
{
//IUI為unity中的接口,所以要在unity中實現一個繼承適配器
public class MainUI:IUI
{
public void Start()
{

}

public void Update()
{

}
}
}
由於IUI是Unity的接口,而MainUI是Hotfix的類,這里有一個ILRuntime中跨域繼承的概念,官方文檔提到如果你想在熱更DLL項目當中繼承一個Unity主工程里的類,或者實現一個主工程里的接口,你需要在Unity主工程中實現一個繼承適配器。

所以我們在Unity中創建一個類 InterfaceIUIAdaptor.cs,實現繼承適配器(根據文檔給的代碼進行修改)

using ILRuntime.CLR.Method;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;
using System;

public class InterfaceIUIAdaptor : CrossBindingAdaptor
{
public override Type BaseCLRType {
get {
return typeof(IUI);//這是你想繼承的那個類
}
}

public override Type AdaptorType {
get {
return typeof(Adaptor);//這是實際的適配器類
}
}

public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
return new Adaptor(appdomain, instance);//創建一個新的實例
}

//實際的適配器類需要繼承你想繼承的那個類,並且實現CrossBindingAdaptorType接口
public class Adaptor : IUI, CrossBindingAdaptorType
{
ILTypeInstance instance;
ILRuntime.Runtime.Enviorment.AppDomain appdomain;

IMethod m_Start;
bool m_StartGot;

IMethod m_Update;
bool m_UpdateGot;

public Adaptor()
{

}

public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
this.appdomain = appdomain;
this.instance = instance;
}

public ILTypeInstance ILInstance { get { return instance; } }

//你需要重寫所有你希望在熱更腳本里面重寫的方法,並且將控制權轉到腳本里去
public void Start()
{
if (!m_StartGot)
{
m_Start = instance.Type.GetMethod("Start", 0);
m_StartGot = true;
}
if (m_Start != null)
{
appdomain.Invoke(m_Start, instance, null);//沒有參數建議顯式傳遞null為參數列表,否則會自動new object[0]導致GC Alloc
}
}

public void Update()
{
if (!m_UpdateGot)
{
m_Update = instance.Type.GetMethod("Update", 0);
m_UpdateGot = true;
}
if (m_Update != null)
{
appdomain.Invoke(m_Update, instance, null);
}
}
}
}
 

讀取Hotfix.dll文件,並執行其內部操作
首先我們將Hotfix工程中,解決方案右鍵生成,生成Hotfix.dll和Hotfix.pdb兩個文件,將這兩個文件拷貝到Unity的StreamingAssets目錄下。

然后我們創建一個新的類 Launch.cs,在這里面我們首先讀取上面的兩個Hotfix文件,然后進行一些ILRuntime的預設置,例如綁定繼承適配器,注冊委托等。最后我們要在里面找到Hotfix中實現IUI接口的類,因為這些類就是我們的UI邏輯類,然后在自己的Start,Update等生命周期方法中,調用Hotfix中IUI類對應的方法。代碼如下:

using ILRuntime.Runtime.Enviorment;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;

public class Launch : MonoBehaviour
{
List<Action> DllUIUpdateList = new List<Action>();

ILRuntime.Runtime.Enviorment.AppDomain appdomain;
void Start()
{
StartCoroutine(LoadILRuntime());
}

IEnumerator LoadILRuntime()
{
//讀取dll文件
appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();

WWW www = new WWW(Application.streamingAssetsPath + "/Hotfix.dll");

while (!www.isDone)
{
yield return null;
}
if (!string.IsNullOrEmpty(www.error))
{
Debug.LogError(www.error);
}
byte[] dll = www.bytes;
www.Dispose();

www = new WWW(Application.streamingAssetsPath + "/Hotfix.pdb");

while (!www.isDone)
{
yield return null;
}
if (!string.IsNullOrEmpty(www.error))
{
Debug.LogError(www.error);
}
byte[] pdb = www.bytes;
using (System.IO.MemoryStream fs = new MemoryStream(dll))
{
using (System.IO.MemoryStream p = new MemoryStream(pdb))
{
appdomain.LoadAssembly(fs, p, new Mono.Cecil.Pdb.PdbReaderProvider());
}
}

OnILRuntimeInit();

OnILRuntimeInitialized();
}

void Update()
{
if (DllUIUpdateList.Count > 0)
{
foreach(var update in DllUIUpdateList)
{
update();
}
}
}

void OnILRuntimeInit()
{
//跨域繼承綁定適配器
appdomain.RegisterCrossBindingAdaptor(new InterfaceIUIAdaptor());
//Button點擊事件的委托注冊
appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction>((act) =>
{
return new UnityEngine.Events.UnityAction(() =>
{
((Action)act)();
});
});
}

void OnILRuntimeInitialized()
{
//獲取Hotfix.dll內部定義的類
List<Type> allTypes = new List<Type>();
var values = appdomain.LoadedTypes.Values.ToList();
foreach (var v in values)
{
allTypes.Add(v.ReflectionType);
}
//去重
allTypes = allTypes.Distinct().ToList();

DllUIUpdateList.Clear();
foreach (var v in allTypes)
{
//找到實現IUI接口的類 Adaptor 前面寫的適配器IUI的類
if (v.IsClass && v.GetInterface("Adaptor") != null)
{
//生成實例
var gs = appdomain.Instantiate<IUI>(v.FullName);

//調用接口方法
gs.Start();
DllUIUpdateList.Add(gs.Update);
}
}
}
}
注:代碼中有一個委托注冊的功能,是因為在Hotfix中調用UGUI的Button的onCkick,需要生成委托轉換器,否則會報錯

 

搭建UI及實現UI邏輯
Demo中,簡單的在場景中創建一個簡單的Button和Text,然后將Launch.cs掛到Canvas上即可。然后在我們之前Hotfix中創建的MainUI.cs中添加我們的UI邏輯:

using UnityEngine;
using UnityEngine.UI;

namespace Hotfix
{
//IUI為unity中的接口,所以要在unity中實現一個繼承適配器
public class MainUI:IUI
{
Button m_btn;
Text m_text;
int count = 0;
bool isClick = false;

public void Start()
{
m_btn = GameObject.Find("Canvas/Button").GetComponent<Button>();
m_text = GameObject.Find("Canvas/Text").GetComponent<Text>();
m_text.text = "MainUI Start";

//點擊事件的委托需要在unity中實現委托轉換器
m_btn.onClick.AddListener(BtnClick);
}

public void Update()
{
if (isClick)
{
if (count % 20 == 0)
{
m_text.text = "MainUI Update" + count / 20;
}
count++;
}
}

void BtnClick()
{
isClick = true;
}
}
}
然后重新生成下dll文件,將原來Unity StreamingAssets下的文件替換掉即可(以后修改邏輯亦是如此,達到熱更的效果)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM