找回密码
 立即注册
查看: 295|回复: 0

Unity xlua 热更

[复制链接]
发表于 2023-4-9 11:48 | 显示全部楼层 |阅读模式
1、先打包AB包,并加密

    对AB包加密
创建加密相关脚本,这里使用 AES 加密对AB包资源进行加密,脚本如下
  1. using System.IO;using System.Text;using UnityEditor;using UnityEngine;publicclassABPackMenu:Editor{[MenuItem("My Tool/AB包加密/创建AB包版本文件/Window 版本")]privatestaticvoidEncrypABPackVersionFile_Window(){
  2.         Debug.Log("加密并创建 Window 平台的AB包版本信息");EncryptAndCreateVersionFile(BuildTarget.StandaloneWindows);}[MenuItem("My Tool/AB包加密/解密AB包版本文件/Window 版本")]privatestaticvoidDecryptABPack_Window(){
  3.         Debug.Log("解密 Window 平台的AB包版本信息");DecryptVersionFile(BuildTarget.StandaloneWindows);}[MenuItem("My Tool/AB包加密/创建AB包版本文件/Android 版本")]privatestaticvoidCreateABPackVersionFile_Android(){
  4.         Debug.Log("加密并创建 Android 平台的AB包版本信息");EncryptAndCreateVersionFile(BuildTarget.Android);}[MenuItem("My Tool/AB包加密/解密AB包版本文件/Android 版本")]privatestaticvoidDecryptABPack_Android(){
  5.         Debug.Log("解密 Android 平台的AB包版本信息");DecryptVersionFile(BuildTarget.Android);}[MenuItem("My Tool/AB包加密/创建AB包版本文件/IOS 版本")]privatestaticvoidCreateABPackVersionFile_IOS(){
  6.         Debug.Log("加密并创建 IOS 平台的AB包版本信息");DecryptVersionFile(BuildTarget.iOS);}[MenuItem("My Tool/AB包加密/解密AB包版本文件/IOS 版本")]privatestaticvoidDecryptABPack_IOS(){
  7.         Debug.Log("解密 IOS 平台的AB包版本信息");DecryptVersionFile(BuildTarget.iOS);}privatestaticvoidEncryptAndCreateVersionFile(BuildTarget e_buildTarget){/// ABPack版本信息保存路径
  8.         Debug.Log("平台是: "+ e_buildTarget.ToString());/// 加密文件存放路径string sBasePath = Application.dataPath +@"/../AssetBundlesEncrypt/"+ e_buildTarget.ToString()+@"/";if(!Directory.Exists(sBasePath)){
  9.             Directory.CreateDirectory(sBasePath);}StringBuilder obj_sb =newStringBuilder();string sAllABPath = Application.dataPath +@"/../AssetBundles/"+ e_buildTarget.ToString();DirectoryInfo obj_folder =newDirectoryInfo(sAllABPath);// 获取输出路径的文件夹管理器
  10.         FileInfo[] arr_allFiles = obj_folder.GetFiles("*", SearchOption.AllDirectories);// 取得所有文件foreach(FileInfo obj_item in arr_allFiles){string sFilePath = obj_item.FullName;// 获取文件全名(包含路径 C:/ D:/ 全路径)string sFileName = obj_item.Name;// //Debug.Log("AB包 全路径 >>>>> " + sFilePath);string sExName = sFilePath.Substring(sFilePath.LastIndexOf(".")+1);// 得到后缀名// 加密后的AB包存放路径string sEncryptABOutPath = sBasePath + sFileName;if(ABPackUtils.ABPackExName.IndexOf(sExName)>-1)// 匹配AB包的后缀名,取得对应的AB包文件{
  11.                 AESEncryptMgr.AESEncryptFile(sFilePath, sEncryptABOutPath);}else{// 不用加密的文件,拷贝的加密后的对应目录中bool bIsReWrite =true;// true=覆盖已存在的同名文件, false 则反之
  12.                 System.IO.File.Copy(sFilePath, sEncryptABOutPath, bIsReWrite);}string sABName = sFilePath.Substring(sFilePath.IndexOf("AssetBundles"));string sFileVersion = MD5Mgr.GetABPackEncryptVersion(sEncryptABOutPath);//Debug.Log("加密AB包的版本字符串是 >>>>> " + sFileVersion);if(sFileVersion ==null){
  13.                 Debug.LogError("有文件没有拿到MD5:"+ sABName);continue;}string sFileSize = Mathf.Ceil(obj_item.Length /1024f).ToString();//文件大小
  14.             sABName = sABName.Replace("\","/");string sABVersionStr = ABPackUtils.GetABPackVersionStr(sABName, sFileVersion, sFileSize);//每个文件的版本信息
  15.             obj_sb.AppendLine(sABVersionStr);// 写入版本文件要构建的内容中,按行写入}string sABVersionFile = sBasePath + ABPackUtils.sABVersionName;//Debug.Log("AB包版本信息文件保存路径是 >>> " + sABVersionFile);// 判断是否存在AB包的版本文件信息,存在则删除
  16.         IOUtils.CreatTextFile(sABVersionFile, obj_sb.ToString());}/// <summary>/// 解密AB包文件/// </summary>/// <param name="e_buildTarget"></param>privatestaticvoidDecryptVersionFile(BuildTarget e_buildTarget){/// ABPack版本信息保存路径
  17.         Debug.Log("平台是: "+ e_buildTarget.ToString());string sAllABFile = Application.dataPath +@"/../AssetBundlesEncrypt/"+ e_buildTarget.ToString();
  18.         Debug.Log("加密版本的AB包文件路径是 >>> "+ sAllABFile);DirectoryInfo obj_folder =newDirectoryInfo(sAllABFile);// 获取输出路径的文件夹管理器
  19.         FileInfo[] arr_allFiles = obj_folder.GetFiles("*", SearchOption.AllDirectories);// 取得所有文件/// 解密文件存放路径string sBasePath = Application.dataPath +@"/../AssetBundlesDecrypt/"+ e_buildTarget.ToString()+@"/";if(!Directory.Exists(sBasePath)){
  20.             Directory.CreateDirectory(sBasePath);}foreach(FileInfo obj_item in arr_allFiles){string sFilePath = obj_item.FullName;// 获取文件全名(包含路径 C:/ D:/ 全路径)
  21.             Debug.Log("AB包 全路径 >>>>> "+ sFilePath);string sExName = sFilePath.Substring(sFilePath.LastIndexOf(".")+1);//得到后缀名// 加密后的AB包存放路径string sDecryptABOutPath = sBasePath + obj_item.Name;if(ABPackUtils.ABPackExName.IndexOf(sExName)>-1)// 匹配AB包的后缀名,取得对应的AB包文件{// 加密后的AB包存放路径
  22.                 AESEncryptMgr.AESDecryptFile(sFilePath, sDecryptABOutPath);}else{// 不用加密的文件,拷贝的加密后的对应目录中bool bIsReWrite =true;// true=覆盖已存在的同名文件, false 则反之
  23.                 System.IO.File.Copy(sFilePath, sDecryptABOutPath, bIsReWrite);}}}}
