开始重头戏了,我们都知道wolong是用来热更代码部分,YooAsset是热更资源部分,所以二者结合起来可以就是热更完美的解决方案。
搭建项目
下载wolong示例项目
用git clone或者直接下载。
用unity打开此项目,发现这个项目菜单栏已经有HybridCLR选项了,点击该选项下方的installer…,安装。
然后我们再用上节课讲的方式将YooAsset加入到这个项目中。同时下载shoot示例项目。
将一些我们常用资源文件夹建好并添加在AssetBundle Collector中
注意:热更新代码资源部分这里要设置成PackRawFile,我想大家应该都知道为什么,我就不在这里过多重复了。
代码修改
找到场景里的LoadDll游戏物体,打开LoadDll脚本修改里面的内容。因为版本原因Host Play Mode、Web Play Mode部位代码和官网的有所差别,官网的可能是旧版的用了会报错,其他模式的大家自行修改大同小异。实在不会就看FsmInitialize脚本,在其中复制符合自己版本的代码。
using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using YooAsset;
/// <summary>
/// 脚本工作流程:
/// 1.下载资源,用yooAsset资源框架进行下载
/// 1.资源文件,ab包
/// 2.热更新dll
/// 3.AOT泛型补充元数据
/// 2.给AOT DLL补充元素据,通过RuntimeApi.LoadMetadataForAOTAssembly
/// 通过实例化prefab,运行热更代码
/// </summary>
public class LoadDll : MonoBehaviour
{
/// <summary>
/// 资源系统运行模式
/// </summary>
public EPlayMode PlayMode = EPlayMode.EditorSimulateMode;
void Start()
{
//StartCoroutine(DownLoadAssets(this.StartGame));
StartCoroutine(DownLoadAssetsByYooAssets(this.StartGame));
}
#region download assets
private static Dictionary<string, byte[]> s_assetDatas = new Dictionary<string, byte[]>();
public static byte[] ReadBytesFromStreamingAssets(string dllName)
{
return s_assetDatas[dllName];
}
private string GetWebRequestPath(string asset)
{
var path = $"{Application.streamingAssetsPath}/{asset}";
if (!path.Contains("://"))
{
path = "file://" + path;
}
return path;
}
//补充元数据dll的列表
//通过RuntimeApi.LoadMetadataForAOTAssembly()函数来补充AOT泛型的原始元数据
private static List<string> AOTMetaAssemblyFiles { get; } = new List<string>()
{
"mscorlib.dll",
"System.dll",
"System.Core.dll",
};
IEnumerator DownLoadAssetsByYooAssets(Action onDownloadComplete)
{
// 1.初始化资源系统
YooAssets.Initialize();
string packageName = "DefaultPackage";
var package = YooAssets.TryGetPackage(packageName);
if (package == null)
{
package = YooAssets.CreatePackage(packageName);
YooAssets.SetDefaultPackage(package);
}
if (PlayMode == EPlayMode.EditorSimulateMode)
{
//编辑器模拟模式
var initParameters = new EditorSimulateModeParameters();
initParameters.SimulateManifestFilePath = EditorSimulateModeHelper.SimulateBuild("DefaultPackage");
yield return package.InitializeAsync(initParameters);
}
else if (PlayMode == EPlayMode.HostPlayMode)
{
//联机运行模式
string defaultHostServer = GetHostServerURL();
string fallbackHostServer = GetHostServerURL();
var initParameters = new HostPlayModeParameters();
initParameters.BuildinQueryServices = new GameQueryServices(); //太空战机DEMO的脚本类,详细见StreamingAssetsHelper
initParameters.DecryptionServices = new GameDecryptionServices();//这里的代码和官网上的代码有差别,官网的代码可能是旧版本的代码会报错已这里的代码为主
initParameters.DeliveryQueryServices = new DefaultDeliveryQueryServices();
initParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
var initOperation = package.InitializeAsync(initParameters);
/*
var createParameters = new HostPlayModeParameters();
createParameters.DecryptionServices = new GameDecryptionServices();
createParameters.BuildinQueryServices = new GameQueryServices();
createParameters.DeliveryQueryServices = new DefaultDeliveryQueryServices();
createParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
initializationOperation = package.InitializeAsync(createParameters);
*/
yield return initOperation;
if (initOperation.Status == EOperationStatus.Succeed)
{
Debug.Log("资源包初始化成功!");
}
else
{
Debug.LogError($"资源包初始化失败:{initOperation.Error}");
}
//string defaultHostServer = GetHostServerURL();
//string fallbackHostServer = GetHostServerURL();
//var createParameters = new HostPlayModeParameters();
//createParameters.DecryptionServices = new GameDecryptionServices();
//createParameters.QueryServices = new GameQueryServices();
//createParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
//initializationOperation = package.InitializeAsync(createParameters);
}
else if (PlayMode == EPlayMode.OfflinePlayMode)
{
//单机模式
var initParameters = new OfflinePlayModeParameters();
yield return package.InitializeAsync(initParameters);
//var createParameters = new OfflinePlayModeParameters();
//createParameters.DecryptionServices = new GameDecryptionServices();
//initializationOperation = package.InitializeAsync(createParameters);
}
else
{
WebGL运行模式
string defaultHostServer = "http://127.0.0.1/CDN/WebGL/v1.0";
string fallbackHostServer = "http://127.0.0.1/CDN/WebGL/v1.0";
var initParameters = new WebPlayModeParameters();
initParameters.BuildinQueryServices = new GameQueryServices(); //太空战机DEMO的脚本类,详细见StreamingAssetsHelper
initParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
var initOperation = package.InitializeAsync(initParameters);
yield return initOperation;
if (initOperation.Status == EOperationStatus.Succeed)
{
Debug.Log("资源包初始化成功!");
}
else
{
Debug.LogError($"资源包初始化失败:{initOperation.Error}");
}
//string defaultHostServer = GetHostServerURL();
//string fallbackHostServer = GetHostServerURL();
//var createParameters = new WebPlayModeParameters();
//createParameters.DecryptionServices = new GameDecryptionServices();
//createParameters.QueryServices = new GameQueryServices();
//createParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
//initializationOperation = package.InitializeAsync(createParameters);
}
//2.获取资源版本
var operation = package.UpdatePackageVersionAsync();
yield return operation;
if (operation.Status != EOperationStatus.Succeed)
{
//更新失败
Debug.LogError(operation.Error);
yield break;
}
string packageVersion = operation.PackageVersion;
Debug.Log($"Updated package Version : {packageVersion}");
//3.更新补丁清单
// 更新成功后自动保存版本号,作为下次初始化的版本。
// 也可以通过operation.SavePackageVersion()方法保存。
bool savePackageVersion = true;
var operation2 = package.UpdatePackageManifestAsync(packageVersion, savePackageVersion);
yield return operation2;
if (operation2.Status != EOperationStatus.Succeed)
{
//更新失败
Debug.LogError(operation2.Error);
yield break;
}
//4.下载补丁包
yield return Download();
//判断是否下载成功
var assets = new List<string>
{
"HotUpdate.dll"
}.Concat(AOTMetaAssemblyFiles);
foreach (var asset in assets)
{
//加载原生文件
RawFileOperationHandle handle = package.LoadRawFileAsync(asset);
yield return handle;
byte[] fileData = handle.GetRawFileData();
//string fileText = handle4.GetRawFileText();
//string filePath = handle4.GetRawFilePath();
s_assetDatas[asset] = fileData;
Debug.Log($"dll:{asset} size:{fileData.Length}");
}
onDownloadComplete();
}
IEnumerator DownLoadAssets(Action onDownloadComplete)
{
var assets = new List<string>
{
"prefabs",
"HotUpdate.dll.bytes",
}.Concat(AOTMetaAssemblyFiles);
foreach (var asset in assets)
{
string dllPath = GetWebRequestPath(asset);
Debug.Log($"start download asset:{dllPath}");
UnityWebRequest www = UnityWebRequest.Get(dllPath);
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:{asset} size:{assetData.Length}");
s_assetDatas[asset] = assetData;
}
}
onDownloadComplete();
}
IEnumerator Download()
{
int downloadingMaxNum = 10;
int failedTryAgain = 3;
var package = YooAssets.GetPackage("DefaultPackage");
var downloader = package.CreateResourceDownloader(downloadingMaxNum, failedTryAgain);
//没有需要下载的资源
if (downloader.TotalDownloadCount == 0)
{
yield break;
}
//需要下载的文件总数和总大小
int totalDownloadCount = downloader.TotalDownloadCount;
long totalDownloadBytes = downloader.TotalDownloadBytes;
//注册回调方法
downloader.OnDownloadErrorCallback = OnDownloadErrorFunction;
downloader.OnDownloadProgressCallback = OnDownloadProgressUpdateFunction;
downloader.OnDownloadOverCallback = OnDownloadOverFunction;
downloader.OnStartDownloadFileCallback = OnStartDownloadFileFunction;
//开启下载
downloader.BeginDownload();
yield return downloader;
//检测下载结果
if (downloader.Status == EOperationStatus.Succeed)
{
//下载成功
Debug.Log("更新完成");
}
else
{
//下载失败
Debug.Log("更新失败");
}
}
/// <summary>
/// 开始下载
/// </summary>
/// <param name="fileName"></param>
/// <param name="sizeBytes"></param>
private void OnStartDownloadFileFunction(string fileName, long sizeBytes)
{
Debug.Log(string.Format("开始下载:文件名:{0},文件大小:{1}", fileName, sizeBytes));
}
/// <summary>
/// 下载完成
/// </summary>
/// <param name="isSucceed"></param>
private void OnDownloadOverFunction(bool isSucceed)
{
Debug.Log("下载" + (isSucceed ? "成功" : "失败"));
}
/// <summary>
/// 更新中
/// </summary>
/// <param name="totalDownloadCount"></param>
/// <param name="currentDownloadCount"></param>
/// <param name="totalDownloadBytes"></param>
/// <param name="currentDownloadBytes"></param>
private void OnDownloadProgressUpdateFunction(int totalDownloadCount, int currentDownloadCount, long totalDownloadBytes, long currentDownloadBytes)
{
Debug.Log(string.Format("文件总数:{0},已下载文件数:{1},下载总大小:{2},已下载大小{3}", totalDownloadCount, currentDownloadCount, totalDownloadBytes, currentDownloadBytes));
}
/// <summary>
/// 下载出错
/// </summary>
/// <param name="fileName"></param>
/// <param name="error"></param>
private void OnDownloadErrorFunction(string fileName, string error)
{
Debug.Log(string.Format("下载出错:文件名:{0},错误信息:{1}", fileName, error));
}
private string GetHostServerURL()
{
//string hostServerIP = "http://10.0.2.2"; //安卓模拟器地址
string hostServerIP = "http://127.0.0.1";
string appVersion = "v1.5";
//资源地址自行修改
#if UNITY_EDITOR
if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.Android)
return $"{hostServerIP}/CDN/Android/{appVersion}";
else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.iOS)
return $"{hostServerIP}/CDN/IPhone/{appVersion}";
else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.WebGL)
return $"{hostServerIP}/CDN/WebGL/{appVersion}";
else
return $"{hostServerIP}/CDN/PC/Package";
#else
if (Application.platform == RuntimePlatform.Android)
return $"{hostServerIP}/CDN/Android/{appVersion}";
else if (Application.platform == RuntimePlatform.IPhonePlayer)
return $"{hostServerIP}/CDN/IPhone/{appVersion}";
else if (Application.platform == RuntimePlatform.WebGLPlayer)
return $"{hostServerIP}/CDN/WebGL/{appVersion}";
else
return $"{hostServerIP}/CDN/PC/Package";
#endif
}
/// <summary>
/// 远端资源地址查询服务类
/// </summary>
private class RemoteServices : IRemoteServices
{
private readonly string _defaultHostServer;
private readonly string _fallbackHostServer;
public RemoteServices(string defaultHostServer, string fallbackHostServer)
{
_defaultHostServer = defaultHostServer;
_fallbackHostServer = fallbackHostServer;
}
string IRemoteServices.GetRemoteMainURL(string fileName)
{
return $"{_defaultHostServer}/{fileName}";
}
string IRemoteServices.GetRemoteFallbackURL(string fileName)
{
return $"{_fallbackHostServer}/{fileName}";
}
}
#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 = ReadBytesFromStreamingAssets(aotDllName);
// 加载assembly对应的dll,会自动为它hook。一旦aot泛型函数的native函数不存在,用解释器版本代码
LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, mode);
Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. mode:{mode} ret:{err}");
}
}
void StartGame()
{
LoadMetadataForAOTAssemblies();
#if !UNITY_EDITOR
_hotUpdateAss = Assembly.Load(ReadBytesFromStreamingAssets("HotUpdate.dll"));
#else
_hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
//Type entryType = _hotUpdateAss.GetType("Entry");
//entryType.GetMethod("Start").Invoke(null, null);
Run_InstantiateComponentByAsset();
}
private void Run_InstantiateComponentByAsset()
{
// 通过实例化assetbundle中的资源,还原资源上的热更新脚本
//AssetBundle ab = AssetBundle.LoadFromMemory(LoadDll.ReadBytesFromStreamingAssets("prefabs"));
var package = YooAssets.GetPackage("DefaultPackage");
//GameObject cube = ab.LoadAsset<GameObject>("Cube");
AssetOperationHandle handle = package.LoadAssetAsync<GameObject>("Cube");
handle.Completed += Handle_Completed;
//GameObject.Instantiate(cube);
}
private void Handle_Completed(AssetOperationHandle obj)
{
GameObject go = obj.InstantiateSync();
Debug.Log($"Prefab name is {go.name}");
}
/// <summary>
/// 默认的分发资源查询服务类
/// </summary>
private class DefaultDeliveryQueryServices : IDeliveryQueryServices
{
public DeliveryFileInfo GetDeliveryFileInfo(string packageName, string fileName)
{
throw new NotImplementedException();
}
public bool QueryDeliveryFiles(string packageName, string fileName)
{
return false;
}
}
}
//代码自行整理
//PS:版本不同可能有一些类名发生变化,请参照现阶段版本自行修改,官网可能更新不及时。
然后再场景里添加Cube游戏物体并再Cube上添加InstantiateByAsset或者自己写一个啥脚本都行。里面有start方法作为程序运行入口。
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
public class InstantiateByAsset : MonoBehaviour
{
public string text;
void Start()
{
Debug.Log($"[InstantiateByAsset] text:{text}, 首次");
}
}
然后将Cube放入刚才新建的Assets/AssetPackage/Prefabs下删除掉Hierarchy场景里的Cube,然后将Cube上的脚本拖入到文件夹HotUpdate中,所有和热更有关的代码都要放在这个文件加下。ps:类似于大家熟悉的xLua中的[hotfix]热更标签,只不过脱离了代码了
然后选中程序集HotUpdate,在上面添加这两个程序集
打包发布
然后返回unity界面,点击Build Settings先生成exe或者apk包程序。
之后返回unity界面。
第一步
第二步
然后会在StreamingAssets文件夹下生成一河滩之前LoadDll代码里的AOT相关的桥接文件。
将着一河滩.dll文件移动到Assets/AssetPackage/Codes文件夹中。
之后的操作大家都很熟悉了。
第一步
点击构建后,又生成了一河滩更多的东西。
将着一河滩东西,移动到之前的安装的资源服务器www文件夹下。打开刚才生成好的exe或apk程序。
完事!
测试
回到unity界面,在Assets/AssetPackage/Scenes中新建场景。
进入新场景,在里面生成UI/Button
在Assets/HotUpdate文件夹下创建Button_click代码。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Button_click : MonoBehaviour
{
private void Start()
{
GetComponent<Button>().onClick.AddListener(() => Debug.Log("老于爱玩亚索"));
}
}
将代码挂到Button上。
然后修改Assets/HotUpdate/InstantiateByAsset.cs
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
using YooAsset;
public class InstantiateByAsset : MonoBehaviour
{
public string text;
void Start()
{
Debug.Log($"[InstantiateByAsset] text:{text}, 第一次");
var package = YooAssets.GetPackage("DefaultPackage");
#region 异步加载场景
string location = "22";//场景名字
var sceneMode = UnityEngine.SceneManagement.LoadSceneMode.Single;
bool suspendLoad = false;
SceneOperationHandle handle = package.LoadSceneAsync(location, sceneMode, suspendLoad);
handle.Completed += o =>
{
//回调逻辑
};
#endregion
}
}
把Console To Screen脚本挂到Main Camera上。
然后重新HybridCLR选项的两步和YooAsset构建。文章来源:https://www.toymoban.com/news/detail-736755.html
- HybridCLR生成一河滩文件后,先将原来的Assets/AssetPackage/Codes下的文件删除,再把新生成的移动到Assets/AssetPackage/Codes下。
- 再用YooAssset构建,可以选择增量
然后将原来的资源服务器上的热更文件删除,复制新的文件上去。
打开之前的exe程序或者apk程序。
场景跳转成功!
至此Unity热更教程结束,感觉确实比XLua、ULua更人性化有没有。文章来源地址https://www.toymoban.com/news/detail-736755.html
到了这里,关于Unity划时代热更方案 YooAsset+HybridCLR(wolong)(原huatuo)(六)完结篇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!