您的位置首页百科知识

现在最新版的unity怎么调中文(按照教程试了,可是看不到语言的选项)?

现在最新版的unity怎么调中文(按照教程试了,可是看不到语言的选项)?

的有关信息介绍如下:

现在最新版的unity怎么调中文(按照教程试了,可是看不到语言的选项)?

在开发商业游戏时,热更新是一个很重要的模块,这里讲的热更新不是指仅仅修复Bug,而是进行游戏功能的更新。简单来讲,就是启动游戏后,跑个条,下载资源和代码,然后再进入游戏。本篇博客所写的内容并不是最优的解,只是完成了热更新这个事情而已,具体使用还需要使用者根据自己的项目来具体来看。这里采用的方案是使用 AssetBundle 和 xLua。使用 AssetBundle 是为了资源的完全自主控制。而整个游戏的逻辑部分,则使用 xLua 来实现。当然,C# 的代码不可能一点没有,只是一些核心的功能模块,一般写好后就不会改变的东西,或者对性能要求很高的东西,放在 C# 就可以。整个功能分为编辑器部分,和运行时部分。编辑器部分就是编 Bundle,生成版本文件等。而运行时部分就是从 CDN 下载版本文件,对比版本号及本地资源是否有要更新的,如果有,则更新,更新完后进入游戏。没有,则直接进入游戏。编辑器部分编辑器部分主要就是生成 Bundle 文件,首先,我是按目录来划分 Bundle 的,任何一个目录下的文件(不包括子目录)则会打成一个 Bundle。例如下面的目录结构Res/ - ConfigBytes/ - UI/ - LuaScripts/ - Data/ - ItemsData/ - CharactersData/首先 Res 目录是资源的主目录,下面有各种子目录(Res 目录下不会有需要打 Bundle 的文件)。ConfigBytes 目录下的文件,会打成一个 Bundle。UI 目录下的文件会打成一个 Bundle。LuaScripts 目录下的文件会打成一个 Bundle。Data 目录下的 ItemsData 目录中的文件会打成一个 Bundle,Data 目录下的 CharactersData 目录会打成一个Bundle。如果 Data 目录下存在文件(非目录),则这些文件会打成一个 Bundle。简单来讲,就是会按文件夹来决定哪些文件打成一个Bundle,检查的时候只会取一个文件夹下的文件,而不会递归取这个文件夹下的子目录。LuaScripts 目录在开发时会放在 Assets 目录外面,与其同级,在编 Bundle 时,会拷贝 LuaScripts 目录及下面的所有文件,按原有目录结构,拷贝到 Res 目录中,并且会将每一个 xxx.lua 文件的扩展名改为 xxx.txt。因为 .lua 在 Unity 中识别不了。打 Bundle 的脚本,会记录每一个资源,所在的 Bundle 名称,最后会生成一个 index.json 文件,这份索引文件记录的,就是每一个资源的加载路径,和所在的 Bundle 名。Bundle 输出后,会生成一个 version.json 的文件,这个文件,记录了每一个 Bundle 的名字,MD5 和 文件大小。而热更新对比一个文件是否需要更新,就是判断远程文件的 MD5 与本地文件的 MD5 是否相同,如果不想同,则需要更新远程文件。以上就是编辑器所做的事情,总结一下就是

