Unity3D HybridCLR 从零开始 与众不同系列(三)

2024-08-11  乐帮网

HybridCLR unity3d

再重复一下最终目标, 一个主程序从网络上加载配置,通过配置得到功能模块(含美术和脚本资源)的下载地址,下载相关内容后动态加载运行功能模块。功能模块和主程序分属不相关的两个项目。
下面开始创建功能模块。

1、新建一个ResHybrid项目做为我们功能模块项目,不再上图可参考上一节。

2、安装 Asset Bundle Browser
由于我们需要通过AssetBundle 方式打包和加载资源所以选择了一个通用打包工具,当然也可以使用yooasset。

在菜单:Windows > Package Manager,单击窗口左上角的 +(添加)按钮。
选择 Add package from git URL…
输入https://github.com/Unity-Technologies/AssetBundles-Browser.git 作为 URL
单击 Add。

github是断断续续能访问 可以使用镜像站 https://githubfast.com/Unity-Technologies/AssetBundles-Browser.git

3、安装HybridCLR 工具 -可参考上一节
在菜单:Windows > Package Manager,单击窗口左上角的 +(添加)按钮。
选择 Add package from git URL…
输入 https://gitee.com/focus-creative-games/hybridclr_unity.git 作为 URL
单击 Add。

4、在Assets中新建两个文件夹 Scripts和Prefabs来放我们的脚本和预制体。

5、在Prefabs中新建一个预制体名为:Player。并且在本目录添加一个Object3D模型 Capsule。如下图:

set

6、在Scripts中新建 Assembly Definition,名为mod, 并且建一个脚本文件:ModBehaviour.cs, 并且挂载到上一步的Capsule中其中代码如下:

using UnityEngine;

public class ModBehaviour : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        transform.position = new Vector3(0, 1, 0);
        Debug.Log("ModBehaviour Start Success.");
    }

}

code

codeadd

7、打包资源(预制体)
在菜单:Windows > AssetBundle Browser,打开界面。找到Assets > Prefabs 中的Player拖住至Configure选项卡中,如图:

再切换至Build选项卡点击Build进行打包。这时我们注意到它默认是生成到 项目根目录/AssetBundles/StandaloneWindows中。稍候会用到。

build

8、打包脚本
菜单:HybridCLR -> CompileDll -> Win64
注意这个平台我们默认的是Win64,和主程序有关。会有日志显示成功,最终生成文件在 项目根目录\HybridCLRData\HotUpdateDlls\StandaloneWindows64 中。 mod.dll 正是我们需要的文件。

9、上传资源
以上两步的资源无要上传到网络上。我上传的地址分别是
mod.dll和第7步生成的 player
http://lebang2020.cn/cdn/unity/HybridDemo/mod.dll
http://lebang2020.cn/cdn/unity/HybridDemo/player

10、加载资源
回到主项目MainHybrid,我们来加载上述资源。在 MainHybrid Asset -> Scripts中新建类 Loader.cs,代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using HybridCLR;
using UnityEngine;
using UnityEngine.Networking;

public class Loader : MonoBehaviour
{
     private  Dictionary<string, string> _datasDic;

    void Start()
    {
        _datasDic = new Dictionary<string, string>();

        foreach (var name in AOTMetaAssemblyFiles)
        {
            _datasDic.TryAdd(name, GetStreamPath(name));
        }
        _datasDic.TryAdd("mod.dll", @"https://lebang2020.cn/cdn/unity/HybridDemo/mod.dll");
        _datasDic.TryAdd("player", @"https://lebang2020.cn/cdn/unity/HybridDemo/player");

        StartCoroutine(DownLoadAssets(_datasDic, this.StartGame));
    }

    #region download assets

    private static Dictionary<string, byte[]> _assemblyDatas = new Dictionary<string, byte[]>();

    

    public static byte[] GetAssemblyData(string dllName)
    {
        return _assemblyDatas[dllName];
    }

    private string GetStreamPath(string asset)
    {
        var path = $"{Application.streamingAssetsPath}/{asset}";
        if (!path.Contains("://"))
        {
            path = "file://" + path;
        }
        return path;
    }
    private static List<string> AOTMetaAssemblyFiles
    {
        get
        {
            return new List<string>()
            {
            "mscorlib.dll.bytes",
            "System.dll.bytes",
            "System.Core.dll.bytes",
            };
        }
    }

