读一读

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


更新流程:

  1. 设置加载资源的远程路径,catalog和Bundle

  2. 检查网络资源表catalog

  3. 下载网络资源表catalog

  4. 通过全局资源lable后去所有的ResourceLocation

  5. 自定义辨别筛选出需要更新Bundle资源的ResourceLocation

  6. 获取下载的大小

  7. 更新下载


    //初始化内置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);
        }
    }
}


设置应用自定义打包

image.png

image.png

image.png


对了,设置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);
        }
    }
}


image.png

image.png


游戏更新就是在游戏启动时或某一时机,触发程序去获取更新的列表类容,然后把新的东西全部下载到本地。而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;
 
        }

image.png