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包完成更新。
每个人对热更都有自己的方法,我在这里算是抛砖引玉了。



