admin管理员组

文章数量:1639832

1、先打包AB包,并加密

  • 对AB包加密

创建加密相关脚本,这里使用 AES 加密对AB包资源进行加密,脚本如下

using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;

public class ABPackMenu : Editor
{
    [MenuItem("My Tool/AB包加密/创建AB包版本文件/Window 版本")]
    private static void EncrypABPackVersionFile_Window()
    {
        Debug.Log("加密并创建 Window 平台的AB包版本信息");
        EncryptAndCreateVersionFile(BuildTarget.StandaloneWindows);
    }

    [MenuItem("My Tool/AB包加密/解密AB包版本文件/Window 版本")]
    private static void DecryptABPack_Window()
    {
        Debug.Log("解密 Window 平台的AB包版本信息");
        DecryptVersionFile(BuildTarget.StandaloneWindows);
    }

    [MenuItem("My Tool/AB包加密/创建AB包版本文件/Android 版本")]
    private static void CreateABPackVersionFile_Android()
    {
        Debug.Log("加密并创建 Android 平台的AB包版本信息");
        EncryptAndCreateVersionFile(BuildTarget.Android);
    }

    [MenuItem("My Tool/AB包加密/解密AB包版本文件/Android 版本")]
    private static void DecryptABPack_Android()
    {
        Debug.Log("解密 Android 平台的AB包版本信息");
        DecryptVersionFile(BuildTarget.Android);
    }

    [MenuItem("My Tool/AB包加密/创建AB包版本文件/IOS 版本")]
    private static void CreateABPackVersionFile_IOS()
    {
        Debug.Log("加密并创建 IOS 平台的AB包版本信息");
        DecryptVersionFile(BuildTarget.iOS);
    }

    [MenuItem("My Tool/AB包加密/解密AB包版本文件/IOS 版本")]
    private static void DecryptABPack_IOS()
    {
        Debug.Log("解密 IOS 平台的AB包版本信息");
        DecryptVersionFile(BuildTarget.iOS);
    }

