HybridCLR+Adressable+Springboot热更

news/2025/2/26 1:46:39

本文章会手把手教大家如何搭建HybridCLR+Adressable+Springboot热更

创作不易,动动发财的小手点个赞。

安装华佗

首先我们按照官网的快速上手指南搭建一个简易的项目:

快速上手 | HybridCLR

注意在热更的代码里添加程序集。把用到的工具放到程序集里。

local程序集:这个程序集不热更,跟游戏一起打包:

注意:不能把热更的代码放到local程序集里,local程序集只能调用非热更代码。

安装Adressable:

 然后开始配置Adressable:

系统配置,没什么需要强调的,根据需求点。

 注意,我是用的是自己的config动态修改打包的位置,配置文件在下面:

自定义远端:

使用host(看后面): 这个也是根据需求点就行。

如果你没有自己的服务器,可以使用Addressable自带的host工具(注意修改配置文件里的信息):

Addressable和工具的config文件:

public  class FrameworkConfig
{
   public static string DownLoadPath = "D:/Desktop/local/test";//打包后,Adressable缓存地址(外部{}引用)
   public static string RemotePath = "http://47.xxx.43.98/files/";//Adressable的服务器地址(外部{}引用)
   public static string BaseUrl = "http://47.xxx.43.98/";
   public static string UploadPath = "http://47.xxx.43.98/upload";//打好的Addressable包的上传的地址
   public static string DeletePath = "http://47.xxx.43.98/files";//删除服务器远端仓库的请求地址
   public static string LoginPath = "http://47.xxx.43.98/login";//登录服务器远端仓库的请求地址
   public static string LogoutPath = "http://47.xxx.43.98/logout";//登出服务器远端仓库的请求地址
   public static string PackPath=@"D:\GameClient\game-client\client\ServerData\StandaloneWindows64";//打好的本地Addressable包的地址
   //   public static string RemoteBuildPath = "ServerData/[BuildTarget]";Build地址需要在Addressable里改
   public static string DLLName = "HotUpdate.dll.bytes";//热更dll在group中的索引
   public static string StartSceneName="Assets/HotUpdate/Scenes/StartScene.unity";//更新后启动场景的group中的索引
   public static string DLLPath = @"../HybridCLRData/HotUpdateDlls/StandaloneWindows64/HotUpdate.dll";//热更dll打包后迁移前的位置
   public static string NewDLLPath = "HotUpdate/Dlls";//热更dll打包后迁移后的位置
   public static string LevelJsonPosition = "D:\\Desktop\\local\\pos.json"; //地图编辑器生成的地图文件的地址
   
}

Q:为什么ip后面还有,A:因为Springboot服务器的http请求需要把写入删除拉取区分。

热更打包,注意把左上角的profile改成自己的(我用的是remote,默认是defaut),给每个包打上标签(更新使用)

热更逻辑:

我们的代码热更方式就是:用Hybrid打出一个热更的dll,然后把dll转存为比特文件,放到Addressable包里,热更到本地后加载新的dll。

启动逻辑:build一个场景,里面放CheckAssetsUpdate 脚本,在所有包体下载完成后,加载包中的StartScene场景。startScene场景里用代码启动游戏启动逻辑。

using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
using static UnityEngine.Rendering.VirtualTexturing.Debugging;

public class CheckAssetsUpdate : MonoBehaviour
{
    private AsyncOperationHandle<long> downloadHandle;
    AsyncOperationHandle remote;
    private StaticLoadingPage loadPage;

    void Start()
    {

        LoadDefDLL();
        StartCoroutine(CheckUpdate());
        loadPage=GetComponent<StaticLoadingPage>();
    }
    private void LoadDefDLL()
    {
        //����dll
        Debug.Log("Starting to check and download assets with label: all");
        List<string> aotDllList = new List<string>
        {
            "System.Core.dll",
            "System.dll",
            "Unity.Addressables.dll",
            "Unity.ResourceManager.dll",
            "UnityEngine.CoreModule.dll",
            "mscorlib.dll",
        };
        foreach (var dllName in aotDllList)
        {
            byte[] dllBytes = File.ReadAllBytes($"{Application.streamingAssetsPath}/{dllName}");
            LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, HomologousImageMode.SuperSet);
            if (err != LoadImageErrorCode.OK)
            {
                Debug.LogError($"Failed to load AOT DLL: {dllName}, Error: {err}");
                // If any AOT DLL fails to load, stop the process
            }
            else
            {
                Debug.Log($"{dllName} 加载成功");
            }
        }
    }
    private IEnumerator CheckUpdate()
    {

        downloadHandle = Addressables.GetDownloadSizeAsync("all");
        //Debug.Log("加载"+ downloadHandle);
        yield return downloadHandle;
        Debug.Log("检查下载资源");
        if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
        {
            if (downloadHandle.Result <= 0)
            {
                Debug.Log("没有更新");
                EnterGame();
            }
            else
            {
                Debug.Log("更新游戏");
                StartCoroutine(Download());
           
            }
        }
        yield return null;
    }

    IEnumerator Download()
    {
        remote = Addressables.DownloadDependenciesAsync("all", true);
        while (!remote.IsDone)
        {
            var bytes = remote.GetDownloadStatus().DownloadedBytes;
            var totalBytes = remote.GetDownloadStatus().TotalBytes;
            var status = remote.GetDownloadStatus();
            float progress = status.Percent;
            Debug.Log($"Download progress : {progress}");
            loadPage.Loading(progress);
            yield return null;
        }

        EnterGame();
    }
    void EnterGame()
    {
 
        Debug.Log("加载了:HotUpdate.dll"+ remote);
        var loadDllAsync = Addressables.LoadAssetAsync<TextAsset>(FrameworkConfig.DLLName);
        loadDllAsync.Completed += OnHotUpdateDllLoaded;
    }

    void OnHotUpdateDllLoaded(AsyncOperationHandle<TextAsset> handle)
    {
        if (handle.Status == AsyncOperationStatus.Succeeded)
        {
           
            Debug.Log("DLL 加载完毕");

            Assembly hotUpdate = null;
            try
            {
                hotUpdate = Assembly.Load(handle.Result.bytes);
                Debug.Log("加载游戏");

                //GameRoot.Instance.Init();
                AsyncOperationHandle<SceneInstance> lastHandle= Addressables.LoadSceneAsync(FrameworkConfig.StartSceneName, LoadSceneMode.Single);
                lastHandle.Completed += (o) =>
                {
                    loadPage.Loading(1);
                    Destroy(loadPage.loadingCanvas.gameObject,2);

                };
            }
            catch (Exception ex)
            {
                Debug.LogError("DLL加载错误: " + ex.Message);
                return;
            }
      
            
   
        }

   
    }
}

