共需要三个步骤

1.LoadDll脚本放在非HotUpdate文件夹下,直接放在Asset下或任意位置

2.需要一个入口场景(如Main,任意命名),和一个开始场景(如Start,任意命名),入口场景也非热更场景,一般我也直接放在Asset文件夹下,其他的都丢寻址资源下面了

3.在Main场景中的任意物体下挂在LoadDll

using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.SceneManagement;
using YooAsset;

/// <summary>
/// 脚本工作流程:
/// 1.下载资源,用yooAsset资源框架进行下载
///    1.资源文件,ab包
///    2.热更新dll
/// 2.给AOT DLL补充元素据,通过RuntimeApi.LoadMetadataForAOTAssembly
/// 3.通过实例化prefab,运行热更代码
/// </summary>
public class LoadDll : MonoBehaviour
{
    /// <summary>
    /// 资源系统运行模式
    /// </summary>
    public EPlayMode PlayMode = EPlayMode.HostPlayMode;

    private ResourcePackage _defaultPackage;

    void Start()
    {
        StartCoroutine(InitYooAssets(StartGame));
    }

    #region YooAsset初始化

    IEnumerator InitYooAssets(Action onDownloadComplete)
    {
        // 1.初始化资源系统
        YooAssets.Initialize();

        string packageName = "DefaultPackage";
        var package = YooAssets.TryGetPackage(packageName) ?? YooAssets.CreatePackage(packageName);
        YooAssets.SetDefaultPackage(package);
        InitializationOperation initializationOperation = null; 
        //===================================适应新版本YooAsset插件的修改===================================
        if (PlayMode == EPlayMode.EditorSimulateMode)
        {
            var buildResult = EditorSimulateModeHelper.SimulateBuild(packageName);
            var packageRoot = buildResult.PackageRootDirectory;
            var editorFileSystemParams = FileSystemParameters.CreateDefaultEditorFileSystemParameters(packageRoot);
            var createParameters = new EditorSimulateModeParameters();
            createParameters.EditorFileSystemParameters = editorFileSystemParams;
            initializationOperation = package.InitializeAsync(createParameters);
        }
        else if (PlayMode == EPlayMode.HostPlayMode)
        {
            string defaultHostServer = GetHostServerURL();
            string fallbackHostServer = GetHostServerURL();
            IRemoteServices remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
            //联机运行模式
            var createParameters = new HostPlayModeParameters();
            createParameters.BuildinFileSystemParameters = null; //设置为NULL解决报错!
            createParameters.CacheFileSystemParameters = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices);
            initializationOperation = package.InitializeAsync(createParameters);

        }

        yield return initializationOperation;
        //===================================适应新版本YooAsset插件的修改===================================
            
        if (initializationOperation.Status == EOperationStatus.Succeed)
        {
            Debug.Log("资源包初始化成功!");
        }
        else
        {
            Debug.LogError($"资源包初始化失败:{initializationOperation.Error}");
        }

        //2.获取资源版本
        var operation = package.RequestPackageVersionAsync();
        yield return operation;

        if (operation.Status != EOperationStatus.Succeed)
        {
            //更新失败
            Debug.LogError(operation.Error);
            yield break;
        }

        string packageVersion = operation.PackageVersion;
        Debug.Log($"更新资源版本 : {packageVersion}");

        //3.更新补丁清单
        // 更新成功后自动保存版本号,作为下次初始化的版本。
        // 也可以通过operation.SavePackageVersion()方法保存。
        var operation2 = package.UpdatePackageManifestAsync(packageVersion);
        yield return operation2;

        if (operation2.Status != EOperationStatus.Succeed)
        {
            //更新失败
            Debug.LogError(operation2.Error);
            yield break;
        }

        //4.下载补丁包
        yield return Download();

        //判断是否下载成功
        var assets = new List<string> { "HotUpdate.dll" }.Concat(AOTMetaAssemblyFiles);
        foreach (var asset in assets)
        {
            var handle = package.LoadAssetAsync<TextAsset>(asset);
            yield return handle;
            var assetObj = handle.AssetObject as TextAsset;
            s_assetDatas[asset] = assetObj;
            Debug.Log($"dll:{asset}   {assetObj == null}");
        }