    private static void EncryptAndCreateVersionFile(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 = new StringBuilder();
        string sAllABPath = Application.dataPath + @"/../AssetBundles/" + e_buildTarget.ToString();
        DirectoryInfo obj_folder = new DirectoryInfo(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>
    private static void DecryptVersionFile(BuildTarget e_buildTarget)
    {
        /// ABPack版本信息保存路径
        Debug.Log("平台是: " + e_buildTarget.ToString());
        string sAllABFile = Application.dataPath + @"/../AssetBundlesEncrypt/" + e_buildTarget.ToString();
        Debug.Log("加密版本的AB包文件路径是 >>> " + sAllABFile);

        DirectoryInfo obj_folder = new DirectoryInfo(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;

public class AESEncryptMgr : SingletonBase<AESEncryptMgr>
{
    /// <summary>
    /// 缓存加密使用的加密相关数据对象
    /// </summary>
    private static AesCryptoServiceProvider _obj_AesCSP;

    /// <summary>
    /// 加密使用的 Key
    /// </summary>
    private const string _sKey = "hycai12365479807";
    /// <summary>
    /// 加密使用的 IV
    /// </summary>
    private const string _sIV = "987465132hycai07";

    /// <summary>
    /// 加密文件
    /// </summary>
    /// <param name="sFilePath"></param>
    /// <param name="sOutSavePath"></param>
    /// <param name="sKey"></param>
    /// <param name="sIV"></param>
    /// <returns></returns>
    public static int AESEncryptFile(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;
        }

        return 1;
    }

    /// <summary>
    /// 解密文件
    /// </summary>
    /// <param name="sFilePath"></param>
    /// <param name="sOutSavePath"></param>
    /// <param name="sKey"></param>
    /// <param name="sIV"></param>
    /// <returns></returns>
    public static int AESDecryptFile(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;
        }
        return 1;
    }

    /// <summary>
    /// 解密文件为Stream数据流
    /// </summary>
    /// <param name="sFilePath"></param>
    /// <param name="sOutSavePath"></param>
    /// <param name="sKey"></param>
    /// <param name="sIV"></param>
    /// <returns></returns>
    public static byte[] AESDecryptFileToStream(string sFilePath, string sKey = _sKey, string sIV = _sIV)
    {
        if (!File.Exists(sFilePath))
        {
            Debug.Log("Error: 不存在AES解密的文件,sFilePath = null !!!");
            return null;
        }


        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 null;
        }

        AesCryptoServiceProvider obj_aesCSP = CreateAES_CSP(sKey, sIV);
        ICryptoTransform obj_trans = obj_aesCSP.CreateDecryptor();
        return AESFileCodingToStream(sFilePath, obj_trans);
    }

    /// <summary>
    /// 根据传入的 key 与 算法的向量 IV 创建 AES 的数据对象
    /// </summary>
    /// <param name="sKey"></param>
    /// <param name="sIV"></param>
    /// <returns></returns>
    public static AesCryptoServiceProvider CreateAES_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 = new byte[16];
        byte[] arr_iv = new byte[16];
        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 = new AesCryptoServiceProvider()
        {
            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>
    private static int AESFileCoding(string sFilePath, string sOutSavePath, ICryptoTransform obj_trans)
    {
        try
        {
            FileStream obj_fileStream; // 创建文件流
            byte[] arr_input = null;
            using (MemoryStream obj_memory = new MemoryStream())
            {
                // 创建加密解密流,从
                using (CryptoStream obj_cryptoStream = new CryptoStream(obj_memory, obj_trans, CryptoStreamMode.Write))
                {
                    obj_fileStream = File.OpenRead(sFilePath); // 从文件中读取数据到文件流中
                    // 将文件流转换成2进制数据
                    using (BinaryReader obj_binaryReader = new BinaryReader(obj_fileStream))
                    {
                        arr_input = new byte[obj_fileStream.Length];
                        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文件转换成功...");
            return 1;
        }
        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>
    private static byte[] AESFileCodingToStream(string sFilePath, ICryptoTransform obj_trans)
    {
        try
        {
            FileStream obj_fileStream; // 创建文件流
            byte[] arr_input = null;
            byte[] arr_data = null;
            using (MemoryStream obj_memory = new MemoryStream())
            {
                // 创建加密解密流,从文件中获取解密到其中
                using (CryptoStream obj_cryptoStream = new CryptoStream(obj_memory, obj_trans, CryptoStreamMode.Write))
                {
                    obj_fileStream = File.OpenRead(sFilePath); // 从文件中读取数据到文件流中
                    // 将文件流转换成2进制数据
                    using (BinaryReader obj_binaryReader = new BinaryReader(obj_fileStream))
                    {
                        arr_input = new byte[obj_fileStream.Length];
                        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);
            return null;
        }
    }
}
  • ABPack相关抽取的工具类方法
using UnityEngine;
/// <summary>
/// AB包相关处理的工具
/// </summary>
public static class ABPackUtils
{
    /// <summary>
    /// AB包的后缀扩展名
    /// </summary>
    private static string _sABPackExName = ".ab";
    public static string ABPackExName { get { return _sABPackExName; } }

    /// <summary>
    /// 缓存AB包版本信息的文件名
    /// </summary>
    private static string _sABVersionName = "ABVersionFile.txt";
    public static string sABVersionName { get { return _sABVersionName; } }

    /// <summary>
    /// 获取AB包的版本信息字符串
    /// </summary>
    /// <param name="sABName">包名(含AssetBundle路径)</param>
    /// <param name="sFileVersionMd5">版本信息的MD5值</param>
    /// <param name="nFileSize">文件大小</param>
    /// <returns></returns>
    public static string GetABPackVersionStr(string sABName, string sFileVersionMd5, string sFileSize)
    {
        return string.Format("{0} {1} {2}", sABName, sFileVersionMd5, sFileSize);
    }

    /// <summary>
    /// 获取不同平台AB包存放路径的字符串
    /// </summary>
    /// <returns></returns>
    public static string GetABPackPathPlatformStr()
    {
        RuntimePlatform obj_platform = Application.platform;
        string sPlatformStr = "/AssetBundles/";
        if (obj_platform == RuntimePlatform.WindowsEditor || obj_platform == RuntimePlatform.WindowsPlayer)
        {
            sPlatformStr += "StandaloneWindows/";
        }
        else if (obj_platform == RuntimePlatform.Android)
        {
            sPlatformStr += "Android/";
        }
        else if (obj_platform == RuntimePlatform.IPhonePlayer)
        {
            sPlatformStr += "iOS/";
        }

        return sPlatformStr;
    }
}

  • IO工具脚本
using System.IO;

public class IOUtils
{
    /// <summary>
    /// 创建txt文件的方法
    /// </summary>
    /// <param name="sFilePath"></param>
    /// <param name="sContent"></param>
    public static void CreatTextFile(string sFilePath, string sContent)
    {
        //文件存在则删除
        if (File.Exists(sFilePath))
        {
            File.Delete(sFilePath);
        }
        using (FileStream obj_versionStream = File.Create(sFilePath))
        {
            using (StreamWriter obj_writer = new StreamWriter(obj_versionStream))
            {
                obj_writer.WriteLine(sContent);
            }
        }
    }

    /// <summary>
    /// 根据文件路径,创建其文件的文件夹路径
    /// </summary>
    /// <param name="sFilePath"></param>
    public static void CreateDirectroryOfFile(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/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;

public class HotUpdateMgr : MonoSingletonBase<HotUpdateMgr>
{
    /// <summary>
    /// _sBaseUrl下载网址
    /// </summary>
#if UNITY_EDITOR || UNITY_STANDALONE_WIN
    public static string _sBaseUrl = "http://127.0.0.1:5858";
#elif UNITY_ANDROID
    public static string _sBaseUrl = "http://192.168.255.10:5858";
#elif UNITY_IPHONE
    public static string _sBaseUrl = "http://192.168.255.10:5858";
#endif

    private string _sABVersionName = "";

    /// <summary>
    /// 本地版本信息缓存路径
    /// </summary>
    private string _sVersionLocalFilePath = "";

    /// <summary>
    /// 同时下载的最大数量
    /// </summary>
    private int _nMaxDownloader = 5;

    /// <summary>
    /// 当前需要下载的AB包数据
    /// </summary>
    List<ABPackInfo> _list_allNeedABPack = new List<ABPackInfo>();

    /// <summary>
    /// 所需下载资源总大小
    /// </summary>
    private float _nDownloadTotalSize = 0;

    /// <summary>
    /// 当前已下载资源的大小
    /// </summary>
    private float _nCurDownloadedSize = 0;

    /// <summary>
    /// AB包下载器
    /// </summary>
    private List<ABDownloader> _list_allABDownloader = new List<ABDownloader>();

    /// <summary>
    /// 客户端的AB版本数据
    /// </summary>
    private Dictionary<string, ABPackInfo> _dict_clientABInfoList = null;

    protected override void Awake()
    {
        string sPlatformStr = ABPackUtils.GetABPackPathPlatformStr();
        _sABVersionName = sPlatformStr + ABPackUtils.sABVersionName;
        _sVersionLocalFilePath = Application.persistentDataPath + _sABVersionName;
        IOUtils.CreateDirectroryOfFile(_sVersionLocalFilePath);
    }

    /// <summary>
    /// 开始热更
    /// </summary>
    public void StartHotUpdate()
    {
        Debug.Log("开始热更 >>>>>> ");
        StartCoroutine(DownloadAllABPackVersion());
    }

    /// <summary>
    /// 解析版本文件,返回一个文件列表
    /// </summary>
    /// <param name="sContent"></param>
    /// <returns></returns>
    public Dictionary<string, ABPackInfo> ConvertToAllABPackDesc(string sContent)
    {
        Dictionary<string, ABPackInfo> dict_allABPackDesc = new Dictionary<string, ABPackInfo>();
        string[] arrLines = sContent.Split('\n');//用回车 字符 \n 分割每一行
        foreach (string item in arrLines)
        {
            string[] arrData = item.Split(' ');//用空格分割每行数据的三个类型
            if (arrData.Length == 3)
            {
                ABPackInfo obj_ABPackData = new ABPackInfo();
                obj_ABPackData.sABName = arrData[0]; // 名称即路径
                obj_ABPackData.sMd5 = arrData[1]; // md5值
                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));
                dict_allABPackDesc.Add(obj_ABPackData.sABName, obj_ABPackData);
            }
        }

        return dict_allABPackDesc;
    }


    /// <summary>
    /// 获取服务端的AB包版本信息
    /// </summary>
    /// <returns></returns>
    IEnumerator DownloadAllABPackVersion()
    {
        string sVersionUrl = _sBaseUrl + @"/" + _sABVersionName;
        //Debug.Log("下载版本数据路径:" + sVersionUrl);

        using (UnityWebRequest uObj_versionWeb = UnityWebRequest.Get(sVersionUrl))
        {
            yield return uObj_versionWeb.SendWebRequest(); // 等待资源下载
            if (uObj_versionWeb.isNetworkError || uObj_versionWeb.isHttpError)
            {
                Debug.LogError("获取版本AB包数据错误: " + uObj_versionWeb.error);
                yield break;
            }
            else
            {
                string sVersionData = uObj_versionWeb.downloadHandler.text;
                //Debug.Log("成功获取到版本相关数据 >>>> \n" + sVersionData);
                CheckNeedDownloadABPack(sVersionData);
            }
        }
    }

    /// <summary>
    /// 检测需要下载
    /// </summary>
    /// <param name="sServerVersionData"></param>
    void CheckNeedDownloadABPack(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[obj_itemData.sABName].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>
    void StartDownloadAllABPack()
    {
        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 = new ABDownloader();
            _list_allABDownloader.Add(obj_downloader);
            StartCoroutine(obj_downloader.DownloadABPack(obj_ABPackDesc));
            _list_allNeedABPack.RemoveAt(0);
        }
    }

    /// <summary>
    /// 切换下载下一个AB包
    /// </summary>
    /// <param name="obj_ABDownloader">需要切换的下载器</param>
    public void ChangeDownloadNextABPack(ABDownloader obj_ABDownloader)
    {
        //Debug.Log("切换下载下一个 AB 包");
        _nCurDownloadedSize += obj_ABDownloader.GetDownloadResSize();

        if (_list_allNeedABPack.Count > 0) // 还存在需要下载的资源,下载器切换资源,继续下载
        {
            StartCoroutine(obj_ABDownloader.DownloadABPack(_list_allNeedABPack[0]));
            _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>
    public void UpdateClientABInfo(ABPackInfo obj_ABPackDecs)
    {
        if (_dict_clientABInfoList == null)
        {
            _dict_clientABInfoList = new Dictionary<string, ABPackInfo>();
        }

        _dict_clientABInfoList[obj_ABPackDecs.sABName] = obj_ABPackDecs;

        StringBuilder obj_sb = new StringBuilder();
        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>
    private void HotUpdateEnd()
    {
        // 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>
public class ABDownloader
{

    /// <summary>
    /// 当前下载器下载的AB包数据
    /// </summary>
    private ABPackInfo _obj_ABDecs;

    /// <summary>
    /// 是否资源下载中
    /// </summary>
    private bool _bIsDownloading = false;
    public bool bIsDownloading { get => _bIsDownloading; }

    /// <summary>
    /// 获取当前下载资源的大小
    /// </summary>
    /// <returns></returns>
    public float GetDownloadResSize()
    {
        if (_obj_ABDecs != null)
        {
            return _obj_ABDecs.nSize;
        }

        return 0;
    }

    /// <summary>
    /// 下载AB包
    /// </summary>
    /// <param name="obj_ABDecs">AB包资源的说明,包含文件名,大小,md5值</param>
    /// <returns></returns>
    public IEnumerator DownloadABPack(ABPackInfo obj_ABDecs)
    {
        _bIsDownloading = true;
        string sDownloadUrl = HotUpdateMgr._sBaseUrl + @"/" + obj_ABDecs.sABName;
        Debug.Log("下载资源:" + sDownloadUrl);
        UnityWebRequest uObj_web = UnityWebRequest.Get(sDownloadUrl);
        yield return uObj_web.SendWebRequest();

        if (uObj_web.isNetworkError || uObj_web.isHttpError)
        {
            Debug.Log("获取AB包 " + sDownloadUrl + " 错误: " + uObj_web.error);
            yield break;
        }
        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;

public class AssetBundleMgr : MonoSingletonBase<AssetBundleMgr>
{

    /// <summary>
    /// 是否使用本地文件
    /// </summary>
    public bool _bIsUseLocalFile = false;

    /// <summary>
    /// 是否使用加密
    /// </summary>
    public bool _bIsEncrypt = true;

    /// <summary>
    /// AB包的主包对象
    /// </summary>
    private AssetBundle _obj_mainABPack = null;
    /// <summary>
    /// AB包主包的相关依赖信息对象
    /// </summary>
    private AssetBundleManifest _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 = new Dictionary<string, AssetBundle>();

    /// <summary>
    /// AB包存放路径
    /// </summary>
    /// <value></value>
    private string _sPathUrl;

    /// <summary>
    /// 存放AB包的主文件夹,主包名 
    /// </summary>
    /// <value></value>
    private string _sMainABName {
        get {
            #if UNITY_IOS
                return "IOS";
            #elif UNITY_ANDROID
                return "Android";
            #else
                return "StandaloneWindows";
            #endif
        }   
    }

    private void Awake()
    {
        string sPlatformStr = ABPackUtils.GetABPackPathPlatformStr();
        if (_bIsUseLocalFile) // 是否从本地中读取
        {
            _sPathUrl = Application.streamingAssetsPath + sPlatformStr;
        }
        else
        {
            _sPathUrl = Application.persistentDataPath + sPlatformStr;
        }
    }

    /// <summary>
    /// 加载AB包
    /// </summary>
    /// <param name="sABName"></param>
    /// <returns></returns>
    private AssetBundle LoadPack(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包解密, 加载流 失败 >>>>>>");
            return null;
        }
        else
        {
            return AssetBundle.LoadFromFile(_sPathUrl + sABName);
        }
    }

    #region 同时加载AB包与资源
    /// <summary>
    /// 加载某个AB包
    /// </summary>
    /// <param name="sABName"></param>
    /// <returns></returns>
    public bool LoadABPack(string sABName)
    {
        if (_obj_mainABPack == null)
        {
            _obj_mainABPack = LoadPack(_sMainABName);
            if (_obj_mainABPack == null) return false;
            _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]))
            {
                obj_relyOnAB = LoadPack(arr_sManifest[i]);
                if (obj_relyOnAB == null) return false;
                _dict_ABObj.Add(arr_sManifest[i], obj_relyOnAB);
            }
        }

        AssetBundle obj_curLoadAB = null;
        if(!_dict_ABObj.ContainsKey(sABName))
        {
            obj_curLoadAB = LoadPack(sABName);
            if (obj_curLoadAB == null) return false;
            _dict_ABObj.Add(sABName, obj_curLoadAB);
        }

        return true;
    }

    /// <summary>
    /// 同步加载AB包中的资源
    /// </summary>
    /// <param name="sABName">AB包的包名</param>
    /// <param name="sResName">AB包的资源名</param>
    /// <returns>返回资源对象,GameObject则是实例后的对象,资源一类的则直接返回,注:错误时返回null</returns>
    public Object LoadABPackRes(string sABName, string sResName)
    {
        bool bIsLoadSuc = LoadABPack(sABName);
        if (!bIsLoadSuc) return null;

        if(_dict_ABObj.ContainsKey(sABName))
        {
            Object uObj_cur = _dict_ABObj[sABName].LoadAsset(sResName);
            if(uObj_cur is GameObject)
            {
                return Instantiate(uObj_cur);
            }
            else
            {
                return uObj_cur;
            }
        }

        return null;
    }

    
    /// <summary>
    /// 同步加载AB包中的资源, 重载增加转换的参数类型
    /// </summary>
    /// <param name="sABName">AB包的包名</param>
    /// <param name="sResName">AB包的资源名</param>
    /// <param name="eToType">加载后的资源转换成的资源类型</param>
    /// <returns>返回资源对象,GameObject则是实例后的对象,资源一类的则直接返回,注:错误时返回null</returns>
    public Object LoadABPackRes(string sABName, string sResName, System.Type eToType)
    {
        bool bIsLoadSuc = LoadABPack(sABName);
        if (!bIsLoadSuc) return null;

        if (_dict_ABObj.ContainsKey(sABName))
        {
            Object uObj_cur = _dict_ABObj[sABName].LoadAsset(sResName, eToType);
            if(uObj_cur is GameObject)
            {
                return Instantiate(uObj_cur);
            }
            else
            {
                return uObj_cur;
            }
        }

        return null;
    }

    /// <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) return null;

        if (_dict_ABObj.ContainsKey(sABName))
        {
            T obj_cur = _dict_ABObj[sABName].LoadAsset<T>(sResName);
            if(obj_cur is GameObject)
            {
                return Instantiate(obj_cur);
            }
            else
            {
                return obj_cur;
            }
        }

        return null;
    }

#endregion

#region 同步加载AB包,异步加载资源
    /// <summary>
    /// 同步加载AB包,异步加载资源
    /// </summary>
    /// <param name="sABName">AB包的包名</param>
    /// <param name="sResName">AB包的资源名</param>
    /// <param name="fun_callback">异步加载完成后的回调方法</param>
    public void LoadResAsync(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>
    private IEnumerator IE_LoadResAsync(string sABName, string sResName, UnityAction<Object> fun_callback)
    {
        if(_dict_ABObj.ContainsKey(sABName))
        {
            AssetBundleRequest uObj_cur = _dict_ABObj[sABName].LoadAssetAsync(sResName);
            yield return 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>
    public void LoadResAsync(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>
    private IEnumerator IE_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);
            yield return 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>
    public void LoadResAsync<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[sABName].LoadAssetAsync<T>(sResName);
            yield return 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>
    public void AsyncLoadABPack(string sABName)
    {
        StartCoroutine(IE_AsyncLoadABPack(sABName));
    }

    /// <summary>
    /// 异步加载AB包(类内部调用用接口)
    /// </summary>
    /// <param name="sABName">AB名称</param>
    /// <returns></returns>
    private IEnumerator IE_AsyncLoadABPack(string sABName)
    {
        if (_obj_mainABPack == null)
        {
            _obj_mainABPack = LoadPack(_sMainABName);
            _obj_mainManifest = _obj_mainABPack.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            yield return _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]))
            {
                obj_relyOnAB = LoadPack(arr_sManifest[i]);
                _dict_ABObj.Add(arr_sManifest[i], obj_relyOnAB);
                yield return 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>
    public void AsyncLoadABPackAndRes(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>
    private IEnumerator IE_AsyncLoadABPackAndRes(string sABName, string sResName, UnityAction<Object> fun_callback)
    {
        yield return StartCoroutine(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>
    public void AsyncLoadABPackAndRes(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>
    private IEnumerator IE_AsyncLoadABPackAndRes(string sABName, string sResName, System.Type eToType, UnityAction<Object> fun_callback)
    {
        yield return StartCoroutine(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>
    public void AsyncLoadABPackAndRes<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
    {
        yield return StartCoroutine(IE_AsyncLoadABPack(sABName));
        StartCoroutine(IE_LoadResAsync<T>(sABName, sResName, fun_callback));
    }

#endregion

#region AB包资源卸载
    /// <summary>
    /// 单个AB包卸载
    /// </summary>
    /// <param name="sABName"></param>
    public void UnLoadABPack(string sABName)
    {
        if(_dict_ABObj.ContainsKey(sABName))
        {
            _dict_ABObj[sABName].Unload(false);
            _dict_ABObj.Remove(sABName);
        }
    }

    /// <summary>
    /// 卸载所有的AB包
    /// </summary>
    public void ClearAllABPack()
    {
        AssetBundle.UnloadAllAssetBundles(false);
        _obj_mainABPack = null;
        _obj_mainManifest = null;

    }
#endregion
}


HotUpdateTest 热更新测试脚本

using UnityEngine;

public class HotUpdateTest : MonoSingletonBase<HotUpdateTest>
{
    void Start()
    {
        HotUpdateMgr.GetInstance().StartHotUpdate();
    }

    public void RunLua()
    {
        LuaInterpreter.GetInstance().RequireLua("HelloWorld");
        LuaInterpreter.GetInstance().RequireLua("Test");
    }

    public void InitShow()
    {
        GameObject obj_cube = AssetBundleMgr.GetInstance().LoadABPackRes<GameObject>("mode.ab", "Cube");
        Debug.Log("实例化 Cube");
    }
}

准备就绪后运行可查看效果

本地的Lua测试代码与Cube材质

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

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

本文标签: Unityxlua