Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二)

这篇具有很好参考价值的文章主要介绍了Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

在Unity中,结合Addressable Asset System(简称:AA)和HybridCLR来实现热更新资源和脚本的控制。AA是Unity的一个强大的资源管理系统,可以用于动态加载和更新资源。HybridCLR是一个用于在Unity中运行C#脚本的工具,可以实现热更新脚本的功能。


实现原理

使用版本:

  • Unity 2022.3.8
  • Addressables 1.21.19
  • HybridCLR 3.4.0

发布WebGL平台:从Unity 2021.3.4+、2022.3.0+版本起,不再需要全局安装,也就是webgl平台的构建过程与其他平台完全相同。

实现思路:

  1. 创建两个代码文件夹
    • 一个用于热更(游戏逻辑代码)
    • 一个用于加载(检测热更,资源下载,加载热更程序集,启动热更代码)
  2. 将在热更文件夹下创建程序集,用于热更游戏逻辑代码
  3. 通过HybridCLR将热更程序集打包成资源包,并将资源包复制到游戏内命名后缀添加“.bytes”
  4. 将3步骤中资源包“HotUptare.dll.btyes”添加到AA的资源管理中
  5. 在加载逻辑中,使用AA的资源更新机制,校验是否需要下载更新(所有AA包,包括代码程序集)
  6. 更新检测完成后,使用AA读取热更代码资源(若有会使用新的),进入游戏流程。

原理解析:

  • 通过使用AA,你可以将游戏中的资源打包成独立的AssetBundle,并在运行时根据需要加载和卸载这些资源。这样,你就可以实现资源的热更新,不需要重新构建和发布整个应用程序。

  • HybridCLR可以帮助你实现热更新脚本的功能。它允许你在Unity中加载和运行C#脚本代码,而无需重新编译整个项目。通过将脚本代码打包成DLL文件,并使用HybridCLR加载和执行这些DLL文件,你可以在游戏运行时动态更新脚本逻辑,实现脚本的热更新。

  • 结合AA和HybridCLR,可以实现资源和脚本的全部热更新控制。我们将资源和脚本分别打包成独立的AssetBundle和DLL文件,然后在游戏运行时根据需要下载和加载这些文件。这样,你可以实现资源和脚本的全部热更新流程。


一,HybridCLR相关操作

1.1 安装HybridCLR

主菜单中点击Windows/Package Manager打开包管理器。如下图所示点击Add package from git URL...,填入https://gitee.com/focus-creative-games/hybridclr_unity.githttps://github.com/focus-creative-games/hybridclr_unity.git

Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

没看过官方文档的,推荐去看官方文档的快速上手

按照官方快速上手的文档将实现:

  1. 创建热更新程序集
  2. 加载热更新程序集,并执行其中热更新代码,打印 Hello, HybridCLR
  3. 修改热更新代码,打印 Hello, World

1.2 打包程序集

设计思路: 推荐将所有需要热更的脚本都放到一个程序集中。这样方便会减少后面游戏热更限制。

创建热更程序集

实现步骤:

  1. 创建程序集作为热更程序集,并命名为“HotUpdate”
  2. 在程序集中添加需要的其他程序集的引用,如:“Unity.Addressable”等(在热更程序集中的代码引用不到Unity或者插件的命名空间,都需要在这里添加)
  3. 程序集的作用域为:所在目录集齐子目录
    Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

1.2 设置面板

将热更程序集添加到热更新Assembly Defintions中:

Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

配置PlayerSettings:

  • 如果你用的hybridclr包低于v4.0.0版本,需要关闭增量式GC(Use Incremental GC) 选项
  • Scripting Backend 切换为 IL2CPP
  • Api Compatability Level 切换为 .Net 4.x(Unity 2019-2020) 或 .Net Framework(Unity 2021+)

1.3 补充元数据

首先安装HybridCLR,点击HybridCLR/Installer弹出面板,再次点击”Install“,等待安装完成即可。
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

然后HybridCLR/Generate/ALL,此步骤会进行:

  1. 生成依赖HotUpdateDlls
  2. 生成裁剪后的aot dll
  3. 桥接函数生成依赖于AOT dll,必须保证已经build过,生成AOT dll。
    Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