复制代码
AES 加密:其中 SingletonBase 只是实现单例的基类,提供对文件加密解密的工具类
  1. using System;using System.IO;using System.Security.Cryptography;using UnityEngine;publicclassAESEncryptMgr:SingletonBase<AESEncryptMgr>{/// <summary>/// 缓存加密使用的加密相关数据对象/// </summary>privatestaticAesCryptoServiceProvider _obj_AesCSP;/// <summary>/// 加密使用的 Key/// </summary>privateconststring _sKey ="hycai12365479807";/// <summary>/// 加密使用的 IV/// </summary>privateconststring _sIV ="987465132hycai07";/// <summary>/// 加密文件/// </summary>/// <param name="sFilePath"></param>/// <param name="sOutSavePath"></param>/// <param name="sKey"></param>/// <param name="sIV"></param>/// <returns></returns>publicstaticintAESEncryptFile(string sFilePath,string sOutSavePath,string sKey = _sKey,string sIV = _sIV){if(!File.Exists(sFilePath)){
  2.             Debug.Log("Error: 不存在AES加密的文件,sFilePath = null !!!");return-1;}int nKeyCount = sKey.Length;int nIvCount = sIV.Length;if(nKeyCount <7|| nKeyCount >16|| nIvCount <7|| nIvCount >16){
  3.             Debug.Log("AES错误,秘钥sKey与sIV偏移量长度必须是8到16位");return-1;}AesCryptoServiceProvider obj_aesCSP =CreateAES_CSP(sKey, sIV);ICryptoTransform obj_trans = obj_aesCSP.CreateEncryptor();if(AESFileCoding(sFilePath, sOutSavePath, obj_trans)==-1){return-1;}return1;}/// <summary>/// 解密文件/// </summary>/// <param name="sFilePath"></param>/// <param name="sOutSavePath"></param>/// <param name="sKey"></param>/// <param name="sIV"></param>/// <returns></returns>publicstaticintAESDecryptFile(string sFilePath,string sOutSavePath,string sKey = _sKey,string sIV = _sIV){if(!File.Exists(sFilePath)){
  4.             Debug.Log("Error: 不存在AES解密的文件,sFilePath = null !!!");return-1;}int nKeyCount = sKey.Length;int nIvCount = sIV.Length;if(nKeyCount <7|| nKeyCount >16|| nIvCount <7|| nIvCount >16){
  5.             Debug.Log("AES错误,秘钥sKey与sIV偏移量长度必须是8到16位");return-1;}AesCryptoServiceProvider obj_aesCSP =CreateAES_CSP(sKey, sIV);ICryptoTransform obj_trans = obj_aesCSP.CreateDecryptor();if(AESFileCoding(sFilePath, sOutSavePath, obj_trans)==-1){return-1;}return1;}/// <summary>/// 解密文件为Stream数据流/// </summary>/// <param name="sFilePath"></param>/// <param name="sOutSavePath"></param>/// <param name="sKey"></param>/// <param name="sIV"></param>/// <returns></returns>publicstaticbyte[]AESDecryptFileToStream(string sFilePath,string sKey = _sKey,string sIV = _sIV){if(!File.Exists(sFilePath)){
  6.             Debug.Log("Error: 不存在AES解密的文件,sFilePath = null !!!");returnnull;}int nKeyCount = sKey.Length;int nIvCount = sIV.Length;if(nKeyCount <7|| nKeyCount >16|| nIvCount <7|| nIvCount >16){
  7.             Debug.Log("AES错误,秘钥sKey与sIV偏移量长度必须是8到16位");returnnull;}AesCryptoServiceProvider obj_aesCSP =CreateAES_CSP(sKey, sIV);ICryptoTransform obj_trans = obj_aesCSP.CreateDecryptor();returnAESFileCodingToStream(sFilePath, obj_trans);}/// <summary>/// 根据传入的 key 与 算法的向量 IV 创建 AES 的数据对象/// </summary>/// <param name="sKey"></param>/// <param name="sIV"></param>/// <returns></returns>publicstaticAesCryptoServiceProviderCreateAES_CSP(string sKey,string sIV){if(_obj_AesCSP !=null){return _obj_AesCSP;}byte[] arr_keySource = System.Text.Encoding.ASCII.GetBytes(sKey);byte[] arr_ivSource = System.Text.Encoding.ASCII.GetBytes(sIV);int nKeySounceLen = arr_keySource.Length;int nIVSounceLen = arr_ivSource.Length;byte[] arr_key =newbyte[16];byte[] arr_iv =newbyte[16];int nKeyTargetLen = arr_key.Length;int nIVTargetLen = arr_iv.Length;// 确保加密Key与IV是在16 byte 内
  8.         nKeyTargetLen = nKeySounceLen > nKeyTargetLen ? nKeyTargetLen : nKeySounceLen;
  9.         nIVTargetLen = nIVSounceLen > nIVTargetLen ? nIVTargetLen : nIVSounceLen;
  10.         System.Array.Copy(arr_keySource, arr_key, nKeyTargetLen);
  11.         System.Array.Copy(arr_ivSource, arr_iv, nIVTargetLen);//string sShowKey = System.Text.Encoding.Default.GetString(arr_key);//Debug.Log("输出 >>>> sShowKey >>>>> " + sShowKey);//string sShowIV = System.Text.Encoding.Default.GetString(arr_iv);//Debug.Log("输出 >>>> sShowIV >>>>>> " + sShowIV);// 创建对称 AES 加密的数据模式对象
  12.         _obj_AesCSP =newAesCryptoServiceProvider(){
  13.             Mode = CipherMode.CBC,// 设置对称算法的运算模式
  14.             Padding = PaddingMode.PKCS7,// 设置对称算法中使用的填充模式
  15.             KeySize =128,// 设置密钥的大小(以位为单位)
  16.             BlockSize =128,// 设置加密操作的块大小(以位为单位)
  17.             Key = arr_key,// 设置用于加密和解密的对称密钥
  18.             IV = arr_iv,// 设置对称算法的初始化向量 (IV)};return _obj_AesCSP;}/// <summary>/// 将加密解密文件转换并保存到 sOutSavePath 路径下/// </summary>/// <param name="sFilePath"></param>/// <param name="sOutSavePath"></param>/// <param name="obj_trans"></param>/// <returns></returns>privatestaticintAESFileCoding(string sFilePath,string sOutSavePath,ICryptoTransform obj_trans){try{FileStream obj_fileStream;// 创建文件流byte[] arr_input =null;using(MemoryStream obj_memory =newMemoryStream()){// 创建加密解密流,从using(CryptoStream obj_cryptoStream =newCryptoStream(obj_memory, obj_trans, CryptoStreamMode.Write)){
  19.                     obj_fileStream = File.OpenRead(sFilePath);// 从文件中读取数据到文件流中// 将文件流转换成2进制数据using(BinaryReader obj_binaryReader =newBinaryReader(obj_fileStream)){
  20.                         arr_input =newbyte[obj_fileStream.Length];
  21.                         obj_binaryReader.Read(arr_input,0, arr_input.Length);}// 解密操作
  22.                     obj_cryptoStream.Write(arr_input,0, arr_input.Length);// 释放解密操作
  23.                     obj_cryptoStream.FlushFinalBlock();// 写入到保存文件中using(obj_fileStream = File.OpenWrite(sOutSavePath)){
  24.                         obj_memory.WriteTo(obj_fileStream);}}}
  25.             Debug.Log("AES文件转换成功...");return1;}catch(Exception obj_ex){
  26.             Debug.Log("Error AES 加密失败  "+ obj_ex.Message);return-1;}}/// <summary>/// 将加密解密文件转换并保存到 sOutSavePath 路径下/// </summary>/// <param name="sFilePath"></param>/// <param name="sOutSavePath"></param>/// <param name="obj_trans"></param>/// <returns></returns>privatestaticbyte[]AESFileCodingToStream(string sFilePath,ICryptoTransform obj_trans){try{FileStream obj_fileStream;// 创建文件流byte[] arr_input =null;byte[] arr_data =null;using(MemoryStream obj_memory =newMemoryStream()){// 创建加密解密流,从文件中获取解密到其中using(CryptoStream obj_cryptoStream =newCryptoStream(obj_memory, obj_trans, CryptoStreamMode.Write)){
  27.                     obj_fileStream = File.OpenRead(sFilePath);// 从文件中读取数据到文件流中// 将文件流转换成2进制数据using(BinaryReader obj_binaryReader =newBinaryReader(obj_fileStream)){
  28.                         arr_input =newbyte[obj_fileStream.Length];
  29.                         obj_binaryReader.Read(arr_input,0, arr_input.Length);}
  30.                     obj_cryptoStream.Write(arr_input,0, arr_input.Length);// 释放解密操作
  31.                     obj_cryptoStream.FlushFinalBlock();//obj_memory.WriteTo(obj_fileStream);
  32.                     arr_data = obj_memory.ToArray();}
  33.                 Debug.Log("AES文件解密到Stream完成...");return arr_data;}}catch(Exception obj_ex){
  34.             Debug.Log("Error AES 加密失败  "+ obj_ex.Message);returnnull;}}}
