需要扩展两个方法出来,一个是修改请求catalog的地址,一个是获取Bundle的大小接口
//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
获取下载的大小
更新下载
//初始化内置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>; }
OperationData为继承IEnumerator,提供异步等待
前面自定义了Bundle的打包方式,现在就要自定义加载方式了,不然就会走原来设置的远程加载的原本逻辑,我们多做了一个内置包列表和复制移动Bundle到Library中的准备工作就没有用了。
注意如果是UnityWebRequestAssetBundle,则不会支持访问data去保存,不过自行缓存,此时只需要判断是不是内置Bundle就行,其他走web。需要自己缓存Bundle的,需要修改UnityWebRequestAssetBundle为UnityWebRequest
新建一个cs文件MyAssetBundleProvider,复制原内容的AssetBundlePrivider到新建cs脚本中,修改相关类名为自己自定义类名。主要修改IAssetBundleResource的BeginOperation方法和WebRequestOperationCompleted方法
//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
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
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(); }
新建脚本,继承自BuildScriptPackedMode,添加特性菜单,生成资源,重写3个方法,把这3个方法复制过来,并修改源码方法为virtual。
Name方法,修改返回的名字,就是你Build看见的名字。
BuildDataImplementation 这里修改的主要是初始化BuildInData,创建Resources/Version目录,存放BuildInData序列化后内容,然后打进包里。
PostProcessBundles 这里修改的主要是,把Bundle生成的名字记录到BuildInData(为准确加载准备,Bundle名字为组名+hash,所以可以直接判断名字),把打出来的Bundle复制到Library(因为都设置为远程加载了),使其可以复制到StreamAssets目录打进包里。
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就都为同一个,更新这个用来检测更新。
工具类,这是网上找的,可以动态的创建或删除组,为资源添加label 加入到组。
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"); } }
静态方法,提供给编辑器扩展菜单使用
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) { //场景目录 MarkLevelDataDir(dir); } //对比移除无用的组 foreach (var iter in originGroups) { if (!iter.Value && originGroupMaps.ContainsKey(iter.Key)) { setting.RemoveGroup(originGroupMaps[iter.Key]); } } AssetDatabase.Refresh(); } 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; bool isTipLabel = false; if (parentName == "Src")//Lua脚本 { isTipLabel = true; } string groudName = string.Format("{0}.{1}", parentName, dir.Name); AASUtility.CreateGroup(groudName); if (originGroups.ContainsKey(groudName)) { originGroups[groudName] = true; } List<string> allFiles = new List<string>(); List<string> allFileGuids = new List<string>(); GetMarkFiles(allFiles, dir); foreach(var fileStr in allFiles) { string uFilePath = UtilityTool.ChangePath(fileStr); string guid = AssetDatabase.AssetPathToGUID(uFilePath); var entry = AASUtility.AddAssetToGroup(guid, groudName); //缩减 可寻址的长度 lua脚本的同步require的路径 entry.address = uFilePath.Replace("Assets/" + "LevelsData" + "/", "").Replace("Src" + "/", ""); allFileGuids.Add(guid); } if (isTipLabel) { AASUtility.SetLabelToAsset(allFileGuids, groudName, true);//脚本设置lable 用于预加载 } AASUtility.SetLabelToAsset(allFileGuids, "default", true);//所有资源都设为default,以前是拿来检测更新的,现在好像没用了 } private static void GetMarkFiles(List<string> list, DirectoryInfo dir) { if (dir.Name == PackPath) return; FileInfo[] files = dir.GetFiles(); foreach (var file in files) { if (file.FullName.EndsWith(".meta")) continue; list.Add(file.FullName); } DirectoryInfo[] dirs = dir.GetDirectories(); foreach (var d in dirs) { GetMarkFiles(list, d); } } }
游戏更新就是在游戏启动时或某一时机,触发程序去获取更新的列表类容,然后把新的东西全部下载到本地。而Addressable的机制似乎是为了当用到的时候才去下载对应的资源,这样好像不太友好,Bundle包大一点的就卡定在那里了。
我尝试过,把组设置成Local的形式,Bundle打进了包体里面,而对这些Local组的Bundle做更新的话据需要做增量包,Addressable可以识别而动态生成Remote的远程组生成远程Bundle,但是每次都要做这样的增量包,组会越来越多,且不容易管理。
后来我再尝试把组都设置成Remote,结果Bundle不打进包体里面,再程序运行的时候需要用时才去下载,就算你用特殊手段把Bundle干进包体里,Load Path记录的也是远程的地址,当你使用到某个资源时还是会走远程下载。
所以为了实现,不做增量包(方便管理),使用整Bundle的形式更新,设置成Remote形式也能打进包里,并能正确的加载内置的Bundle,需要自定义Addressable的打包和加载Bundle。
const string PreloadLabel = "preload"; static Dictionary<string, UnityEngine.Object> resources = new Dictionary<string, UnityEngine.Object>(); static bool preloaded = false; public static bool Preloaded { get { return preloaded; } } public static async Task Preload() { Debug.Log("Preloading Addressables. Time: " + Time.realtimeSinceStartup + " Frame: " + Time.frameCount); var list = await Addressables.LoadResourceLocationsAsync(PreloadLabel, null).Task; if (list == null) return; List<string> keys = new List<string>(list.Count); foreach (var key in list) { keys.Add(key.PrimaryKey); } var r2 = await Addressables.LoadAssetsAsync<UnityEngine.Object>(list, null).Task; if (r2 != null) { if (r2.Count != keys.Count) { Debug.LogError("Loaded objects count differs from key count!"); return; } for (int i = 0; i < keys.Count; i++) resources.Add(keys[i], r2[i]); Debug.Log("Addressables Preloaded. Time: " + Time.realtimeSinceStartup + " Frame: " + Time.frameCount); } preloaded = true; }