注意:考虑到热更新的内容比较多,我将热更新的内容分开,并全部整合放在【unity游戏开发——热更新】专栏里,感兴趣的小伙伴可以前往逐一查看学习。

前置知识

实践

YooAssetHybridCLR的基础使用的知识就不重复讲解了,还不懂的小伙伴可以点击前面前置知识前往了解。

1、加载热更新程序集

HybridCLR是原生运行时实现,因此调用Assembly Assembly.Load(byte[])即可加载热更新程序集。

创建Assets/LoadDll.cs脚本

using System;
using System.Linq;
using System.Reflection;
using UnityEngine;
using YooAsset;

public class LoadDll : MonoBehaviour
{

    void Start()
    {
#if !UNITY_EDITOR
        // 打包后及非编辑器模式下

        // 使用YooAsset 
        var package = YooAssets.GetPackage("DefaultPackage");

        //HotUpdate.dll.bytes
        AssetHandle handle = package.LoadAssetSync<TextAsset>("Assets/Res/HotDll/HotUpdate.dll.bytes");
        TextAsset text = handle.AssetObject as TextAsset;
        Assembly hotUpdateAss = Assembly.Load(text.bytes);
#else
        // Editor编辑器模式下,HotUpdate.dll.bytes已经被自动加载,不需要加载,重复加载反而会出问题。
        // Editor下无需加载,直接查找获得HotUpdate程序集
        Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
    }
}

2、调用热更新代码

显然,主工程不能直接引用热更新代码。有多种方式可以从主工程调用热更新程序集中的代码,这里通过反射来调用热更新代码。

在LoadDll.Start函数后面添加反射调用代码,如下:

void Start()
{
	//...

    //得到Hello类调用函数
    Type type = hotUpdateAss.GetType("Hello");
    type.GetMethod("Run").Invoke(null, null);
}

3、在main场景中创建一个GameObject对象,挂载LoadDll脚本。

在这里插入图片描述

4、打包生成HotUpdate.dll

运行菜单 HybridCLR/Generate/All 进行必要的生成操作。这一步不可遗漏!!!
在这里插入图片描述
{proj}/HybridCLRData/HotUpdateDlls/StandaloneWindows64(MacOS下为StandaloneMacXxx)目录下的HotUpdate.dll复制到Assets/Res/HotDll/HotUpdate.dll.bytes,注意,要加.bytes后缀!!!这样才能被加载!
在这里插入图片描述
在这里插入图片描述

5、YooAsset资源中加入HotUpdate.dll

在这里插入图片描述
然后打一次AB包
在这里插入图片描述
将打包出来的Version文件和Bytes文件放入到StreamingAssets/yoo/DefaultPackage
在这里插入图片描述
在这里插入图片描述

6、生成的AB包资源全部复制放在本地资源服务器,启动服务器

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

7、修改默认场景MyYooAssetTest脚本的UpdateDone函数中,书写加载Main场景的代码

#region 热更新结束回调
private async void UpdateDone()
{
    // 场景加载
    string scenePath = "Assets/Res/Scene/Main.unity";
    var sceneMode = LoadSceneMode.Single;
    var physicsMode = LocalPhysicsMode.None;
    bool suspendLoad = false;
    SceneHandle handle = _package.LoadSceneAsync(scenePath, sceneMode, physicsMode, suspendLoad);
    await handle.Task;
    Debug.Log("场景名字是:" + handle.SceneName);
}
#endregion

在这里插入图片描述

完整代码

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using YooAsset;

/// <summary>
/// YooAsset资源系统测试脚本
/// </summary>
public class MyYooAssetTest : MonoBehaviour
{
    string hostServerIP = "http://127.0.0.1";//服务器地址
    string appVersion = "v1.0"; //版本号

    public EPlayMode PlayMode = EPlayMode.HostPlayMode;//资源系统运行模式
    public string packageName = "DefaultPackage"; //默认包名
    private ResourcePackage _package = null; //资源包对象