报错解决文档

专门记录一些坑,遇到报错问题可以来这里解决:

【有道云笔记】HybridCLR+Addressables热更
https://note.youdao.com/s/2QhPpppU

或者去官网。

源码:

larito/GameClient (客户端)

larito/StaticServer (静态服务器)


http://www.niftyadmin.cn/n/5867049.html

相关文章

本地部署DeepSeek-R1(Ollama+Docker+OpenWebUI知识库)

安装Ollama 打开 Ollama官网 https://ollama.com/下载安装 Ollama服务默认只允许本机访问&#xff0c;修改允许其它主机访问 OLLAMA_HOST0.0.0.0 ollama serve也可以添加系统环境变量 都知道模型体积很大&#xff0c;顺便也通过环境变量修改模型存放位置&#xff0c;我这…

最长递增子序列(贪心算法)思路+源码

文章目录 题目[](https://leetcode.cn/problems/longest-increasing-subsequence/)算法原理源码总结题目 首先,要掌握动态规划加二分查找 算法原理 1.回顾dp的解法 状态表示:dp[i]表示:以i位置的元素为结尾的所有的子序列中,最长递增子序列的长度 状态转移方程:dp[i]= m…

2. MySQL的数据目录(详解讲解)

2. MySQL的数据目录(详解讲解) 文章目录 2. MySQL的数据目录(详解讲解)1. MySQL8 的主要目录结构1.1 相关命令目录1.2 配置文件目录 2. 数据库和文件系统的关系2.1 查看默认数据库2.2 数据库在文件系统中的表示 3. 表在文件系统中的表示3.1 InnoDB存储引擎模式3.2 MyISAM存储引…

【HeadFirst系列之HeadFirstJava】第5天之超强力方法 —— 从战舰游戏到循环控制

编写程序&#xff1a;超强力方法 —— 从战舰游戏到循环控制 在《Head First Java》的第五章节中&#xff0c;作者通过一个简单的战舰游戏示例&#xff0c;深入讲解了如何编写Java程序&#xff0c;并重点介绍了方法和循环控制的使用。这一章节的核心思想是&#xff1a;通过模块…

MySQL 最左前缀原则:原理、应用与优化

目录 引言 什么是复合索引&#xff1f; 什么是最左前缀原则&#xff1f; 示例 最左前缀原则的原理 最左前缀原则的应用场景 1. 等值查询 2. 范围查询 3. 部分列查询 4. 排序和分组 最左前缀原则的优化技巧 1. 合理设计复合索引 2. 避免跳过索引列 3. 覆盖索引 4.…

API技术深度解析:构建高效、安全与可扩展的接口服务

在当今的数字化时代&#xff0c;应用程序接口&#xff08;API&#xff0c;Application Programming Interface&#xff09;已成为连接不同软件系统和服务的桥梁&#xff0c;是推动数字化转型的关键技术之一。无论是企业内部系统集成、第三方服务接入&#xff0c;还是面向开发者…

[java基础-JVM篇]2_垃圾收集器与内存分配策略

学习权重30%。 理解问题&#xff1a; 垃圾收集器的特点与 运作原理&#xff0c;JVM自动内存分配与回收的主要规则。 目录 判断对象存活 引用计数法Reference Counting 缺陷 可达性分析&#xff08;Reachability Analysis&#xff09;算法 可作为GC Roots的对象 引用的扩展 进行…

CI/CD的定义

CI/CD&#xff08;持续集成/持续部署&#xff09;是一种软件开发实践&#xff0c;旨在通过自动化构建、测试和部署过程&#xff0c;提高软件开发的效率和质量。以下是对CI/CD的详细介绍&#xff1a; CI/CD的定义 持续集成&#xff08;CI&#xff09;Continuous Integration (…