简述 Unity热更新指的是不重新安装游戏而直接更新资源和Lua代码(本质上也是一种资源)
基本原理 更新无非就是两种途径:
直接下载新的资源然后替换旧资源,但是很多时候旧资源都是只读的,替换不了 下载新的资源后放在一个固定的地方,加载资源时优先查找是否有新的资源,没有新资源时采取加载旧资源。Lua代码的加载也是如此。 基于安卓平台的安装文件只读的特性,一般就是走第二条路。
具体流程 资源打包:所有需要热更新的资源全部打包到 Application.streamingAssetsPath
文件里
在游戏启动时,需要对比版本号。如果有大版本更新,则需要更新安装包;如果有小版本更新,则下载更新文件
CheckUpdate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 public IEnumerator CheckUpdate ( ) { var VERSION_FILE_NAME = "version.txt" ; var versionResult = new GetVersionResult(); var serverVersionFileURL = GetServerFileURL(VERSION_FILE_NAME); yield return GetVersion (serverVersionFileURL, versionResult ) ; var serverVersion = versionResult.version; var appVersionFileURL = GetAppFileURL(VERSION_FILE_NAME); yield return GetVersion (appVersionFileURL, versionResult ) ; var appVersion = versionResult.version; var dataVersionFileURL = GetDataFileURL(VERSION_FILE_NAME); yield return GetVersion (dataVersionFileURL, versionResult ) ; var dataVersion = versionResult.version; } public static string GetAppFilePath (string fileName ) {#if UNITY_EDITOR return "" ; #elif UNITY_ANDROID return null ; #elif UNITY_STANDALONE || UNITY_IOS return $"{Application.streamingAssetsPath} /{fileName} " ; #endif return null ; } public static string GetAppFileURL (string fileName ) {#if UNITY_ANDROID return $"{Application.streamingAssetsPath} /{fileName} " ; #elif UNITY_EDITOR || UNITY_STANDALONE || UNITY_IOS return $"file:///{GetAppFilePath(fileName)} " ; #endif return null ; } public static string GetDataFilePath (string fileName ) { return $"{Application.persistentDataPath} /{fileName} " ; } public static string GetDataFileURL (string fileName ) { return $"file:///{GetDataFilePath(fileName)} " ; } public static string GetServerFileURL (string fileName ) { const string SERVER_URL = "file:///{D:/tmp/HotUpdate}" ; return $"{SERVER_URL} /{fileName} " ; } static int [] ParseVersionString (string version ) { var veri = new int [3 ]; var vers = version.Split('.' ); for (var i = 0 ; i < 3 ; i++) veri[i] = int .Parse(vers[i]); return veri; } class GetTextResult { public string text; } static IEnumerator GetText (string url, GetTextResult result ) { using (var req = UnityWebRequest.Get(url)) { yield return req.SendWebRequest(); if (req.isDone && req.result == UnityWebRequest.Result.Success) { result.text = req.downloadHandler.text; } else { result.text = null ; } } } class GetVersionResult { public int [] version; } static IEnumerator GetVersion (string url, GetVersionResult result ) { var textResult = new GetTextResult(); yield return GetText (url, textResult ) ; if (textResult.text == null ) { result.version = null ; } else { result.version = ParseVersionString(textResult.text); } }
下载APK:从服务器下载安装包(就是二进制数据),保存到数据文件路径,然后调用java的方法来安装
更新文件:从服务器下载文件列表 files.txt
,里面的每行分别是相对文件路径、文件MD5、文件字节大小,比如:
1 2 3 4 lua/lua.unity3d|60813be4a561c30dbfb8e37923788405|49687 StreamingAssets|7dc37bfbe7d153d83b71d579c4a9cd56|37135 StreamingAssets.manifest|95750ba9aee6b40eca7d6759e5947db7|153908 version.txt|01105f7fb6caff1985eba568aefddcf1|14
遍历文件列表,判断文件是否存在本地,以及文件MD5是否发生了变化(跟版本号一样,本地也有两个文件列表需要对比),是的话则直接下载相应文件到数据文件目录(如果是以前就有的数据文件,会直接被覆盖掉)
资源文件加载:用 File.Exists
判断是否有对应下载的新数据文件(数据文件路径可以直接读写,自然也可以判断文件存在与否),如果有,则加载新文件,否则用安装包里的
Lua代码加载:需要扩展Lua文件加载函数,也是先判断是否有对应的数据文件,然后再加载。以ToLua为例,需要继承 LuaFileUtils
。如果Lua文件都是打包成AssetBundle的话,则需要重写 AddBundle
方法。
关于Lua自定义文件路径
ToLua.OpenLibs
的第一步就是注册了 ToLua.Loader
作为一个加载函数到 package.loaders
(参考此处 )。而这个加载函数里会使用 LuaFileUtils.ReadZipFile
加载AssetBudle中的指定文件,核心实现为:
1 2 3 4 5 TextAsset luaCode = zipFile.LoadAsset<TextAsset>(fileName); if (luaCode != null ) { buffer = luaCode.bytes; Resources.UnloadAsset(luaCode); }