    public int downloadingMaxNum = 10;//最大下载数量
    public int filedTryAgain = 3;//失败重试次数
    private ResourceDownloaderOperation _downloader;//下载器
    private UpdatePackageManifestOperation _operationManifest;//更新清单

    void Awake()
    {
        Debug.Log($"资源系统运行模式:{PlayMode}");
        Application.targetFrameRate = 60;//设置帧率
        Application.runInBackground = true;//设置后台运行
        DontDestroyOnLoad(gameObject);//确保该对象不会在场景切换时销毁
    }

    IEnumerator Start()
    {
        //1.初始化YooAsset资源系统
        YooAssets.Initialize();

        //2.初始化资源包
        yield return StartCoroutine(InitPackage());

        //3.获取资源版本
        yield return StartCoroutine(UpdatePackageVersion());

        //4.获取文件清单
        yield return StartCoroutine(UpdateManifest());

        //5.创建资源下载器
        CreateDownloader();

        //5.开始下载资源文件
        yield return StartCoroutine(BeginDownload());

        //6.清理未使用的缓存文件
        ClearFiles();
    }


    #region 初始化资源包
    private IEnumerator InitPackage()
    {
        // 获取或创建资源包对象
        _package = YooAssets.TryGetPackage(packageName);
        if (_package == null)
            _package = YooAssets.CreatePackage(packageName);

        // 编辑器下的模拟模式
        InitializationOperation initializationOperation = null;
        if (PlayMode == EPlayMode.EditorSimulateMode)
        {
            var buildResult = EditorSimulateModeHelper.SimulateBuild(packageName);
            var packageRoot = buildResult.PackageRootDirectory;
            var createParameters = new EditorSimulateModeParameters();
            createParameters.EditorFileSystemParameters = FileSystemParameters.CreateDefaultEditorFileSystemParameters(packageRoot);
            initializationOperation = _package.InitializeAsync(createParameters);
        }

        // 单机运行模式
        if (PlayMode == EPlayMode.OfflinePlayMode)
        {
            var createParameters = new OfflinePlayModeParameters();
            createParameters.BuildinFileSystemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
            initializationOperation = _package.InitializeAsync(createParameters);
        }

        // 联机运行模式
        if (PlayMode == EPlayMode.HostPlayMode)
        {
            //创建远端服务实例,用于资源请求
            string defaultHostServer = GetHostServerURL();
            string fallbackHostServer = GetHostServerURL();
            IRemoteServices remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);


            // 创建联机模式参数,并设置内置及缓存文件系统参数
            // HostPlayModeParameters createParameters = new HostPlayModeParameters
            // {
            //     //创建内置文件系统参数
            //     BuildinFileSystemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(),
            //     //创建缓存系统参数
            //     CacheFileSystemParameters = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices)
            // };
            // 创建联机模式参数,并设置内置及缓存文件系统参数
            HostPlayModeParameters createParameters = new HostPlayModeParameters
            {
                //创建内置文件系统参数
                BuildinFileSystemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters(),
                //创建缓存系统参数
                CacheFileSystemParameters = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices,new FileOffsetDecryption())
            };
            //执行异步初始化
            initializationOperation = _package.InitializeAsync(createParameters);
        }

        // WebGL运行模式
        if (PlayMode == EPlayMode.WebPlayMode)
        {
#if UNITY_WEBGL && WEIXINMINIGAME && !UNITY_EDITOR
            var createParameters = new WebPlayModeParameters();
			string defaultHostServer = GetHostServerURL();
            string fallbackHostServer = GetHostServerURL();
            string packageRoot = $"{WeChatWASM.WX.env.USER_DATA_PATH}/__GAME_FILE_CACHE"; //注意:如果有子目录,请修改此处!
            IRemoteServices remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
            createParameters.WebServerFileSystemParameters = WechatFileSystemCreater.CreateFileSystemParameters(packageRoot, remoteServices);
            initializationOperation = _package.InitializeAsync(createParameters);
#else
            var createParameters = new WebPlayModeParameters();
            createParameters.WebServerFileSystemParameters = FileSystemParameters.CreateDefaultWebServerFileSystemParameters();
            initializationOperation = _package.InitializeAsync(createParameters);
#endif
        }

        yield return initializationOperation;

        // 如果初始化失败弹出提示界面
        if (initializationOperation.Status != EOperationStatus.Succeed)
        {
            Debug.LogWarning($"{initializationOperation.Error}");
            PatchEventDefine.InitializeFailed.SendEventMessage();
        }
        else
        {
            Debug.Log("初始化成功-------------------------");
        }
    }

    /// <summary>
    /// 获取资源服务器地址
    /// </summary>
    private string GetHostServerURL()
    {

#if UNITY_EDITOR
        if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.Android)
            return $"{hostServerIP}/CDN/Android/{appVersion}";
        else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.iOS)
            return $"{hostServerIP}/CDN/IPhone/{appVersion}";
        else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.WebGL)
            return $"{hostServerIP}/CDN/WebGL/{appVersion}";
        else
            return $"{hostServerIP}/CDN/PC/{appVersion}";