第 5 步,之所以是要拷贝到 StreamingAssets 目录,是为了用户在第一次安装游戏时,运行时逻辑会先判断本地有没有资源,如果没有,或者版本号小于 StreamingAssets 中的 version.json 文件版本号,则需要将 StreamingAssets 目录下的 Bundle 及 version.json 文件拷贝到 Persistent 目录下,这样就不用第一次安装游戏,还需要跑条更新资源了。当然,虽然有了这个过程,拷贝完后,正常的版本检查还是会做。以上就是编辑器下做的事情,下面为运行时的流程运行时资源更新部分版本检查及更新逻辑,可以放在一个独立的场景中去进行,一旦更新完成后,就直接跳转场景到游戏启动场景,这个逻辑比较简单清晰,不容易出错。在游戏启动时,首先会去远程拉取 version.json 文件,然后根据 version.json 文件中的版本号与本地 version.json 文件中的版本号进行对比。如果不一样,则需要根据 version.json 文件中的 Bundle 信息,看一下哪一些 Bundle 需要更新,找到需要更新的 Bundle 后,依次下载始可。因为 version.json 中包含了每一个 Bundle 的文件大小,所以这里的下载进度条的进度,也是可以计算出来的。在对比版本号时,需要根据自己游戏的实际进行,分为大版本号和热更新版本号,如果大版本号不一样,则直接不用判断 Bundle 了,让用户进不了游戏,弹窗告诉用户去下载最新的安装包即可。如果大版本一样,则进行热更新的逻辑。这里的大版本判断,不当要根据 version.json 文件里的版本号来判断,最好是根据包里代码中或者包里的某个配置文件中的版本号来判断,因为 version.json 文件是在手机的可读写目录,对于 Android 来说,是很容易随意找到这个文件,然后改掉的,从而绕过热更新。在 Bundle 都下载完后,需要将远程的 version.json 文件写入本地,覆盖本地的 version.json 文件。最后,再进行一步本地资源校对,就是计算每一个本地 Bundle MD5,是否与 version.json 中的 MD5 一致,如果不一致,则需要弹窗告诉玩家需要手动修复资源,或者直接自动下载覆盖。手动修复也就是从远程重新下载资源进行覆盖。最后一步资源校对通过后,则跳到游戏逻辑开始场景。我是将版本检查和更新的逻辑放在了 C# 实现的,当然,也可以放在 lua 来实现,不过需要在更新完后,重新创建整个 lua 环境,以保证使用了最新的资源。运行时资源加载部分资源的加载,可以使用一个资源管理器脚本来实现。资源管理器在初始化时首先要加载 Bundle 的 AssetBundleManifest 信息,这个资源里记录了各个 Bundle 与其他 Bundle 的依赖关系。然后加载 index 文件,也就是我们一开始生成的资源索引文件,这样才能知道哪一个资源,在哪一个 Bundle 里。当要加载一个资源时,传入资源加载路径,首先会根据 index 文件中的信息,找到这个 Bundle,然后从 Manifest 信息中,读取这个 Bundle 的依赖 Bundle,如果有,则先加载依赖,最后,再加载当前 Bundle。Bundle加载完后,从 Bundle 中加载资源。具体代码(仅供参考)AssetBundleBuilder.cs 编辑器下编 Bundle 的代码using System.Collections.Generic; using UnityEngine; using UnityEditor; using UnityEditor.Build; using System; using System.IO; using System.Text; using System.Linq; using System.Xml.Linq; using System.Security.Cryptography; using UnityEditor.Build.Reporting; // 注意:BundleCombineConfig.json 中的配置,目录最后!不要!加上 '/'publicclassAssetBundleBuilder{ privatestaticstring RES_TO_BUILD_PATH = "Assets/Res/"; privatestaticstring MANIFEST_FILES_PATH = string.Format("{0}/../BundleManifest/", Application.dataPath); privatestatic StringBuilder IndexFileContent = null; privatestatic StringBuilder VersionFileContent = null; privatestatic MD5 md5 = null; privatestatic BuildAssetBundleOptions BuildOption = BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.ForceRebuildAssetBundle; privatestatic BundleCombineConfig combineConfig = null; privatestatic Dictionary<string, int> combinePathDict = null; privatestaticstring version = "0.0.0"; privatestaticbool copyToStreaming = false; private static voidInitBuilder() { IndexFileContent = new StringBuilder(); VersionFileContent = new StringBuilder(); md5 = new MD5CryptoServiceProvider(); combineConfig = null; combinePathDict = new Dictionary<string, int>(); } private static voidWriteIndexFile(string key, string value) { IndexFileContent.AppendFormat("{0}:{1}", key, value); IndexFileContent.AppendLine(); } private static voidWriteVersionFile(string key, string value1, long value2) { VersionFileContent.AppendFormat("{0}:{1}:{2}", key, value1, value2); VersionFileContent.AppendLine(); } private static longGetFileSize(string fileName) { try { FileInfo fileInfo = new FileInfo(fileName); return fileInfo.Length; } catch (Exception ex) { thrownew Exception("GetFileSize() fail, error:" + ex.Message); } } private static stringGetMD5(byte[] retVal) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < retVal.Length; i++) { sb.Append(retVal[i].ToString("x2")); } return sb.ToString(); } private static stringGetMD5HashFromFile(string fileName) { try { FileStream file = new FileStream(fileName, FileMode.Open); byte[] retVal = md5.ComputeHash(file); file.Close(); return GetMD5(retVal); } catch (Exception ex) { thrownew Exception("GetMD5HashFromFile() fail, error:" + ex.Message); } } static stringGetBundleName(string path) { byte[] md5Byte = md5.ComputeHash(Encoding.Default.GetBytes(path)); string str = GetMD5(md5Byte) + ".assetbundle"; return str; } privateclassBuildBundleData { private AssetBundleBuild build = new AssetBundleBuild(); privateList<string> assets = new List<string>(); privateList<string> addresses = new List<string>(); publicBuildBundleData(string bundleName) { build.assetBundleName = bundleName; } public voidAddAsset(string filePath) { string addressableName = GetAddressableName(filePath); assets.Add(filePath); addresses.Add(addressableName); WriteIndexFile(addressableName, build.assetBundleName); } public AssetBundleBuild Gen() { build.assetNames = assets.ToArray(); build.addressableNames = addresses.ToArray(); return build; } } private static stringGetAddressableName(string file_path) { string addressable_name = file_path; addressable_name = addressable_name.Replace(RES_TO_BUILD_PATH, ""); int dot_pos = addressable_name.LastIndexOf('.'); if (dot_pos != -1) { int count = addressable_name.Length - dot_pos; addressable_name = addressable_name.Remove(dot_pos, count); } return addressable_name; } private static string[] GetTopDirs(string rPath) { return Directory.GetDirectories(rPath, "*", SearchOption.TopDirectoryOnly); } private static voidCopyLuaDir() { // Copy Luastring luaOutPath = Application.dataPath + "/../LuaScripts"; string luaInPath = Application.dataPath + "/Res/LuaScripts"; DeleteLuaDir(); MoeUtils.DirectoryCopy(luaOutPath, luaInPath, true, ".txt"); AssetDatabase.Refresh(); } private static voidDeleteLuaDir() { string luaInPath = Application.dataPath + "/Res/LuaScripts"; if (Directory.Exists(luaInPath)) { Directory.Delete(luaInPath, true); } } public static voidBuildBundleWithVersion(string v, bool copy) { version = v; copyToStreaming = copy; BuildAssetBundle(); } [MenuItem("Tools/Build Bundles")]private static voidBuildAssetBundle() { if (version == "0.0.0") { Debug.LogErrorFormat("请确认版本号"); return; } CopyLuaDir(); InitBuilder(); LoadBundleCombineConfig(); Dictionary<string, BuildBundleData> bundleDatas = new Dictionary<string, BuildBundleData>(); IndexFileContent.Clear(); VersionFileContent.Clear(); List<DirBundleInfo> dirList = new List<DirBundleInfo>(); // ============================ Queue<DirBundleInfo> dirQueue = new Queue<DirBundleInfo>(); dirQueue.Enqueue(new DirBundleInfo(RES_TO_BUILD_PATH)); while (dirQueue.Count > 0) { DirBundleInfo rootDirInfo = dirQueue.Dequeue(); if (rootDirInfo.dir != RES_TO_BUILD_PATH) { if (combinePathDict.ContainsKey(rootDirInfo.dir)) { rootDirInfo.combine2Dir = rootDirInfo.dir; } dirList.Add(rootDirInfo); } foreach (string subDir inGetTopDirs(rootDirInfo.dir)) { DirBundleInfo subDirInfo = new DirBundleInfo(subDir); subDirInfo.combine2Dir = rootDirInfo.combine2Dir; dirQueue.Enqueue(subDirInfo); Debug.LogFormat("Dir: {0}, Combine2Dir: {1}", subDirInfo.dir, subDirInfo.combine2Dir); } } foreach (DirBundleInfo dirInfo in dirList) { string[] files = GetFiles(dirInfo.dir, SearchOption.TopDirectoryOnly); if (files.Length > 0) { Debug.LogFormat("Dir: {0}, FileCount: {1}", dirInfo.dir, files.Length); string bundleDirName = dirInfo.BundleDirName; BuildBundleData bbData = null; if (bundleDatas.ContainsKey(bundleDirName)) { bbData = bundleDatas[bundleDirName]; } else { bbData = new BuildBundleData(GetBundleName(bundleDirName)); bundleDatas.Add(bundleDirName, bbData); } foreach (string file in files) { bbData.AddAsset(file); } } } List<AssetBundleBuild> bundleBuildList = new List<AssetBundleBuild>(); foreach (BuildBundleData data in bundleDatas.Values) { bundleBuildList.Add(data.Gen()); } string index_file_path = string.Format("{0}{1}.txt", RES_TO_BUILD_PATH, "index"); File.WriteAllText(index_file_path, IndexFileContent.ToString()); AssetDatabase.ImportAsset(index_file_path); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); AssetBundleBuild indexBuild = new AssetBundleBuild(); indexBuild.assetBundleName = "index"; indexBuild.assetNames = newstring[] { index_file_path }; indexBuild.addressableNames = newstring[] { "index" }; bundleBuildList.Add(indexBuild); string bundleExportPath = string.Format("{0}/{1}/", Application.dataPath + "/../streaming", "Bundles"); if (Directory.Exists(bundleExportPath)) { Directory.Delete(bundleExportPath, true); } Directory.CreateDirectory(bundleExportPath); if (Directory.Exists(MANIFEST_FILES_PATH)) { Directory.Delete(MANIFEST_FILES_PATH, true); } Directory.CreateDirectory(MANIFEST_FILES_PATH); BuildPipeline.BuildAssetBundles(bundleExportPath, bundleBuildList.ToArray(), BuildOption, EditorUserBuildSettings.activeBuildTarget); AssetDatabase.Refresh(); DeleteLuaDir(); AssetDatabase.Refresh(); // VersionProfile List<VersionBundleInfo> versionBundleList = new List<VersionBundleInfo>(); MoeVersionInfo versionInfo = new MoeVersionInfo(); versionInfo.version = version; versionInfo.asset_date = DateTime.Now.ToString("yyyyMMddHHmm"); string[] ab_files = Directory.GetFiles(bundleExportPath); foreach (string ab_file in ab_files) { if (Path.GetExtension(ab_file) == ".manifest") { string new_path = ab_file.Replace(bundleExportPath, MANIFEST_FILES_PATH); File.Move(ab_file, new_path); } else { Debug.LogFormat("BundleName: {0}", ab_file); var data = File.ReadAllBytes(ab_file); using (var abStream = new AssetBundleStream(ab_file, FileMode.Create)) { abStream.Write(data, 0, data.Length); } string md5 = GetMD5HashFromFile(ab_file); long size = GetFileSize(ab_file); string bundleName = string.Format("Bundles/{0}", Path.GetFileName(ab_file)); VersionBundleInfo bInfo = new VersionBundleInfo(); bInfo.bundle_name = bundleName; bInfo.md5 = md5; bInfo.size = size; versionBundleList.Add(bInfo); } } versionInfo.bundles = versionBundleList.ToArray(); string versionInfoText = Newtonsoft.Json.JsonConvert.SerializeObject(versionInfo); File.WriteAllText(string.Format("{0}/{1}", bundleExportPath, "version.json"), versionInfoText); if (copyToStreaming) { CopyBundleToStreaming(bundleExportPath); } MoveToVersionDir(bundleExportPath, version); AssetDatabase.Refresh(); } private static voidMoveToVersionDir(string rootBundlePath, string version) { string destPath = rootBundlePath + "/" + version; Directory.CreateDirectory(destPath); destPath += "/Bundles"; Directory.CreateDirectory(destPath); string[] files = GetFiles(rootBundlePath, SearchOption.TopDirectoryOnly); foreach (string file in files) { string fileName = System.IO.Path.GetFileName(file); string destFilePath = destPath + "/" + fileName; File.Move(file, destFilePath); } } private static voidCopyBundleToStreaming(string bundleExportPath) { string destPath = Application.streamingAssetsPath + "/Bundles"; if (Directory.Exists(destPath)) { Directory.Delete(destPath, true); } MoeUtils.DirectoryCopy(bundleExportPath, destPath, true); } private static string[] GetFiles(string path, SearchOption so) { string[] files = Directory.GetFiles(path, "*", so); List<string> fileList = new List<string>(); foreach (string file in files) { string ext = Path.GetExtension(file); if (ext == ".meta" || ext == ".DS_Store") { continue; } fileList.Add(file); } return fileList.ToArray(); } classDirBundleInfo { publicstring dir; publicstring combine2Dir; publicbool IsCombine { get { return !string.IsNullOrEmpty(combine2Dir); } } publicstring BundleDirName { get { if (IsCombine) { return combine2Dir; } else { return dir; } } } publicDirBundleInfo(string dir, string combine2Dir = null) { this.dir = dir; this.combine2Dir = combine2Dir; } } classBundleCombineConfig { publicstring[] combieDirs; } private static voidLoadBundleCombineConfig() { string path = Application.dataPath + RES_TO_BUILD_PATH.Replace("Assets", "") + "BundleCombineConfig.json"; if (File.Exists(path)) { string text = File.ReadAllText(path); if (!string.IsNullOrEmpty(text)) { combineConfig = Newtonsoft.Json.JsonConvert.DeserializeObject<BundleCombineConfig>(text); if (combineConfig != null) { Debug.LogFormat("Bundle合并配置成功!"); foreach (string cPath in combineConfig.combieDirs) { if (!combinePathDict.ContainsKey(cPath)) { combinePathDict.Add(cPath, 0); } } } } } } }MoeVersionManager.cs 资源版本检查及 Bundle 更新逻辑using UnityEngine; using System.Collections; using System.Collections.Generic; using BestHTTP; using System; publicclassMoeVersionManager : MoeSingleton<MoeVersionManager> { conststring REMOTE_URL = "这里改成自己的CDN域名或IP"; staticstring VERSION_FILE_DIR; staticstring VERSION_FILE_PATH; staticstring IN_VERSION_FILE_PATH; private MoeVersionInfo currVersionInfo = null; private MoeVersionInfo remoteVersionInfo = null; private UpdateInfo updateInfo = null; privatestatic OnVersionStateParam versionStateParam = new OnVersionStateParam(); privatestatic OnUpdateProgressParam updateProgressParam = new OnUpdateProgressParam(); privatestatic OnVersionMsgBoxParam msgBoxParam = new OnVersionMsgBoxParam(); privateenum EnProcessType { Normal, Fix, } private Action<EnProcessType> actionTryUnCompress = null; private Action<EnProcessType> actionUpdateVersionFile = null; private Action<EnProcessType> actionUpdateBundles = null; private Action<EnProcessType> actionCheckAssets = null; private Action<EnProcessType> actionForceUpdateVersionFile = null; protected override voidInitOnCreate() { VERSION_FILE_DIR = Application.persistentDataPath + "/Bundles/"; VERSION_FILE_PATH = Application.persistentDataPath + "/Bundles/version.json"; IN_VERSION_FILE_PATH = Application.streamingAssetsPath + "/Bundles/version.json"; Debug.LogFormat("{0}", VERSION_FILE_PATH); InitProcessChain(); StartNormalProcess(); } private voidInitProcessChain() { this.actionTryUnCompress = (EnProcessType param) => { Debug.LogFormat("Action>>> 解压: {0}", param); this.currVersionInfo = LoadVersionInfo(VERSION_FILE_PATH); // if (!CheckBundleCorrect())if (currVersionInfo == null) { UpdateUIState("正在解压资源"); UnCompressBundle(); this.currVersionInfo = LoadVersionInfo(VERSION_FILE_PATH); } else { // 判断是不是更新包,也就是StreamingAssets里的版本是否比Persistent版本高,如果高的话,再次解压Bundle MoeVersionInfo inVersionInfo = LoadVersionInfo(IN_VERSION_FILE_PATH); if (inVersionInfo != null) { int[] inVersionDigit = inVersionInfo.GetVersionDigitArray(); int[] currVersionDigit = this.currVersionInfo.GetVersionDigitArray(); // if (inVersionInfo.GetVersionLong() > this.currVersionInfo.GetVersionLong())if (inVersionDigit[0] > currVersionDigit[0] || inVersionDigit[1] > currVersionDigit[1] || inVersionDigit[2] > currVersionDigit[2]) { // 包里的版本比Persistent的版本高,可能玩家进行了大版本更新,重新解压 Debug.LogFormat("包里的Bundle版本 > Persistent Bundle 版本,重新解压"); UpdateUIState("正在解压资源"); UnCompressBundle(); this.currVersionInfo = LoadVersionInfo(VERSION_FILE_PATH); } else { Debug.LogFormat("包里Bundle版本 <= Persistent Bundle版本,无需解压~"); } } else { Debug.LogErrorFormat("逻辑错误,从StreamingAssets 中加载VersionInfo文件失败"); } } }; this.actionUpdateVersionFile = (EnProcessType param) => { Debug.LogFormat("Action>>> 获取远程版本文件: {0}", param); StartCoroutine(TryUpdateVersion((bool ok, bool majorUpdate) => { if (ok) { if (majorUpdate) { // 调用商店 OnMsgBox("新的大版本已更新,请下载最新安装包!", "确定", () => { JumpToDownloadMarket(); }); } else { // 成功了,接下来更新Bundlethis.actionUpdateBundles?.Invoke(param); } } else { // 版本文件更新失败,弹窗询问 OnMsgBox("版本信息获取失败,请检查网络连接!", "重试", () => { this.actionUpdateVersionFile?.Invoke(param); }); } })); }; this.actionForceUpdateVersionFile = (EnProcessType param) => { Debug.LogFormat("Action>>> 强制获取远程版本文件: {0}", param); TryDeleteBundleDir(); TryCreateBundleDir(); StartCoroutine(TryUpdateVersion((bool ok, bool majorUpdate) => { if (ok) { if (majorUpdate) { // 调用商店 OnMsgBox("新的大版本已更新,请下载最新安装包!", "确定", () => { JumpToDownloadMarket(); }); } else { // 成功了,接下来更新Bundlethis.actionUpdateBundles?.Invoke(param); } } else { // 版本文件更新失败,弹窗询问 OnMsgBox("版本信息获取失败,请检查网络连接!", "重试", () => { this.actionForceUpdateVersionFile?.Invoke(param); }); } }, true)); }; this.actionUpdateBundles = (EnProcessType param) => { Debug.LogFormat("Action>>> 更新Bundle: {0}", param); StartCoroutine(TryUpdateBundle((bool ok) => { if (ok) { // 成功了,接下来检查资源,this.actionCheckAssets?.Invoke(param); } else { OnMsgBox("资源下载失败,请检查网络连接!", "重试", () => { this.actionUpdateBundles(param); }); } })); }; this.actionCheckAssets = (EnProcessType param) => { Debug.LogFormat("Action>>> 校对资源: {0}", param); if (!CheckBundleCorrect()) { // 更新完了,本地Bundle还是不对 Debug.LogFormat("更新完Bundle后,发现文件不对"); if (param == EnProcessType.Normal) { OnMsgBox("资源有错误,请修复客户端!", "修复", () => { this.actionForceUpdateVersionFile?.Invoke(EnProcessType.Fix); }); } else { OnMsgBox("客户端修复失败,请重新下载安装包!", "确定", () => { JumpToDownloadMarket(); }); } } else { UpdateUIState("进入游戏"); MoeEventManager.Inst.SendEvent(EventID.Event_OnUpdateEnd); } }; } // 跳转到下载商店private voidJumpToDownloadMarket() { Application.OpenURL("https://taptap.com"); } private voidStartNormalProcess() { TryCreateBundleDir(); this.actionTryUnCompress?.Invoke(EnProcessType.Normal); this.actionUpdateVersionFile?.Invoke(EnProcessType.Normal); } private voidStartFixProcess() { this.actionForceUpdateVersionFile(EnProcessType.Fix); } private MoeVersionInfo LoadVersionInfo(string path) { Debug.LogFormat("加载 Version 文件: {0}", path); try { if (System.IO.File.Exists(path)) { string text = System.IO.File.ReadAllText(path); if (!string.IsNullOrEmpty(text)) { MoeVersionInfo vInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<MoeVersionInfo>(text); if (vInfo != null) { Debug.LogFormat("Version 信息加载成功: {0}", vInfo.version); return vInfo; } } else { Debug.LogFormat("Version 文件内容为空"); } } else { Debug.LogFormat("Version 文件不存在"); } } catch (System.Exception e) { Debug.LogErrorFormat("读取Version文件出错: {0}", e.ToString()); } returnnull; } ///<summary>/// 从 StreamingAssets 里将Bundle拷贝到 Persistent 目录里 ///</summary>private voidUnCompressBundle() { TryDeleteBundleDir(); TryCreateBundleDir(); Debug.LogFormat("尝试从 Steaming 拷贝Bundle 到 Persistent"); try { if (System.IO.File.Exists(IN_VERSION_FILE_PATH)) { string text = System.IO.File.ReadAllText(IN_VERSION_FILE_PATH); Debug.LogFormat("Text: {0}", text); MoeVersionInfo inVersionInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<MoeVersionInfo>(text); if (inVersionInfo != null) { // 拷贝 Bundleforeach (VersionBundleInfo bundleInfo in inVersionInfo.bundles) { string srcFilePath = string.Format("{0}/{1}", Application.streamingAssetsPath, bundleInfo.bundle_name); string destFilePath = string.Format("{0}/{1}", Application.persistentDataPath, bundleInfo.bundle_name); Debug.LogFormat("拷贝Bundle, {0} -> {1}", srcFilePath, destFilePath); System.IO.File.Copy(srcFilePath, destFilePath, true); } // 拷贝 Version文件 System.IO.File.Copy(IN_VERSION_FILE_PATH, VERSION_FILE_PATH, true); } } else { Debug.LogErrorFormat("解压失败,StreamingAssets 中没有 Version 文件"); } } catch (System.Exception e) { Debug.LogErrorFormat("Bundle拷贝出错! {0}", e.ToString()); } } public voidTryCreateBundleDir() { if (!System.IO.Directory.Exists(VERSION_FILE_DIR)) { Debug.LogFormat("创建 Persistent Bundle 目录"); System.IO.Directory.CreateDirectory(VERSION_FILE_DIR); } else { Debug.LogFormat("Persistent Bundle 目录已存在,不需要创建"); } } public voidTryDeleteBundleDir() { if (System.IO.Directory.Exists(VERSION_FILE_DIR)) { System.IO.Directory.Delete(VERSION_FILE_DIR, true); } } private stringGetLocalBundleMD5(string bundle_name) { string bundleFilePath = string.Format("{0}/{1}", Application.persistentDataPath, bundle_name); if (System.IO.File.Exists(bundleFilePath)) { string md5 = MoeUtils.GetMD5HashFromFile(bundleFilePath); return md5; } returnnull; } ///<summary>/// 检查当前的Bundle是否正确 ///</summary>///<returns></returns>public boolCheckBundleCorrect() { if (currVersionInfo != null) { foreach (VersionBundleInfo bundleInfo in currVersionInfo.bundles) { string bundleFilePath = string.Format("{0}/{1}", Application.persistentDataPath, bundleInfo.bundle_name); bool matched = false; if (GetLocalBundleMD5(bundleInfo.bundle_name) == bundleInfo.md5) { matched = true; } else { Debug.LogErrorFormat("MD5 不匹配: {0}, FileMD5: {1}, bInfoMD5: {2}", bundleInfo.bundle_name, GetLocalBundleMD5(bundleInfo.bundle_name), bundleInfo.md5); } if (!matched) { returnfalse; } } Debug.LogFormat("本地Bundle文件检完全正确"); returntrue; } else { returnfalse; } } ///<summary>//////</summary>///<param name="callback"><是否成功,是否是强更></param>///<param name="force"></param>///<returns></returns>private IEnumerator TryUpdateVersion(System.Action<bool, bool> callback, bool force = false) { UpdateUIState("正在检查更新"); this.remoteVersionInfo = null; this.updateInfo = null; string remoteVersionUrl = REMOTE_URL + "/fishing/version.json"; Debug.LogFormat("开始下载远程 Version 文件: {0}", remoteVersionUrl); HTTPRequest request = new HTTPRequest(new System.Uri(remoteVersionUrl), false, true, null).Send(); while (request.State < HTTPRequestStates.Finished) { yield return newWaitForSeconds(0.1f); } if (request.State == HTTPRequestStates.Finished && request.Response.IsSuccess) { string remoteVersionText = request.Response.DataAsText; if (!string.IsNullOrEmpty(remoteVersionText)) { MoeVersionInfo remoteVersionInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<MoeVersionInfo>(remoteVersionText); if (remoteVersionInfo != null) { Debug.LogFormat("远程 Version 文件解析成功, Version: {0}", remoteVersionInfo.version); // 判断是否要更新 int appMajorVersion = AppConfig.Inst.GetMajorVersion(); // 判断是否要强更int remoteMajor = remoteVersionInfo.GetMajorVersion(); if (remoteMajor > appMajorVersion) { // 这是一个需要强更的版本,需要提示用户去商店下载 Debug.LogFormat("发现强更版本,需要重新下包,进行大版本更新!"); callback?.Invoke(true, true); callback = null; UpdateUIState("新的大版本已更新,请下载最新安装包!"); } else { // 强制修复if (force) { this.remoteVersionInfo = remoteVersionInfo; List<VersionBundleInfo> updateBundleList = new List<VersionBundleInfo>(); updateBundleList.AddRange(remoteVersionInfo.bundles); // 有需要更新的包this.updateInfo = new UpdateInfo(); this.updateInfo.remoteVersionInfo = remoteVersionInfo; this.updateInfo.updateBundleList = updateBundleList; Debug.LogFormat("强制更新,有需要更新的Bundle"); callback?.Invoke(true, false); callback = null; } else { // 正常更新int[] remoteVersionDigit = remoteVersionInfo.GetVersionDigitArray(); int[] currVersionDigit = this.currVersionInfo == null ? newint[] { 0, 0, 0 } : this.currVersionInfo.GetVersionDigitArray(); // if (this.currVersionInfo == null || remoteVersionInfo.GetVersionLong() > this.currVersionInfo.GetVersionLong())if (remoteVersionDigit[0] > currVersionDigit[0] || remoteVersionDigit[1] > currVersionDigit[1] || remoteVersionDigit[2] > currVersionDigit[2]) { Debug.LogFormat("这次需要热更新"); this.remoteVersionInfo = remoteVersionInfo; List<VersionBundleInfo> updateBundleList = new List<VersionBundleInfo>(); foreach (VersionBundleInfo rBInfo in remoteVersionInfo.bundles) { if (GetLocalBundleMD5(rBInfo.bundle_name) != rBInfo.md5) { updateBundleList.Add(rBInfo); } } // 有需要更新的包this.updateInfo = new UpdateInfo(); this.updateInfo.remoteVersionInfo = remoteVersionInfo; this.updateInfo.updateBundleList = updateBundleList; Debug.LogFormat("有需要更新的Bundle"); callback?.Invoke(true, false); callback = null; } else { Debug.LogFormat("远程版本号 {0} <= 本地版本号 {1},无需更新!", remoteVersionInfo.version, this.currVersionInfo.version); callback?.Invoke(true, false); callback = null; } } } } else { Debug.LogErrorFormat("远程 Version 文件反序列化失败: {0}", remoteVersionText); } } else { Debug.LogErrorFormat("远程 Version 文件内容为空"); } } else { Debug.LogErrorFormat("远程 Version 文件下载失败: {0}, {1}", request.State, request.Response.StatusCode); } BestHTTP.PlatformSupport.Memory.BufferPool.Release(request.Response.Data); callback?.Invoke(false, false); } private IEnumerator TryUpdateBundle(System.Action<bool> callback) { if (this.remoteVersionInfo != null && this.updateInfo != null) { long totalSize = 0; foreach (VersionBundleInfo bInfo inthis.updateInfo.updateBundleList) { totalSize += bInfo.size; } UpdateUIDownload(totalSize, 0); long downloadedSize = 0; bool hasError = false; foreach (VersionBundleInfo bInfo inthis.updateInfo.updateBundleList) { Debug.LogFormat("Bundle信息 {0} | {1}", GetLocalBundleMD5(bInfo.bundle_name), bInfo.md5); if (GetLocalBundleMD5(bInfo.bundle_name) != bInfo.md5) { string remoteBundleUrl = string.Format("{0}/fishing/{1}/{2}", REMOTE_URL, this.updateInfo.remoteVersionInfo.version, bInfo.bundle_name); Debug.LogFormat("开始更新Bundle: {0}", remoteBundleUrl); HTTPRequest request = new HTTPRequest(new System.Uri(remoteBundleUrl), false, true, null).Send(); while (request.State < HTTPRequestStates.Finished) { yield return newWaitForSeconds(0.1f); } if (request.State == HTTPRequestStates.Finished && request.Response.IsSuccess) { downloadedSize += bInfo.size; string bundleWritePath = Application.persistentDataPath + "/" + bInfo.bundle_name; // 写入Bundle文件 System.IO.File.WriteAllBytes(bundleWritePath, request.Response.Data); Debug.LogFormat("{0} 更新完成", bInfo.bundle_name); UpdateUIDownload(totalSize, downloadedSize); } else { Debug.LogErrorFormat("{0} 下载出错: {1}, {2}", bInfo.bundle_name, request.State, request.Response.IsSuccess); callback?.Invoke(false); callback = null; hasError = true; break; } yieldreturnnull; BestHTTP.PlatformSupport.Memory.BufferPool.Release(request.Response.Data); } else { Debug.LogFormat("!!!!!!!!!!! 本地已存在需要更新的 {0},跳过下载", bInfo.bundle_name); downloadedSize += bInfo.size; UpdateUIDownload(totalSize, downloadedSize); } } if (!hasError) { Debug.LogFormat("写入远程 Version 文件"); // 最后写入Version文件string versionText = Newtonsoft.Json.JsonConvert.SerializeObject(this.updateInfo.remoteVersionInfo); System.IO.File.WriteAllText(VERSION_FILE_PATH, versionText); yieldreturnnull; // 重新加载一遍本地文件this.currVersionInfo = LoadVersionInfo(VERSION_FILE_PATH); UpdateUIState("更新完成"); } } else { Debug.LogFormat("无需要更新,前置数据不足: remoteVersionInfo is Null: {0}, updateInfo is Null: {1}", this.remoteVersionInfo == null, this.updateInfo == null); } callback?.Invoke(true); } privateclassUpdateInfo { public MoeVersionInfo remoteVersionInfo; public List<VersionBundleInfo> updateBundleList; } private voidUpdateUIState(string msg) { versionStateParam.state = msg; MoeEventManager.Inst.SendEvent(EventID.Event_OnVersionState, versionStateParam); } private voidUpdateUIDownload(long total, long downloaded) { updateProgressParam.totalUpdateSize = total; updateProgressParam.nowUpdatedSize = downloaded; MoeEventManager.Inst.SendEvent(EventID.Event_OnUpdateProgress, updateProgressParam); } private voidOnMsgBox(string msg, string btnText, System.Action callback) { msgBoxParam.msg = msg; msgBoxParam.btnText = btnText; msgBoxParam.callback = callback; MoeEventManager.Inst.SendEvent(EventID.Event_OnVersionMsgBox, msgBoxParam); } }MoeReleaseAssetBundleManager.cs 运行时资源管理器using System.Collections; using System.Collections.Generic; using UnityEngine; using System.IO; publicclassMoeReleaseAssetBundleManager : IMoeResAgent{ conststring INDEX_FILE = "index"; privateDictionary<int, Object> _resources = new Dictionary<int, Object>(); privateDictionary<int, AssetBundle> _bundles = new Dictionary<int, AssetBundle>(); privateDictionary<int, string> _bundles_index = new Dictionary<int, string>(); private AssetBundleManifest _manifest = null; public voidInit() { InitAndLoadManifestFile(); InitAndLoadIndexFile(); } private voidInitAndLoadIndexFile() { _bundles_index.Clear(); AssetBundle indexBundle = LoadBundleSync(INDEX_FILE); TextAsset ta = indexBundle.LoadAsset<TextAsset>(INDEX_FILE); if (ta == null) { Debug.LogErrorFormat("Index 文件加载失败!"); return; } string[] lines = ta.text.Split('\n'); char[] trim = newchar[] { '\r', '\n' }; if (lines != null && lines.Length > 0) { for (int i = 0; i < lines.Length; ++i) { string line = lines[i].Trim(trim); if (string.IsNullOrEmpty(line)) { continue; } string[] pair = line.Split(':'); if (pair.Length != 2) { Debug.LogErrorFormat("Index 行数据有问题: {0}", line); continue; } int hash = pair[0].GetHashCode(); if (_bundles_index.ContainsKey(hash)) { Debug.LogErrorFormat("Index 文件中存在相同的路径: {0}", pair[0]); } else { _bundles_index.Add(hash, pair[1]); } } } if (_bundles_index.Count != 0) { Debug.LogFormat("Bundle Index 初始化完成"); } else { Debug.LogErrorFormat("Index 文件数据为空"); } indexBundle.Unload(true); indexBundle = null; } private voidInitAndLoadManifestFile() { AssetBundle manifestBundle = LoadBundleSync("Bundles"); _manifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); manifestBundle.Unload(false); manifestBundle = null; } public T LoadAsset<T>(string path) where T : UnityEngine.Object { UnityEngine.Object obj = Load(path); if (obj != null) { return obj as T; } returnnull; } public byte[] LoadLuaCode(string path) { string assetPath = string.Format("LuaScripts/{0}", path); TextAsset ta = LoadAsset<TextAsset>(assetPath); if (ta != null) { return ta.bytes; } returnnull; } private UnityEngine.Object Load(string assetPath) { if (string.IsNullOrEmpty(assetPath)) { returnnull; } int pathHash = assetPath.GetHashCode(); Object obj = null; if (_resources.TryGetValue(pathHash, out obj)) { if (obj == null) { _resources.Remove(pathHash); } else { return obj; } } AssetLoadInfo loadInfo = GetAssetLoadInfo(assetPath); // 加载依赖Bundle for (int i = 0; i < loadInfo.dependencies.Length; ++i) { if (LoadBundleSync(loadInfo.dependencies[i]) == null) { Debug.LogErrorFormat("加载依赖Bundle出错,资源 {0}, 主Bundle:{1}, 依赖:{2}", assetPath, loadInfo.mainBundle, loadInfo.dependencies[i]); returnnull; } } AssetBundle mainBundle = LoadBundleSync(loadInfo.mainBundle); if (mainBundle == null) { Debug.LogErrorFormat("加载主Bundle出错,资源:{0},主Bundle:{1}", assetPath, loadInfo.mainBundle); returnnull; } obj = mainBundle.LoadAsset(assetPath); if (obj == null) { Debug.LogErrorFormat("从Bundle加载资源失败,资源:{0},主Bundle:{1}", assetPath, loadInfo.mainBundle); returnnull; } _resources.Add(pathHash, obj); return obj; } private AssetBundle LoadBundleSync(string bundleName) { int bundleHash = bundleName.GetHashCode(); AssetBundle bundle = null; if (!_bundles.TryGetValue(bundleHash, out bundle)) { #if UNITY_EDITORstring rootPath = Application.dataPath + "/../streaming"; #elsestring rootPath = Application.persistentDataPath; #endifstring bundleLoadPath = System.IO.Path.Combine(rootPath, string.Format("Bundles/{0}", bundleName)); Debug.LogFormat(">>>> 加载Bundle: {0}", bundleLoadPath); using (var fileStream = new AssetBundleStream(bundleLoadPath, FileMode.Open, FileAccess.Read, FileShare.None, 1024 * 4, false)) { bundle = AssetBundle.LoadFromStream(fileStream); } // bundle = AssetBundle.LoadFromFile(bundleLoadPath); if (bundle != null) { _bundles.Add(bundleHash, bundle); } else { Debug.LogErrorFormat("Bundle 加载失败 {0}, LoadPath: {1}", bundleName, bundleLoadPath); } } else { // Debug.LogFormat("Bundle {0} 已加载,直接返回", bundleName); } return bundle; } private stringGetAssetOfBundleFileName(string assetPath) { int assetHash = assetPath.GetHashCode(); string bundleName; if (_bundles_index.TryGetValue(assetHash, out bundleName)) { return bundleName; } returnstring.Empty; } private AssetLoadInfo GetAssetLoadInfo(string assetPath) { AssetLoadInfo loadInfo = new AssetLoadInfo(); loadInfo.assetPath = assetPath; loadInfo.mainBundle = GetAssetOfBundleFileName(assetPath); loadInfo.dependencies = _manifest.GetAllDependencies(loadInfo.mainBundle); return loadInfo; } privateclassAssetLoadInfo { publicstring assetPath; publicstring mainBundle; publicstring[] dependencies; } }