- A+
一、描述
游戏更新就是在游戏启动时或某一时机,触发程序去获取更新的列表类容,然后把新的东西全部下载到本地。而Addressable的机制似乎是为了当用到的时候才去下载对应的资源,这样好像不太友好,Bundle包大一点的就卡定在那里了。
我尝试过,把组设置成Local的形式,Bundle打进了包体里面,而对这些Local组的Bundle做更新的话据需要做增量包,Addressable可以识别而动态生成Remote的远程组生成远程Bundle,但是每次都要做这样的增量包,组会越来越多,且不容易管理。
后来我再尝试把组都设置成Remote,结果Bundle不打进包体里面,再程序运行的时候需要用时才去下载,就算你用特殊手段把Bundle干进包体里,Load Path记录的也是远程的地址,当你使用到某个资源时还是会走远程下载。
所以为了实现,不做增量包(方便管理),使用整Bundle的形式更新,设置成Remote形式也能打进包里,并能正确的加载内置的Bundle,需要自定义Addressable的打包和加载Bundle。
二、一键动态标志资源地址
工具类,这是网上找的,可以动态的创建或删除组,为资源添加label 加入到组。
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 |
using System.Collections.Generic; using UnityEditor.AddressableAssets.Settings; using UnityEngine; public class AASUtility : UnityEditor.Editor { public static UnityEditor.AddressableAssets.Settings.AddressableAssetSettings GetSettings() { //アドレサブルアセットセッティング取得 var d = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEditor.AddressableAssets.Settings.AddressableAssetSettings>( "Assets/AddressableAssetsData/AddressableAssetSettings.asset" ); return d; } public static UnityEditor.AddressableAssets.Settings.AddressableAssetGroup CreateGroup(string groupName, bool setAsDefaultGroup = false) { //アドレサブルアセットセッティング取得 var s = GetSettings(); //スキーマ生成 List<UnityEditor.AddressableAssets.Settings.AddressableAssetGroupSchema> schema = new List<UnityEditor.AddressableAssets.Settings.AddressableAssetGroupSchema>() { ScriptableObject.CreateInstance<UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema>(), ScriptableObject.CreateInstance<UnityEditor.AddressableAssets.Settings.GroupSchemas.ContentUpdateGroupSchema>(), }; //グループの作成 var f = s.groups.Find((g) => { return g.name == groupName; }); if (f == null) { f = s.CreateGroup(groupName, setAsDefaultGroup, false, true, schema); } return f; } public static AddressableAssetEntry AddAssetToGroup(string assetGuid, string groupName) { if (assetGuid.Equals("")) { Debug.Log($"assetGuid is empty, groupName: {groupName}"); return null; } var s = GetSettings(); var g = CreateGroup(groupName); var entry = s.CreateOrMoveEntry(assetGuid, g); return entry; } public static void SetLabelToAsset(List<string> assetGuidList, string label, bool flag) { var s = GetSettings(); //ラベルを追加するように呼んでおく。追加されていないと設定されない。 s.AddLabel(label); List<UnityEditor.AddressableAssets.Settings.AddressableAssetEntry> assetList = new List<UnityEditor.AddressableAssets.Settings.AddressableAssetEntry>(); s.GetAllAssets(assetList, true); foreach (var assetGuid in assetGuidList) { var asset = assetList.Find((a) => { return a.guid == assetGuid; }); if (asset != null) { asset.SetLabel(label, flag); } } } public static void RemoveAssetFromGroup(string assetGuid) { var s = GetSettings(); s.RemoveAssetEntry(assetGuid); } public static void RemoveAllGroups() { var s = GetSettings(); var list = s.groups; List<AddressableAssetGroup> temp_list = new List<AddressableAssetGroup>(); for (int i = list.Count - 1; i >= 0; i--) { temp_list.Add(list[i]); } for (int i = temp_list.Count - 1; i >= 0; i--) { s.RemoveGroup(temp_list[i]); } } //public static void AddGroup(string groupName, bool setAsDefaultGroup, bool readOnly, bool postEvent, List<AddressableAssetGroupSchema> schemasToCopy, params Type[] types) //{ // var s = GetSettings(); // s.CreateGroup(groupName, setAsDefaultGroup,readOnly,postEvent,schemasToCopy,types); //} public static void BuildPlayerContent() { //System.Threading.Thread.Sleep(30000); var d = GetSettings(); d.ActivePlayerDataBuilderIndex = 3; //AddressableAssetSettings.CleanPlayerContent(d.ActivePlayerDataBuilder); AddressableAssetSettings.BuildPlayerContent(); } public static void CleanPlayerContent() { // var d = GetSettings(); // d.ActivePlayerDataBuilderIndex = 3; //AddressableAssetSettings.CleanPlayerContent(d.ActivePlayerDataBuilder); AddressableAssetSettings.CleanPlayerContent(); UnityEditor.Build.Pipeline.Utilities.BuildCache.PurgeCache(false); // AssetImportMgr.OnDataBuilderComplete(); } static public void Test() { var d = GetSettings(); var matguid = UnityEditor.AssetDatabase.AssetPathToGUID("Assets/Data/hogeMat.mat"); AddAssetToGroup(matguid, "CreatedGroup"); ////List<string> assetGuidList = new List<string>() { matguid }; ////SetLabelToAsset(assetGuidList, "mat", true); //CreateGroup("CreatedGroup"); } } |
静态方法,提供给编辑器扩展菜单使用
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 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using System.IO; using UnityEditor.AddressableAssets.Settings.GroupSchemas; using UnityEditor.AddressableAssets.Settings; using UnityEngine.ResourceManagement.ResourceProviders; public class AddressableTool { private static string PackPath = Config.PackPath; private static string AssetDataPath = Config.AssetDataPath; private static Dictionary<string, bool> originGroups; private static Dictionary<string, AddressableAssetGroup> originGroupMaps; //严谨一级目录下有需要打包文件,全用二级目录做好分配 public static void MarkAssets() { //AASUtility.RemoveAllGroups(); 不要移除所有的组 为改变guid 导致每次的bundle的hash都不一样 //记录原来的组 originGroups = new Dictionary<string, bool>(); originGroupMaps = new Dictionary<string, AddressableAssetGroup>(); var setting = AASUtility.GetSettings(); var groups = setting.groups; if (groups != null) { foreach (var g in groups) { originGroups.Add(g.Name, false); originGroupMaps.Add(g.Name, g); } } string assetPath = Path.Combine(Application.dataPath, AssetDataPath); DirectoryInfo[] levelsDataDirs = new DirectoryInfo(assetPath).GetDirectories(); foreach (var dir in levelsDataDirs) { MarkDir(dir, null); MarkLevelDataDir(dir); } //对比移除无用的组 foreach (var iter in originGroups) { if (!iter.Value && originGroupMaps.ContainsKey(iter.Key)) { setting.RemoveGroup(originGroupMaps[iter.Key]); } } SetAllGroupsToAssetBundleEncryptProvider(); SetAllGroupsToRemoteNoStatic(); AssetDatabase.SaveAssets(); } private static void MarkLevelDataDir(DirectoryInfo dir) { string parentName = dir.Name; DirectoryInfo[] dirs = dir.GetDirectories(); foreach (var d in dirs) { MarkDir(d, parentName); } } private static void MarkDir(DirectoryInfo dir,string parentName) { if (dir.Name == PackPath) return; string tipName = ""; if (dir.Name == Config.HotUpdateDir) { tipName = Config.HotUpdateDir; } else if (dir.Name == Config.TableDataDir) { tipName = Config.TableDataDir; } List<string> allFiles = new List<string>(); List<string> allFileGuids = new List<string>(); string groudName = ""; if(string.IsNullOrEmpty(parentName)) { groudName = dir.Name; GetMarkFiles(allFiles, dir, false); } else { groudName = string.Format("{0}_{1}", parentName, dir.Name); GetMarkFiles(allFiles, dir); } if (allFiles.Count == 0) { return; } AASUtility.CreateGroup(groudName); if (originGroups.ContainsKey(groudName)) { originGroups[groudName] = true; } foreach(var fileStr in allFiles) { string uFilePath = UtilityTool.ChangePath(fileStr); string guid = AssetDatabase.AssetPathToGUID(uFilePath); var entry = AASUtility.AddAssetToGroup(guid, groudName); entry.address = uFilePath.Replace("Assets/" + "LevelsData" + "/", ""); allFileGuids.Add(guid); } if (!string.IsNullOrEmpty(tipName)) { AASUtility.SetLabelToAsset(allFileGuids, tipName, true); } AASUtility.SetLabelToAsset(allFileGuids, "default", true); } private static void GetMarkFiles(List<string> list, DirectoryInfo dir, bool sub = true) { if (dir.Name == PackPath) return; FileInfo[] files = dir.GetFiles(); foreach (var file in files) { if (file.FullName.EndsWith(".meta")) continue; list.Add(file.FullName); } if(!sub) return; DirectoryInfo[] dirs = dir.GetDirectories(); foreach (var d in dirs) { GetMarkFiles(list, d); } } public static void MarkSelectFiles() { string[] guids = Selection.assetGUIDs; foreach (var guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); Debug.Log(path); if (path.IndexOf(AssetDataPath) < 0) continue; string groupName = GetGroupNameByAssetPath(path); AASUtility.CreateGroup(groupName); var entry = AASUtility.AddAssetToGroup(guid, groupName); entry.address = path.Replace("Assets/" + "LevelsData" + "/", ""); } } public static string GetGroupNameByAssetPath(string assetPath) { string[] dirs = assetPath.Split('/'); string group = dirs[1]; for (int i = 2; i < dirs.Length - 1; i++) { group = $"{group}.{dirs[i]}"; } return group; } public static void SetAllGroupsToRemoteNoStatic() { AddressableAssetSettings settings = AASUtility.GetSettings(); settings.OverridePlayerVersion = "999"; foreach (var group in settings.groups) { SetGroupsSetting(group, true, true, BundledAssetGroupSchema.BundleNamingStyle.AppendHash, AddressableAssetSettings.kRemoteBuildPath, AddressableAssetSettings.kRemoteLoadPath, false); } } private static void SetGroupsSetting(AddressableAssetGroup group, bool UseAssetBundleCache, bool UseAssetBundleCrc, BundledAssetGroupSchema.BundleNamingStyle BundleNaming, string BuildPath, string LoadPath, bool StaticContent) { BundledAssetGroupSchema bundledAssetGroupSchema = group.GetSchema<BundledAssetGroupSchema>(); if (bundledAssetGroupSchema == null) { bundledAssetGroupSchema = group.AddSchema<BundledAssetGroupSchema>(); } //bundledAssetGroupSchema.IncludeInBuild = true; bundledAssetGroupSchema.UseAssetBundleCache = UseAssetBundleCache; bundledAssetGroupSchema.UseAssetBundleCrc = UseAssetBundleCrc; bundledAssetGroupSchema.UseAssetBundleCrcForCachedBundles = true; bundledAssetGroupSchema.BundleNaming = BundleNaming; bundledAssetGroupSchema.BuildPath.SetVariableByName(group.Settings, BuildPath); bundledAssetGroupSchema.LoadPath.SetVariableByName(group.Settings, LoadPath); bundledAssetGroupSchema.SetAssetBundleProviderType(typeof(MyAssetBundleProvider)); EditorUtility.SetDirty(bundledAssetGroupSchema); ContentUpdateGroupSchema contentUpdateGroupSchema = group.GetSchema<ContentUpdateGroupSchema>(); if (contentUpdateGroupSchema == null) { contentUpdateGroupSchema = group.AddSchema<ContentUpdateGroupSchema>(); } contentUpdateGroupSchema.StaticContent = StaticContent; EditorUtility.SetDirty(contentUpdateGroupSchema); } private static void SetAllGroupsToAssetBundleEncryptProvider() { AddressableAssetSettings settings = AASUtility.GetSettings(); foreach (var group in settings.groups) { BundledAssetGroupSchema bundledAssetGroupSchema = group.GetSchema<BundledAssetGroupSchema>(); if (bundledAssetGroupSchema == null) { bundledAssetGroupSchema = group.AddSchema<BundledAssetGroupSchema>(); } bundledAssetGroupSchema.SetAssetBundleProviderType(typeof(MyAssetBundleProvider)); EditorUtility.SetDirty(bundledAssetGroupSchema); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } public static void BuildPlayerContent() { AASUtility.BuildPlayerContent(); } } |
三、自定义Bundle打包方式
新建脚本,继承自BuildScriptPackedMode,添加特性菜单,生成资源,重写3个方法,把这3个方法复制过来,并修改源码方法为virtual。
Name方法,修改返回的名字,就是你Build看见的名字。
BuildDataImplementation 这里修改的主要是初始化BuildInData,创建Resources/Version目录,存放BuildInData序列化后内容,然后打进包里。
PostProcessBundles 这里修改的主要是,把Bundle生成的名字记录到BuildInData(为准确加载准备,Bundle名字为组名+hash,所以可以直接判断名字),把打出来的Bundle复制到Library(因为都设置为远程加载了),使其可以复制到StreamAssets目录打进包里。
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 149 150 151 152 153 154 |
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using UnityEditor.AddressableAssets.Build; using UnityEditor.AddressableAssets.Build.DataBuilders; using UnityEditor.AddressableAssets.Settings; using UnityEditor.AddressableAssets.Settings.GroupSchemas; using UnityEditor.Build.Pipeline.Interfaces; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets.Initialization; using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.ResourceManagement.ResourceProviders; [CreateAssetMenu(fileName = "MyBuildScriptPackedMode.asset", menuName = "Addressable Assets/Data Builders/MyBuildScriptPackedMode")] public class MyBuildScriptPackedMode : BuildScriptPackedMode { public override string Name { get { return "My Build"; }//自定义Build的名字 } protected override TResult BuildDataImplementation<TResult>(AddressablesDataBuilderInput builderInput) { TResult result = default(TResult); var timer = new Stopwatch(); timer.Start(); InitializeBuildContext(builderInput, out AddressableAssetsBuildContext aaContext); using (m_Log.ScopedStep(LogLevel.Info, "ProcessAllGroups")) { var errorString = ProcessAllGroups(aaContext); if (!string.IsNullOrEmpty(errorString)) result = AddressableAssetBuildResult.CreateResult<TResult>(null, 0, errorString); } /////START 初始化buildInData AssetBundleManager.GetInstance().buildInData = new BuildInBundleData(); var targetDir = Path.Combine(Application.dataPath, "Resources"); if (!Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir); targetDir = Path.Combine(targetDir, "Version"); if (!Directory.Exists(targetDir)) Directory.CreateDirectory(targetDir); var targetPath = Path.Combine(targetDir, "BuildInBundleName.bytes"); if (File.Exists(targetPath)) { File.Delete(targetPath); } /////END 初始化buildInData if (result == null) { result = DoBuild<TResult>(builderInput, aaContext); } if (result != null) result.Duration = timer.Elapsed.TotalSeconds; ////START 序列化保存本次打包的内置包列表 var BuildInJson = JsonUtility.ToJson(AssetBundleManager.GetInstance().buildInData); File.WriteAllText(targetPath, BuildInJson); ////END 序列化保存本次打包的内置包列表 return result; } public override void PostProcessBundles(AddressableAssetGroup assetGroup, List<string> buildBundles, List<string> outputBundles, IBundleBuildResults buildResult, ResourceManagerRuntimeData runtimeData, List<ContentCatalogDataEntry> locations, FileRegistry registry, Dictionary<string, ContentCatalogDataEntry> primaryKeyToCatalogEntry, Dictionary<string, string> bundleRenameMap, List<Action> postCatalogUpdateCallbacks) { var schema = assetGroup.GetSchema<BundledAssetGroupSchema>(); if (schema == null) return; var path = schema.BuildPath.GetValue(assetGroup.Settings); if (string.IsNullOrEmpty(path)) return; for (int i = 0; i < buildBundles.Count; ++i) { if (primaryKeyToCatalogEntry.TryGetValue(buildBundles[i], out ContentCatalogDataEntry dataEntry)) { var info = buildResult.BundleInfos[buildBundles[i]]; var requestOptions = new AssetBundleRequestOptions { Crc = schema.UseAssetBundleCrc ? info.Crc : 0, UseCrcForCachedBundle = schema.UseAssetBundleCrcForCachedBundles, UseUnityWebRequestForLocalBundles = schema.UseUnityWebRequestForLocalBundles, Hash = schema.UseAssetBundleCache ? info.Hash.ToString() : "", ChunkedTransfer = schema.ChunkedTransfer, RedirectLimit = schema.RedirectLimit, RetryCount = schema.RetryCount, Timeout = schema.Timeout, BundleName = Path.GetFileNameWithoutExtension(info.FileName), BundleSize = GetFileSize(info.FileName), ClearOtherCachedVersionsWhenLoaded = schema.AssetBundledCacheClearBehavior == BundledAssetGroupSchema.CacheClearBehavior.ClearWhenWhenNewVersionLoaded }; dataEntry.Data = requestOptions; int extensionLength = Path.GetExtension(outputBundles[i]).Length; string[] deconstructedBundleName = outputBundles[i].Substring(0, outputBundles[i].Length - extensionLength).Split('_'); string reconstructedBundleName = string.Join("_", deconstructedBundleName, 1, deconstructedBundleName.Length - 1) + ".bundle"; outputBundles[i] = ConstructAssetBundleName(assetGroup, schema, info, reconstructedBundleName); dataEntry.InternalId = dataEntry.InternalId.Remove(dataEntry.InternalId.Length - buildBundles[i].Length) + outputBundles[i]; dataEntry.Keys[0] = outputBundles[i]; ReplaceDependencyKeys(buildBundles[i], outputBundles[i], locations); if (!m_BundleToInternalId.ContainsKey(buildBundles[i])) m_BundleToInternalId.Add(buildBundles[i], dataEntry.InternalId); if (dataEntry.InternalId.StartsWith("http:\\")) dataEntry.InternalId = dataEntry.InternalId.Replace("http:\\", "http://").Replace("\\", "/"); if (dataEntry.InternalId.StartsWith("https:\\")) dataEntry.InternalId = dataEntry.InternalId.Replace("https:\\", "https://").Replace("\\", "/"); } else { UnityEngine.Debug.LogWarningFormat("Unable to find ContentCatalogDataEntry for bundle {0}.", outputBundles[i]); } UnityEngine.Debug.Log(outputBundles[i]); if (!AssetBundleManager.GetInstance().buildInData.BuildInBundleNames.Contains(outputBundles[i])) { //添加打包的Bundle记录,内置Bundle AssetBundleManager.GetInstance().buildInData.BuildInBundleNames.Add(outputBundles[i]); } var targetPath = Path.Combine(path, outputBundles[i]); var srcPath = Path.Combine(assetGroup.Settings.buildSettings.bundleBuildPath, buildBundles[i]); bundleRenameMap.Add(buildBundles[i], outputBundles[i]); CopyFileWithTimestampIfDifferent(srcPath, targetPath, m_Log); AddPostCatalogUpdatesInternal(assetGroup, postCatalogUpdateCallbacks, dataEntry, targetPath, registry); //复制到Library 打包到包里面 string RuntimePath = UnityEngine.AddressableAssets.Addressables.RuntimePath; string destPath = Path.Combine(System.Environment.CurrentDirectory, RuntimePath, PlatformMappingService.GetPlatformPathSubFolder().ToString(), outputBundles[i]); if (!Directory.Exists(Path.GetDirectoryName(destPath))) Directory.CreateDirectory(Path.GetDirectoryName(destPath)); if (!File.Exists(destPath)) { File.Copy(targetPath, destPath); } registry.AddFile(targetPath); } } } |
设置应用为自定义打包
对了,设置AddressableAssetSettings的PlayerVersionOverried为固定版本,打包出来的catalog就都为同一个,更新这个用来检测更新。
四、自定义Bundle加载方式
前面自定义了Bundle的打包方式,现在就要自定义加载方式了,不然就会走原来设置的远程加载的原本逻辑,我们多做了一个内置包列表和复制移动Bundle到Library中的准备工作就没有用了。
注意如果是UnityWebRequestAssetBundle,则不会支持访问data去保存,不过自行缓存,此时只需要判断是不是内置Bundle就行,其他走web。需要自己缓存Bundle的,需要修改UnityWebRequestAssetBundle为UnityWebRequest
新建一个cs文件MyAssetBundleProvider,复制原内容的AssetBundlePrivider到新建cs脚本中,修改相关类名为自己自定义类名。主要修改IAssetBundleResource的BeginOperation方法和WebRequestOperationCompleted方法
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 |
//MyAssetBundleProvider.cs private void BeginOperation() { string path = m_ProvideHandle.Location.InternalId; var url = m_ProvideHandle.ResourceManager.TransformInternalId(m_ProvideHandle.Location); string bundleName = Path.GetFileName(url); // if a path starts with jar:file, it is an android embeded resource. The resource is a local file but cannot be accessed by // FileStream(called in LoadWithDataProc) directly // Need to use webrequest's async call to get the content. if (AssetBundleManager.GetInstance().buildInData != null && AssetBundleManager.GetInstance().buildInData.BuildInBundleNames.Contains(bundleName))//本地资源,内置包 { string streamPath = UnityEngine.AddressableAssets.Addressables.RuntimePath + "/" + PlatformMappingService.GetPlatformPathSubFolder() + "/" + bundleName; Debug.Log("LoadOne:" + streamPath); var crc = m_Options == null ? 0 : m_Options.Crc; CompleteBundleLoad(AssetBundle.LoadFromFile(streamPath)); } else if (AssetBundleManager.GetInstance().IsCache(bundleName)) //已经下载过 缓存到本地的Bundle { string cachePath = Path.Combine(AssetBundleManager.GetInstance().GetBundleCachePath(), bundleName); Debug.Log("LoadTwo:" + cachePath); var crc = m_Options == null ? 0 : m_Options.Crc; CompleteBundleLoad(AssetBundle.LoadFromFile(cachePath)); } else if (ResourceManagerConfig.ShouldPathUseWebRequest(path)) //真正需要下载的Bundle { Debug.Log("DownloadThree:" + url); var req = CreateWebRequest(m_ProvideHandle.Location); req.disposeDownloadHandlerOnDispose = false; m_WebRequestQueueOperation = WebRequestQueue.QueueRequest(req); if (m_WebRequestQueueOperation.IsDone) { m_RequestOperation = m_WebRequestQueueOperation.Result; m_RequestOperation.completed += WebRequestOperationCompleted; } else { m_WebRequestQueueOperation.OnComplete += asyncOp => { m_RequestOperation = asyncOp; m_RequestOperation.completed += WebRequestOperationCompleted; }; } } else { m_RequestOperation = null; m_ProvideHandle.Complete<MyAssetBundleResource>(null, false, new Exception(string.Format("Invalid path in AssetBundleProvider: '{0}'.", path))); } } private void WebRequestOperationCompleted(AsyncOperation op) { UnityWebRequestAsyncOperation remoteReq = op as UnityWebRequestAsyncOperation; var webReq = remoteReq.webRequest; if (!UnityWebRequestUtilities.RequestHasErrors(webReq, out UnityWebRequestResult uwrResult)) { m_downloadHandler = webReq.downloadHandler; string path = m_ProvideHandle.ResourceManager.TransformInternalId(m_ProvideHandle.Location); string bundleName = Path.GetFileName(path); AssetBundleManager.GetInstance().CacheBundle(bundleName, m_downloadHandler.data);//主要是在这里加了一个保存到本地的方法 if (!m_Completed) { m_ProvideHandle.Complete(this, true, null); m_Completed = true; } } else { m_downloadHandler = webReq.downloadHandler; m_downloadHandler.Dispose(); m_downloadHandler = null; string message = string.Format("Web request {0} failed with error '{1}', retrying ({2}/{3})...", webReq.url, webReq.error, m_Retries, m_Options.RetryCount); if (m_Retries < m_Options.RetryCount) { Debug.LogFormat(message); BeginOperation(); m_Retries++; } else { var exception = new Exception(string.Format( "RemoteAssetBundleProvider unable to load from url {0}, result='{1}'.", webReq.url, webReq.error)); m_ProvideHandle.Complete<MyAssetBundleResource>(null, false, exception); } } webReq.Dispose(); } |
读取内置包列表,缓存Bundle到本地,检查是否有缓存,都是使用 AssetBundleManager.cs
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 |
using System.Collections.Generic; using UnityEngine; using System; using System.IO; [Serializable] public class BuildInBundleData { public List<string> BuildInBundleNames = new List<string>(); } public class AssetBundleManager { private static AssetBundleManager _instance = null; private string cachePath = ""; public BuildInBundleData buildInData; public static AssetBundleManager GetInstance() { if (_instance == null) _instance = new AssetBundleManager(); return _instance; } AssetBundleManager() { cachePath = Path.Combine(Application.persistentDataPath, "ab"); if (!Directory.Exists(cachePath)) { Directory.CreateDirectory(cachePath); } } public void Init() { #if !UNITY_EDITOR var json = Resources.Load<TextAsset>("Version/BuildInBundleName"); buildInData = JsonUtility.FromJson<BuildInBundleData>(json.text); #endif } public String GetBundleCachePath() { return cachePath; } public bool IsCache(string bundleName) { string filePath = Path.Combine(AssetBundleManager.GetInstance().GetBundleCachePath(), bundleName); return File.Exists(filePath); } public void CacheBundle(string bundlename, byte[] bytes) { string filePath = Path.Combine(GetBundleCachePath(), bundlename); if (File.Exists(filePath)) File.Delete(filePath); File.WriteAllBytes(filePath, bytes); string realName = GetRealBundleName(bundlename); string oldPath = PlayerPrefs.GetString(realName, ""); if (oldPath != "") File.Delete(oldPath); PlayerPrefs.SetString(realName, filePath); } public string GetRealBundleName(string bundlename) { if (string.IsNullOrEmpty(bundlename)) return ""; int index = bundlename.LastIndexOf("_"); return bundlename.Substring(0, index); } } |
自定义写完后,需要应用使用上这个自定义加载Provider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public static void SetAllGroupToRemote() { var s = AASUtility.GetSettings(); var groups = s.groups; foreach (var group in groups) { BundledAssetGroupSchema bundledAssetGroupSchema = group.GetSchema<BundledAssetGroupSchema>(); if (bundledAssetGroupSchema == null) { bundledAssetGroupSchema = group.AddSchema<BundledAssetGroupSchema>(); } bundledAssetGroupSchema.BuildPath.SetVariableByName(group.Settings, AddressableAssetSettings.kRemoteBuildPath); bundledAssetGroupSchema.LoadPath.SetVariableByName(group.Settings, AddressableAssetSettings.kRemoteLoadPath); bundledAssetGroupSchema.SetAssetBundleProviderType(typeof(MyAssetBundleProvider)); //主要是这 EditorUtility.SetDirty(bundledAssetGroupSchema); } AssetDatabase.Refresh(); } |
五、检测更新并下载更新
需要扩展两个方法出来,一个是修改请求catalog的地址,一个是获取Bundle的大小接口
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 |
//Addressables.cs public static void SetRemoteCatalogLocation(string newLocation) { m_Addressables.SetRemoteCatalogLocation(newLocation); } public static long GetResourceLocationSize(IResourceLocation location) { return m_Addressables.GetResourceLocationSize(location); } //AddressablesImpl.cs public void SetRemoteCatalogLocation(string location) { for (var i = 0; i < m_ResourceLocators.Count; i++) { if (m_ResourceLocators[i].CanUpdateContent) { var locationInfo = m_ResourceLocators[i] as ResourceLocatorInfo; if (locationInfo.HashLocation != null) { locationInfo.HashLocation.InternalId = location; } } } } public long GetResourceLocationSize(IResourceLocation location) { long size = 0; var sizeData = location.Data as ILocationSizeData; if (sizeData != null) size = sizeData.ComputeSize(location, ResourceManager); return size; } |
更新流程:
- 设置加载资源的远程路径,catalog和Bundle
- 检查网络资源表catalog
- 下载网络资源表catalog
- 通过全局资源lable后去所有的ResourceLocation
- 自定义辨别筛选出需要更新Bundle资源的ResourceLocation
- 获取下载的大小
- 更新下载
其中OperationData为继承IEnumerator,提供异步等待,导出到lua层,lua层可以根据判断这个步骤是否完成,从而进入到下一步骤。
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 |
//初始化内置Bundle包列表 AssetBundleManager.cs public void Init() { #if !UNITY_EDITOR var json = Resources.Load<TextAsset>("Version/BuildInBundleName"); buildInData = JsonUtility.FromJson<BuildInBundleData>(json.text); #endif } //AddressableManager.cs //1.设置远程加载地址 public void SetAddressableRemoteResCdnUrl(string remoteUrl) { Debug.Log("SetAddressableRemoteUrl remoteUrl = " + remoteUrl); if (string.IsNullOrEmpty(remoteUrl)) { return; } //设置catalog的请求路径 string newLocation = remoteUrl + "/" + "catalog_999.hash";//后缀对应为AddressableAssetSettings的PlayerVersionOverried Addressables.SetRemoteCatalogLocation(newLocation); //设置location的transfrom func Addressables.InternalIdTransformFunc = (IResourceLocation location) => { string internalId = location.InternalId; if (internalId != null && internalId.StartsWith("http")) { var fileName = Path.GetFileName(internalId); string newInternalId = remoteUrl + "/" + fileName; return newInternalId; } else { return location.InternalId; } }; } //2.检查网络资源表 public OperationData CheckForCatalogUpdates() { var aOperation = Addressables.CheckForCatalogUpdates(false); return OperationData.Get(aOperation); } //3.下载对应资源表 public OperationData UpdateCatalogs(List<string> catlogs) { var aOperation = Addressables.UpdateCatalogs(catlogs,false); return OperationData.Get(aOperation); } //4.通过全局lable获取所有的ResourceLocation public OperationData GetCheckContentList(string label) { CheckContentList.Clear(); var aOperation = Addressables.LoadResourceLocationsAsync(new List<string> { label }, Addressables.MergeMode.Union); return OperationData.Get(aOperation,false, OnGetCheckContentList); } //5.筛选出需要更新的内容 -1错误 0为没有更新 public int GetUpdateContentList() { if (CheckContentList == null) { Debug.LogError("获取所有的ResourceLocation为空"); return -1; } if (AssetBundleManager.GetInstance().buildInData == null) { #if !UNITY_EDITOR Debug.LogError("获取内置Bundle配置为空"); return -1; #else return 0; #endif } IList<IResourceLocation> updateContent = new List<IResourceLocation>(); foreach (var item in CheckContentList) { string bundleName; if (item.HasDependencies) { foreach (var dep in item.Dependencies) { bundleName = Path.GetFileName(dep.InternalId); if (AssetBundleManager.GetInstance().buildInData.BuildInBundleNames.Contains(bundleName)) { } else if (AssetBundleManager.GetInstance().IsCache(bundleName)) { } else { updateContent.Add(dep); } } } } CheckContentList = updateContent; return CheckContentList.Count; } //6.获取需要下载的大小 public long GetDownloadSize(string label) { long size = 0; foreach (IResourceLocation location in CheckContentList.Distinct()) { size += Addressables.GetResourceLocationSize(location); } return size; } //7.下载更新 public OperationData DownloadDependenciesAsync(string label) { var aOperation = Addressables.DownloadDependenciesAsync(CheckContentList); return OperationData.Get(aOperation); } public void OnGetCheckContentList(object list) { CheckContentList = list as IList<IResourceLocation>; } |
2021 年 9 月 7 日 下午 8:55 沙发
有没有工程提供学习一下