#else
        if (Application.platform == RuntimePlatform.Android)
            return $"{hostServerIP}/CDN/Android/{appVersion}";
        else if (Application.platform == RuntimePlatform.IPhonePlayer)
            return $"{hostServerIP}/CDN/IPhone/{appVersion}";
        else if (Application.platform == RuntimePlatform.WebGLPlayer)
            return $"{hostServerIP}/CDN/WebGL/{appVersion}";
        else
            return $"{hostServerIP}/CDN/PC/{appVersion}";
#endif
    }

    /// <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}";
        }
    }
    #endregion

    #region 获取资源版本
    private IEnumerator UpdatePackageVersion()
    {
        // 发起异步版本请求
        RequestPackageVersionOperation operation = _package.RequestPackageVersionAsync();
        yield return operation;

        // 处理版本请求结果
        if (operation.Status != EOperationStatus.Succeed)
        {
            Debug.LogWarning(operation.Error);
        }
        else
        {
            Debug.Log($"请求的版本: {operation.PackageVersion}");
            appVersion = operation.PackageVersion;
        }
    }
    #endregion

    #region 获取文件清单
    private IEnumerator UpdateManifest()
    {
        _operationManifest = _package.UpdatePackageManifestAsync(appVersion);
        yield return _operationManifest;

        // 处理文件清单结果
        if (_operationManifest.Status != EOperationStatus.Succeed)
        {
            Debug.LogWarning(_operationManifest.Error);
            PatchEventDefine.PackageManifestUpdateFailed.SendEventMessage();
            yield break;
        }
        else
        {
            Debug.Log("更新资源清单成功-------------------");
        }
    }
    #endregion

    #region 创建资源下载器
    void CreateDownloader()
    {
        _downloader = _package.CreateResourceDownloader(downloadingMaxNum, filedTryAgain);
        if (_downloader.TotalDownloadCount == 0)
        {
            Debug.Log("没有需要更新的文件");
            UpdateDone();
        }
        else
        {
            // 发现新更新文件后,挂起流程系统
            // 注意:开发者需要在下载前检测磁盘空间不足
            int count = _downloader.TotalDownloadCount;
            long bytes = _downloader.TotalDownloadBytes;
            Debug.Log($"需要更新{count}个文件, 大小是{bytes / 1024 / 1024}MB");
        }
    }
    #endregion

    #region 开始下载资源文件
    private IEnumerator BeginDownload()
    {
        _downloader.DownloadErrorCallback = DownloadErrorCallback;// 单个文件下载失败
        _downloader.DownloadUpdateCallback = DownloadUpdateCallback;// 下载进度更新
        _downloader.BeginDownload();//开始下载
        yield return _downloader;

        // 检测下载结果
        if (_downloader.Status != EOperationStatus.Succeed)
        {
            Debug.LogWarning(_operationManifest.Error);
            yield break;
        }
        else
        {
            Debug.Log("下载成功-------------------");
        }
    }

    // 单个文件下载失败
    public static void DownloadErrorCallback(DownloadErrorData errorData)
    {
        string fileName = errorData.FileName;
        string errorInfo = errorData.ErrorInfo;
        Debug.Log($"下载失败, 文件名: {fileName}, 错误信息: {errorInfo}");
    }

    // 下载进度更新
    public static void DownloadUpdateCallback(DownloadUpdateData updateData)
    {
        int totalDownloadCount = updateData.TotalDownloadCount;
        int currentDownloadCount = updateData.CurrentDownloadCount;
        long totalDownloadSizeBytes = updateData.TotalDownloadBytes;
        long currentDownloadSizeBytes = updateData.CurrentDownloadBytes;
        Debug.Log($"下载进度: {currentDownloadCount}/{totalDownloadCount}, " +
                  $"{currentDownloadSizeBytes / 1024}KB/{totalDownloadSizeBytes / 1024}KB");
    }
    #endregion

    #region 清理未使用的缓存文件
    void ClearFiles()
    {
        var operationClear = _package.ClearCacheFilesAsync(EFileClearMode.ClearUnusedBundleFiles);// 清理未使用的文件
        operationClear.Completed += Operation_Completed;// 添加清理完成回调
    }

    //文件清理完成
    private void Operation_Completed(AsyncOperationBase obj)
    {
        UpdateDone();
    }
    #endregion

    #region 热更新结束回调
    private async void UpdateDone()
    {
        // 场景加载
        string scenePath = "Assets/Res/Scene/Main.unity";
        var sceneMode = LoadSceneMode.Single;
        var physicsMode = LocalPhysicsMode.None;
        bool suspendLoad = false;
        SceneHandle handle = _package.LoadSceneAsync(scenePath, sceneMode, physicsMode, suspendLoad);
        await handle.Task;
        Debug.Log("场景名字是:" + handle.SceneName);
    }
    #endregion
}