将需要进行补充的dll添加到Steamingassets,并在HybridCLR Settings/补充元数据AOT dlls将文件名填进去。
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

需要补充的dll文件生成在,dll编译根目录:HybridCLRData/HotUpdateDlls:
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

设置后再代码中补充元数据部分的逻辑,就可以顺利通过了:

同样将需要补充元数据的dll名称放到AOTMetaAssemblyFiles
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

注意:当读取StreamingAssets文件夹下资源时,Android平台是和其他平台不一致的,需要单独处理,处理方法在完整代码中注释里面写了。
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

1.4 代码加载流程

【逻辑思路简介,详细看后面代码讲解】
在资源更新之后补充元数据,然后读取热更程序集,启动游戏:
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

这个桥接,可以是从热更加载场景跳转到游戏场景或者加载游戏主预制体都可以。(反正是要启动热更程序集中的代码执行流程)
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新


二,Addressable资源管理

2.1 生成热更代码资源

点击HybridCLR/CompileDll/ActiveBuildTarget,编译目标平台热更程序集代码:
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

2.2 创建资源组

按照需求创建资源组,并将HybridCLR打包的程序集当做资源包托管的AA:
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

2.3 设置资源更新

设置远程资源包下载地址:
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

将需要热更的资源包设置为远程资源包:
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新


三,实现代码

3.1 包内逻辑

不支持热更
要做的事:检测热更,资源下载,加载热更程序集,启动热更代码(桥接)

启动代码挂载如下:
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

内容如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using HybridCLR;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;

/// <summary>
/// Loading页检测更新并下载资源
/// </summary>
public class AADownloadManager : MonoBehaviour
{
    /// <summary>
    /// 显示下载状态和进度
    /// </summary>
    public Text updateText;
    public Image progressImage;
    public Button retryButton;

    private AsyncOperationHandle downloadDependencies;

    // 当前下载文件索引
    private int downLoadIndex = 0;

    // 下载完成文件个数
    private int downLoadCompleteCount = 0;

    // 下载每组资源大小
    List<long> downLoadSizeList;
    
    /// <summary>
    /// 下载多个文件列表
    /// </summary>
    List<string> downLoadKeyList;
    List<IResourceLocator> resourceLocators;
    
    // 总大小
    long totalSize = 0;
    // 下载进度
    float curProgressSize = 0;
    
    // 下载到那个资源
    int progressIndex = 0;
    
    // 下载资源总大小
    private long downLoadTotalSize = 0;

    // 当前下载大小
    private float curDownLoadSize = 0;

    void Start()
    {
        Screen.sleepTimeout = SleepTimeout.NeverSleep;
        Application.targetFrameRate = 60;
        
        downLoadIndex = 0;
        
        retryButton.gameObject.SetActive(false);
        InitAddressable();
    }

    /// <summary>
    /// 初始化 --> 加载远端的配置文件
    /// </summary>
    private async void InitAddressable()
    {
        ShowHintText(0, "正在初始化配置...");
        var initAddress = Addressables.InitializeAsync(false);
        await initAddress.Task;
        if (initAddress.Status == AsyncOperationStatus.Failed)
        {
            Debug.LogError("初始化失败");
            ShowHintText(0, "初始化失败");
            StartGame();
            return;
        }

        CheckUpdateAsset();
        Addressables.Release(initAddress);
    }

    /// <summary>
    /// 检查是否有更新
    /// </summary>
    private async void CheckUpdateAsset()
    {
        ShowHintText(0, "正在检测更新配置...");
        retryButton.gameObject.SetActive(false);
        var checkCatLogUpdate = Addressables.CheckForCatalogUpdates(false);
        await checkCatLogUpdate.Task;
        if (checkCatLogUpdate.Status != AsyncOperationStatus.Succeeded)
        {
            Debug.LogError("检测更新失败");
            ShowHintText(0, "检测更新失败");
            
            // 展示重试按钮
            retryButton.gameObject.SetActive(true);
            retryButton.onClick.RemoveAllListeners();
            retryButton.onClick.AddListener(CheckUpdateAsset);
        }
        downLoadKeyList = checkCatLogUpdate.Result;
        if (downLoadKeyList.Count <= 0)
        {
            Debug.Log("无可更新内容,直接进入游戏...");
            ShowHintText(1, "无可更新内容");
            StartGame();
            return;
        }
        else
        {
            Debug.Log($"有{downLoadKeyList.Count}个资源需要更新");
            CheckUpdateAssetSize();
        }
        Addressables.Release(checkCatLogUpdate);
    }

