需要扩展两个方法出来,一个是修改请求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;
}