栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 游戏开发 > 其他

【Unity资源热更之路 第一步资源打包】

其他 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Unity资源热更之路 第一步资源打包】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

Unity资源热更之路  第一步资源打包
  • 前言
  • 一、热更是什么?
  • 二、打包步骤
    • 1.给资源命名并打包
    • 2.生成打包资源的配置文件
  • 总结


前言

    目前在国内的大环境下,基本手游除了单机,大部分在线游戏都采用的是热更新,所以想成为一个初级的Unity游戏开发者,基本的热更流程是有必要掌握的。


一、热更是什么?

    热更简单来说就是一种不需要更新应用包的在线资源更新方法,能快速有效帮助开发者更新修复bug和修改功能,也能简化用户更新流程。

二、打包步骤 1.给资源命名并打包

    简单介绍一下流程,首先先设定工具项,然后为所有要打包的资源命名,命名完成执行打包
主要代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public class TestHot:MonoBehaviour
{
    public const string ConfigBundleDir = "Data/Config/";
    public const string ExtName = ".unity3d"; //(.unity3d)素材扩展名
    /// 
    /// 导出资源
    /// 
    [MenuItem("工具/资源打包/生成资源")]
    public static void TryBuildAllAssetBundles()
    {
		//这个工具类下面会写        
        EditorCoroutineRunner.StartEditorCoroutine(BuildAllAssetBundles());
    }


    public static IEnumerator BuildAllAssetBundles()
    {
        //这里使用时间来计算唯一值,判定是否要查询有文件需要热更
        //也可以自己定义一个资源版本号判断

        DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(2022, 1, 1));
        int ver = ((int)((DateTime.Now - startTime).TotalMinutes));

        yield return null;
        MakeAssetBundleNames();
        yield return null;

        var outputPath = Application.streamingAssetsPath.Replace("\", "/");

        yield return new WaitForSeconds(0.3f);

        BuildPipeline.BuildAssetBundles(outputPath, BuildAssetBundleOptions.None, EditorUserBuildSettings.activeBuildTarget);
        yield return new WaitForSeconds(0.3f);
		//生成资源文件
        CreateAssetBundleFileInfo(ver);
        yield return null;
        AssetDatabase.Refresh();
        yield break;
    }

    //给资源设置名称(设定为路径名)
    public static void MakeAssetBundleNames()
    {
        string baseBunldDir = "Assets/Resources/";
        // 设置新的资源名
        foreach (string filepath in Directory.GetFiles(baseBunldDir, "*.*", SearchOption.AllDirectories))
        {
            if (filepath.EndsWith(".meta")) continue;

            var importer = AssetImporter.GetAtPath(filepath);
            if (importer == null)
            {
                continue;
            }

            string bundleName = filepath.Substring(baseBunldDir.Length);

            bundleName = bundleName.Replace("\", "/").ToLower();
            
            if (bundleName.StartsWith(ConfigBundleDir.ToLower()))  //config全部打到一个文件夹中
            {
                bundleName = StringUtil.SubstringIndexOf(bundleName, '/', 1);
            }
            importer.assetBundleName = bundleName + ExtName;
            UnityEngine.Debug.Log("ab包名称:" + bundleName + ExtName);
            if (bundleName.IndexOf(" ") != -1)
                UnityEngine.Debug.LogError(bundleName);
        }

        Debug.Log("设置全部资源AssetBundle名称完成!");
    }
}

public class StringUtil
{
    /// 
    /// 字符串截取
    /// 
    /// 字符串
    /// 截取字符
    /// 从c出现的的次数索引开始截 -1不截取, 0第一次,1第二次截
    /// 
    public static string SubstringIndexOf(string str, char c, int cIndex)
    {
        if (cIndex < 0)
            return str;
        int sum = 0;
        int lastIndex = -1;
        for (int i = 0; i < str.Length; i++)
        {
            if (str[i] == c)
            {
                if (cIndex == sum)
                    return str.Remove(i);
                sum++;
                lastIndex = i;
            }
        }
        if (lastIndex == -1)
            return str;
        return str.Remove(lastIndex);
    }
}


  新建一个工具类,工具类代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class EditorCoroutineRunner
{
    private class EditorCoroutine : IEnumerator
    {
        private Stack executionStack;

        public EditorCoroutine(IEnumerator iterator)
        {
            this.executionStack = new Stack();
            this.executionStack.Push(iterator);
        }

        public bool MoveNext()
        {
            IEnumerator i = this.executionStack.Peek();

            if (i.MoveNext())
            {
                object result = i.Current;
                if (result != null && result is IEnumerator)
                {
                    this.executionStack.Push((IEnumerator)result);
                }

                return true;
            }
            else
            {
                if (this.executionStack.Count > 1)
                {
                    this.executionStack.Pop();
                    return true;
                }
            }

            return false;
        }

        public void Reset()
        {
            throw new System.NotSupportedException("This Operation Is Not Supported.");
        }

        public object Current
        {
            get { return this.executionStack.Peek().Current; }
        }

        public bool Find(IEnumerator iterator)
        {
            return this.executionStack.Contains(iterator);
        }
    }

    private static List editorCoroutineList;
    private static List buffer;

    public static IEnumerator StartEditorCoroutine(IEnumerator iterator)
    {
        if (editorCoroutineList == null)
        {
            editorCoroutineList = new List();
        }
        if (buffer == null)
        {
            buffer = new List();
        }
        if (editorCoroutineList.Count == 0)
        {
            EditorApplication.update += Update;
        }

        buffer.Add(iterator);

        return iterator;
    }

    private static bool Find(IEnumerator iterator)
    {
        foreach (EditorCoroutine editorCoroutine in editorCoroutineList)
        {
            if (editorCoroutine.Find(iterator))
            {
                return true;
            }
        }

        return false;
    }

    private static void Update()
    {
        editorCoroutineList.RemoveAll
        (
            coroutine => { return coroutine.MoveNext() == false; }
        );
        
        if (buffer.Count > 0)
        {
            foreach (IEnumerator iterator in buffer)
            {
                if (!Find(iterator))
                {
                    editorCoroutineList.Add(new EditorCoroutine(iterator));
                }
            }

            buffer.Clear();
        }

        if (editorCoroutineList.Count == 0)
        {
            EditorApplication.update -= Update;
        }
    }
}

2.生成打包资源的配置文件

  在之前打包TestHot类中继续添加以下代码代码:

    /// 
    /// 生成打包资源文件列表
    /// 
    public static void CreateAssetBundleFileInfo(int ver)
    {
        string abRootPath = Application.streamingAssetsPath.Replace("\", "/"); ;
        string abFilesPath = abRootPath + "/" + "ABPackSet.txt";
        if (File.Exists(abFilesPath))
            File.Delete(abFilesPath);

        var abFileList = new List(Directory.GetFiles(abRootPath, "*" + ExtName, SearchOption.AllDirectories));

        FileStream fs = new FileStream(abFilesPath, FileMode.CreateNew);
        StreamWriter sw = new StreamWriter(fs);

        sw.WriteLine(ver + "|" + DateTime.Now.ToString("u"));
        for (int i = 0; i < abFileList.Count; i++)
        {
            string file = abFileList[i];
            long size = 0;
            string md5 = MD5File(file, out size);
            string value = file.Replace(abRootPath, string.Empty).Replace("\", "/");
            sw.WriteLine(value + "|" + md5 + "|" + size);
        }

        sw.Close();
        fs.Close();
        Debug.Log("ABPackSet文件生成完成");
    }

    /// 
    /// 计算文件的MD5值,并返回文件大小
    /// 
    public static string MD5File(string file, out long size)
    {
        if (!File.Exists(file))
        {
            size = 0;
            return "";
        }
        FileStream fs = new FileStream(file, FileMode.Open);
        size = fs.Length;
        MD5 md5 = new MD5CryptoServiceProvider();
        byte[] retVal = md5.ComputeHash(fs);
        fs.Close();

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < retVal.Length; i++)
        {
            sb.Append(retVal[i].ToString("x2"));
        }
        return sb.ToString();
    }

    最后说一下,我从代码中不难看出我将要打包资源的路径设置为了Assets/Resources/ 文件夹下面(记得放一些资源再测试),而生成路径问我设置为Application.StreamAssets(我这里是window平台测试,安卓要注意),最后再下图所示地方生成就OK了。


总结

    目前完成了热更的第一步,资源打包并生成相应配置,其中打包资源配置文件尤为重要。这里简单将一下个人对热更流程浅显的理解,热更一般每次打完ab包后,打包资源配置文件和ab包放到资源服务器上,然后,用户启动应用时就能比较资源版本号判断是否需要更新。如果需要更新进而判断每个文件的MD5值,因为文件只要改变了,MD5值也会随之改变,所以就能知道是哪个ab包改变了就下载更新哪个。最后下载文件替换ab包完成更新。
    每个人对热更都有自己的方法,我在这里算是抛砖引玉了。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/901440.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号