    private async void CheckUpdateAssetSize()
    {
        ShowHintText(0, "正在校验更新资源大小...");
        retryButton.gameObject.SetActive(false);
        // true:自动清除缓存 ,更新资源列表,是否自动释放
        var updateCatLog = Addressables.UpdateCatalogs(true, downLoadKeyList, false);
        await updateCatLog.Task;
        if (updateCatLog.Status != AsyncOperationStatus.Succeeded)
        {
            Debug.LogError("更新资源列表失败");
            ShowHintText(0, "更新资源列表失败");
            
            // 展示重试按钮
            retryButton.gameObject.SetActive(true);
            retryButton.onClick.RemoveAllListeners();
            retryButton.onClick.AddListener(CheckUpdateAssetSize);
            return;
        }

        resourceLocators = updateCatLog.Result;
        Addressables.Release(updateCatLog);
        
        AsyncOperationHandle<long> operationHandle = default;
        foreach (var item in resourceLocators)
        {
            operationHandle = Addressables.GetDownloadSizeAsync(item.Keys);
            await operationHandle.Task;
            downLoadSizeList.Add(operationHandle.Result);
            totalSize += operationHandle.Result;
        }
        Debug.Log($"获取到的下载大小:{totalSize / 1048579f} M");
        Addressables.Release(operationHandle);

        if (totalSize <= 0)
        {
            Debug.Log("无可更新内容");
            ShowHintText(1, "无可更新内容");
            StartGame();
            return;
        }
        
        Debug.Log($"有{downLoadKeyList.Count}个资源需要更新");
        ShowHintText(0, $"有{downLoadKeyList.Count}个资源需要更新");
        
        progressIndex = 0;
        DownloadAsset();
    }

    private async void DownloadAsset()
    {
        ShowHintText(0, "正在更新资源...");
        retryButton.gameObject.SetActive(false);
        for (int i = progressIndex; i < resourceLocators.Count; i++)
        {
            var item = resourceLocators[i];
            AsyncOperationHandle asyncOperationHandle = Addressables.DownloadDependenciesAsync(item.Keys);
            //await asyncOperationHandle.Task;
            while (asyncOperationHandle.IsDone)
            {
                if (asyncOperationHandle.Status == AsyncOperationStatus.Succeeded)
                {
                    Debug.Log($"下载成功:{item}...");
                }
                else
                {
                    Debug.LogError($"下载失败:{item},显示重试按钮,下载到第{progressIndex}个资源...");
                    progressIndex = i;
                    
                    retryButton.gameObject.SetActive(true);
                    retryButton.onClick.RemoveAllListeners();
                    retryButton.onClick.AddListener(DownloadAsset);
                }
                
                float progress = asyncOperationHandle.PercentComplete;
                curProgressSize += downLoadSizeList[i] * progress;
                Debug.Log($"{item} ;progress:{progress}; downLoadSizeList:{downLoadSizeList[i]}...");
                ShowHintText(curProgressSize / (totalSize * 1.0f), "正在更新资源...");
                await Task.Yield();
            }
        }
        
        Debug.Log("下载完成");
        StartGame();
    }
    
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            Debug.Log("清理缓存...");
            // 清理缓存
            Caching.ClearCache();
        }
    }

    void ShowHintText(float progress, string text)
    {
        if (updateText != null)
        {
            updateText.text = text;
        }

        if (progressImage != null)
        {
            progressImage.fillAmount = progress;
        }
    }

    #region CLR -- 进入游戏

    private Assembly _hotUpdateAss;

    async void StartGame()
    {
        LoadMetadataForAOTAssemblies();
#if UNITY_EDITOR       
        string hotUpdateName = "HotUpdate";
        _hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == hotUpdateName);
