Unity资源热更新框架

这篇具有很好参考价值的文章主要介绍了Unity资源热更新框架。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

什么是热更新?

        游戏上线后,玩家下载第一个版本(1G左右或者更大),在之后运营的过程中,如果需要更换UI显示,或者修改游戏的逻辑,这个时候,如果不使用热更新,就需要重新打包,然后让玩家重新下载,很显然体验非常不好。 热更新可以在不重新下载客户端的情况下,更新游戏的内容。 如王者荣耀,经常有下载补丁的时候。

如何判断哪些文件需要更新?

        为了知道我们需要更新的内容,我们就要知道哪些文件发生了改变,或者新增文件?所以我们需要在本地保存一份 需要热更新文件信息(名称,大小、Md5值)的 配置文件。在添加或改变资源时打新的热更包时我们和这个配置文件进行比较,相同资源名称的Md5值不一致,或者在配置文件中找不到该资源配置,就说明这个资源是发生改变或新增的,需要被加进热更包中。

如何实现热更新?

        上面我们知道了哪些文件需要被热更新,那么我们需要把这些文件放到服务器上,并记录这次补丁包的信息(版本信息、第几次热更,以及这些资源的详细信息配置)。用户打开App后会去拉取这个配置文件,并找到最后一次热更的资源信息与本地的资源进行MD5校验,不通过的就加入到热更列表,下载后保存到本地上,下次进入游戏的时候MD5就校验成功不会在出现重新下载服务器资源的情况,至此我们大致的思路就完成了。

资源热更流程图

unity热更新框架,Unity,c#,java,开发语言

打包时记录版本信息及所有资源信息

  • 包名
  • 版本号
  • 资源MD5文件信息(name,文件md5,size)
  • 保存到本地(Xml文件或二进制文件)

一键生成热更资源

如何生成AB包,以及实现: 一键生成热更资源

热更包及配置文件

数据结构:

using System;
using System.Collections.Generic;
using System.Xml.Serialization;

namespace Hot
{
    [Serializable]
    public class GameVersion
    {
        [XmlElement]
        public ServerVersionInfo[] ServerInfo;
    }
    /// <summary>
    /// 当前游戏版本对应的所有补丁
    /// </summary>
    [Serializable]
    public class ServerVersionInfo
    {
        [XmlAttribute]
        public string Version;
        [XmlElement]
        public List<Patches> Patches = new List<Patches>();
    }
    /// <summary>
    /// 一个总补丁包信息
    /// </summary>
    [Serializable]
    public class Patches
    {
        [XmlAttribute]
        public int Version;        // 第几次热更
        [XmlAttribute] 
        public string Desc;
        [XmlElement]
        public List<Patch> patches = new List<Patch>();
    }

    /// <summary>
    /// 单个补丁包信息
    /// </summary>
    [Serializable]
    public class Patch
    {
        [XmlAttribute]
        public string Name;
        [XmlAttribute]
        public string Url;
        [XmlAttribute]
        public long Size;
        [XmlAttribute]
        public string MD5;
    }
}

服务器部署

Apache服务器搭建:

我这边使用Apache: Apache Download
下载后将期解压到需要放置的目录下

找到 Apache24/conf/httpd.conf  将 Define SRVROOT改成Apache的解压目录,端口号默认时80,如果被占用可以自行修改

Define SRVROOT "F:\WebServer/Apache24"

unity热更新框架,Unity,c#,java,开发语言

 运行 httpd.exe文件,测试可以在浏览器下访问 localhost 可以方位代表成功

服务器文件部署:

在  ...\Apache24\htdocs 文件夹下新建存放需要热更的AssetBundle的文件

unity热更新框架,Unity,c#,java,开发语言

 文件夹0.1: 版本文件夹,

文件夹 1: 第一次需要热更的资源

添加在服务器里添加GameVersion.xml文件:对应上面的 ServerInfo数据结构,每次有新的热更包时就往xml里添加 Patche.xml里的内容,需要回退的话只需要删除对应Patches的补丁配置