复制代码
    ABPack相关抽取的工具类方法
  1. using UnityEngine;/// <summary>/// AB包相关处理的工具/// </summary>publicstaticclassABPackUtils{/// <summary>/// AB包的后缀扩展名/// </summary>privatestaticstring _sABPackExName =".ab";publicstaticstring ABPackExName {get{return _sABPackExName;}}/// <summary>/// 缓存AB包版本信息的文件名/// </summary>privatestaticstring _sABVersionName ="ABVersionFile.txt";publicstaticstring sABVersionName {get{return _sABVersionName;}}/// <summary>/// 获取AB包的版本信息字符串/// </summary>/// <param name="sABName">包名(含AssetBundle路径)</param>/// <param name="sFileVersionMd5">版本信息的MD5值</param>/// <param name="nFileSize">文件大小</param>/// <returns></returns>publicstaticstringGetABPackVersionStr(string sABName,string sFileVersionMd5,string sFileSize){returnstring.Format("{0} {1} {2}", sABName, sFileVersionMd5, sFileSize);}/// <summary>/// 获取不同平台AB包存放路径的字符串/// </summary>/// <returns></returns>publicstaticstringGetABPackPathPlatformStr(){RuntimePlatform obj_platform = Application.platform;string sPlatformStr ="/AssetBundles/";if(obj_platform == RuntimePlatform.WindowsEditor || obj_platform == RuntimePlatform.WindowsPlayer){
  2.             sPlatformStr +="StandaloneWindows/";}elseif(obj_platform == RuntimePlatform.Android){
  3.             sPlatformStr +="Android/";}elseif(obj_platform == RuntimePlatform.IPhonePlayer){
  4.             sPlatformStr +="iOS/";}return sPlatformStr;}}
