Unity3D热更新实战演练

作者:朔宇
大家好,给大家介绍一下,封面是我老婆。

前两篇文章中,我们了解了Unity中的热更新及热更新方案Tolua的使用方法,在本文中,我们将会通过一个具体的案例,来了解Unity中热更新的使用方法。
完整的Tolua热更新框架读者可以在基于uGUI+tolua的简单游戏框架下载:https://github.com/jarjin/LuaFramework_UGUI,并且学习。本文中的案例及代码旨在为读者提供热更新思路。
热更新流程
在unity中,C#部分无法热更新,我们需要热更的只有lua代码部分。项目完成并上线运营时,我们需要部署资源配置列表到服务器,接下来我们便可以将lua代码及相关资源上传至服务器。当游戏运行时首先需要从服务器上的资源配置列表中按照我们规定的查询方式(如MD5/CRC)来查询其资源是否为最新,若有新的资源便可更新至本地,更新完成后,启动游戏。 我们可以从下图来了解具体的流程:

Demo
和之前一样,为了让读者更清晰的了解unity热更新,我们通过一个实际的案例来进行学习和探讨。
案例使用的是在Unity3D_ToLua下篇中的跳一跳项目,在文章最后会提供github地址供读者下载,读者可配合项目来阅读本文
在学习热更新之前,我们在之前跳一跳项目的文章基础上增加一些元素,如下图所示,我们在开始界面增加一些图片资源,以进行图片资源的打包:

接下来我们分步研究tolua热更新
注意:我们的热更新主要流程以及各种管理器调用最好使用c#来进行编写,因为这些部分一般来说不会去做更新,使用lua会造成不必要的性能损耗
1.打包资源
我们首先要使用AssetBundle把我们所需的lua文件及资源文件这两部分打包到StreaminAssets文件夹,打包逻辑代码在Packager.cs中。
StreaminAssets目录会随着Unity最终生成包原目录打包出去,游戏客户端可以通过代码读取到里面的资源,并且把里面的资源复制到玩家的手机本地存储里面 。
代码如下,我们根据不同平台,把这两部分的资源进行打包:
[MenuItem("LuaFrameWork/Build iPhone Resource", false, 100)]
public static void BuildiPhoneResource()
{
BuildTarget target;
target = BuildTarget.iOS;
BuildAssetResource(target);
}
[MenuItem("LuaFrameWork/Build Android Resource", false, 101)]
public static void BuildAndroidResource()
{
BuildAssetResource(BuildTarget.Android);
}
[MenuItem("LuaFrameWork/Build Windows Resource", false, 102)]
public static void BuildWindowsResource()
{
BuildAssetResource(BuildTarget.StandaloneWindows);
}
然后我们查看效果,菜单栏已经又了LuaFrameWork的菜单选项

我们依照需要打包的平台来选择,在Project面板中,打包之前的文件目录如下图所示

