Unity xlua 热更

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",注:记得修改 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.com/hycai007/Learn_HotUpdate

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值