<?xml version="1.0"?>
<GameVersion>
    <ServerInfo Version="1.0.1">
        <Patches Version="1" Desc="测试热更">
            <patches Name="AssetBundle" Url="http://127.0.0.1/AssetBundle/1.0.1/1/AssetBundle" Size="1130" MD5="7baa969436d20f0e1b8a41e78d3cb23d" />
            <patches Name="audio" Url="http://127.0.0.1/AssetBundle/1.0.1/1/audio" Size="20555034" MD5="f4fc534e2615ca3a0c199e29a4f22df6" />
            <patches Name="image" Url="http://127.0.0.1/AssetBundle/1.0.1/1/image" Size="563082" MD5="1da1ee923a73386d0871f0287954ffa4" />
            <patches Name="material" Url="http://127.0.0.1/AssetBundle/1.0.1/1/material" Size="8010" MD5="08fc079c4a19f701c3e914e2d591f7a8" />
            <patches Name="prefab" Url="http://127.0.0.1/AssetBundle/1.0.1/1/prefab" Size="5034" MD5="90bd7fb6bee13fdabc642bb445dbaa98" />
        </Patches>
    </ServerInfo>
</GameVersion>

到这里热更新的准备都已完成,接下来就是实现热更流程

文件下载基类

using System;
using System.Collections;
using System.IO;

namespace Hot
{
    public abstract class DownloadItemBase
    {
        protected string url;
        public string Url => url;
        
        protected string fileName;
        public string FileName => fileName;
        
        protected string fileNameWithoutExt;
        public string FileNameWithoutExt => fileNameWithoutExt;

        protected string ext;
        public string Ext;

        protected string fullName;
        public string FullName => fullName;
        
        protected string fullNameWithoutExt;
        public string FullNameWithoutExt => fullNameWithoutExt;

        protected long size;
        public long Size => size;

        protected bool isLoading = false;
        public bool IsLoading = false;

        public DownloadItemBase(string savePath,string url,long size)
        {
            isLoading = false;
            this.url = url;
            fileNameWithoutExt = Path.GetFileNameWithoutExtension(url);
            ext = Path.GetExtension(url);
            fileName = Path.GetFileName(url);
            fullName = $"{savePath}/{fileName}";
            fullNameWithoutExt = $"{savePath}/{fileNameWithoutExt}";
            this.size = size;
        }
        
        public abstract void Destroy();
        
        public abstract IEnumerator StartDownload(Action<bool> callBack);
        
        public abstract float GetCurProgress();
    }
}

AB包文件下载类

using System;
using System.Collections;
using Core.Utlis;
using UnityEngine;
using UnityEngine.Networking;

namespace Hot
{
    public class ABDownloadItem:DownloadItemBase
    {
        private UnityWebRequest webRequest;
        public ABDownloadItem(string savePath, string url, long size) : base(savePath, url, size)
        {
        }
        
        public override void Destroy()
        {
            webRequest.Dispose();
        }
        
        public override IEnumerator StartDownload(Action<bool> callBack = null)
        {
            webRequest = UnityWebRequest.Get(Url);
            webRequest.timeout = 30;
            isLoading = true;
            yield return webRequest.SendWebRequest();
            if (webRequest.result == UnityWebRequest.Result.Success)
            {
                FileUtils.SaveFile(FullName, webRequest.downloadHandler.data);
                if(null != callBack) callBack(true);
            }
            else
            {
                Debug.LogError($"download {Url} fail err: {webRequest.error}");
                if(null != callBack) callBack(false);
            }
            isLoading = false;
        }
        
        public override float GetCurProgress()
        {
            return webRequest != null ? webRequest.downloadProgress : 0;
        }

    }
}

核心热更新管理类

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Build;
using Core.Base;
using Core.Utlis;
using UnityEngine;
using UnityEngine.Networking;

namespace Hot
{
    public class HotManager:Singleton<HotManager>
    {
        private string ServerGameVersionPath = $"{Application.persistentDataPath}/GameVersion.xml";
        private string version;
        private string packageName;
        private GameVersion gameVersion;
        private string hotDesc;
        public string HotDesc => hotDesc;
        
