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。如下图:
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.");
}
}
7、打包资源(预制体)
在菜单:Windows > AssetBundle Browser,打开界面。找到Assets > Prefabs 中的Player拖住至Configure选项卡中,如图:
再切换至Build选项卡点击Build进行打包。这时我们注意到它默认是生成到 项目根目录/AssetBundles/StandaloneWindows中。稍候会用到。
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中设置如下:
13、发布运行
菜单 File -> Build Setting 中我们点击Build按钮,生成发布文件。然后运行,效果如下:
可见资源和脚本都正常加载并执行了。
还有一个彩蛋,如果这时我们直接运行而不是发布后运行exe文件会收到一个错误:
The referenced script (ModBehaviour) on this Behaviour is missing!
这个是又怎么解决呢?下一节我们再说。
关注我的微信公众号
在公众号里留言交流
投稿邮箱:1052839972@qq.com
庭院深深深几许?杨柳堆烟,帘幕无重数。
玉勒雕鞍游冶处,楼高不见章台路。
雨横风狂三月暮。门掩黄昏,无计留春住。
泪眼问花花不语,乱红飞过秋千去。
如果感觉对您有帮助
欢迎向作者提供捐赠
这将是创作的最大动力