        _defaultPackage = package;
        onDownloadComplete();
    }
    
    /// <summary>
    /// MARKER 🐎
    /// 设置服务器 地址 端口号 目标平台
    /// </summary>
    /// <returns></returns>
    private string GetHostServerURL()
    {
        //模拟下载地址,8084为Nginx里面设置的端口号,项目名,平台名
        return "http://127.0.0.1:8084/Tv1Game/PC";
    }

    /// <summary>
    /// 远端资源地址查询服务类
    /// </summary>
    private class RemoteServices : IRemoteServices
    {
        private readonly string _defaultHostServer;
        private readonly string _fallbackHostServer;

        public RemoteServices(string defaultHostServer, string fallbackHostServer)
        {
            _defaultHostServer = defaultHostServer;
            _fallbackHostServer = fallbackHostServer;
        }

        string IRemoteServices.GetRemoteMainURL(string fileName)
        {
            return $"{_defaultHostServer}/{fileName}";
        }

        string IRemoteServices.GetRemoteFallbackURL(string fileName)
        {
            return $"{_fallbackHostServer}/{fileName}";
        }
    }
    
//===================================适应新版本YooAsset插件的修改===================================
//     /// <summary>
//     /// 资源文件查询服务类
//     /// </summary>
//     internal class GameQueryServices : IBuildinQueryServices
//     {
//         public bool Query(string packageName, string fileName, string fileCRC)
//         {
// #if UNITY_IPHONE
//             throw new Exception("Ios平台需要内置资源");
//             return false;
// #else
//             return false;
// #endif
//         }
//     }
//===================================适应新版本YooAsset插件的修改===================================

    #endregion

    #region 下载热更资源

    IEnumerator Download()
    {
        int downloadingMaxNum = 10;
        int failedTryAgain = 3;
        var package = YooAssets.GetPackage("DefaultPackage");
        var downloader = package.CreateResourceDownloader(downloadingMaxNum, failedTryAgain);

        //没有需要下载的资源
        if (downloader.TotalDownloadCount == 0)
        {
            yield break;
        }

        //需要下载的文件总数和总大小
        int totalDownloadCount = downloader.TotalDownloadCount;
        long totalDownloadBytes = downloader.TotalDownloadBytes;

        //===================================适应新版本YooAsset插件的修改===================================
        //注册回调方法
        downloader.DownloadErrorCallback = OnDownloadErrorFunction;
        downloader.DownloadUpdateCallback = OnDownloadProgressUpdateFunction;
        downloader.DownloadFinishCallback = OnDownloadOverFunction;
        downloader.DownloadFileBeginCallback = OnStartDownloadFileFunction;
        //===================================适应新版本YooAsset插件的修改===================================

        //开启下载
        downloader.BeginDownload();
        yield return downloader;

        //检测下载结果
        if (downloader.Status == EOperationStatus.Succeed)
        {
            //下载成功
            Debug.Log("更新完成");
        }
        else
        {
            //下载失败
            Debug.Log("更新失败");
        }
    }

    //===================================适应新版本YooAsset插件的修改===================================
    /// <summary>
    /// 开始下载
    /// </summary>
    private void OnStartDownloadFileFunction(DownloadFileData downloadFileData)
    {
        Debug.Log($"开始下载:文件名:{downloadFileData.FileName},文件大小:{downloadFileData.FileSize}");
    }

    /// <summary>
    /// 下载完成
    /// </summary>
    private void OnDownloadOverFunction(DownloaderFinishData downloaderFinishData)
    {
        Debug.Log("下载" + (downloaderFinishData.Succeed ? "成功" : "失败"));
    }

    /// <summary>
    /// 更新中
    /// </summary>
    private void OnDownloadProgressUpdateFunction(DownloadUpdateData downloadUpdateData)
    {
        Debug.Log($"文件总数:{downloadUpdateData.TotalDownloadCount},已下载文件数:{downloadUpdateData.CurrentDownloadCount},下载总大小:{downloadUpdateData.TotalDownloadBytes},已下载大小{downloadUpdateData.CurrentDownloadBytes}");
    }

    /// <summary>
    /// 下载出错
    /// </summary>
    /// <param name="errorData"></param>
    private void OnDownloadErrorFunction(DownloadErrorData errorData)
    {
        Debug.Log($"下载出错:包名:{errorData.PackageName} 文件名:{errorData.FileName},错误信息:{errorData.ErrorInfo}");
    }
    //===================================适应新版本YooAsset插件的修改===================================

    #endregion

    #region 补充元数据

    //补充元数据dll的列表
    //通过RuntimeApi.LoadMetadataForAOTAssembly()函数来补充AOT泛型的原始元数据
    private static List<string> AOTMetaAssemblyFiles { get; } = new() { "mscorlib.dll", "System.dll", "System.Core.dll", };
    private static Dictionary<string, TextAsset> s_assetDatas = new Dictionary<string, TextAsset>();
    private static Assembly _hotUpdateAss;
    
    public static byte[] ReadBytesFromStreamingAssets(string dllName)
    {
        if (s_assetDatas.ContainsKey(dllName))
        {
            return s_assetDatas[dllName].bytes;
        }

        return Array.Empty<byte>();
    }

    

    /// <summary>
    /// 为aot assembly加载原始metadata, 这个代码放aot或者热更新都行。
    /// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行
    /// </summary>
    private static void LoadMetadataForAOTAssemblies()
    {
        /// 注意,补充元数据是给AOT dll补充元数据,而不是给热更新dll补充元数据。
        /// 热更新dll不缺元数据,不需要补充,如果调用LoadMetadataForAOTAssembly会返回错误
        HomologousImageMode mode = HomologousImageMode.SuperSet;
        foreach (var aotDllName in AOTMetaAssemblyFiles)
        {
            byte[] dllBytes = ReadBytesFromStreamingAssets(aotDllName);
            // 加载assembly对应的dll,会自动为它hook。一旦aot泛型函数的native函数不存在,用解释器版本代码
            LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, mode);
            Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. mode:{mode} ret:{err}");
        }
    }

    #endregion

    #region 运行测试

    void StartGame()
    {
        // 加载AOT dll的元数据
        LoadMetadataForAOTAssemblies();
    
        // 加载热更dll - 增加更严格的检查
        byte[] hotUpdateBytes = ReadBytesFromStreamingAssets("HotUpdate.dll");
    
        if (hotUpdateBytes == null || hotUpdateBytes.Length == 0)
        {
            Debug.LogError("HotUpdate.dll 加载失败,字节数为0");
            return;
        }
    
        try
        {
#if !UNITY_EDITOR
        _hotUpdateAss = Assembly.Load(hotUpdateBytes);
        Debug.Log($"热更新程序集加载成功: {_hotUpdateAss.FullName}");
#else
            _hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies()
                .First(a => a.GetName().Name == "HotUpdate");
#endif
        
            if (_hotUpdateAss == null)
            {
                Debug.LogError("热更新程序集加载失败");
                return;
            }
        
            Debug.Log("热更新程序集加载完成,开始运行热更代码");
        
            // 先初始化配置表,再实例化对象
            
            StartCoroutine(Run_InstantiateComponentByAsset());
        }
        catch (Exception e)
        {
            Debug.LogError($"加载热更新程序集时出错: {e}");
        }
    }

    IEnumerator Run_InstantiateComponentByAsset()
    {
        // 通过实例化assetbundle中的资源,还原资源上的热更新脚本
        var package = YooAssets.GetPackage("DefaultPackage");
        Debug.Log("开始加载初始场景");
        SceneHandle sceneHandle = package.LoadSceneAsync("Start", LoadSceneMode.Single);
        while (!sceneHandle.IsDone)
        {
            //Debug.Log("等待场景加载");
            yield return null;
        }

        if (sceneHandle.Status == EOperationStatus.Succeed)
        {
            Debug.Log("场景加载成功");
        }
        else
        {
            Debug.Log("场景加载失败");
        }
        
    }

    #endregion
}

注意事项:

1.记得替换掉你的热更服务器下载地址 代码中搜索MARKER可以找到更改的地方

2.记得BuildSetting添加场景打包

3.挂在LoadDll脚本的物体在打包的时候切换为HostPlayMode

4.程序集别忘了引用

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