#else
        //_hotUpdateAss = Assembly.Load(ReadBytesFromStreamingAssets(hotUpdateName));
        
        string hotUpdateName = "HotUpdate.dll.bytes";
        Debug.Log($"异步加载资源Key路径:{hotUpdateName}");
        AsyncOperationHandle<TextAsset> handle = Addressables.LoadAssetAsync<TextAsset>(hotUpdateName);
        await handle.Task;
        Debug.Log($"异步加载资源Key状态:{handle.Status}");
        if (handle.Status != AsyncOperationStatus.Succeeded)
        {
            Debug.LogError($"异步加载资源失败,资源Key路径:{hotUpdateName},\n异常 {handle.OperationException}");
            //throw new Exception($"异步加载资源失败,资源Key路径:{hotUpdateName},\n异常 {handle.OperationException}");
        }

        Debug.Log($"异步加载资源大小:{handle.Result.dataSize}");
        _hotUpdateAss = Assembly.Load(handle.Result.bytes);
#endif
        await Task.Yield();
        Type entryType = _hotUpdateAss.GetType("GameEntry");
        entryType.GetMethod("Start").Invoke(null, null);
    }
    
    private void OnHotUpdateLoaded(AsyncOperationHandle<TextAsset> handle)
    {
        Debug.LogError("handle.Status: " + handle.Status);
        if (handle.Status == AsyncOperationStatus.Succeeded)
        {
            TextAsset hotUpdateAsset = handle.Result;
            byte[] assemblyBytes = hotUpdateAsset.bytes;

            // 将程序集读取到内存中
            Assembly hotUpdateAssembly = Assembly.Load(assemblyBytes);
            Type entryType = hotUpdateAssembly.GetType("GameEntry");
            entryType.GetMethod("Start").Invoke(null, null);
        }
        else
        {
            Debug.LogError("Failed to load HotUpdate.dll.bytes: " + handle.OperationException);
        }
    }

    private static List<string> AOTMetaAssemblyFiles { get; } = new List<string>()
    {
        "mscorlib.dll.bytes",
        "System.dll.bytes",
        "System.Core.dll.bytes",
        "Unity.ResourceManager.dll.bytes",
    };
    
    /// <summary>
    /// 为aot assembly加载原始metadata, 这个代码放aot或者热更新都行。
    /// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行
    /// </summary>
    private 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}");
        }
    }
    
    private byte[] ReadBytesFromStreamingAssets(string abName)
    {
        Debug.Log($"ReadAllBytes name: {abName}");
#if UNITY_ANDROID
            AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.PrivacyActivity");
            //jc.CallStatic<byte[]>("getFromAssets", name);
            AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
            byte[] oldBytes = jo.Call<byte[]>("getFromAssets", abName);
            return oldBytes;

            #region 在Android工程中添加读取方法

            // import java.io.File;
            // import java.io.FileInputStream;
            // import java.io.FileNotFoundException;
            // import java.io.FileOutputStream;
            // import java.io.IOException;
            // import java.io.InputStream;

            // public byte[] getFromAssets(String fileName) {
            //     Log.e("****", "getFromAssets:" + fileName);
            //     try {
            //         //得到资源中的Raw数据流
            //         InputStream in = getResources().getAssets().open(fileName);
            //         //得到数据的大小
            //         int length = in.available();
            //
            //         byte[] buffer = new byte[length];
            //             //读取数据
            //             in.read(buffer);
            //             //依test.txt的编码类型选择合适的编码,如果不调整会乱码
            //             //res = EncodingUtils.getString(buffer, "BIG5");
            //             //关闭
            //             in.close();
            //
            //         return buffer;
            //     } catch (Exception e) {
            //         e.printStackTrace();
            //         return null;
            //     }
            // }

            #endregion

#else
        byte[] oldBytes = File.ReadAllBytes(Application.streamingAssetsPath + "/" + abName);
        return oldBytes;
#endif
    }
    
    #endregion
}

3.2 热更代码

支持热更
要做的事入:桥接热更进入,游戏逻辑

桥接热更入口:切换到新场景后,自动启动游戏逻辑

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.SceneManagement;