8、打包运行

打开Build Settings对话框,点击Build And Run,打包并且运行热更新示例工程。可以看到Hello, HybridCLR
在这里插入图片描述
不要忘记了,打包要配置好场景信息。顺序记得不要错了,确保初始化YooAsset在第一个场景执行
在这里插入图片描述

并开启允许从http下载
在这里插入图片描述
效果
在这里插入图片描述

9、测试热更新

修改Assets/HotUpdate/Hello.cs的Run函数

public class Hello
{
    public static void Run()
    {
        Debug.Log("代码热更新成功");
    }
}

运行菜单命令HybridCLR/CompileDll/ActiveBulidTarget重新编译热更新代码。
在这里插入图片描述
{proj}/HybridCLRData/HotUpdateDlls/StandaloneWindows64(MacOS下为StandaloneMacXxx)目录下的HotUpdate.dll复制替换掉工程中已经存在的HotUpdate.dll.bytes,也要加上后缀bytes
在这里插入图片描述
检查YooAsset资源引用有没有丢失
在这里插入图片描述

重新用YooAsset打AB包,全部放到资源服务器上
在这里插入图片描述
在这里插入图片描述
双击重新运行之前打包好的exe运行包。
在这里插入图片描述
发现打印语句变了,实现代码热更新!!
在这里插入图片描述


专栏推荐

地址
【unity游戏开发入门到精通——C#篇】
【unity游戏开发入门到精通——unity通用篇】
【unity游戏开发入门到精通——unity3D篇】
【unity游戏开发入门到精通——unity2D篇】
【unity实战】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架/工具集开发】
【unity游戏开发——模型篇】
【unity游戏开发——InputSystem】
【unity游戏开发——Animator动画】
【unity游戏开发——UGUI】
【unity游戏开发——联网篇】
【unity游戏开发——优化篇】
【unity游戏开发——shader篇】
【unity游戏开发——编辑器扩展】
【unity游戏开发——热更新】

完结

好了,我是向宇,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!
在这里插入图片描述

Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