• Unity Addressables 的使用相关


      随着 Unity 的 Addressables 逐渐完善, 已经可以替代其它的打包加载卸载等工具的功能了, 今天做了一下测试, 有那么几个挺好的地方 : 

      1. AssetReferenceT<T> 引用的对象, 也正确参与 Addressables 相关加载卸载逻辑.

      2. 有一些贴心设计比如场景如果加入到 Addressables Groups 中的话, 如果被 Build Settings 引用到, 会自动取消引用 :

      场景会自动取消引用, 如果强行勾选 Build Settings 里的场景, 会自动删除 Addressables Groups 中的引用. 这样就保证了场景不会在打包时重复了.

      不过场景打包的资源仍然跟 Addressables 是分开管理的.

      比如下面这样, Test001 勾选为默认场景, 场景里有一张贴图 : 

      而在场景中又通过代码加载同样一张贴图, 就会变成这样资源重复了 :

      说明场景的贴图是被 Resources 那边管理的, 这张贴图只能通过 Resources.UnloadUnusedAssets(); 来卸载.

      所以场景还是用一个空场景作为起始场景, 再通过 Addressables 加载其它场景就行了.

      5. 以前 AssetBundle 会出现的默认材质 Shader 重复编译问题, 猜测应该没问题, 测试看看 : 

     

      看来一般状况下不管怎样打包和分包, 都不会造成材质 Shader 的重复编译了.

      -------------------------------

      看了一下生成出来的 AssetBundle 包, 感觉并不是很正确, 这里的场景是引用了一个 Prefab, 而所有的材质和贴图引用都是通过 Prefab 来的, 如果我只对场景进行打包的话, 得到的是下面这样的结果 : 

      如果把场景和 Prefab 都进行打包的话, 得到下面的结果 : 

      可见场景的大小并没有改变, 并且 Prefab 的大小也是跟场景差不多, 这就说明场景和 Prefab 都把引用资源给打包了.......

      查了一下, 原来是创建材质 Group 的时候代码添加错误导致的, 通过代码创建需要制定相应的控制器, 这里记录一下 : 

        public static List<Type> SchemaTypes
        {
            get
            {
                return new List<Type>() {
                  typeof(  UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema),
                  typeof(  UnityEditor.AddressableAssets.Settings.GroupSchemas.ContentUpdateGroupSchema),
                };
            }
        }
        private static void AddNewAsset(UnityEngine.Object asset, string groupName)
        {
            var settings = AddressableAssetSettingsDefaultObject.Settings;
            if(settings)
            {
                var assetPath = AssetDatabase.GetAssetPath(asset);
                var group = settings.FindGroup(groupName) ?? settings.CreateGroup(groupName, false, false, false, new List<AddressableAssetGroupSchema> { settings.DefaultGroup.Schemas[0] }, SchemaTypes.ToArray());
                if(group)
                {
                    var guid = AssetDatabase.AssetPathToGUID(assetPath);
                    var entry = settings.CreateOrMoveEntry(guid, group);
                    entry.SetAddress(assetPath, true);
                }
            }
        }

      PS : 创建资源 Group 可以创建很多个, 或者在一个Group 里面选择分开打包, 下面的都得到差不多的包, 材质全是独立的 Assetbundle 包, 不知道在加载时有没有性能差别.

      对于不把材质作为显式打包的设置的话, 只引用场景和 Prefab, 状况就回到前面资源重复的状态了 : 

      可以猜想到重复引用的资源, 仍然会被重复打包. 那么加载出来的资源, 就必然又会重复了 : 

     材质重复

     图片也重复了

      重新把材质引用到 Addressables Group 里面来, 就正常了 :

      所以目前来说, 它还不是一个手动就能维护的打包系统, 仍然需去厉遍所有资源的引用数量, 重复引用资源仍然要自己控制生成相应 Groups, 要不然资源加载的控制也是无从说起的. 目前它的好处就是把 Shader 的编译问题给解决了, 这在以前是个老大难问题......

      而且通过查看文件大小, 可以看出 Addressables 把多余的变体给删除了 : 

     

      同样的材质 Addressables 和普通 BuildPipeline.BuildAssetBundles 打包出来的大小天差地别, 应该是在标准材质的变体上

      PS : 不过之前测试时用了两个 Canvas 和 AssetReferenceT<Texture> 引用了同一张图片, 可是图片没有进行 Addressables 的显式引用, 不过加载的时候也没有产生资源重复的情况, 估计是对于图片和 Mesh 这类的唯一性资源, Addressables 已经自己做了处理了吧

      又错了, 之前测试的时候是把两个 Canvas 放在同一个 Group 里面了, 而且 AssetReferenceT<Texture> 这个序列化会自动把引用的图片加到 Addressables 里面去, 造成了图片资源不会重复加载的假象 : 

      当两个 UI 处于不同的包的时候, 并且没有图片的显式引用的话, 仍然会在打包和加载的时候造成重复.

      再来看看图集的使用情况, 最近很奇怪 Unity 右键菜单找不到创建 SpriteAtlas 的操作.......

      

      创建了两个 spriteatlas, 因为图片没有加入 Addressables 所以是灰色的, 加载通过先加载 SpriteAtlas 然后再获取下面的图片, 跟我们的常识一样, 能够正常合批.

      可是如果把其中的一些图片加入到 Group 里面来, 就容易出问题了 : 

      加入前

      加入后

      

      应该是图片独立出去之后, 作为普通图片存在了, 并且非PO2所以图片不压缩, 就很大.

      如果独立的图片跟 SpriteAtlas 同一个包, 它又能够作为图集的一部分了 : 

      尺寸也恢复了, 所以如果有需求, 需要把图片和图集放在同一个包里. 不过即使这样通过 SpriteAtlas 加载出来的图片, 跟直接通过资源加载的图片, 仍然是不同的, 这个需要记住 : 

        Addressables.LoadAssetAsync<SpriteAtlas>("Assets/Atlas/SA2.spriteatlas").Completed += (_atlas) =>
        {
            imgs[0].sprite = _atlas.Result.GetSprite("bg_icon");
            Debug.Log("spriteatlas : " + imgs[0].sprite.name);
    
            Addressables.LoadAssetAsync<Sprite>("Assets/Sprites/SP1/bg_icon.png").Completed += (_sp) =>
            {
                imgs[1].sprite = _sp.Result;
                Debug.Log("sprite : " + imgs[1].sprite.name);
    
                Debug.Log(imgs[0].sprite == imgs[1].sprite);
            };
    
            Addressables.LoadAssetAsync<Sprite>("Assets/Sprites/SP1/ic_pos.png").Completed += (_sp) =>
            {
                imgs[2].sprite = _sp.Result;
                Debug.Log("sprite : " + imgs[2].sprite.name);
            };
        };

      结果没有什么意外, 不过从 SpriteAtlas 来的图片, 是一个克隆, 可能每次通过接口获取都会造成克隆.

      意外的是通过资源加载的 Sprite, 它们都能够正确合批, 比如上面的三张图, 能够在一次 draw 中绘制......根据观察, 通过上面的资源接口加载出来的 Sprite, 也是依赖于 SpriteAtlas 的, 自动加载了图集. 所以目前看来, 图集都是能正确合批的.

      还有一个问题, 以前 AssetBundleName 设置的时候, 如果场景的设置跟资源的设置是一样的话, 打包会报错, 告诉你场景不能跟一般资源打在一个包里 : 

      在 Addressables Group 里面是可以设置到一个 Group 里面的, 并且打包设置为 Pack Together 的话, 也是不会报错的 : 

      资源和场景设置在了同一个 Group 中一起打包

      生成的包查看一下, 发现它自动把场景和资源给分开了, build 出来两个 AssetBundle 包 : 

      生成了一个 _assets_all 的包和 _scenes_all 的包, 自动帮你分好包了.

    ---------- 一些补充 ------------

    1. 在 Group 编辑的时候可以看到一般新加资源会自动命名为带了扩展名的路径, 这个就保证了它的唯一性, 不过在不断修改的过程中比如 Assets/A.mat 文件位置变为 Assets/Materials/A.mat 之后, 它在 Addressables 里面的 Key 也是不会变的, 还是老样子, 好处是代码不需要变动.

      问题是如果又加入一个 Assets/A.mat 资源, 就会有两个相同的名称资源了 : 

      就会造成混乱, 其实还是永远保持路径为 Key 才是最准确的, 虽然会因为资源位置移动造成代码需要修改的情况.

      PS : 在这个面板右键有一个快捷方式简化 Key 名称, 可是没有还原 Key 为文件路径的方法, 有点搞笑 : 

      只能用代码去改回 :

        var settings = AddressableAssetSettingsDefaultObject.Settings;
        if(settings)
        {
            foreach(var group in settings.groups)
            {
                foreach(var entry in group.entries)
                {
                    entry.SetAddress(entry.AssetPath, true);
                }
            }
        }

    2. 文件夹也能设置为 Addressables 资源, 估计跟 Resources 一样能够读取整个文件夹 : 

      测试了一下, 不行...

      文件夹拖入 Group 只能展现文件夹, 如果里面的资源没有勾选 Addressable 的话, 是灰色的, 如果勾选了的话就变成跟 Assets/Materials 同级的了, 不能放到里面去了......

      不管在任何设置下, 都不能通过 Assets/Materials 作为 Key 来加载文件夹下的资源 : 

        Addressables.LoadAssetsAsync<Material>("Assets/Materials", (_obj) => { }).Completed += (_list)=> 
        {
            var mats = _list.Result;
            foreach (var mat in mats)
            {
                Debug.Log(mat.name);
            }
        };

      会报错......既然不能加载, 为啥要让文件夹也能放到 Addressables 系统里, 理解不能.

    3. 回调

      找了半天才找到, AssetBundle Build 的回调 :

     UnityEditor.AddressableAssets.Build.BuildScript.buildCompleted

      现在 External Tools 里面把源码 package 之类的都勾上, 可以在工程中查看源码

    4. 出现了打包经典BUG : 

    Could not find a part of the path ""

      以前打包也会有这个问题, 设置包名的时候文件名过长会出现这个问题, 一般情况下如果太长又需要它的包名是唯一的话, 直接用 GUID 代替包名即可 : 

        var guid = AssetDatabase.AssetPathToGUID(assetPath);
        if(assetPath.Length > 80)
        {
            var setPath = System.IO.Path.GetFileName(assetPath) + "_" + guid;
            Debug.LogError("路径过长 : " + assetPath + "\n修改为 : " + setPath);
            assetPath = setPath;
        }

      打包选项可以选择添加一些头头尾尾的比如哈希值, 所以包名会比想象的要长, 特别是处于根目录下的包, 你需要添加 GUID 作为唯一性, 打包系统也会添加头尾给你比如 :

      自己添加 GUID -> 

      打包系统添加头部和尾部哈希值 -> 

      最终长度就会变得很长, 超过 windows 的限制...

    ====================================================================

      一些使用上的问题.

    1. 批量设置资源的时候, 如果每个资源的 postEvent 都是 true 的话, 会非常慢, 一般没有特殊需求可以都不进行 event 通知.

    2. 在设置完之后刷一次 event 就可以刷新 Addressables Groups 面板显示了 : 

    settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryModified, null, true);
  • 相关阅读:
    开发新技术展望系列课程(视频课程讲师:徐晓卓)
    VSTS风暴系列课程(视频课程讲师:王京京/王兴明/王然)
    Mysql索引的数据结构及索引优化
    CAP原则,分布式场景下为何只能取其二
    为什么使用Redission解决高并发场景分布式锁问题
    Java面试题(6)Redis
    外企英语面试常见问题及核心话术
    Nacos&Eureka&Zookeeper
    j2ee中DAO设计模式
    第一个随笔
  • 原文地址:https://www.cnblogs.com/tiancaiwrk/p/15945882.html
Copyright © 2020-2023  润新知