HybridCLR的推廣已經(jīng)做得相當(dāng)好了,而且熱更領(lǐng)域突然殺出一匹黑馬,熱度很高,不再多做介紹,可以點(diǎn)擊進(jìn)入HybridCLR開源地址了解詳情。
在此之前用過tolua和xlua熱更框架, 因?yàn)镃#開發(fā)方式實(shí)在太爽,想支持熱更又不想使用弱類型語言,于是對ILRuntime和HybridCLR進(jìn)行了評估,了解后毫不猶豫選擇了HybridCLR方案,盡管它還比較新。選它的原因很簡單,它最接近于原生C#開發(fā)方式,雖然性能相比原生AOT還有一定差距,但是與其它熱更方案相比,絕對是熱更領(lǐng)域顛覆性的存在,尤其是近期解決了泛型元數(shù)據(jù)補(bǔ)充、橋接函數(shù)生成的痛點(diǎn),非常的好用!
感謝HybridCLR作者讓我趕上了一個(gè)好時(shí)代,遠(yuǎn)離lua屎山??
下面記錄一下Windows Unity2021.3.12f1環(huán)境下UGF + HybridCLR熱更接入過程。
GameFramework接入HybridCLR,維護(hù)五年以上的自用游戲框架,工作流完善,適合快速開發(fā),已無私共享:?GitHub - sunsvip/GF_HybridCLR
HybridCLR接入非常非常非常簡單! 而且把已完成的純C#開發(fā)游戲接入HybridCLR實(shí)現(xiàn)熱更也同樣簡單??偟脕碚f接入HybridCLR只需要以下步驟:
1,把代碼分離成非熱更程序集和熱更程序集;
2,把熱更程序集打成dll,作為資源以供下載更新.
3,下載更新熱更dll,加載熱更dll,調(diào)用HybridCLR函數(shù)為AOT補(bǔ)充泛型元數(shù)據(jù)。
4,通過反射進(jìn)入熱更邏輯,Over.
0. 環(huán)境準(zhǔn)備
① git環(huán)境,用于下載更新hybridclr
② C++編譯庫,在VisualStudio Install面板勾選C++開發(fā). 用于編譯il2cpp
[2022.11.1更新]: 最新正式版HybridCLR接入流程:
HybridCLR已經(jīng)正式發(fā)布了Unity Package Manager版本插件,進(jìn)一步規(guī)范化工作流。相比之前版本的接入方式有些改變,當(dāng)前最新版本0.9.0接入方式如下:
HybridCLR UPM版地址:https://github.com/focus-creative-games/hybridclr_unity.git
?①打開Package Manager通過 git URL添加插件,把https://github.com/focus-creative-games/hybridclr_unity.git粘貼進(jìn)去即可:
?②點(diǎn)擊頂部菜單HybridCLR->Installer安裝HybridCLR
?③點(diǎn)擊頂部菜單HybridCLR->Settings添加熱更程序集:
?④配置熱更程序集:
首先要明白什么是熱更新程序集,HybridCLR會把熱更新程序集打成一個(gè)dll文件,在游戲運(yùn)行時(shí)注入這個(gè)熱更dll,然后就能執(zhí)行熱更dll中的邏輯了。
我們需要先把代碼分離開,分成兩個(gè)程序集,一個(gè)是內(nèi)置程序集(Builtin),一個(gè)是熱更程序集(Hotfix)。把內(nèi)置代碼和熱更代碼分別放到不同的文件夾,然后在內(nèi)置程序所在文件夾創(chuàng)建一個(gè)名為Builtin的Assembly Definition文件,在熱更程序文件夾創(chuàng)建一個(gè)名為Hotfix的Assembly Definition文件。注意,Hotfix程序集可以引用Builtin程序集,但是Builtin不能直接引用Hotfix程序集。
內(nèi)置程序集也就是邏輯比較靠前,無法熱更,必須打進(jìn)包體的代碼。內(nèi)置程序集邏輯主要包含下載更新資源(包括熱更dll資源)、調(diào)用System.Reflection.Assembly.Load(hotfixDll.bytes)載入熱更dll的二進(jìn)制數(shù)據(jù),然后通過反射調(diào)用Hotfix程序集中的函數(shù)進(jìn)入熱更新邏輯。
程序集拆分為內(nèi)置程序集(Builtin)和熱更程序集(Hotfix)后,將熱更新程序集拖入HybridCLR->Settings->Hot Update Assembly Definitions;
Hot Update Assemblies不需要配置,它默認(rèn)返回Hot Update Assembly Definitions中配置的程序集名字列表;
Output Link File是HybridCLR工具自動生成代碼裁剪配置文件link.xml;
Output AOT Generic Reference File是HybridCLR自動生成AOT泛型元數(shù)據(jù)補(bǔ)充;
?⑤AOT元數(shù)據(jù)補(bǔ)充:
由于編譯成熱更dll后會丟失值類型泛型的數(shù)據(jù)類型,所以需要把Hotfix中用到的值類型泛型數(shù)據(jù)提前補(bǔ)充到AOT里,在元數(shù)據(jù)共享機(jī)制下,Hotfix程序就能正確識別那些值類型泛型數(shù)據(jù)。
HybridCLR Settings里預(yù)留了Patch AOT Assemblies字段,用于配置AOT dll文件補(bǔ)充元數(shù)據(jù)。此字段HybridCLR沒有用到,只是預(yù)留給用戶使用的空白字段。
AOT 補(bǔ)充元數(shù)據(jù)需要用戶調(diào)用接口自行補(bǔ)充。補(bǔ)充AOT元數(shù)據(jù)沒有先后順序要求,因此可以寫一個(gè)小工具便于把AOT dll名字添加到Patch AOT Assemblies列表:
勾選AOT Dll點(diǎn)擊Save會自動添加到Patch AOT Assemblies列表,并將對應(yīng)dll文件移動到Resources/AotDlls下,然后運(yùn)行時(shí)通過Resources.LoadAll<TextAsset>("AotDlls")加載全部AOT DLL然后調(diào)用RuntimeApi.LoadMetadataForAOTAssembly進(jìn)行AOT元數(shù)據(jù)補(bǔ)充;
?工具代碼:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.UIElements;
internal enum ConfigEditorMode
{
StripLinkConfig,
AotDllConfig
}
public class StripLinkConfigWindow : EditorWindow
{
private class ItemData
{
public bool isOn;
public string dllName;
public ItemData(bool isOn, string dllName)
{
this.isOn = isOn;
this.dllName = dllName;
}
}
private Vector2 scrollPosition;
private string[] selectedDllList;
private List<ItemData> dataList;
private GUIStyle normalStyle;
private GUIStyle selectedStyle;
ConfigEditorMode mode;
private void OnEnable()
{
normalStyle = new GUIStyle();
normalStyle.normal.textColor = Color.white;
selectedStyle = new GUIStyle();
selectedStyle.normal.textColor = Color.green;
dataList = new List<ItemData>();
RefreshListData();
}
internal void SetEditorMode(ConfigEditorMode mode)
{
this.mode = mode;
RefreshListData();
}
private void OnGUI()
{
EditorGUILayout.BeginVertical();
if (dataList.Count <= 0)
{
EditorGUILayout.HelpBox("未找到程序集,請先Build項(xiàng)目以生成程序集.", MessageType.Warning);
}
else
{
switch (mode)
{
case ConfigEditorMode.StripLinkConfig:
EditorGUILayout.HelpBox("勾選需要添加到Link.xml的程序集,然后點(diǎn)擊保存生效.", MessageType.Info);
break;
case ConfigEditorMode.AotDllConfig:
EditorGUILayout.HelpBox("勾選需要添加到AOT元數(shù)據(jù)補(bǔ)充的dll,然后點(diǎn)擊保存生效.", MessageType.Info);
break;
}
}
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, false, true);
for (int i = 0; i < dataList.Count; i++)
{
EditorGUILayout.BeginHorizontal();
var item = dataList[i];
item.isOn = EditorGUILayout.ToggleLeft(item.dllName, item.isOn, item.isOn ? selectedStyle : normalStyle);
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Select All", GUILayout.Width(100)))
{
SelectAll(true);
}
if (GUILayout.Button("Cancel All", GUILayout.Width(100)))
{
SelectAll(false);
}
GUILayout.FlexibleSpace();
if (GUILayout.Button("Reload", GUILayout.Width(120)))
{
RefreshListData();
}
if (GUILayout.Button("Save", GUILayout.Width(120)))
{
switch (mode)
{
case ConfigEditorMode.StripLinkConfig:
if (MyGameTools.Save2LinkFile(GetCurrentSelectedList()))
{
EditorUtility.DisplayDialog("Strip LinkConfig Editor", "Update link.xml success!", "OK");
}
break;
case ConfigEditorMode.AotDllConfig:
if (MyGameTools.Save2AotDllList(GetCurrentSelectedList()))
{
EditorUtility.DisplayDialog("AOT dlls Editor", "Update AOT dll List success!", "OK");
}
break;
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void SelectAll(bool isOn)
{
foreach (var item in dataList)
{
item.isOn = isOn;
}
}
private string[] GetCurrentSelectedList()
{
List<string> result = new List<string>();
foreach (var item in dataList)
{
if (item.isOn)
{
result.Add(item.dllName);
}
}
return result.ToArray();
}
private void RefreshListData()
{
dataList.Clear();
switch (mode)
{
case ConfigEditorMode.StripLinkConfig:
selectedDllList = MyGameTools.GetSelectedAssemblyDlls();
break;
case ConfigEditorMode.AotDllConfig:
selectedDllList = MyGameTools.GetSelectedAotDlls();
break;
}
foreach (var item in MyGameTools.GetProjectAssemblyDlls())
{
dataList.Add(new ItemData(IsInSelectedList(item), item));
}
}
private bool IsInSelectedList(string dllName)
{
return ArrayUtility.Contains(selectedDllList, dllName);
}
}
?自定義一個(gè)菜單用于一鍵編譯dll并把熱更dll和AOT泛型補(bǔ)充dll復(fù)制到Assets指定目錄以便打包dll到AssetBundle做熱更:
/// <summary>
/// 把熱更新dll拷貝到指定目錄
/// </summary>
/// <param name="target">平臺</param>
/// <param name="desDir">拷貝到目標(biāo)目錄</param>
/// <param name="copyAotMeta">是否同時(shí)拷貝AOT元數(shù)據(jù)補(bǔ)充dll</param>
/// <returns></returns>
public static string[] CopyHotfixDllTo(BuildTarget target, string desDir, bool copyAotMeta = true)
{
List<string> failList = new List<string>();
string hotfixDllSrcDir = HybridCLR.Editor.SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
foreach (var dll in HybridCLR.Editor.SettingsUtil.PatchingHotUpdateAssemblyFiles)
{
string dllPath = UtilityBuiltin.ResPath.GetCombinePath(hotfixDllSrcDir, dll);
if (File.Exists(dllPath))
{
string dllBytesPath = UtilityBuiltin.ResPath.GetCombinePath(desDir, Utility.Text.Format("{0}.bytes", dll));
File.Copy(dllPath, dllBytesPath, true);
}
else
{
failList.Add(dllPath);
}
}
var aotDlls = HybridCLRSettings.Instance.patchAOTAssemblies.Select(dll => dll + ".dll").ToArray();
if (copyAotMeta)
{
var failNames = CopyAotDllsToProject(target);
failList.AddRange(failNames);
}
var hotfixListFile = UtilityBuiltin.ResPath.GetCombinePath(Application.dataPath, ConstBuiltin.HOT_FIX_DLL_DIR, "HotfixFileList.txt");
File.WriteAllText(hotfixListFile, UtilityBuiltin.Json.ToJson(HybridCLR.Editor.SettingsUtil.HotUpdateAssemblyFiles.ToArray()), System.Text.Encoding.UTF8);
AssetDatabase.Refresh();
return failList.ToArray();
}
public static string[] CopyAotDllsToProject(BuildTarget target)
{
List<string> failList = new List<string>();
var aotDlls = HybridCLRSettings.Instance.patchAOTAssemblies.Select(dll => dll + ".dll").ToArray();
string aotDllDir = HybridCLR.Editor.SettingsUtil.GetAssembliesPostIl2CppStripDir(target);
string aotSaveDir = UtilityBuiltin.ResPath.GetCombinePath(Application.dataPath, "Resources", ConstBuiltin.AOT_DLL_DIR);
if (Directory.Exists(aotSaveDir))
{
Directory.Delete(aotSaveDir,true);
}
Directory.CreateDirectory(aotSaveDir);
foreach (var dll in aotDlls)
{
string dllPath = UtilityBuiltin.ResPath.GetCombinePath(aotDllDir, dll);
if (!File.Exists(dllPath))
{
Debug.LogWarning($"ab中添加AOT補(bǔ)充元數(shù)據(jù)dll:{dllPath} 時(shí)發(fā)生錯(cuò)誤,文件不存在。裁剪后的AOT dll在BuildPlayer時(shí)才能生成,因此需要你先構(gòu)建一次游戲App后再打包。");
failList.Add(dllPath);
continue;
}
string dllBytesPath = UtilityBuiltin.ResPath.GetCombinePath(aotSaveDir, Utility.Text.Format("{0}.bytes", dll));
File.Copy(dllPath, dllBytesPath, true);
}
return failList.ToArray();
}
⑥ 加載并補(bǔ)充AOT元數(shù)據(jù):
var aotMetaDlls = Resources.LoadAll<TextAsset>("AotDlls");
foreach (var dll in aotMetaDlls)
{
var success = RuntimeApi.LoadMetadataForAOTAssembly(dll.bytes, HomologousImageMode.SuperSet) == LoadImageErrorCode.OK;
}
?什么情況下需要把dll添加到Patch AOT Assemblies進(jìn)行AOT元數(shù)據(jù)補(bǔ)充?
AOT元數(shù)據(jù)補(bǔ)充,顧名思義是為AOT補(bǔ)充元數(shù)據(jù),元數(shù)據(jù)補(bǔ)充dll一定是AOT dll而不是熱更dll。
例如,項(xiàng)目用到了LitJson插件,插件放到了非熱更部分(AOT),LitJson有很多方法用到了值類型泛型T,當(dāng)熱更dll中的邏輯調(diào)用了AOT里帶有值類型泛型的函數(shù)就會報(bào)錯(cuò),因此需要將LitJson.dll添加到補(bǔ)充Patch AOT Assemblies列表里,游戲啟動后先進(jìn)行AOT元數(shù)據(jù)補(bǔ)充,熱更dll再調(diào)用值類型泛型函數(shù)就不會報(bào)錯(cuò)了。
AOT元數(shù)據(jù)補(bǔ)充dll只需要出包時(shí)打進(jìn)包里就行,不要作為熱更資源。
GF接入HybridCLR:
1. 下載hybridclr_trial示例工程并導(dǎo)入U(xiǎn)GF框架
HybridCLR目前還不是以Unity插件的形式提供,需要下載hybridclr_trial示例工程提取提供的Dll生成工具和橋接函數(shù)生成工具, 以及HybridCLR Runtime接口。
2. 安裝配置HybridCLR環(huán)境
首先找到hybridclr_trial\HybridCLRData\init_local_il2cpp_data.bat, 以文本方式打開,并做如下配置。
@echo off
set IL2CPP_BRANCH=2021.3.1 //這里Unity2020.3.x填2020.3.33; Unity2021.3.x填2021.3.1
...
...
set IL2CPP_PATH=C:\Program Files\Unity\Hub\Editor\2021.3.1f1\Editor\Data\il2cpp //填自己Unity安裝目錄下的il2cpp文件夾所在路徑
......
目前HybridCLR支持的Unity版本為Unity2020.3系列和2021.3系列,即Unity2020.3.x使用il2cpp 2020.3.33分支,Unity2021.3.x使用il2cpp 2021.3.1分支。然后把IL2CPP_PATH配置為自己Unity安裝目錄下il2cpp所在路徑。
對應(yīng)關(guān)系:
Unity 2020.x => 使用HybridCLR il2cpp 2020.3.33分支;IL2CPP_PATH配置為Unity2020.3.33安裝目錄下的il2cpp
Unity 2021.x => 使用HybridCLR il2cpp 2021.3.1分支;L2CPP_PATH配置為Unity2021.3.1安裝目錄下的il2cpp
建議:
HybridCLR魔改了Unity 2020.3.33和Unity 2021.3.1兩個(gè)版本的il2cpp,分別兼容Unity 2020.x和Unity 2021.x.?
建議il2cpp和HybridCLR魔改版il2cpp的Unity版本保持一致,即如果你是用Unity2020.x開發(fā),就把IL2CPP_PATH設(shè)置為Unity2020.3.33安裝目錄下的il2cpp,使用Unity2021.x開發(fā)就把IL2CPP_PATH設(shè)置為Unity2021.3.1安裝目錄下的il2cpp. il2cpp版本不一致否則很有可能遇到奇怪的bug.
為了方便可以把Unity2020.3.33和Unity2021.3.1的il2cpp文件夾提取出來放到項(xiàng)目里。
修改完成后右鍵以管理員方式運(yùn)行init_local_il2cpp_data.bat,win11下先右鍵打開Windows PowerShell終端,然后在終端中運(yùn)行。.bat會自動從git下載hybridclr和魔改后支持dll動態(tài)加載的il2cpp庫,并將IL2CPP_PATH配置的Unity原生il2cpp庫與魔改后的il2cpp文件合并。Unity使用魔改過的il2cpp編譯也就從底層支持了C#熱更新。
?3. 程序集分離,劃分為Builtin.Runtime(內(nèi)置)程序集和Hotfix(熱更)程序集。
為什么要分離程序集?
正如你不能自己把自己舉起來,所以需要一個(gè)起到橋梁作用的內(nèi)置程序負(fù)責(zé)初始化一些比較靠前的邏輯,比如啟動游戲后需要先把熱更新邏輯Hotfix.dll從資源服務(wù)器下載下來,并加載程序集。加載完成后通過反射調(diào)用Hotfix.dll中的入口函數(shù)切換到熱更新邏輯。
程序集拆分為Builtin.Runtime和Hitfix,即內(nèi)置程序集和熱更新程序集兩個(gè)即可,把內(nèi)置程序和熱更新程序分離到不同的文件夾,在兩個(gè)文件夾下創(chuàng)建分別創(chuàng)建Assembly Definition. 然后將熱更新程序集引用內(nèi)置程序集。
?4. 熱更邏輯入口HotfixEntry
由于Hotfix程序集是以熱更資源的形式存在,不會被編譯成.so進(jìn)入安裝包,所以Builtin程序集不能直接調(diào)用Hotfix程序集,否則編譯時(shí)會報(bào)錯(cuò)。這就需要一個(gè)HotfixEntry作為進(jìn)入熱更邏輯的入口,使用反射的方式進(jìn)入Hotfix程序集。
為了盡可能把邏輯放到熱更新以達(dá)到更大可控性,Builtin程序集只處理比較靠前的邏輯,比如更新資源和熱更dll,初始化熱更dll. 熱更dll初始化完成后就進(jìn)入熱更程序集把所有邏輯交由熱更新程序集完成。由于GF不支持動態(tài)追加Procedure,所以進(jìn)入熱更程序集流程時(shí)需要重新為熱更新Procedure創(chuàng)建有限狀態(tài)機(jī),并切換到熱更新流程。在Builtin程序集初始化完熱更dll后通過反射調(diào)用HotfixEntry.StartHotfixLogic()進(jìn)入熱更新邏輯。
//加載熱更新Dll完成,進(jìn)入熱更邏輯
if (loadedProgress >= totalProgress)
{
Log.Info("熱更dll加載完成, 開始進(jìn)入HotfixEntry");
loadedProgress = -1;
#if !DISABLE_HYBRIDCLR
var hotfixDll = GFBuiltin.Hotfix.GetHotfixClass("HotfixEntry");
if (hotfixDll == null)
{
Log.Error("獲取熱更入口類HotfixEntry失敗!");
return;
}
hotfixDll.GetMethod("StartHotfixLogic").Invoke(null, new object[] { true });
#else
HotfixEntry.StartHotfixLogic(false);
#endif
using GameFramework;
using GameFramework.Fsm;
using GameFramework.Procedure;
using UnityGameFramework.Runtime;
/// <summary>
/// 熱更邏輯入口
/// </summary>
public class HotfixEntry
{
public static void StartHotfixLogic(bool enableHotfix)
{
Log.Info("Hotfix Enable:{0}", enableHotfix);
GFBuiltin.Fsm.DestroyFsm<IProcedureManager>();
var fsmManager = GameFrameworkEntry.GetModule<IFsmManager>();
var procManager = GameFrameworkEntry.GetModule<IProcedureManager>();
//手動把熱更新程序集的流程添加進(jìn)來
ProcedureBase[] procedures = new ProcedureBase[]
{
new PreloadProcedure(),
new ChangeSceneProcedure(),
new MenuProcedure(),
new GameProcedure(),
new GameOverProcedure()
};
procManager.Initialize(fsmManager, procedures);
procManager.StartProcedure<PreloadProcedure>();//默認(rèn)啟動熱更新程序集的預(yù)加載流程
}
}
5. 打包流程及注意事項(xiàng)
0.通過HybridCLR內(nèi)置工具生成橋接函數(shù)(MethodBridge)、生成dll(CompileDll):
1. HybridCLR必須要先Build工程,目的是Build時(shí)生成代碼裁剪后的dll以供HybridCLR進(jìn)行AOT元數(shù)據(jù)補(bǔ)充。
由于il2cpp編譯后泛型函數(shù)的原始函數(shù)體元數(shù)據(jù)會丟失,無法創(chuàng)建出AOT泛型函數(shù)的實(shí)例就會導(dǎo)致報(bào)錯(cuò)。AOT有泛型共享機(jī)制,利用這一特性,我們只需要在內(nèi)置程序集中實(shí)例化的類中添加泛型函數(shù)的調(diào)用,該泛型函數(shù)的元數(shù)據(jù)就會建立并共享。
HybridCLR已經(jīng)默認(rèn)在RefTypes.cs中補(bǔ)充了常用的泛型元數(shù)據(jù):
(最新版HybridCLR做了優(yōu)化,已經(jīng)沒了)
?泛型函數(shù)在開發(fā)中使用非常頻繁,自己寫的泛型值類型函數(shù)必須提前注冊到AOT以泛型共享, 如下示例,只需在會實(shí)例化的類中添加這些泛型函數(shù)調(diào)用,RefBuiltinAOT()和RefLitJson()無需有任何地方調(diào)用。泛型元數(shù)據(jù)問題也是目前HybridCLR最大的痛點(diǎn),HybridCLR官方RoadMap不久后就會出一個(gè)自動掃描添加泛型函數(shù)的工具以解決這一痛點(diǎn)。(官方已經(jīng)解決了這個(gè)痛點(diǎn))
HybridCLR還支持通過dll自動補(bǔ)充元數(shù)據(jù), 例如, 我項(xiàng)目里內(nèi)置了LitJson插件,可以通過HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly() 從LitJson.dll自動補(bǔ)充AOT元數(shù)據(jù)
#region 提前把熱更新使用到的值類型泛型注冊到AOT,否則報(bào)錯(cuò)
/// <summary>
/// 注冊元數(shù)據(jù)到AOT
/// </summary>
void RefBuiltinAOT()
{
var param = RefParams.Acquire();
param.GetValue<bool>(default, default);
param.GetValue<int>(default, default);
param.GetValue<float>(default, default);
param.GetValue<double>(default, default);
param.GetValue<Vector2>(default, default);
param.GetValue<Vector3>(default, default);
param.GetValue<Vector4>(default, default);
param.GetValue<Vector2Int>(default, default);
param.GetValue<Vector3Int>(default, default);
param.GetValue<Quaternion>(default, default);
param.GetValue<Rect>(default, default);
param.GetValue<Bounds>(default, default);
param.GetValue<Color>(default, default);
param.GetValue<Color32>(default, default);
param.TryGetValue<bool>(default, out bool _);
param.TryGetValue<int>(default, out int _);
param.TryGetValue<float>(default, out float _);
param.TryGetValue<double>(default, out double _);
param.TryGetValue<Vector2>(default, out Vector2 _);
param.TryGetValue<Vector3>(default, out Vector3 _);
param.TryGetValue<Vector4>(default, out Vector4 _);
param.TryGetValue<Vector2Int>(default, out Vector2Int _);
param.TryGetValue<Vector3Int>(default, out Vector3Int _);
param.TryGetValue<Quaternion>(default, out Quaternion _);
param.TryGetValue<Rect>(default, out Rect _);
param.TryGetValue<Bounds>(default, out Bounds _);
param.TryGetValue<Color>(default, out Color _);
param.TryGetValue<Color32>(default, out Color32 _);
}
void RefLitJson()
{
LitJson.JsonMapper.RegisterExporter<Vector2>(default);
LitJson.JsonMapper.RegisterExporter<Vector3>(default);
LitJson.JsonMapper.RegisterExporter<Vector2Int>(default);
LitJson.JsonMapper.RegisterExporter<Vector3Int>(default);
LitJson.JsonMapper.RegisterExporter<Vector4>(default);
LitJson.JsonMapper.RegisterExporter<Quaternion>(default);
LitJson.JsonMapper.RegisterExporter<Color>(default);
LitJson.JsonMapper.RegisterExporter<Color32>(default);
LitJson.JsonMapper.RegisterExporter<Bounds>(default);
LitJson.JsonMapper.RegisterExporter<Rect>(default);
}
#endregion
2. 橋接函數(shù)丟失
如果運(yùn)行時(shí)遇到報(bào)錯(cuò)ExecutionEngineException: GetManaged2NativeMethodPointer not support. signature:xxx(簽名),需要把手動補(bǔ)充到HybridCLR\Editor\HybridCLR\Generators\GeneratorConfig.cs中,這也是HybridCLR最大的痛點(diǎn)之一,好在官方剛剛完善了橋接函數(shù)的問題,后續(xù)還會完善橋接函數(shù)生成工具以徹底消除此問題。
/// <summary>
/// 如果提示缺失橋接函數(shù),將提示缺失的簽名加入到下列列表是簡單的做法。
/// 這里添加64位App缺失的橋接函數(shù)簽名
/// </summary>
/// <returns></returns>
public static List<string> PrepareCustomMethodSignatures64()
{
return new List<string>
{
"vi8i8",
"i4i8i8i4i4i8i8",
"i8i8S12",
"S12i8S12",
"S12i8S12S12",
"i16i8i16i16",
};
}
/// <summary>
/// 如果提示缺失橋接函數(shù),將提示缺失的簽名加入到下列列表是簡單的做法。
/// 這里添加32位App缺失的橋接函數(shù)簽名
/// </summary>
/// <returns></returns>
public static List<string> PrepareCustomMethodSignatures32()
{
return new List<string>
{
"vi4i4",
"S12i4S12S12",
};
}
3. 把Build后生成的Strip(裁剪)過的dll打包成AssetBundle
Build后HybridCLR會將Strip后的dll復(fù)制到項(xiàng)目根目錄的HybridCLRData\AssembliesPostIl2CppStrip下。
可以寫個(gè)工具監(jiān)聽Build進(jìn)程,Build完成后自動把Strip后的dll復(fù)制到Assets指定目錄,并修改為Unity支持的資源擴(kuò)展名以供打包AB:
#if UNITY_EDITOR
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using HybridCLR;
using System.IO;
using GameFramework;
public class BuildAppListener : IPostprocessBuildWithReport, IPreprocessBuildWithReport, IPostBuildPlayerScriptDLLs
{
public int callbackOrder => 100;
public void OnPostBuildPlayerScriptDLLs(BuildReport report)
{
//Debug.LogFormat("OnPostBuildPlayerScriptDLLs:{0}", report.name);
}
public void OnPostprocessBuild(BuildReport report)
{
Debug.Log("OnPostprocessBuild:");
BuildTarget target = report.summary.platform;
//CompileDllHelper.CompileDll(target);
var hotfixDllDir = UtilityExt.Path.GetCombinePath(Application.dataPath, ConstBuiltin.HOT_FIX_DLL_DIR);
try
{
if (!Directory.Exists(hotfixDllDir))
{
Directory.CreateDirectory(hotfixDllDir);
}
else
{
var dllFils = Directory.GetFiles(hotfixDllDir);
for (int i = dllFils.Length - 1; i >= 0; i--)
{
File.Delete(dllFils[i]);
}
}
CopyHotfixDllTo(target, hotfixDllDir);
}
catch (System.Exception e)
{
Debug.LogErrorFormat("生成熱更新dll文件失敗:{0}", e.Message);
throw;
}
}
public void OnPreprocessBuild(BuildReport report)
{
Debug.Log("OnPreprocessBuild:");
}
public static void CopyHotfixDllTo(BuildTarget target, string desDir, bool copyAotMeta = true)
{
string hotfixDllSrcDir = BuildConfig.GetHotFixDllsOutputDirByTarget(target);
foreach (var dll in BuildConfig.AllHotUpdateDllNames)
{
string dllPath = UtilityExt.Path.GetCombinePath(hotfixDllSrcDir, dll);
if (File.Exists(dllPath))
{
string dllBytesPath = UtilityExt.Path.GetCombinePath(desDir, Utility.Text.Format("{0}.bytes", dll));
File.Copy(dllPath, dllBytesPath, true);
}
}
if (copyAotMeta)
{
string aotDllDir = BuildConfig.GetAssembliesPostIl2CppStripDir(target);
foreach (var dll in BuildConfig.AOTMetaDlls)
{
string dllPath = UtilityExt.Path.GetCombinePath(aotDllDir, dll);
if (!File.Exists(dllPath))
{
Debug.LogError($"ab中添加AOT補(bǔ)充元數(shù)據(jù)dll:{dllPath} 時(shí)發(fā)生錯(cuò)誤,文件不存在。裁剪后的AOT dll在BuildPlayer時(shí)才能生成,因此需要你先構(gòu)建一次游戲App后再打包。");
continue;
}
string dllBytesPath = UtilityExt.Path.GetCombinePath(desDir, Utility.Text.Format("{0}.bytes", dll));
File.Copy(dllPath, dllBytesPath, true);
}
}
AssetDatabase.Refresh();
}
}
#endif
3. 把熱更dll添加到GF的AB打包工具并打包AB
4. Build項(xiàng)目并測試。
GF_HybridCLR
https://github.com/sunsvip/GF_HybridCLR
GF_HybridCLR是已經(jīng)集成好的GameFramework + HybridCLR開發(fā)框架,有著一整套完善的工作流。包含對GameFramework的擴(kuò)展,數(shù)據(jù)表生成工具、代碼生成工具、熱更新工作流,AssetBundle加密解密。并且針對HybridCLR增加了傻瓜式的安裝、更新工具,支持一鍵開關(guān)HybridCLR熱更新。
使用方法:
1. 首先要確保安裝了git環(huán)境: Git下載
2. 下載GF_HybridCLR工程,點(diǎn)擊Unity菜單欄HybridCLR->Setup 自動配置HybridCLR環(huán)境;
3. 點(diǎn)擊HybridCLR->Update一鍵更新HybridCLR到最新版本
?2022.8.21更新:
官方也新增了一鍵安裝HybridCLR功能,我直接移植過來了。另外為了使用方便,提取了Unity 2021.3.1版本的il2cpp放在項(xiàng)目里,增加了一鍵從il2cpp壓縮文件安裝HybridCLR的功能。
使用方法如圖:
工具默認(rèn)會根據(jù)當(dāng)前Unity版本去查找匹配的HybridCLR il2cpp版本(2020.3.3或2021.3.1),如果你沒有安裝這兩個(gè)版本就會使用當(dāng)前Unity安裝目錄下的il2cpp, 但是il2cpp版本不匹配很有可能出現(xiàn)兼容問題。
所以我以壓縮包的形式內(nèi)置了Unity 2021.3.1版本的il2cpp,點(diǎn)擊Use Buitin il2cpp程序會自動解壓并使用,然后點(diǎn)擊install安裝即可。(注,如果用的是Unity 2020.x,需要自行提取Unity 2020.3.33版本的il2cpp,壓縮為il2cpp_Unity2020_3_33.zip放到項(xiàng)目的HybridCLRData目錄下)
手機(jī)端熱更實(shí)測:文章來源:http://www.zghlxwxcb.cn/news/detail-410809.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-410809.html
到了這里,關(guān)于【UGF】GameFramework接入HybridCLR(wolong)臥龍C#熱更框架的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!