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);