    IEnumerator DownLoadAssets(Dictionary<string, string>  assets,Action onDownloadComplete)
    {
        foreach (var kp in assets)
        {
            Debug.Log($"start download asset:{kp.Key}");
            UnityWebRequest www = UnityWebRequest.Get(kp.Value);
            yield return www.SendWebRequest();

#if UNITY_2020_1_OR_NEWER
            if (www.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(www.error);
            }
#else
            if (www.isHttpError || www.isNetworkError)
            {
                Debug.Log(www.error);
            }
#endif
            else
            {
                // Or retrieve results as binary data
                byte[] assetData = www.downloadHandler.data;
                Debug.Log($"dll:{kp.Key}  size:{assetData.Length}");
                _assemblyDatas[kp.Key] = assetData;
            }
        }

        onDownloadComplete();
    }

    #endregion

    private static Assembly _hotUpdateAss;

    /// <summary>
    /// 为aot assembly加载原始metadata, 这个代码放aot或者热更新都行。
    /// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行
    /// </summary>
    private static void LoadMetadataForAOTAssemblies()
    {
        /// 注意,补充元数据是给AOT dll补充元数据,而不是给热更新dll补充元数据。
        /// 热更新dll不缺元数据,不需要补充,如果调用LoadMetadataForAOTAssembly会返回错误
        /// 
        HomologousImageMode mode = HomologousImageMode.SuperSet;
        foreach (var aotDllName in AOTMetaAssemblyFiles)
        {
            byte[] dllBytes = GetAssemblyData(aotDllName);
            // 加载assembly对应的dll,会自动为它hook。一旦aot泛型函数的native函数不存在,用解释器版本代码
            LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, mode);
            Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. mode:{mode} ret:{err}");
        }
    }

    void StartGame()
    {
        LoadMetadataForAOTAssemblies();
        _hotUpdateAss = Assembly.Load(GetAssemblyData("mod.dll"));
        Run_InstantiateComponentByAsset();

        StartCoroutine(DelayAndQuit());
    }

    IEnumerator DelayAndQuit()
    {
        for (int i = 10; i >= 1 ; i--)
        {
            UnityEngine.Debug.Log($"将于{i}s后自动退出");
            yield return new WaitForSeconds(2f);
        }
        Application.Quit();
    }

    private static void Run_InstantiateComponentByAsset()
    {
        // 通过实例化assetbundle中的资源,还原资源上的热更新脚本
        AssetBundle ab = AssetBundle.LoadFromMemory(GetAssemblyData("player"));
        GameObject player = ab.LoadAsset<GameObject>("Assets/Prefabs/Player.prefab");
        GameObject.Instantiate(player);
    }
}

11、挂载脚本
上一步编写的Loader.cs需要挂载在 Main场景中。在场景中新建一个空的GameObject名为Loader,拖拽Loader.cs至Loader下进行挂载。
如下图:

12、运行准备

以上完成了内容准备后,在MainHybrid运行前需要做以下准备
菜单:HybridCLR -> Generate -> ALL 来编译生成相关文件
添加AOT dll补充元数据文件,在Asset下新建文件夹 StreamingAssets ,在 项目根目录 \HybridCLRData\AssembliesPostIl2CppStrip\StandaloneWindows64\找到  "mscorlib.dll"、"System.dll"、"System.Core.dll"三个文件改名为 "mscorlib.dll.bytes"、"System.dll.bytes"、"System.Core.dll.bytes" 并复制到 StreamingAssets下。

为了方便写清输出日志我们把窗口调整小一点儿,在MainHybrid项目的File -> Build Settings中 左下角 Player Settings 中找到 Player 在Resolution中设置如下:

setting

13、发布运行
菜单 File -> Build Setting 中我们点击Build按钮,生成发布文件。然后运行,效果如下:

result

可见资源和脚本都正常加载并执行了。
还有一个彩蛋,如果这时我们直接运行而不是发布后运行exe文件会收到一个错误:

The referenced script (ModBehaviour) on this Behaviour is missing!
这个是又怎么解决呢?下一节我们再说。

 

公众号二维码

关注我的微信公众号
在公众号里留言交流
投稿邮箱:1052839972@qq.com

庭院深深深几许?杨柳堆烟,帘幕无重数。
玉勒雕鞍游冶处,楼高不见章台路。
雨横风狂三月暮。门掩黄昏,无计留春住。
泪眼问花花不语,乱红飞过秋千去。

欧阳修

付款二维码

如果感觉对您有帮助
欢迎向作者提供捐赠
这将是创作的最大动力