Unity热更新-HybirdCLR + Addressable 一

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选项,所以默认资源都是跟随工程打包的,那么我们下一章节再去阐述资源和代码的热更新逻辑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值