        // 服务器上需要热更的补丁包
        private List<Patch> serverHotPatches = new List<Patch>();
        private Dictionary<string,Patch> serverHotPatchDic = new Dictionary<string, Patch>();
        // 需要下载的补丁包
        private List<Patch> downLoadPatchs = new List<Patch>();
        // 下载完成的补丁包
        private List<Patch> alreadyPatchLists = new List<Patch>();
        private ABDownloadItem curDownloadItem;
        private int reloadCount = 0;
        private float hotAllSize = 0;
        public float HotAllSize => hotAllSize;

        private bool isLoading = false;
        public bool IsLoading => isLoading;
        // 加载完成回调
        private Action hotCompeleteHandler;
        // 加载失败回调
        private Action<List<Patch>> hotFailHandler;

        private MonoBehaviour corMono;
        public void Init(MonoBehaviour mono)
        {
            corMono = mono;
        }

        /// <summary>
        /// 检查版本是否需要更新
        /// </summary>
        /// <param name="callBack"></param>
        public void CheckVersionNeedHot(Action<bool> callBack)
        {
            VersionInfo versionInfo = XmlSerializerOpt.Deserialize<VersionInfo>(PathUtlis.LOCAL_VERSION_PATH);
            version = versionInfo.Version;
            packageName = versionInfo.PackageName;

            corMono.StartCoroutine(LoadServerGameVersion(() =>
            {
                // 判断是否需要热更
                GetServerPatches();
                CheckDownloadPatches();
                hotAllSize = serverHotPatches.Sum(x => x.Size);
                callBack(downLoadPatchs.Count > 0);
            }));
        }

        private IEnumerator LoadServerGameVersion(Action callBack)
        {
            UnityWebRequest webRequest = UnityWebRequest.Get("http://127.0.0.1/GameVersion.xml");
            webRequest.timeout = 30;
            yield return webRequest.SendWebRequest();

            if (webRequest.result != UnityWebRequest.Result.Success)
            {
                Debug.LogError($"加载服务器游戏配置失败: {webRequest.error}");
            }
            else
            {
                Debug.Log(ServerGameVersionPath);
                if(File.Exists(ServerGameVersionPath)) File.Delete(ServerGameVersionPath);
                FileUtils.SaveFile(ServerGameVersionPath,webRequest.downloadHandler.data);
                gameVersion = XmlSerializerOpt.Deserialize<GameVersion>(ServerGameVersionPath);
            }

            callBack();
        }

        private void GetServerPatches()
        {
            if (gameVersion != null && gameVersion.ServerInfo != null)
            {
                for (int i = 0; i < gameVersion.ServerInfo.Length; i++)
                {
                    if (gameVersion.ServerInfo[i].Version == version)
                    {
                        List<Patches> patches = gameVersion.ServerInfo[i].Patches;
                        if (patches != null && patches.Count > 0)
                        {
                            serverHotPatches = patches[patches.Count - 1].patches;
                            hotDesc = patches[patches.Count - 1].Desc;
                        }
                        break;
                    }
                }
            }
        }

        // 检查需要去下载的补丁
        private void CheckDownloadPatches()
        {
            downLoadPatchs.Clear();
            for (int i = 0; i < serverHotPatches.Count; i++)
            {
                serverHotPatchDic.Add(serverHotPatches[i].Name, serverHotPatches[i]);
                AddDownloadPatch(serverHotPatches[i]);
            }
        }

        private void AddDownloadPatch(Patch patch)
        {
            string savePath = $"{PathUtlis.LocalAssetBundlePath}/{patch.Name}";
            if (!File.Exists(savePath))
            {
                downLoadPatchs.Add(patch);
            }
            else
            {
                if (patch.MD5 != MD5Utils.GenerateMD5(savePath))
                {
                    downLoadPatchs.Add(patch);
                }
            }
        }

        public void StartHot(Action hotCompeleteHandler,Action<List<Patch>> hotFailHandler)
        {
            this.hotCompeleteHandler = hotCompeleteHandler;
            this.hotFailHandler = hotFailHandler;
            corMono.StartCoroutine(StartLoad());
        }