打包后可以看到,生成了StreaminAssets目录,以及目录下的打包及依赖文件。
然后我们来看打包模块的主要逻辑代码:
public static void BuildAssetResource(BuildTarget target)
{
if (Directory.Exists(Tools.DataPath))
{
Directory.Delete(Tools.DataPath, true);
}
string streamPath = Application.streamingAssetsPath;
if (Directory.Exists(streamPath))
{
Directory.Delete(streamPath, true);
}
Directory.CreateDirectory(streamPath);
AssetDatabase.Refresh();
maps.Clear();
HandleLuaBundle();
HandleResourcesBundle();
string resPath = "Assets/" + AppConst.AssetDir;
BuildPipeline.BuildAssetBundles(resPath, maps.ToArray(), BuildAssetBundleOptions.None, target);
BuildFileIndex();
string streamDir = Application.dataPath + "/" + AppConst.LuaTempDir;
if (Directory.Exists(streamDir)) Directory.Delete(streamDir, true);
AssetDatabase.Refresh();
}
这部分代码中,我们主要看HandleLuaBundle() 和 HandleResourcesBundle()
在HandleResourcesBundle()中,我们对Resources下的素材资源按照Unity的规则进行打包成assetbundle文件:
static void HandleResourcesBundle()
{
string resPath = AppDataPath + "/" + AppConst.AssetDir + "/";
if (!Directory.Exists(resPath)) Directory.CreateDirectory(resPath);
AddBuildMap("Prefabs" + AppConst.ExtName, "*.prefab", "Assets/Resources/Prefabs");
AddBuildMap("Textures" + AppConst.ExtName, "*.jpg", "Assets/Resources/Textures");
AddBuildMap("Textures" + AppConst.ExtName, "*.png", "Assets/Resources/Textures");
}
HandleLuaBundle()中我们主要做了这几件事:
(1)在StreamingAssets目录下面新建lua目录,用于存放编码后的lua文件。
(2)遍历Lua目录下面所有的lua文件,并且根据目录结构创建相应目录树。 这两部分代码过长,可以直接下载github中的项目查看。
(3)将需要的包的文件,统一遍历计算出MD5,生成到files.txt里面。下面是计算MD5的流程。
public static string md5file(string file)
{
try
{
FileStream fs = new FileStream(file, FileMode.Open);
System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.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();
}
catch (Exception ex)
{
throw new Exception("md5file() fail, error:" + ex.Message);
}
}
for (int i = 0; i < files.Count; i++)
{
string file = files[i];
string ext = Path.GetExtension(file);
if (file.EndsWith(".meta") || file.Contains(".DS_Store")) continue;
string md5 = Tools.md5file(file);
string value = file.Replace(resPath, string.Empty);
sw.WriteLine(value + "|" + md5);
}
到这里,我们的打包流程就结束了,接下来就是更新的流程说明。
2.资源更新
更新逻辑代码我们放在了GameManager.cs文件中
我们直接来看更新逻辑中的主要部分
IEnumerator OnUpdateResource()
这这个函数中我们同样分步骤来说明:
(1)初始化操作,查找更新地址。
(2)请求更新列表文件files.txt
(3)分析Web服务器上的files.txt文件内容,遍历检查本地的文件结构是否完整、MD5是否匹配。
(4)如果MD5不匹配,或本地文件不存在,就创建下载请求。并协程下载文件(这里我们可以使用线程来下载)
(5)更新完成
最后,在热更新完成后,我们在GameManager.cs中启动lua管理器没执行lua逻辑代码
public void OnResourceInited()
{
ResManager.Initialize(AppConst.AssetDir, delegate() {
Debug.Log("Initialize OK!!!");
this.OnInitialize();
});
}
void OnInitialize()
{
LuaManager.Instance.LuaClient.luaState.DoFile("Login.lua");
LuaManager.Instance.LuaClient.CallFunc("Login.Awake", this.gameObject);
}
具体代码可以下载GitHub中的项目查看。
到此为止,我们的热更新流程已经全部结束了,接下来的工作就是解包AssetBundle文件及依赖文件,然后启动lua虚拟机,运行程序。项目中的解包工作由ResourceManager.cs来负责。
然后我们来看具体的效果: 首先我们将生成好的一系列文件上传至服务器(这里我上传到阿里云对象存储,读者可以根据自己的需求和现有的资源上传,不过要记得更改AppConst.cs中的WebUrl地址)

接下来我们删除我们的lua文件及图片资源文件以及StreaminAssets目录

然后我们生成新的AssetBundle资源


点击运行后,我们再来查看当前的StreaminAssets及AssetBundle


我们lua热更新相关的文章到此也告一段落,热更新流程和普通的更新流程并没有什么不同,但因为我们使用tolua进行逻辑的编写,更新下来的lua代码不需要编译便可以执行,这也是热更新的基本思想及其实现的目的。
这里提示读者,本文中涉及代码只是精简的框架逻辑,主要作为引导及教学用途,如需要在实际项目中使用希望读者可以根据自己的需求进行扩展,如需在商业项目中使用,可以使用:https://github.com/jarjin/LuaFramework_UGUI
本文资源地址:https://github.com/Etnly/ToLuaDemo2
最后,感谢ToLua作者阿蒙及LuaFramework作者骏擎【CP】为Unity3D热更新作出的贡献。
想系统学习游戏开发的童鞋,欢迎访问 http://levelpp.com/
游戏开发搅基QQ群:869551769
微信公众号:皮皮关