复制代码
    IO工具脚本
  1. using System.IO;publicclassIOUtils{/// <summary>/// 创建txt文件的方法/// </summary>/// <param name="sFilePath"></param>/// <param name="sContent"></param>publicstaticvoidCreatTextFile(string sFilePath,string sContent){//文件存在则删除if(File.Exists(sFilePath)){
  2.             File.Delete(sFilePath);}using(FileStream obj_versionStream = File.Create(sFilePath)){using(StreamWriter obj_writer =newStreamWriter(obj_versionStream)){
  3.                 obj_writer.WriteLine(sContent);}}}/// <summary>/// 根据文件路径,创建其文件的文件夹路径/// </summary>/// <param name="sFilePath"></param>publicstaticvoidCreateDirectroryOfFile(string sFilePath){//Debug.Log($"根据文件创建对应的文件夹路径 文件 >>>> {sFilePath}");if(!string.IsNullOrEmpty(sFilePath)){string sDirName = Path.GetDirectoryName(sFilePath);if(!Directory.Exists(sDirName)){//Debug.Log($"不存在路径 {sDirName},");
  4.                 Directory.CreateDirectory(sDirName);}//else//{//    Debug.Log($"已存在路径 >>>> {sDirName},");//}}}}
复制代码
    打包 AB 包
    打包AB包,请看另外一篇文章 AssetBundle - 工具与打包操作 https://blog.csdn.net/hycai_007/article/details/121380465
测试AB包,自行选择资源


如果说后缀名不为"*.ab",注:记得修改 ABPackUtils 的对应属性


AB包生产成功后会在对应项目的文件夹下生成 AssetBundles 文件




然后对生成的AssetBundles进行加密,点击对对应平台的AB包进行加密


创建成功后会在Console窗口有对应日志输出,并在项目文件夹中生成 AssetBundlesEncrypt 文件夹,其内部就是AB包加密后的文件






2、搭建本地AB下载服务器

从项目的 NetBox Server 文件夹中取得安装包进行安装,安装后,创建我们自己的挂载网址


在任意创建文件夹,在文件夹中创建文件 main.box,并写入如下内容



在当前文件夹中创建上述图中的 “Web” 路径,在其添加 AssetBundles 存放路径与 index.asp 的测试网页



index.asp 文件内容则如下
<%="TEST #@@#@#@#@#@ V@#@$@@ SSSSS GGGGG "%>
    测试环境是否搭建成功


    其电脑右下角就会出现对应icon