/// <summary>
/// 游戏入口 -- 热更桥接
/// </summary>
public static class GameEntry
{
    public static void Start()
    {
        Debug.Log("[GameEntry::Start] 热更完成进入游戏场景");
        //SceneManager.LoadScene("Scenes/MainScene");
        Addressables.LoadSceneAsync("MainScene");
    }
}

3.3 打包工具类

拓展编辑器脚本,方便后续打包逻辑:

  1. 编译目标平台热更脚本
  2. 打包AA包资源
  3. 上传资源包到OSS

Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

注意:修改为自己的资源目标路径和远程资源路径

执行后可导出apk或者进行xCode打包

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using HybridCLR.Editor;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Build;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;

namespace Editor
{
    public class BuidlEditorTools
    {
        [MenuItem("Build/1. 编译目标平台热更脚本", false, 301)]
        public static void CompileDllActiveBuildTargetCopy()
        {
            HybridCLR.Editor.Commands.CompileDllCommand.CompileDll(EditorUserBuildSettings.activeBuildTarget);

            Debug.Log($"Compile Dll Active Build Target Copy Finished!");

            CopyDllToAssets();
        }

        // 复制热更的DLL到资源目录,以备用AB包导出
        //[MenuItem("Build/2. 复制热更的DLL到资源目录", false, 301)]
        public static void CopyDllToAssets()
        {
            BuildTarget target = EditorUserBuildSettings.activeBuildTarget;
            string buildDir = SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target);
            // 项目配置的热更dll
            for (int i = 0; i < HybridCLRSettings.Instance.hotUpdateAssemblyDefinitions.Length; i++)
            {
                string fileName = HybridCLRSettings.Instance.hotUpdateAssemblyDefinitions[i].name + ".dll";
                string sourcePath = Directory.GetFiles(buildDir).ToList().Find(hotPath => hotPath.Contains(fileName));
                if (string.IsNullOrEmpty(sourcePath))
                {
                    Debug.Log($"热更程序集不存在: {buildDir} / {fileName}");
                    Debug.LogError($"热更程序集不存在: {buildDir} / {fileName}");
                    continue;
                }

                // 将程序集添加后缀 .bytes 并复制到AB包路径下
                string newFileName = fileName + ".bytes";
                //todo... 你的工程:目标目录路径 //Assets/Res/AOTAssembly/HotUpdate.dll.bytes
                string targetDirectory = Application.dataPath + "/Res/AOTAssembly";

                Debug.Log($"目标目录路径:{targetDirectory} ");
                // 检查源文件是否存在
                if (File.Exists(sourcePath))
                {
                    // 构建目标文件的完整路径
                    string destinationPath = Path.Combine(targetDirectory, newFileName);
                    // 检查目标目录是否存在,如果不存在则创建
                    if (!Directory.Exists(targetDirectory))
                    {
                        Directory.CreateDirectory(targetDirectory);
                    }

                    // 如果目标文件已经存在,则删除
                    if (File.Exists(destinationPath))
                    {
                        File.Delete(destinationPath);
                    }

                    // 将源文件复制到目标目录下,并修改名称
                    File.Copy(sourcePath, destinationPath);
                    // 刷新资源,使其在 Unity 编辑器中可见
                    AssetDatabase.Refresh();
                    Debug.Log("File copied successfully!");
                }
                else
                {
                    Debug.LogError("Source file does not exist!");
                }
            }

            Debug.Log("复制热更的DLL到资源目录 完成!!!");
        }

        [MenuItem("Build/2. 打包AA包资源", false, 302)]
        public static void BuildPackageAB()
        {
            // AddressableAssetSettings.BuildPlayerContent();
            Debug.LogError("去用Addressable Group Build 进行打包AB或者热更AB...");
        }