        private IEnumerator StartLoad(List<Patch> patches = null)
        {
            if (patches == null)
            {
                patches = downLoadPatchs;
            }

            if (!Directory.Exists(PathUtlis.LocalAssetBundlePath))
                Directory.CreateDirectory(PathUtlis.LocalAssetBundlePath);
            
            List<ABDownloadItem> downloadItems = new List<ABDownloadItem>();
            for (int i = 0; i < patches.Count; i++)
            {
                downloadItems.Add(new ABDownloadItem(PathUtlis.LocalAssetBundlePath,patches[i].Url,patches[i].Size));
            }

            isLoading = true;
            for (int i = 0; i < downloadItems.Count; i++)
            {
                ABDownloadItem item = downloadItems[i];
                curDownloadItem = item;
                yield return corMono.StartCoroutine(item.StartDownload((success) =>
                {
                    if (success)
                    {
                        Patch patch = FindPatch(item.FileName);
                        if (patch != null)
                        {
                            if(!alreadyPatchLists.Contains(patch)) alreadyPatchLists.Add(patch);
                        }
                    }
                    else
                    {
                        Debug.LogError($"{item.FileName} 下载失败,尝试重新下载");
                    }
                    item.Destroy();
                }));
            }

            // 重新比较文件md5,避免文件下载失败
            yield return VerifyMD5(downLoadPatchs);
        }

        //校验下载后的文件
        private IEnumerator VerifyMD5(List<Patch> patches)
        {
            List<Patch> downPatchList = new List<Patch>();
            for (int i = 0; i < patches.Count; i++)
            {
                Patch patch = patches[i];
                string savePath = $"{PathUtlis.LocalAssetBundlePath}/{patch.Name}";
                if (!File.Exists(savePath))
                {
                    downPatchList.Add(patch);
                }
                else
                {
                    if (patch.MD5 != MD5Utils.GenerateMD5(savePath))
                    {
                        downPatchList.Add(patch);
                    }
                }
            }
            
            if (downPatchList.Count > 0)
            {
                reloadCount++;
                if (reloadCount < 5)
                {
                    yield return corMono.StartCoroutine(StartLoad(downPatchList));
                }
                else
                {
                    isLoading = false;
                    if (null != hotFailHandler) hotFailHandler(downPatchList);
                }
            }
            else
            {
                isLoading = false;
                if (null != hotCompeleteHandler) hotCompeleteHandler();
            }
        }

        private Patch FindPatch(string name)
        {
            Patch patch = null;
            serverHotPatchDic.TryGetValue(name, out patch);
            return patch;
        }

        public float GetProgress()
        {
            float loadedSize = alreadyPatchLists.Sum(x => x.Size);
            float curloadSize = curDownloadItem.GetCurProgress() * curDownloadItem.Size;
            float progress = (loadedSize + curloadSize) / hotAllSize;
            return progress;
        }
    }
}

UI测试效果图

unity热更新框架,Unity,c#,java,开发语言unity热更新框架,Unity,c#,java,开发语言

还有个问题,这样下载下来的资源会直接被别人拿走使用,为了数据的安全,我们可以对资源进行加密处理,我使用的是AES,也没有什么难点,就是在一键生成AB包后使用AES对文件加密,然后加载资源的时候使用 字节数组加载,LoadFromMemory的缺点就是多占一份内存,对于内

存吃紧的就不适合用了,或者参考:Unity3D加密Assetbundle(不占内存)

    private void DecryptAssetBundle()
    {
        string abPath = Path.Combine(PathUtlis.AssetBundlePath, path);
        // 解密被加载的AB包
        byte[] result = AESUtils.AESFileDecryptToByte(abPath,"ENCRYPT_KEY");
        if (result == null)
        {
            Debug.LogError($"AES Decrypt {abPath} file fail");  
            return;
        }
        AssetBundle asset = AssetBundle.LoadFromMemory(result);
    }

一下是项目Damo地址:
https://download.csdn.net/download/weixin_41316824/87937390文章来源地址https://www.toymoban.com/news/detail-691456.html