3、搭建客户端热更框架

    热更新控制器:从服务端获取 ABVersionFile.txt 的文件数据与前端缓存的 ABVersionFile.txt 数据进行比对,确定所需更新的AB包资源,创建 _nMaxDownloader 最大数量的下载器进行下载AB包,直至所有资源下载完成。进入下一阶段 HotUpdateEnd 方法
  1. using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.Networking;using System.IO;using System.Text;publicclassHotUpdateMgr:MonoSingletonBase<HotUpdateMgr>{/// <summary>/// _sBaseUrl下载网址/// </summary>#if UNITY_EDITOR || UNITY_STANDALONE_WINpublicstaticstring _sBaseUrl ="http://127.0.0.1:5858";#elif UNITY_ANDROIDpublicstaticstring _sBaseUrl ="http://192.168.255.10:5858";#elif UNITY_IPHONEpublicstaticstring _sBaseUrl ="http://192.168.255.10:5858";#endifprivatestring _sABVersionName ="";/// <summary>/// 本地版本信息缓存路径/// </summary>privatestring _sVersionLocalFilePath ="";/// <summary>/// 同时下载的最大数量/// </summary>privateint _nMaxDownloader =5;/// <summary>/// 当前需要下载的AB包数据/// </summary>
  2.     List<ABPackInfo> _list_allNeedABPack =newList<ABPackInfo>();/// <summary>/// 所需下载资源总大小/// </summary>privatefloat _nDownloadTotalSize =0;/// <summary>/// 当前已下载资源的大小/// </summary>privatefloat _nCurDownloadedSize =0;/// <summary>/// AB包下载器/// </summary>private List<ABDownloader> _list_allABDownloader =newList<ABDownloader>();/// <summary>/// 客户端的AB版本数据/// </summary>private Dictionary<string, ABPackInfo> _dict_clientABInfoList =null;protectedoverridevoidAwake(){string sPlatformStr = ABPackUtils.GetABPackPathPlatformStr();
  3.         _sABVersionName = sPlatformStr + ABPackUtils.sABVersionName;
  4.         _sVersionLocalFilePath = Application.persistentDataPath + _sABVersionName;
  5.         IOUtils.CreateDirectroryOfFile(_sVersionLocalFilePath);}/// <summary>/// 开始热更/// </summary>publicvoidStartHotUpdate(){
  6.         Debug.Log("开始热更 >>>>>> ");StartCoroutine(DownloadAllABPackVersion());}/// <summary>/// 解析版本文件,返回一个文件列表/// </summary>/// <param name="sContent"></param>/// <returns></returns>public Dictionary<string, ABPackInfo>ConvertToAllABPackDesc(string sContent){
  7.         Dictionary<string, ABPackInfo> dict_allABPackDesc =newDictionary<string,ABPackInfo>();string[] arrLines = sContent.Split('\n');//用回车 字符 \n 分割每一行foreach(string item in arrLines){string[] arrData = item.Split(' ');//用空格分割每行数据的三个类型if(arrData.Length ==3){ABPackInfo obj_ABPackData =newABPackInfo();
  8.                 obj_ABPackData.sABName = arrData[0];// 名称即路径
  9.                 obj_ABPackData.sMd5 = arrData[1];// md5值
  10.                 obj_ABPackData.nSize =int.Parse(arrData[2]);// AB包大小//Debug.Log(string.Format("解析的路径:{0}\n解析的MD5:{1}\n解析的文件大小KB:{2}", obj_ABPackData.sABName, obj_ABPackData.sMd5, obj_ABPackData.nSize));
  11.                 dict_allABPackDesc.Add(obj_ABPackData.sABName, obj_ABPackData);}}return dict_allABPackDesc;}/// <summary>/// 获取服务端的AB包版本信息/// </summary>/// <returns></returns>IEnumeratorDownloadAllABPackVersion(){string sVersionUrl = _sBaseUrl +@"/"+ _sABVersionName;//Debug.Log("下载版本数据路径:" + sVersionUrl);using(UnityWebRequest uObj_versionWeb = UnityWebRequest.Get(sVersionUrl)){yieldreturn uObj_versionWeb.SendWebRequest();// 等待资源下载if(uObj_versionWeb.isNetworkError || uObj_versionWeb.isHttpError){
  12.                 Debug.LogError("获取版本AB包数据错误: "+ uObj_versionWeb.error);yieldbreak;}else{string sVersionData = uObj_versionWeb.downloadHandler.text;//Debug.Log("成功获取到版本相关数据 >>>> \n" + sVersionData);CheckNeedDownloadABPack(sVersionData);}}}/// <summary>/// 检测需要下载/// </summary>/// <param name="sServerVersionData"></param>voidCheckNeedDownloadABPack(string sServerVersionData){//Debug.Log("运行平台:" + Application.platform);//Debug.Log("本地版本文件路径是:" + _sVersionLocalFilePath);
  13.         Dictionary<string, ABPackInfo> dict_serverDownList =ConvertToAllABPackDesc(sServerVersionData);// 服务端获取的资源下载列表if(File.Exists(_sVersionLocalFilePath)){//Debug.Log("存在本地,对比服务器版本信息");string sClientVersionData = File.ReadAllText(_sVersionLocalFilePath);// 本地版本信息
  14.             _dict_clientABInfoList =ConvertToAllABPackDesc(sClientVersionData);// 客户端本地缓存的资源下载列表//遍历服务器文件foreach(ABPackInfo obj_itemData in dict_serverDownList.Values){// 存在对应已下载文件,对比Md5值是否一致if(_dict_clientABInfoList.ContainsKey(obj_itemData.sABName)){// md5值不一致,则更新文件if(_dict_clientABInfoList[obj_itemData.sABName].sMd5 != obj_itemData.sMd5){
  15.                         _list_allNeedABPack.Add(obj_itemData);
  16.                         _nDownloadTotalSize = _nDownloadTotalSize + obj_itemData.nSize;//Debug.Log("MD5 值不一样,资源存在变更,增加文件 >>>>> " + obj_itemData.sABName);}}else{
  17.                     _list_allNeedABPack.Add(obj_itemData);
  18.                     _nDownloadTotalSize = _nDownloadTotalSize + obj_itemData.nSize;}}}else// 如果说不存在本地缓存,那就直接下载所有的AB包{foreach(ABPackInfo obj_itemData in dict_serverDownList.Values){
  19.                 _list_allNeedABPack.Add(obj_itemData);
  20.                 _nDownloadTotalSize = _nDownloadTotalSize + obj_itemData.nSize;//Debug.Log("所需下载文件 >>>>> " + obj_itemData.sABName);}}StartDownloadAllABPack();}/// <summary>/// 开始下载所有所需下载的AB包资源/// </summary>/// <param name="list_allABPack"></param>voidStartDownloadAllABPack(){int nMaxCount = _list_allNeedABPack.Count;if(nMaxCount <=0){HotUpdateEnd();return;}int nNeedCount = Mathf.Min(nMaxCount, _nMaxDownloader);for(int i =0; i < nNeedCount; i++){ABPackInfo obj_ABPackDesc = _list_allNeedABPack[0];ABDownloader obj_downloader =newABDownloader();
  21.             _list_allABDownloader.Add(obj_downloader);StartCoroutine(obj_downloader.DownloadABPack(obj_ABPackDesc));
  22.             _list_allNeedABPack.RemoveAt(0);}}/// <summary>/// 切换下载下一个AB包/// </summary>/// <param name="obj_ABDownloader">需要切换的下载器</param>publicvoidChangeDownloadNextABPack(ABDownloader obj_ABDownloader){//Debug.Log("切换下载下一个 AB 包");
  23.         _nCurDownloadedSize += obj_ABDownloader.GetDownloadResSize();if(_list_allNeedABPack.Count >0)// 还存在需要下载的资源,下载器切换资源,继续下载{StartCoroutine(obj_ABDownloader.DownloadABPack(_list_allNeedABPack[0]));
  24.             _list_allNeedABPack.RemoveAt(0);}else{bool bIsDownloadSuc =true;// 资源是否全部下载完成foreach(ABDownloader obj_downloader in _list_allABDownloader){if(obj_downloader.bIsDownloading)// 存在一个下载中,即表示当前还有未下载完成的部分{
  25.                     bIsDownloadSuc =false;break;}}if(bIsDownloadSuc)// 已完成全部下载{HotUpdateEnd();}}}/// <summary>/// 更新本地缓存的AB包版本数据/// </summary>/// <param name="obj_ABPackDecs"></param>publicvoidUpdateClientABInfo(ABPackInfo obj_ABPackDecs){if(_dict_clientABInfoList ==null){
  26.             _dict_clientABInfoList =newDictionary<string,ABPackInfo>();}
  27.         _dict_clientABInfoList[obj_ABPackDecs.sABName]= obj_ABPackDecs;StringBuilder obj_sb =newStringBuilder();foreach(ABPackInfo obj_temp in _dict_clientABInfoList.Values){
  28.             obj_sb.AppendLine(ABPackUtils.GetABPackVersionStr(obj_temp.sABName, obj_temp.sMd5, obj_temp.nSize.ToString()));}
  29.         IOUtils.CreatTextFile(_sVersionLocalFilePath, obj_sb.ToString());}/// <summary>/// 热更新结束,进入下一个阶段/// </summary>privatevoidHotUpdateEnd(){// TODO 进入下一个阶段
  30.         Debug.Log("热更新: 已完成所有的AB包下载, 进入下一个阶段 TODO");
  31.         HotUpdateTest.GetInstance().RunLua();
  32.         HotUpdateTest.GetInstance().InitShow();}}