        [MenuItem("Build/3. 上传资源包到OSS", false, 303)]
        public static async void UpLoadABOSS()
        {
            #region 注释:通过指定的名称获取组别
            // AddressableAssetSettings addressableSettings = AssetDatabase.LoadAssetAtPath<AddressableAssetSettings>("Assets/AddressableAssetsData/AddressableAssetSettings.asset");
            //
            // if (addressableSettings == null)
            // {
            //     Debug.LogError("Addressable Asset Settings not found.");
            //     return;
            // }
            //
            // List<AddressableAssetGroup> groups = addressableSettings.groups;
            // foreach (AddressableAssetGroup group in groups)
            // {
            //     string groupName = group.Name;
            //     string profileName = addressableSettings.activeProfileId;
            //     BundledAssetGroupSchema schema = group.GetSchema<BundledAssetGroupSchema>();
            //
            //     if (schema != null)
            //     {
            //         // Access the configuration data from the profile
            //         Debug.Log($"Group: {groupName}, Profile: {profileName}");
            //         Debug.Log($"Remote Build Path: {schema.BuildPath.GetValue(addressableSettings) as string}");
            //         Debug.Log($"Remote Build Path: {schema.LoadPath.GetValue(addressableSettings) as string}");
            //
            //         // Access other properties as needed
            //     }
            // }

            // // 获取Profile设置
            // AddressableAssetProfileSettings profileSettings = addressableSettings.profileSettings;
            //
            // if (profileSettings == null)
            // {
            //     Debug.LogError("Addressable Profile Settings not found.");
            //     return;
            // }
            //
            // AddressableAssetGroup remoteGroup = addressableSettings.FindGroup("Prefabs");
            // BundledAssetGroupSchema bundledAssetGroupSchema = remoteGroup.GetSchema<BundledAssetGroupSchema>();
            //
            // if (bundledAssetGroupSchema != null)
            // {
            //     string remoteBuildPath = bundledAssetGroupSchema.BuildPath.GetValue(addressableSettings) as string;
            //     string remoteLoadPath = bundledAssetGroupSchema.LoadPath.GetValue(addressableSettings) as string;
            //     Debug.Log($"Remote Build Path 111 : {remoteBuildPath}");
            //     Debug.Log($"Remote Build Path 111 : {remoteLoadPath}");
            // }

            #endregion

            #region 注释:微信小游戏资源地址
            // var config = UnityUtil.GetEditorConf();
            // var uploadResCDN = config.ProjectConf.CDN;
            // if (string.IsNullOrEmpty(config.ProjectConf.DST) || string.IsNullOrEmpty(config.ProjectConf.CDN))
            // {
            //     Debug.LogError("请先在设置项目CDN地址");
            //     return;
            // }
            // var fullResPath = config.ProjectConf.DST + "/webgl";
            #endregion

            // todo... 修改:打包的资源路径
            var fullResPath = Application.dataPath.Replace("Assets","")
                              + "ServerData/" + EditorUserBuildSettings.activeBuildTarget;
            // todo... 修改:上传CDN的资源路径
            var uploadResCDN = "wx/Test/"+ EditorUserBuildSettings.activeBuildTarget;
            
            Debug.Log($"开始上传 {BuildTarget.WebGL.ToString()} 平台的资源,上传目录:{fullResPath}");
            await UploadResToOSS.StartUploadOssClient(fullResPath, uploadResCDN);
            Debug.Log($"上传OSS:{EditorUserBuildSettings.activeBuildTarget.ToString()}平台的完整资源上传成功");
        }
    }
}

四,示例工程源码分享

工程目录作用:
Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二),《Unity 之 Addressable》,unity,游戏引擎,热更新

源码文件在文章开头链接 或 点击下方卡片,回复“华佗”或“热更”获取资源包文章来源地址https://www.toymoban.com/news/detail-821905.html