到了这里,关于Unity资源热更新框架的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Unity框架学习--资源管理器

    1、Inspector窗口拖拽         在脚本中用public声明变量,然后在Inspector窗口把要加载的资源拖拽给该脚本的变量。         不建议在大型项目使用。在公司的项目也不要用。         如果你是独立游戏开发者,则可以用。         不支持热更新。 2、Resources         用Reso

    2024年02月12日
    浏览(47)
  • Unity 之 Addressable可寻址系统 -- 资源热更新 -- 进阶(四)

    概述:Unity可寻址系统的资源热更是我遇到过最简单的热更方式了。只需修改资源组然后发布资源热更即可。本篇文章就来为讲解AA的资源热更,并通过CCD实现资源热更完整流程。 Addressables 将资源的引用和打包分开处理,可加快运行模式下和运行版本的项目迭代。系统将资源

    2023年04月08日
    浏览(77)
  • Unity 热更新技术 |(七)完整热更新实战案例学习使用,包括资源热更新及代码热更新【文末书】

    🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN 🙉 🎄 学习专栏推荐:Unity系统学习专栏 🌲 游戏制作专栏推荐:游戏制作 🌲Unity实战100例专栏推荐:Unity 实战100例 教程 🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📆 未来很长

    2023年04月17日
    浏览(41)
  • unity tolua热更新框架教程(2)

    增加脚本luamain,继承luaclient 建立第一个场景GameMain,在对象GameMain挂载脚本LuaMain,启动场景 看到打印,lua被成功加载 lua入口及调用堆栈 这里会执行main.lua文件的main函数 在此处配置C#导出的代码 实例 导出logexport类,添加代码后 生成wrap文件 这样lua即可调用C#函数 luamain添加

    2024年02月09日
    浏览(33)
  • unity打造基于xLua热更新框架

    xLua是一款基于Lua的热更新框架,它可以在游戏运行时动态加载Lua脚本,实现游戏逻辑的更新。在使用xLua框架之前,我们需要先了解一些基础知识。 对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游戏开发的技术大佬,欢迎你

    2024年02月16日
    浏览(52)
  • Unity 3D模型展示框架篇之资源打包、加载、热更(二)

    本项目将整合之前Unity程序基础小框架专栏在Unity 3D模型展示项目基础上进行整合,并记录了集成过程中对原脚本的调整过程。增加了Asset Bundle+ILRuntime热更新技术流程。 本篇文章介绍如何对更新进行代码检测以及使用更新资源服务器进行资源热更新。 创建登录UI预制体 Log

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

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

    2024年02月03日
    浏览(50)
  • 【Unity3D热更新】Unity3D 零成本、高性能的C#的热更新框架:HybridCLR

    推荐阅读 CSDN主页 GitHub开源地址 Unity3D插件分享 简书地址 我的个人博客 QQ群:1040082875 大家好,我是佛系工程师 ☆恬静的小魔龙☆ ,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 相信只要做过热更新的小伙伴,都被热更新搞过心态吧。 我有一个小伙伴,本来是面向

    2024年01月16日
    浏览(53)
  • Unity Xlua热更新框架(五):Lua和UI管理

    :::info Lua存在两种加载器,一种默认加载器(env.DoString(\\\"require(‘test’)\\\"直接用了默认加载其),直接调用StreamingAssets中的脚本);一种是自定义加载器(env.AddLoader(Envpath)),优先于默认加载器(下文DoString就是从自定义加载器的路径读取的),并且当Lua代码执行require函数时,

    2024年02月06日
    浏览(55)
  • Unity之如何接入HybridCLR(代号wolong,原huatuo)热更新框架

    HybridCLR(代号wolong)是一个特性完整、零成本、高性能、低内存的近乎完美的Unity全平台原生c#热更方案。 官方提供了完整的教程:HybridCLR快速上手 官方还提供了一个简易的小Demo,演示了如何使用HybridCLR实现热更新。hybridclr_trial 官方文档其实已经非常详细了,我写本文目的主要

    2024年02月12日
    浏览(44)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包