复制代码
-AB包资源下载器 : AB包下载器,进行AB包资源下载,然后对AB包进行保存在本地,将下载完成的版本信息写入到本地的 ABVersionFile.txt 文件中记录
  1. using System.Collections;using System.IO;using UnityEngine;using UnityEngine.Networking;/// <summary>/// AB包下载器/// </summary>publicclassABDownloader{/// <summary>/// 当前下载器下载的AB包数据/// </summary>privateABPackInfo _obj_ABDecs;/// <summary>/// 是否资源下载中/// </summary>privatebool _bIsDownloading =false;publicbool bIsDownloading {get=> _bIsDownloading;}/// <summary>/// 获取当前下载资源的大小/// </summary>/// <returns></returns>publicfloatGetDownloadResSize(){if(_obj_ABDecs !=null){return _obj_ABDecs.nSize;}return0;}/// <summary>/// 下载AB包/// </summary>/// <param name="obj_ABDecs">AB包资源的说明,包含文件名,大小,md5值</param>/// <returns></returns>publicIEnumeratorDownloadABPack(ABPackInfo obj_ABDecs){
  2.         _bIsDownloading =true;string sDownloadUrl = HotUpdateMgr._sBaseUrl +@"/"+ obj_ABDecs.sABName;
  3.         Debug.Log("下载资源:"+ sDownloadUrl);UnityWebRequest uObj_web = UnityWebRequest.Get(sDownloadUrl);yieldreturn uObj_web.SendWebRequest();if(uObj_web.isNetworkError || uObj_web.isHttpError){
  4.             Debug.Log("获取AB包 "+ sDownloadUrl +" 错误: "+ uObj_web.error);yieldbreak;}else{string sABPath = Application.persistentDataPath +@"/"+ obj_ABDecs.sABName;
  5.             Debug.Log("AB包 保存本地路径是:"+ sABPath);
  6.             IOUtils.CreateDirectroryOfFile(sABPath);if(!File.Exists(sABPath)){
  7.                 File.Create(sABPath).Dispose();}
  8.             File.WriteAllBytes(sABPath, uObj_web.downloadHandler.data);// 下载完成后,更新本地版本数据
  9.             HotUpdateMgr.GetInstance().UpdateClientABInfo(obj_ABDecs);
  10.             _bIsDownloading =false;
  11.             HotUpdateMgr.GetInstance().ChangeDownloadNextABPack(this);}}}
