Unity热更新-HybirdCLR + Addressable 一
1、开发准备环境
HybridCLR支持Unity 2019-2022的所有LTS版本,我采用的是2021.3.5,unity的安装以及visual studio的安装这里就不多介绍了
2、创建一个Unity的空项目
命名为HybirdAA,接着创建两个场景Entry、MainScene,Entry作为启动场景,分别在Entry场景里添加一个Image和Text,进行图片颜色和Text内容修改,两个场景有区别即可
3、安装HybridCLR
主菜单中点击Windows/Package Manager打开包管理器。如下图所示点击Add package from git URL…,填入https://gitee.com/focus-creative-games/hybridclr_unity.git或https://github.com/focus-creative-games/hybridclr_unity.git

一般推荐使用gitee的地址,github下载经常会失败,也可以先把 com.code-philosophy.hybridclr clone或者下载到本地,将文件夹改名为com.code-philosophy.hybridclr,直接移动到项目的Packages目录下
4、初始化热更新模块
打开菜单HybridCLR/Installer…, 点击安装按钮进行安装。 等待一会,安装完成会显示版本号,如下图所示

5、创建 HotFix 热更新模块
1、创建 Assets/HotFix目录
2、如果你们的项目把Assembly-CSharp作为AOT程序集,在目录下 右键 Create/Assembly Definition,创建一个名为HotFix的程序集模块,建议关闭热更新程序集的auto reference选项。因为Assembly-CSharp是最顶层assembly,开启此选项后会自动引用剩余所有assembly,包括热更新程序集,很容易就出现失误引用热更新程序集导致打包失败的情况
3、在HotFix文件夹里创建一个App.cs文件备用
6、配置HybridCLR
打开菜单 HybridCLR/Settings, 在Hot Update Assemblies配置项中添加HotFix程序集,如下图:
7、配置PlayerSettings
1、如果你用的hybridclr包低于v4.0.0版本,需要关闭增量式GC(Use Incremental GC) 选项
2、Scripting Backend 切换为 IL2CPP
3、Api Compatability Level 切换为 .Net 4.x(Unity 2019-2020) 或 .Net Framework(Unity 2021+)

4、生成AOT所需要的文件,打开菜单HybirdCLR/Generate/All, 耐心等待一会,执行完成会在Assets同级目录HybridCLRData文件夹多出的内容如下:

8、安装Addressable
打开菜单Window/PackageManager, 筛选为Unity Rigistry,并搜索Addressables,点击install 即可
9、创建可寻址资源组
1、打开菜单Window/Asset Management/Addressable/Groups ,在弹出的窗口里点击Create Addressables Settings 创建界面如下

2、在Assets 目录下创建文件夹AddressableAssets, 用于存在热更资源,接着在AddressableAssets文件夹里创建Scenes、HotFixDlls文件夹,并把上面创建的两个场景,放在Scenes里,此时在此工程目录找到HybridCLRData/HotUpdateDlls/StandaloneWindows64/HotFix.dll,并拷贝到HotFixDlls里,改名为HotFix.dll.bytes,补充AOT元素也需要拷贝到HotFixDlls文件夹里,补充元素目录为HybridCLRData/AssembliesPostIl2CppStrip/StandaloneWindows64/mscorlib.dll、System.Core.dll、System.dll,同样需要在后缀加上.bytes,不然Unity识别不了,如下图

3、此时拖拽AddressableAssets中的dll文件以及场景文件到DefaultLocalGroup栏目里,并进行改名(方便根据名称加载),改名可以在资源的Inspector里进行修改,在该面板里也会发现资源前Addressable复选框被选中了,说明是热更资源:

10、创建主程序逻辑
1、在Assets 目录下创建文件夹Main,在Main目录下 右键 Create/Assembly Definition,创建一个名为Main的程序集模块,并添加引用如下图:

2、在Main文件价夹下新建脚本LoadDll,并把此脚本挂载到MainCamera组件上,该脚本是属于Main程序集的,代码如下
using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class LoadDll : MonoBehaviour
{
private Assembly hotUpdateAss;
private int aotAssemblyCount;
private List<string> aotDllList = new List<string>{"mscorlib.dll","System.dll","System.Core.dll"};
void Start()
{
aotAssemblyCount = aotDllList.Count;
LoadMetadataForAOTAssemblies();
}
void LoadHotFix_Completed(AsyncOperationHandle<TextAsset> obj)
{
hotUpdateAss = Assembly.Load(obj.Result.bytes);
DoMainLogic();
}
void LoadAot_Completed(AsyncOperationHandle<TextAsset> obj)
{
int err = (int)RuntimeApi.LoadMetadataForAOTAssembly(obj.Result.bytes, HomologousImageMode.SuperSet);
Debug.Log($"LoadMetadataForAOTAssembly:{obj.Result.name}. ret:{err}");
aotAssemblyCount--;
if (aotAssemblyCount <= 0)
{
Debug.Log("load meta done");
#if !UNITY_EDITOR
Addressables.LoadAssetAsync<TextAsset>("HotFix.dll").Completed += LoadHotFix_Completed;
#else
hotUpdateAss = AppDomain.CurrentDomain.GetAssemblies().First(assembly => assembly.GetName().Name == "HotFix");
DoMainLogic();
#endif
}
}
/// <summary>
/// 加载AOT程序集
/// </summary>
void LoadMetadataForAOTAssemblies()
{
foreach (var aotDllName in aotDllList)
{
Addressables.LoadAssetAsync<TextAsset>(aotDllName).Completed += LoadAot_Completed;
}
}
void DoMainLogic()
{
Debug.Log("execu main logic");
Type type = hotUpdateAss.GetType("HotFix.App");
type.GetMethod("Main").Invoke(null, null);
}
}
3、通过LoadDll脚本加载补充程序集 ,然后调用热更新代码执行热更逻辑,下面我们在热更新HotFix文件夹下找到脚本App并做以下修改,入口方法main设置为静态函数,方便外部调用,通过异步方法调用切换场景
using UnityEngine.AddressableAssets;
namespace HotFix
{
public class App
{
public static int Main()
{
LoadScene();
return 0;
}
/// <summary>
/// 切换场景
/// </summary>
static async void LoadScene()
{
var handler = await Addressables.LoadSceneAsync("MainScene").Task;
handler.ActivateAsync();
}
}
}
4、此时发现App脚本按照如上修改会报错,缺少程序集引用,需要我们手动添加,找到HotFix目录,点击HotFix程序集,查看Inspector面板,在Assembly Definition References选项里添加如下引用

5、此时在就可以在启动脚本LoadDLL调用热更新代码,方法如下
void DoMainLogic()
{
Debug.Log("execu main logic");
Type type = hotUpdateAss.GetType("HotFix.App");
type.GetMethod("Main").Invoke(null, null);
}
6、接着在LoadMetadataForAOTAssemblies方法里调用
IEnumerator LoadMetadataForAOTAssemblies()
{
List<string> aotDllList = new List<string>
{
"mscorlib.dll",
"System.dll",
"System.Core.dll", // 如果使用了Linq,需要这个
// "Newtonsoft.Json.dll",
// "protobuf-net.dll",
};
yield return null;
foreach (var aotDllName in aotDllList)
{
TextAsset aotDll = Addressables.LoadAssetAsync<TextAsset>(aotDllName).WaitForCompletion();
int err = (int)RuntimeApi.LoadMetadataForAOTAssembly(aotDll.bytes, HomologousImageMode.SuperSet);
Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. ret:{err}");
}
Debug.Log("load meta done");
DoMainLogic();
}
11、打包测试
1、打开菜单File/Build Settings, 把Entry场景拖住到Scenes In Build 里,并添加日志输出脚本ConsoleToSceen,并挂载到MainCamera上,需要重新编译热更脚本,打开菜单HybirdCLR/CompileDll/Win64(选择对应的平台),执行完毕,然后替换HotFix.dll到AddressableAssets/HotFixDlls文件夹里,打包运行结果如下:

发现有报错,这是因为AOT泛型函数实例化缺失引起的,错误日志告诉我们缺失哪个AOT函数实例化,那就在主工程里加上对这个函数的调用,使得il2cpp在打包时能生成这个泛型函数的代码。 主工程里任意地方加个泛型AOT函数调用都可以,可以在Main文件夹里添加RefTypes.cs脚本,补充元素脚本内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Scripting;
[assembly: Preserve]
enum IntEnum : int
{
A,
B,
}
public class RefTypes : MonoBehaviour
{
public void RefUnityEngine()
{
GameObject.Instantiate<GameObject>(null);
Instantiate<GameObject>(null, null);
Instantiate<GameObject>(null, null, false);
Instantiate<GameObject>(null, new Vector3(), new Quaternion());
Instantiate<GameObject>(null, new Vector3(), new Quaternion(), null);
this.gameObject.AddComponent<RefTypes>();
gameObject.AddComponent(typeof(RefTypes));
}
public void RefNullable()
{
// nullable
int? a = 5;
object b = a;
}
public void RefContainer()
{
new List<object>()
{
new Dictionary<int, int>(),
new Dictionary<int, long>(),
new Dictionary<int, object>(),
new Dictionary<long, int>(),
new Dictionary<long, long>(),
new Dictionary<long, object>(),
new Dictionary<object, long>(),
new Dictionary<object, object>(),
new SortedDictionary<int, long>(),
new SortedDictionary<int, object>(),
new SortedDictionary<long, int>(),
new SortedDictionary<long, object>(),
new HashSet<int>(),
new HashSet<long>(),
new HashSet<object>(),
new List<int>(),
new List<long>(),
new List<float>(),
new List<double>(),
new List<object>(),
new ValueTuple<int, int>(1, 1),
new ValueTuple<long, long>(1, 1),
new ValueTuple<object, object>(1, 1),
};
}
class RefStateMachine : IAsyncStateMachine
{
public void MoveNext()
{
throw new NotImplementedException();
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
throw new NotImplementedException();
}
}
public void RefAsyncMethod()
{
var stateMachine = new RefStateMachine();
TaskAwaiter aw = default;
var c0 = new AsyncTaskMethodBuilder();
c0.Start(ref stateMachine);
c0.AwaitUnsafeOnCompleted(ref aw, ref stateMachine);
c0.SetException(null);
c0.SetResult();
var c1 = new AsyncTaskMethodBuilder();
c1.Start(ref stateMachine);
c1.AwaitUnsafeOnCompleted(ref aw, ref stateMachine);
c1.SetException(null);
c1.SetResult();
var c2 = new AsyncTaskMethodBuilder<bool>();
c2.Start(ref stateMachine);
c2.AwaitUnsafeOnCompleted(ref aw, ref stateMachine);
c2.SetException(null);
c2.SetResult(default);
var c3 = new AsyncTaskMethodBuilder<int>();
c3.Start(ref stateMachine);
c3.AwaitUnsafeOnCompleted(ref aw, ref stateMachine);
c3.SetException(null);
c3.SetResult(default);
var c4 = new AsyncTaskMethodBuilder<long>();
c4.Start(ref stateMachine);
c4.AwaitUnsafeOnCompleted(ref aw, ref stateMachine);
c4.SetException(null);
var c5 = new AsyncTaskMethodBuilder<float>();
c5.Start(ref stateMachine);
c5.AwaitUnsafeOnCompleted(ref aw, ref stateMachine);
c5.SetException(null);
c5.SetResult(default);
var c6 = new AsyncTaskMethodBuilder<double>();
c6.Start(ref stateMachine);
c6.AwaitUnsafeOnCompleted(ref aw, ref stateMachine);
c6.SetException(null);
c6.SetResult(default);
var c7 = new AsyncTaskMethodBuilder<object>();
c7.Start(ref stateMachine);
c7.AwaitUnsafeOnCompleted(ref aw, ref stateMachine);
c7.SetException(null);
c7.SetResult(default);
var c8 = new AsyncTaskMethodBuilder<IntEnum>();
c8.Start(ref stateMachine);
c8.AwaitUnsafeOnCompleted(ref aw, ref stateMachine);
c8.SetException(null);
c8.SetResult(default);
var c9 = new AsyncVoidMethodBuilder();
var b = AsyncVoidMethodBuilder.Create();
c9.Start(ref stateMachine);
c9.AwaitUnsafeOnCompleted(ref aw, ref stateMachine);
c9.SetException(null);
c9.SetResult();
Debug.Log(b);
}
}
2、修改之后,需要打开菜单HybirdCLR/Generate/All,并且HybirdCLR/CompileDll/Win64(选择对应的平台),把新生成的dll替换到AddressableAssets/HotFixDlls文件夹里,再次打包运行正常,如何知道运行正常呢,即场景成功切换到了MainScene场景
3、上面打的包,我们先采用本地资源,验证热更新插件和addressable的结合使用,我们也可以看到,在工程目录AddressableAssetsData/AddressableAssetSettings中检视面板ContentUpdate栏中的Bulid Remote Catalog默认是不勾选的,以及Addressable Group窗口点击Profile/Manage Profiles 弹窗里 Remote栏目采用的是Editor Hosted选项,所以默认资源都是跟随工程打包的,那么我们下一章节再去阐述资源和代码的热更新逻辑
3523

被折叠的 条评论
为什么被折叠?