到了这里,关于Unity 之 Addressable可寻址系统 -- HybridCLR(华佗)+Addressable实现资源脚本全热更 -- 实战(二)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity HybridCLR + Xlua + Addressable 要点记要

    老工程原本是C#,想做热更,于是接入了Xlua和Addressable。由于工程老,人手也不够,只是新代码使用Xlua,老功能(尤其是核心战斗还是C#)。大半年后觉得并不能达到预期需求。于是通过再接入Hybrid来解决最终问题。 HybridCLR 的接入,以及原理这里就不重复说明了,在官网上有

    2024年02月09日
    浏览(48)
  • Unity热更模块基于 HybridCLR + Addressable

    代码地址: GitHub - ManoKing/FFramework: 基于HybridCLR + Addressable的热更新框架,提供例子基于QFramework+URP开发 基于HybridCLR + Addressable的热更新框架,提供例子基于QFramework+URP开发 - GitHub - ManoKing/FFramework: 基于HybridCLR + Addressable的热更新框架,提供例子基于QFramework+URP开发 https://github.

    2023年04月14日
    浏览(47)
  • Unity最新热更新框架 hybridclr_addressable

    GitHub:YMoonRiver/hybridclr_addressable: 开箱即用的商业游戏框架,集成了主流的开发工具。将主流的GameFramework修改,支持Addressable和AssetBundle,已完善打包工具和流程。 (github.com) # 新增GameFramework Addressables 开箱即用 # 新增循环列表 ### UnityGameFramework   **此框架参考:**    `GameFramework

    2024年02月03日
    浏览(49)
  • HyBridCLR(华佗热更)踩坑记录

            官方说明:HybridCLR扩充了il2cpp的代码,使它由纯AOT runtime变成‘AOT+Interpreter’ 混合runtime,进而原生支持动态加载assembly,使得基于il2cpp backend打包的游戏不仅能在Android平台,也能在IOS、Consoles等限制了JIT的平台上高效地以 AOT+interpreter 混合模式执行,从底层彻底支持了

    2024年01月25日
    浏览(65)
  • Unity 如何基于addressable来做资源管理

    一、什么是Addressable系统 Addressable系统是Unity 2018.3版本中推出的一种资源管理方案,它可以帮助我们更好地管理游戏中的资源。在以往的游戏开发中,我们通常使用Resources来进行资源加载,但是这种方式存在很多问题,比如加载速度慢、内存占用高等。Addressable系统可以解决这

    2024年02月16日
    浏览(39)
  • [游戏开发]Unity Addressable打包策略

    Addressables的基础看这篇文章 Addressable全教程 最近公司在写新的框架,决定放弃老的bundle打包方式,使用Addressable,因此我要捋顺新的打包方案,并且解决所有疑问。 打包的最终目的是:build出一堆Bundle文件,而哪些因素会影响出包结果,总结一下有3个关键因素和1个非关键因

    2024年02月17日
    浏览(50)
  • Unity之Addressable使用注意事项

    Profile文件中配置资源构建路径和资源首次加载路径,资源如何设置了缓存,在首次加载之后会将再用缓存在缓存目录,后面将直接从缓存目录中读取,方便项目发包时候进行使用 AddressableAssetSettings文件 DisableCatalogUpdateOnStartup 勾选改选项,禁止自动更新,项目资源下载一般需

    2024年02月08日
    浏览(41)
  • Unity 热更新基础HybridCLR:安装部署(HybridCLR手记二)

    官方地址:  关于HybridCLR (focus-creative-games.github.io) Win 下 打包时遇到 xxxxil2cpplibil2cpputilsIl2CppHashMap.h(71): error C2039: \\\'hash_compare\\\': is not a member of \\\'stdext\\\' 这是.net 7发布后最新版本vs改动打破了一些向后兼容性引起。你可以回退2022的旧版本或者使用2019之类的版本。 所以建议使用

    2024年02月10日
    浏览(39)
  • Unity 热更新基础HybridCLR:Windows平台使用(HybridCLR手记二)

    【注意:官网教程现已完善,请您访问官网进行学习,此教程现已失效,官网版本已更新至4.0以上,感谢您的观看,官网请访问:快速上手 | HybridCLR (code-philosophy.com)】 项目是根据官网的示例工程进行修改的,版本参数如下: unity:2020.3.30(2023.5.29日更新至wolong最新版,经测试依

    2024年02月01日
    浏览(42)
  • Unity中实现HybridCLR热更新

    一:前言 HybridCLR又称作huatuo(华佗)、wolong(卧龙)热更方案,底层是C++编写的,是一种热更新方案,与Lua、ILRuntime等都是不同的热更方案 HybridCLR扩充了il2cpp的代码,使它由纯AOT runtime变成AOT+Interpreter混合runtime,进而支持动态加载assembly,实现热更新 HybridCLR官网 HybridCLR热更

    2024年02月07日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包