复制代码
    AB包解压加载资源管理器 :从AB包中取得资源使用,注:_bIsEncrypt 属性,可控制是否使用是读取加密的AssetBundler的
  1. using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.Events;publicclassAssetBundleMgr:MonoSingletonBase<AssetBundleMgr>{/// <summary>/// 是否使用本地文件/// </summary>publicbool _bIsUseLocalFile =false;/// <summary>/// 是否使用加密/// </summary>publicbool _bIsEncrypt =true;/// <summary>/// AB包的主包对象/// </summary>privateAssetBundle _obj_mainABPack =null;/// <summary>/// AB包主包的相关依赖信息对象/// </summary>privateAssetBundleManifest _obj_mainManifest =null;/// <summary>/// AB包不能重复加载,重复加载会报错/// 用字典的方式来储存,已经加载过的AB包/// </summary>/// <typeparam name="string"> AB包路径 </typeparam>/// <typeparam name="AssetBundle"> AB包对象 </typeparam>/// <returns></returns>private Dictionary<string, AssetBundle> _dict_ABObj =newDictionary<string,AssetBundle>();/// <summary>/// AB包存放路径/// </summary>/// <value></value>privatestring _sPathUrl;/// <summary>/// 存放AB包的主文件夹,主包名 /// </summary>/// <value></value>privatestring _sMainABName {get{#if UNITY_IOSreturn"IOS";#elif UNITY_ANDROIDreturn"Android";#elsereturn"StandaloneWindows";#endif}}privatevoidAwake(){string sPlatformStr = ABPackUtils.GetABPackPathPlatformStr();if(_bIsUseLocalFile)// 是否从本地中读取{
  2.             _sPathUrl = Application.streamingAssetsPath + sPlatformStr;}else{
  3.             _sPathUrl = Application.persistentDataPath + sPlatformStr;}}/// <summary>/// 加载AB包/// </summary>/// <param name="sABName"></param>/// <returns></returns>privateAssetBundleLoadPack(string sABName){bool bIsEncryptFile =false;if(sABName.Contains(ABPackUtils.ABPackExName)){
  4.             bIsEncryptFile =true;}if(_bIsEncrypt && bIsEncryptFile){//Debug.Log("加载AB包文件路径 To Stream >>>> " + _sPathUrl + sABName);byte[] arr_abData = AESEncryptMgr.AESDecryptFileToStream(_sPathUrl + sABName);if(arr_abData !=null){//Debug.Log("AB包解密, 加载流 成功 >>>>>>");return AssetBundle.LoadFromMemory(arr_abData);}
  5.             Debug.LogError("Error AB包解密, 加载流 失败 >>>>>>");returnnull;}else{return AssetBundle.LoadFromFile(_sPathUrl + sABName);}}#region 同时加载AB包与资源/// <summary>/// 加载某个AB包/// </summary>/// <param name="sABName"></param>/// <returns></returns>publicboolLoadABPack(string sABName){if(_obj_mainABPack ==null){
  6.             _obj_mainABPack =LoadPack(_sMainABName);if(_obj_mainABPack ==null)returnfalse;
  7.             _obj_mainManifest = _obj_mainABPack.LoadAsset<AssetBundleManifest>("AssetBundleManifest");}// 加载sABName的所有依赖包AssetBundle obj_relyOnAB =null;string[] arr_sManifest = _obj_mainManifest.GetAllDependencies(sABName);for(int i =0; i < arr_sManifest.Length; i++){// 未加载过的才进行加载,已加载的AB包,不能重复加载if(!_dict_ABObj.ContainsKey(arr_sManifest[i])){
  8.                 obj_relyOnAB =LoadPack(arr_sManifest[i]);if(obj_relyOnAB ==null)returnfalse;
  9.                 _dict_ABObj.Add(arr_sManifest[i], obj_relyOnAB);}}AssetBundle obj_curLoadAB =null;if(!_dict_ABObj.ContainsKey(sABName)){
  10.             obj_curLoadAB =LoadPack(sABName);if(obj_curLoadAB ==null)returnfalse;
  11.             _dict_ABObj.Add(sABName, obj_curLoadAB);}returntrue;}/// <summary>/// 同步加载AB包中的资源/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <returns>返回资源对象,GameObject则是实例后的对象,资源一类的则直接返回,注:错误时返回null</returns>publicObjectLoadABPackRes(string sABName,string sResName){bool bIsLoadSuc =LoadABPack(sABName);if(!bIsLoadSuc)returnnull;if(_dict_ABObj.ContainsKey(sABName)){Object uObj_cur = _dict_ABObj[sABName].LoadAsset(sResName);if(uObj_cur is GameObject){returnInstantiate(uObj_cur);}else{return uObj_cur;}}returnnull;}/// <summary>/// 同步加载AB包中的资源, 重载增加转换的参数类型/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="eToType">加载后的资源转换成的资源类型</param>/// <returns>返回资源对象,GameObject则是实例后的对象,资源一类的则直接返回,注:错误时返回null</returns>publicObjectLoadABPackRes(string sABName,string sResName,System.Type eToType){bool bIsLoadSuc =LoadABPack(sABName);if(!bIsLoadSuc)returnnull;if(_dict_ABObj.ContainsKey(sABName)){Object uObj_cur = _dict_ABObj[sABName].LoadAsset(sResName, eToType);if(uObj_cur is GameObject){returnInstantiate(uObj_cur);}else{return uObj_cur;}}returnnull;}/// <summary>/// 同步加载AB包中的资源,重载的泛型方法/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <returns>返回资源对象,GameObject则是实例后的对象,资源一类的则直接返回,注:错误时返回null</returns>public T LoadABPackRes<T>(string sABName,string sResName)where T: Object
  12.     {bool bIsLoadSuc =LoadABPack(sABName);if(!bIsLoadSuc)returnnull;if(_dict_ABObj.ContainsKey(sABName)){T obj_cur = _dict_ABObj[sABName].LoadAsset<T>(sResName);if(obj_cur is GameObject){returnInstantiate(obj_cur);}else{return obj_cur;}}returnnull;}#endregion#region 同步加载AB包,异步加载资源/// <summary>/// 同步加载AB包,异步加载资源/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>publicvoidLoadResAsync(string sABName,string sResName, UnityAction<Object> fun_callback){bool bIsLoadSuc =LoadABPack(sABName);if(bIsLoadSuc)return;StartCoroutine(IE_LoadResAsync(sABName, sResName, fun_callback));}/// <summary>/// 异步操作:同步加载AB包,异步加载资源/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>/// <returns></returns>privateIEnumeratorIE_LoadResAsync(string sABName,string sResName, UnityAction<Object> fun_callback){if(_dict_ABObj.ContainsKey(sABName)){AssetBundleRequest uObj_cur = _dict_ABObj[sABName].LoadAssetAsync(sResName);yieldreturn uObj_cur;if(uObj_cur.asset is GameObject){fun_callback(Instantiate(uObj_cur.asset));}else{fun_callback(uObj_cur.asset);}}else{
  13.             Debug.LogError("异步加载资源失败,未找到AB包");}}/// <summary>/// 同步加载AB包,异步加载资源/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>publicvoidLoadResAsync(string sABName,string sResName,System.Type eToType, UnityAction<Object> fun_callback){bool bIsLoadSuc =LoadABPack(sABName);if(bIsLoadSuc)return;StartCoroutine(IE_LoadResAsync(sABName, sResName, eToType, fun_callback));}/// <summary>/// 异步操作:同步加载AB包,异步加载资源/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>/// <returns></returns>privateIEnumeratorIE_LoadResAsync(string sABName,string sResName,System.Type eToType, UnityAction<Object> fun_callback){if(_dict_ABObj.ContainsKey(sABName)){AssetBundleRequest uObj_cur = _dict_ABObj[sABName].LoadAssetAsync(sResName, eToType);yieldreturn uObj_cur;if(uObj_cur.asset is GameObject){fun_callback(Instantiate(uObj_cur.asset));}else{fun_callback(uObj_cur.asset);}}else{
  14.             Debug.LogError("异步加载资源失败,未找到AB包");}}/// <summary>/// 同步加载AB包,异步加载资源/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>publicvoidLoadResAsync<T>(string sABName,string sResName, UnityAction<T> fun_callback)where T: Object
  15.     {bool bIsLoadSuc =LoadABPack(sABName);if(bIsLoadSuc)return;StartCoroutine(IE_LoadResAsync<T>(sABName, sResName, fun_callback));}/// <summary>/// 异步操作:同步加载AB包,异步加载资源/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>/// <returns></returns>private IEnumerator IE_LoadResAsync<T>(string sABName,string sResName, UnityAction<T> fun_callback)where T: Object
  16.     {if(_dict_ABObj.ContainsKey(sABName)){AssetBundleRequest uObj_cur = _dict_ABObj[sABName].LoadAssetAsync<T>(sResName);yieldreturn uObj_cur;if(uObj_cur.asset is GameObject){fun_callback(Instantiate(uObj_cur.asset)as T);}else{fun_callback(uObj_cur.asset as T);}}else{
  17.             Debug.LogError("异步加载资源失败,未找到AB包");}}#endregion#region 异步加载AB包,异步加载资源/// <summary>/// 异步加载AB包(外部调用接口)/// </summary>/// <param name="sABName">AB名称</param>publicvoidAsyncLoadABPack(string sABName){StartCoroutine(IE_AsyncLoadABPack(sABName));}/// <summary>/// 异步加载AB包(类内部调用用接口)/// </summary>/// <param name="sABName">AB名称</param>/// <returns></returns>privateIEnumeratorIE_AsyncLoadABPack(string sABName){if(_obj_mainABPack ==null){
  18.             _obj_mainABPack =LoadPack(_sMainABName);
  19.             _obj_mainManifest = _obj_mainABPack.LoadAsset<AssetBundleManifest>("AssetBundleManifest");yieldreturn _obj_mainManifest;}// 加载sABName的所有依赖包AssetBundle obj_relyOnAB =null;string[] arr_sManifest = _obj_mainManifest.GetAllDependencies(sABName);for(int i =0; i < arr_sManifest.Length; i++){// 未加载过的才进行加载,已加载的AB包,不能重复加载if(!_dict_ABObj.ContainsKey(arr_sManifest[i])){
  20.                 obj_relyOnAB =LoadPack(arr_sManifest[i]);
  21.                 _dict_ABObj.Add(arr_sManifest[i], obj_relyOnAB);yieldreturn obj_relyOnAB;}}AssetBundle obj_curLoadAB =null;if(!_dict_ABObj.ContainsKey(sABName)){{
  22.                 obj_curLoadAB =LoadPack(sABName);
  23.                 _dict_ABObj.Add(sABName, obj_curLoadAB);}}}/// <summary>/// 异步加载AB包与资源,只返回Object,类型外部自己传参(外部调用接口)/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>publicvoidAsyncLoadABPackAndRes(string sABName,string sResName, UnityAction<Object> fun_callback){StartCoroutine(IE_AsyncLoadABPackAndRes(sABName, sResName, fun_callback));}/// <summary>/// 异步加载AB包与资源(类内部调用接口)/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>/// <returns></returns>privateIEnumeratorIE_AsyncLoadABPackAndRes(string sABName,string sResName, UnityAction<Object> fun_callback){yieldreturnStartCoroutine(IE_AsyncLoadABPack(sABName));StartCoroutine(IE_LoadResAsync(sABName, sResName, fun_callback));}/// <summary>/// 异步加载AB包与资源,传资源转换类型(类内部调用接口)/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="eToType">资源转换类型</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>publicvoidAsyncLoadABPackAndRes(string sABName,string sResName,System.Type eToType, UnityAction<Object> fun_callback){StartCoroutine(IE_AsyncLoadABPackAndRes(sABName, sResName, eToType, fun_callback));}/// <summary>/// 异步加载AB包与资源(类内部调用接口)/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>/// <returns></returns>privateIEnumeratorIE_AsyncLoadABPackAndRes(string sABName,string sResName,System.Type eToType, UnityAction<Object> fun_callback){yieldreturnStartCoroutine(IE_AsyncLoadABPack(sABName));StartCoroutine(IE_LoadResAsync(sABName, sResName, eToType, fun_callback));}/// <summary>/// 异步加载AB包与资源,重载使用泛型数据(外部调用接口)/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>/// <typeparam name="T">泛型</typeparam>publicvoidAsyncLoadABPackAndRes<T>(string sABName,string sResName, UnityAction<T> fun_callback)where T: Object
  24.     {StartCoroutine(IE_AsyncLoadABPackAndRes<T>(sABName, sResName, fun_callback));}/// <summary>/// 异步加载AB包与资源,重载使用泛型数据(类内部调用接口)/// </summary>/// <param name="sABName">AB包的包名</param>/// <param name="sResName">AB包的资源名</param>/// <param name="fun_callback">异步加载完成后的回调方法</param>/// <typeparam name="T">泛型</typeparam>/// <returns></returns>private IEnumerator IE_AsyncLoadABPackAndRes<T>(string sABName,string sResName, UnityAction<T> fun_callback)where T: Object
  25.     {yieldreturnStartCoroutine(IE_AsyncLoadABPack(sABName));StartCoroutine(IE_LoadResAsync<T>(sABName, sResName, fun_callback));}#endregion#region AB包资源卸载/// <summary>/// 单个AB包卸载/// </summary>/// <param name="sABName"></param>publicvoidUnLoadABPack(string sABName){if(_dict_ABObj.ContainsKey(sABName)){
  26.             _dict_ABObj[sABName].Unload(false);
  27.             _dict_ABObj.Remove(sABName);}}/// <summary>/// 卸载所有的AB包/// </summary>publicvoidClearAllABPack(){
  28.         AssetBundle.UnloadAllAssetBundles(false);
  29.         _obj_mainABPack =null;
  30.         _obj_mainManifest =null;}#endregion}
复制代码
HotUpdateTest 热更新测试脚本
  1. using UnityEngine;publicclassHotUpdateTest:MonoSingletonBase<HotUpdateTest>{voidStart(){
  2.         HotUpdateMgr.GetInstance().StartHotUpdate();}publicvoidRunLua(){
  3.         LuaInterpreter.GetInstance().RequireLua("HelloWorld");
  4.         LuaInterpreter.GetInstance().RequireLua("Test");}publicvoidInitShow(){GameObject obj_cube = AssetBundleMgr.GetInstance().LoadABPackRes<GameObject>("mode.ab","Cube");
  5.         Debug.Log("实例化 Cube");}}
复制代码
准备就绪后运行可查看效果
本地的Lua测试代码与Cube材质




热更新执行后的实际效果是



Unity版本 2019.4
项目Git:https://github.com/hycai007/Learn_HotUpdate

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-22 11:52 , Processed in 0.092955 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表