Unity xlua 热更
1、先打包AB包,并加密对AB包加密
创建加密相关脚本,这里使用 AES 加密对AB包资源进行加密,脚本如下
using System.IO;using System.Text;using UnityEditor;using UnityEngine;publicclassABPackMenu:Editor{privatestaticvoidEncrypABPackVersionFile_Window(){
Debug.Log("加密并创建 Window 平台的AB包版本信息");EncryptAndCreateVersionFile(BuildTarget.StandaloneWindows);}privatestaticvoidDecryptABPack_Window(){
Debug.Log("解密 Window 平台的AB包版本信息");DecryptVersionFile(BuildTarget.StandaloneWindows);}privatestaticvoidCreateABPackVersionFile_Android(){
Debug.Log("加密并创建 Android 平台的AB包版本信息");EncryptAndCreateVersionFile(BuildTarget.Android);}privatestaticvoidDecryptABPack_Android(){
Debug.Log("解密 Android 平台的AB包版本信息");DecryptVersionFile(BuildTarget.Android);}privatestaticvoidCreateABPackVersionFile_IOS(){
Debug.Log("加密并创建 IOS 平台的AB包版本信息");DecryptVersionFile(BuildTarget.iOS);}privatestaticvoidDecryptABPack_IOS(){
Debug.Log("解密 IOS 平台的AB包版本信息");DecryptVersionFile(BuildTarget.iOS);}privatestaticvoidEncryptAndCreateVersionFile(BuildTarget e_buildTarget){/// ABPack版本信息保存路径
Debug.Log("平台是: "+ e_buildTarget.ToString());/// 加密文件存放路径string sBasePath = Application.dataPath +@"/../AssetBundlesEncrypt/"+ e_buildTarget.ToString()+@"/";if(!Directory.Exists(sBasePath)){
Directory.CreateDirectory(sBasePath);}StringBuilder obj_sb =newStringBuilder();string sAllABPath = Application.dataPath +@"/../AssetBundles/"+ e_buildTarget.ToString();DirectoryInfo obj_folder =newDirectoryInfo(sAllABPath);// 获取输出路径的文件夹管理器
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包文件{
AESEncryptMgr.AESEncryptFile(sFilePath, sEncryptABOutPath);}else{// 不用加密的文件,拷贝的加密后的对应目录中bool bIsReWrite =true;// true=覆盖已存在的同名文件, false 则反之
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){
Debug.LogError("有文件没有拿到MD5:"+ sABName);continue;}string sFileSize = Mathf.Ceil(obj_item.Length /1024f).ToString();//文件大小
sABName = sABName.Replace("\\","/");string sABVersionStr = ABPackUtils.GetABPackVersionStr(sABName, sFileVersion, sFileSize);//每个文件的版本信息
obj_sb.AppendLine(sABVersionStr);// 写入版本文件要构建的内容中,按行写入}string sABVersionFile = sBasePath + ABPackUtils.sABVersionName;//Debug.Log("AB包版本信息文件保存路径是 >>> " + sABVersionFile);// 判断是否存在AB包的版本文件信息,存在则删除
IOUtils.CreatTextFile(sABVersionFile, obj_sb.ToString());}/// <summary>/// 解密AB包文件/// </summary>/// <param name="e_buildTarget"></param>privatestaticvoidDecryptVersionFile(BuildTarget e_buildTarget){/// ABPack版本信息保存路径
Debug.Log("平台是: "+ e_buildTarget.ToString());string sAllABFile = Application.dataPath +@"/../AssetBundlesEncrypt/"+ e_buildTarget.ToString();
Debug.Log("加密版本的AB包文件路径是 >>> "+ sAllABFile);DirectoryInfo obj_folder =newDirectoryInfo(sAllABFile);// 获取输出路径的文件夹管理器
FileInfo[] arr_allFiles = obj_folder.GetFiles("*", SearchOption.AllDirectories);// 取得所有文件/// 解密文件存放路径string sBasePath = Application.dataPath +@"/../AssetBundlesDecrypt/"+ e_buildTarget.ToString()+@"/";if(!Directory.Exists(sBasePath)){
Directory.CreateDirectory(sBasePath);}foreach(FileInfo obj_item in arr_allFiles){string sFilePath = obj_item.FullName;// 获取文件全名(包含路径 C:/ D:/ 全路径)
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包存放路径
AESEncryptMgr.AESDecryptFile(sFilePath, sDecryptABOutPath);}else{// 不用加密的文件,拷贝的加密后的对应目录中bool bIsReWrite =true;// true=覆盖已存在的同名文件, false 则反之
System.IO.File.Copy(sFilePath, sDecryptABOutPath, bIsReWrite);}}}}AES 加密:其中 SingletonBase 只是实现单例的基类,提供对文件加密解密的工具类
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)){
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){
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)){
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){
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)){
Debug.Log("Error: 不存在AES解密的文件,sFilePath = null !!!");returnnull;}int nKeyCount = sKey.Length;int nIvCount = sIV.Length;if(nKeyCount <7|| nKeyCount >16|| nIvCount <7|| nIvCount >16){
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;byte[] arr_iv =newbyte;int nKeyTargetLen = arr_key.Length;int nIVTargetLen = arr_iv.Length;// 确保加密Key与IV是在16 byte 内
nKeyTargetLen = nKeySounceLen > nKeyTargetLen ? nKeyTargetLen : nKeySounceLen;
nIVTargetLen = nIVSounceLen > nIVTargetLen ? nIVTargetLen : nIVSounceLen;
System.Array.Copy(arr_keySource, arr_key, nKeyTargetLen);
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 加密的数据模式对象
_obj_AesCSP =newAesCryptoServiceProvider(){
Mode = CipherMode.CBC,// 设置对称算法的运算模式
Padding = PaddingMode.PKCS7,// 设置对称算法中使用的填充模式
KeySize =128,// 设置密钥的大小(以位为单位)
BlockSize =128,// 设置加密操作的块大小(以位为单位)
Key = arr_key,// 设置用于加密和解密的对称密钥
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)){
obj_fileStream = File.OpenRead(sFilePath);// 从文件中读取数据到文件流中// 将文件流转换成2进制数据using(BinaryReader obj_binaryReader =newBinaryReader(obj_fileStream)){
arr_input =newbyte;
obj_binaryReader.Read(arr_input,0, arr_input.Length);}// 解密操作
obj_cryptoStream.Write(arr_input,0, arr_input.Length);// 释放解密操作
obj_cryptoStream.FlushFinalBlock();// 写入到保存文件中using(obj_fileStream = File.OpenWrite(sOutSavePath)){
obj_memory.WriteTo(obj_fileStream);}}}
Debug.Log("AES文件转换成功...");return1;}catch(Exception obj_ex){
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)){
obj_fileStream = File.OpenRead(sFilePath);// 从文件中读取数据到文件流中// 将文件流转换成2进制数据using(BinaryReader obj_binaryReader =newBinaryReader(obj_fileStream)){
arr_input =newbyte;
obj_binaryReader.Read(arr_input,0, arr_input.Length);}
obj_cryptoStream.Write(arr_input,0, arr_input.Length);// 释放解密操作
obj_cryptoStream.FlushFinalBlock();//obj_memory.WriteTo(obj_fileStream);
arr_data = obj_memory.ToArray();}
Debug.Log("AES文件解密到Stream完成...");return arr_data;}}catch(Exception obj_ex){
Debug.Log("Error AES 加密失败"+ obj_ex.Message);returnnull;}}}
ABPack相关抽取的工具类方法
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){
sPlatformStr +="StandaloneWindows/";}elseif(obj_platform == RuntimePlatform.Android){
sPlatformStr +="Android/";}elseif(obj_platform == RuntimePlatform.IPhonePlayer){
sPlatformStr +="iOS/";}return sPlatformStr;}}
IO工具脚本
using System.IO;publicclassIOUtils{/// <summary>/// 创建txt文件的方法/// </summary>/// <param name="sFilePath"></param>/// <param name="sContent"></param>publicstaticvoidCreatTextFile(string sFilePath,string sContent){//文件存在则删除if(File.Exists(sFilePath)){
File.Delete(sFilePath);}using(FileStream obj_versionStream = File.Create(sFilePath)){using(StreamWriter obj_writer =newStreamWriter(obj_versionStream)){
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},");
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 方法
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>
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();
_sABVersionName = sPlatformStr + ABPackUtils.sABVersionName;
_sVersionLocalFilePath = Application.persistentDataPath + _sABVersionName;
IOUtils.CreateDirectroryOfFile(_sVersionLocalFilePath);}/// <summary>/// 开始热更/// </summary>publicvoidStartHotUpdate(){
Debug.Log("开始热更 >>>>>> ");StartCoroutine(DownloadAllABPackVersion());}/// <summary>/// 解析版本文件,返回一个文件列表/// </summary>/// <param name="sContent"></param>/// <returns></returns>public Dictionary<string, ABPackInfo>ConvertToAllABPackDesc(string sContent){
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();
obj_ABPackData.sABName = arrData;// 名称即路径
obj_ABPackData.sMd5 = arrData;// md5值
obj_ABPackData.nSize =int.Parse(arrData);// AB包大小//Debug.Log(string.Format("解析的路径:{0}\n解析的MD5:{1}\n解析的文件大小KB:{2}", obj_ABPackData.sABName, obj_ABPackData.sMd5, obj_ABPackData.nSize));
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){
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);
Dictionary<string, ABPackInfo> dict_serverDownList =ConvertToAllABPackDesc(sServerVersionData);// 服务端获取的资源下载列表if(File.Exists(_sVersionLocalFilePath)){//Debug.Log("存在本地,对比服务器版本信息");string sClientVersionData = File.ReadAllText(_sVersionLocalFilePath);// 本地版本信息
_dict_clientABInfoList =ConvertToAllABPackDesc(sClientVersionData);// 客户端本地缓存的资源下载列表//遍历服务器文件foreach(ABPackInfo obj_itemData in dict_serverDownList.Values){// 存在对应已下载文件,对比Md5值是否一致if(_dict_clientABInfoList.ContainsKey(obj_itemData.sABName)){// md5值不一致,则更新文件if(_dict_clientABInfoList.sMd5 != obj_itemData.sMd5){
_list_allNeedABPack.Add(obj_itemData);
_nDownloadTotalSize = _nDownloadTotalSize + obj_itemData.nSize;//Debug.Log("MD5 值不一样,资源存在变更,增加文件 >>>>> " + obj_itemData.sABName);}}else{
_list_allNeedABPack.Add(obj_itemData);
_nDownloadTotalSize = _nDownloadTotalSize + obj_itemData.nSize;}}}else// 如果说不存在本地缓存,那就直接下载所有的AB包{foreach(ABPackInfo obj_itemData in dict_serverDownList.Values){
_list_allNeedABPack.Add(obj_itemData);
_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;ABDownloader obj_downloader =newABDownloader();
_list_allABDownloader.Add(obj_downloader);StartCoroutine(obj_downloader.DownloadABPack(obj_ABPackDesc));
_list_allNeedABPack.RemoveAt(0);}}/// <summary>/// 切换下载下一个AB包/// </summary>/// <param name="obj_ABDownloader">需要切换的下载器</param>publicvoidChangeDownloadNextABPack(ABDownloader obj_ABDownloader){//Debug.Log("切换下载下一个 AB 包");
_nCurDownloadedSize += obj_ABDownloader.GetDownloadResSize();if(_list_allNeedABPack.Count >0)// 还存在需要下载的资源,下载器切换资源,继续下载{StartCoroutine(obj_ABDownloader.DownloadABPack(_list_allNeedABPack));
_list_allNeedABPack.RemoveAt(0);}else{bool bIsDownloadSuc =true;// 资源是否全部下载完成foreach(ABDownloader obj_downloader in _list_allABDownloader){if(obj_downloader.bIsDownloading)// 存在一个下载中,即表示当前还有未下载完成的部分{
bIsDownloadSuc =false;break;}}if(bIsDownloadSuc)// 已完成全部下载{HotUpdateEnd();}}}/// <summary>/// 更新本地缓存的AB包版本数据/// </summary>/// <param name="obj_ABPackDecs"></param>publicvoidUpdateClientABInfo(ABPackInfo obj_ABPackDecs){if(_dict_clientABInfoList ==null){
_dict_clientABInfoList =newDictionary<string,ABPackInfo>();}
_dict_clientABInfoList= obj_ABPackDecs;StringBuilder obj_sb =newStringBuilder();foreach(ABPackInfo obj_temp in _dict_clientABInfoList.Values){
obj_sb.AppendLine(ABPackUtils.GetABPackVersionStr(obj_temp.sABName, obj_temp.sMd5, obj_temp.nSize.ToString()));}
IOUtils.CreatTextFile(_sVersionLocalFilePath, obj_sb.ToString());}/// <summary>/// 热更新结束,进入下一个阶段/// </summary>privatevoidHotUpdateEnd(){// TODO 进入下一个阶段
Debug.Log("热更新: 已完成所有的AB包下载, 进入下一个阶段 TODO");
HotUpdateTest.GetInstance().RunLua();
HotUpdateTest.GetInstance().InitShow();}}-AB包资源下载器 : AB包下载器,进行AB包资源下载,然后对AB包进行保存在本地,将下载完成的版本信息写入到本地的 ABVersionFile.txt 文件中记录
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){
_bIsDownloading =true;string sDownloadUrl = HotUpdateMgr._sBaseUrl +@"/"+ obj_ABDecs.sABName;
Debug.Log("下载资源:"+ sDownloadUrl);UnityWebRequest uObj_web = UnityWebRequest.Get(sDownloadUrl);yieldreturn uObj_web.SendWebRequest();if(uObj_web.isNetworkError || uObj_web.isHttpError){
Debug.Log("获取AB包 "+ sDownloadUrl +" 错误: "+ uObj_web.error);yieldbreak;}else{string sABPath = Application.persistentDataPath +@"/"+ obj_ABDecs.sABName;
Debug.Log("AB包 保存本地路径是:"+ sABPath);
IOUtils.CreateDirectroryOfFile(sABPath);if(!File.Exists(sABPath)){
File.Create(sABPath).Dispose();}
File.WriteAllBytes(sABPath, uObj_web.downloadHandler.data);// 下载完成后,更新本地版本数据
HotUpdateMgr.GetInstance().UpdateClientABInfo(obj_ABDecs);
_bIsDownloading =false;
HotUpdateMgr.GetInstance().ChangeDownloadNextABPack(this);}}}
AB包解压加载资源管理器 :从AB包中取得资源使用,注:_bIsEncrypt 属性,可控制是否使用是读取加密的AssetBundler的
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)// 是否从本地中读取{
_sPathUrl = Application.streamingAssetsPath + sPlatformStr;}else{
_sPathUrl = Application.persistentDataPath + sPlatformStr;}}/// <summary>/// 加载AB包/// </summary>/// <param name="sABName"></param>/// <returns></returns>privateAssetBundleLoadPack(string sABName){bool bIsEncryptFile =false;if(sABName.Contains(ABPackUtils.ABPackExName)){
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);}
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){
_obj_mainABPack =LoadPack(_sMainABName);if(_obj_mainABPack ==null)returnfalse;
_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)){
obj_relyOnAB =LoadPack(arr_sManifest);if(obj_relyOnAB ==null)returnfalse;
_dict_ABObj.Add(arr_sManifest, obj_relyOnAB);}}AssetBundle obj_curLoadAB =null;if(!_dict_ABObj.ContainsKey(sABName)){
obj_curLoadAB =LoadPack(sABName);if(obj_curLoadAB ==null)returnfalse;
_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.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.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
{bool bIsLoadSuc =LoadABPack(sABName);if(!bIsLoadSuc)returnnull;if(_dict_ABObj.ContainsKey(sABName)){T obj_cur = _dict_ABObj.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.LoadAssetAsync(sResName);yieldreturn uObj_cur;if(uObj_cur.asset is GameObject){fun_callback(Instantiate(uObj_cur.asset));}else{fun_callback(uObj_cur.asset);}}else{
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.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{
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
{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
{if(_dict_ABObj.ContainsKey(sABName)){AssetBundleRequest uObj_cur = _dict_ABObj.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{
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){
_obj_mainABPack =LoadPack(_sMainABName);
_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)){
obj_relyOnAB =LoadPack(arr_sManifest);
_dict_ABObj.Add(arr_sManifest, obj_relyOnAB);yieldreturn obj_relyOnAB;}}AssetBundle obj_curLoadAB =null;if(!_dict_ABObj.ContainsKey(sABName)){{
obj_curLoadAB =LoadPack(sABName);
_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
{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
{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)){
_dict_ABObj.Unload(false);
_dict_ABObj.Remove(sABName);}}/// <summary>/// 卸载所有的AB包/// </summary>publicvoidClearAllABPack(){
AssetBundle.UnloadAllAssetBundles(false);
_obj_mainABPack =null;
_obj_mainManifest =null;}#endregion}HotUpdateTest 热更新测试脚本
using UnityEngine;publicclassHotUpdateTest:MonoSingletonBase<HotUpdateTest>{voidStart(){
HotUpdateMgr.GetInstance().StartHotUpdate();}publicvoidRunLua(){
LuaInterpreter.GetInstance().RequireLua("HelloWorld");
LuaInterpreter.GetInstance().RequireLua("Test");}publicvoidInitShow(){GameObject obj_cube = AssetBundleMgr.GetInstance().LoadABPackRes<GameObject>("mode.ab","Cube");
Debug.Log("实例化 Cube");}}准备就绪后运行可查看效果
本地的Lua测试代码与Cube材质
热更新执行后的实际效果是
Unity版本 2019.4
项目Git:https://github.com/hycai007/Learn_HotUpdate
页:
[1]