孙广东 2018.7.21
原文 : Addressable Assets Systemを完全に理解する
Unite 2018 Tokyo上有一个关于 Addressable Assets System的演讲!
演讲资料:(要翻墙) 【Unite 2018 Tokyo】そろそろ楽がしたい!新アセットバンドルワークフロー&リソースマネージャー詳細解説
日文肯定是不好看懂!
两个命名空间 :
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement;
你可以使用 Addressable Assets System 做些什么?
到目前为止,我们如何处理Resources&AssetBundle的结构?
观点如下:
- 你能做什么
- 使用任意字符串(地址)加载资产
- 您可以等效地处理Resources和AssetBundle(您可以自由更改与该地址对应的资产的位置)
- 使用引用计数器卸载管理
- AssetBundle的模拟模式(移动AssetBundle,无需构建)
- 通过分析器可视化负载情况
- 你不能做什么
- 它与Variants不对应(因为它可以通过设计地址解析来对应,所以它首先变得不必要)
实际上,它似乎能够解决每个人独立实施的所有事情。
Addressable Assets System的 内部结构
- Addressable Assets System 是由 Addressables和ResourceManager组成的系统!
- Addressables和ResourceManager位于不同的包中
- Addressables 依赖于 ResourceManager
- 在PackageManager中引入Addressables时,ResourceManager会自动包含
- 全部用C#编写
- ResourceManager是一个用于正确管理资源加载和卸载的框架
- 使用引用计数执行卸载管理
- DI可以定制加载过程等
- 它具有方便的功能,例如显示资源加载情况的分析器
- Addressables是一个使用地址管理资产的系统
我上次在Unite之前写了这样一篇文章《Unity2018.2から搭載される(らしい)Resource ManagerとAddressable Asset Systemについて調べてみた》,但此时我只是在调查ResourceManager,它处于单方面状态...... (这很有意义,因为我知道ResourceManager的内部机制)
Addressables的概念
Addressables是一种将资产视为Addressable Assets(可寻址资产)并显着改变现有工作流程的系统。
到目前为止,您已经抛弃了AssetBundle的知识,让我们记住以下概念。
- 所有资产都属于集团
- 对于每个组,您可以更改包装方法 和放置(本地,远程等)等
- 除非移动组,否则无需更改代码,除非地址更改
- 所有资产都有地址
- 地址可以任意指定
- 从脚本加载资产时使用地址
- 默认情况下,路径是地址
- 通过使用AssetReference,您可以在不使用地址的情况下从Inspector上设置引用
- 即使地址已更改,您也可以关注
- 地址和资产之间的对应关系记录在内容目录中
- 由json序列化
- 包括依赖性信息等
- 在初始化时加载
- 您可以更改每个Asset的设置
- 您可以分析配置文件,例如构建目标和加载源,并可以切换
- 例如,您可以将资产加载源切换到本地存储/本地服务器/登台服务器/生产服务器等。
- 您可以在Play模式下更改操作方法
- 不需要AssetBundle构建的模式,从构建的AssetBundle加载的模式等。
ResourceManager的设计
ResourceManager是Addressables后端的系统,由以下类组成。
- ResourceLocation:具有资产位置和依赖性
- ResourceProvider:实现资源加载/卸载
- SceneProvider:具有场景加载/卸载实现
- InstanceProvider:具有实例创建/管理的实现
ResourceManager在初始化时注册要使用的提供程序,并在加载时传递ResourceLocation,使用适当的Provider执行加载处理。
除了内置的,您可以注册提供商的实现,也可以自己注册。
Addressables以下列方式与ResourceManager一起使用。
- 在初始化时注册相应的Provider
- 将地址转换为ResourceLocation并传递它ResourceManager
操作流程
我将以易于理解的图表显示一系列操作的流程。
初始化处理
应用程序启动时设置Addressables(RuntimeInitializeOnLoadMethod)
- 加载内容目录
- 将各种提供程序注册到ResourceManager(DI)
我会的。
加载资产
资产的加载在以下流程中完成。
- 将地址(字符串或AssetReference)传递给Addressables
- Addressables将传递的地址转换为ResourceLocation并将其传递给ResourceManager
- 为了正确加载传递的ResourceLocation,ResourceManager调用Provider来构造并返回AsyncOperation
- 在收到的AsyncOperation中注册加载完成时的处理
换句话说,我们实际上实现了第1部分和第4部分。
完全理解Addressable Assets System可寻址资产系统
从这里,让我们实际触摸并尝试完全理解它。
安装方法
使用Unity 2018.2或更高版本创建新项目,并使用PackageManager进行安装。
但是,由于它现在是预览版,因此无法从PackageManager安装。
因此,编辑manifest.json如下。
(注 上面的0.1.2 版本是我在柏林的Unite视屏中看到的最新版本, 但是这个博文中提到的是 0.0.22)
manifest.json
{
"dependencies": {
"com.unity.addressables": "0.0.22-preview"
}
}
准备资产
一切都没问题,我们将准备资产进行测试。
〜/ Assets / AddressableAssets下面列出了以下资产。
- boys
-
- boys_01.png
- boys_02.png
- boys_03.png
- boys_04.png
- boys_05.png
- girls
-
- girls_01.png
- girls_02.png
- girls_03.png
- girls_04.png
- girls_05.png
- ui
-
- buttons
- ok.png
- cancel.png
我借用了稻林先生的照片。
https://www.irasutoya.com/2014/10/faceicons.html
https://www.irasutoya.com/2013/10/blog-post_5077.html 男孩
https://www.irasutoya.com/2013/10/blog-post_3974.html 女孩
为资产提供地址
初始设定
您可以从 Window -> Asset Management -> Addressable Assets 中打开管理工具。
单击“Create Addressable Settings”时,将创建“Addressable Assets”的设置。
设置保存在 Assets / AddressableAssetsData 下。
请注意,删除此数据将删除地址等的设置。
创建数据时,将显示以下显示。
从Addressables窗口设置
树中的Default Local Group 资产。
插入目录时会出现这种情况,但在此状态下无法处理 。
灰色资产未得到解决,无法访问它们。
要分配地址,每个资产必须直接放在该组下。
目前,没有准备的功能,称为“可寻址一次都某个目录下的资产”,手动或做什么,你需要在脚本设置它。
我制作了一个脚本来转换目录条目,因为它很麻烦。如果插入资产的根目录并执行以下脚本,则所有资产都直接在组下移动。
using UnityEditor.AddressableAssets; using System.IO; public static AddressableAssetsUtil { [UnityEditor.MenuItem("Addressable/MoveSubEntryToRoot")] public static void MoveSubEntryToRoot() { var aaSettings = AddressableAssetSettings.GetDefault(false, false); // 直接在组下移动所有子条目 var entries = aaSettings.GetAllAssets(true, true); foreach (var entry in entries) { entry.address = entry.assetPath; aaSettings.CreateOrMoveEntry(entry.guid, entry.parentGroup); } // 删除变为空的目录条目 entries = aaSettings.GetAllAssets(true, false); foreach (var entry in entries) { var isDirectory = Directory.Exists(entry.assetPath); if (isDirectory) aaSettings.RemoveAssetEntry(entry.guid); } } } |
如果变成如下那就OK。
然而,仍然有一个问题,如果它包含两个或两个以上/(斜杠)要解决,还有的是,有时并不在某些情况下 以及工作中的错误。
出于这个原因,让我们通过右键单击所选的所有资产条目来运行Simplify Entry Names 。
您现在可以使用地址访问资产了。
从Inspector设置地址
虽然我没有使用上图的过程设置,但也可以从Inspector编辑资产地址。
加载资产
制作一个正确放置Image的场景。
让我们用另一个加载了Addressables的资产替换这个Image。
使用地址加载
首先将新脚本附加到Image对象上。
制作以下脚本。一个简单的例子,在启动和替换m_img的精灵时加载boy_02。
using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.UI; public class TestMonoBehavior : MonoBehaviour { public Image m_img; void Start() { // //加载指定的地址 Addressables.LoadAsset<Sprite>("boy_02") .Completed += op => { m_img.sprite = op.Result; }; } } |
对于m_img,将Image的引用在 Inspector上设置为:
如果这样做,您可以看到Sprite被替换。
使用AssetReference加载
使用AssetReference,您可以从Inspector设置可寻址资产引用,而无需对地址进行硬编码。
让我们改变上面的脚本如下。
- 变化
- 将 AssetReference 添加到作为 成员
- 将LoadAsset的参数更改为AssetReference
using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.UI; public class TestMonoBehavior : MonoBehaviour { // AssetReferenceTypeRestriction:可以限制可以设置的资产类型 //因为没有Sprite选项,所以我将其设置为Texture 2D [AssetReferenceTypeRestriction(typeof(Texture2D))] public AssetReference m_ref; public Image m_img; void Start() { // 使用AssetReference作为参数读取 Addressables.LoadAsset<Sprite>(m_ref) .Completed += op => { m_img.sprite = op.Result; }; } } |
接下来,从Inspector设置AssetReference。让我们建立一个不同的资产。
可从下拉菜单中选择。
如果这样做,您可以看到Sprite已更改为设置资产。
作为AssetReference的一项功能,即使地址发生变化,也可以遵循更改。 AssetReference因为它拥有国内资产的GUID,只要GUID不会改变,你可以遵循。
使用Profiler分析器检查负载状态
Window -> Asset Management -> Resource Profiler 打开。
如果在打开Profiler的情况下运行它,则会显示加载状态。
可以从分析器中读取以下信息。
- 发生加载事件时
- 已加载资产的地址
- 资产参考计数
- 加载资产来源,加载方法
Play模式下改变
您可以从Addressables窗口右上角的设置图标更改播放模式。
- Fast Mode: 快速模式:直接加载文件而不打包,快速但Profiler获取的信息较少
- Virtual Mode:虚拟模式:在不打包的情况下模拟靠近AssetBundle的操作
- Packed Mode:打包模式:实际上是从AssetBundle打包和加载
让我们实际改变播放模式,看看探查器中的显示是如何变化的。
Fast Mode快速模式
在此模式下,我们使用 AssetDatabase.LoadAssetAtPath 直接加载文件。虽然AssetBundle构建可以不要求立即执行的,它不能在探查证实,如果他们被列入其中AssetBundle资产。
Virtual Mode虚拟模式
与FastMode不同,您可以查看哪个AssetBundle包含资产。
此模式最终还加载了AssetDatabase.LoadAssetAtPath 。
虽然AssetBundle构建是没有必要的,第一为了构建AssetBundle和资产之间的对应关系。
还可以通过设置虚拟通信速度来模拟加载时间。
Packed Mode打包模式
在这种模式下,实际构建并加载AssetBundle。在编辑器中运行时,它会在不构建 时自动构建。 (判决方法等细节尚未调查)
分析器中显示的信息与VirtualMode几乎相同。
在结束时,你必须加载使用 AssetBundle.LoadFromFileAsync 和 UnityWebRequestAssetBundle.GetAssetBundle(URI uri) 的AssetBundle。
更改资产的目标(加载源)
通过改变资产的GroupType场,它能够改变要在其中放置详细(负载源)的资产。
通过右键单击“Addressables”窗口上的组,可以从上下文菜单更改GroupType。
GroupType有以下三种。
- Local Packed Content(默认设置)
- 只读当地环境
- 构建StreamingAssets并从StreamingAssets加载
- LocalAssetBundleProvider用于加载
- 在内部,我们使用AssetBundle.LoadFromFileAsync
- 无法进行详细设置
- Remote Packed Content远程打包内容
- 与Local不同,您可以设置构建和加载源
- RemoteAssetBundleProvider用于加载
- 我们使用UnityWebRequestAssetBundle.GetAssetBundle(URI uri)内部
- 由于未传递版本version和哈希hash,因此不会进行缓存
- 由于UnityWebRequest也可以加载本地文件,加载源文件:也可以从本地加载它//
- Advanced Packed Content高级打包内容
- 与Remote一样,您可以设置构建目标和加载源
- 您可以指定要用于加载的提供程序
- 当您想要自定义加载处理(加密,专有协议等)时使用
从服务器加载资产
这次我将在本地启动服务器并从那里加载。
小组设定
在“Addressables”窗口中,右键单击该组并将其更改为“Remote Packed Content远程打包内容”。
如果选择处于此状态的组,则设置项将显示在窗口的底部。
- Packing Mode包装方式:选择AssetBundle的包装方式。
- Pack Together: 打包在一起:放在一个AssetBundle中
- Pack Separately: 单独打包:单独的AssetBundle
- Build Path:构建路径:在哪里构建AssetBundle
- Load Prefix:加载前缀:用于加载的路径
- 在模式字符串传递给短提供商(UnityWebRequest),{Load Prefix负载前缀} / {路径AssetBundle}变为最终URI
让我们将Packing Mode打包模式更改为Separately单独,Build Path构建路径,将Load Prefix加载前缀到Remote远程。
Load Prefix加载前缀现在是localhost。
如果您在此状态下播放Play模式设置为“Packed已打包”模式,则加载将失败。
将AssetBundle放在服务器上
在这里,确保AssetBundle是在ServerData下构建的。
如果未正确构建,则可能由于缓存的影响而无法构建。让我们准备并执行以下脚本 。
using UnityEditor.AddressableAssets; public static AddressableAssetsUtil { [UnityEditor.MenuItem("Addressable/Build")] public static void Build() { //应该预留BuildTarget BuildScript.PrepareRuntimeData(true, false, true, true, false, UnityEditor.BuildTargetGroup.Standalone, UnityEditor.BuildTarget.StandaloneOSX); } } |
准备好AssetBundle时,让我们在本地设置AssetBundle分发服务器。这是一个很好的东西,甚至连nginx的Apache的,但在设置文档根目录建立{项目的路径}目的地/ ServerData。
点是http://localhost/{BuildTarget}/{AssetBundle的路径} 命中的文件的AssetBundle状态。
将它与Apache标准配合使用会很有趣。 (当我们谈论如何设置服务器时,我们不会在这里详细说明)
如果您在服务器上运行它并且可以正常加载它,您可以看到它是使用分析器从localhost加载的。
使用配置文件切换负载源
可以使用Profile更改Build Path构建路径,Load Prefix加载前缀设置。
这允许,例如,负载的源文件 file://localhost,而暂存服务器,即,例如在每个切换到生产服务器,也能够通过简单地切换配置文件来实现。
从窗口左上角选择“Manage Profiles管理配置文件”以打开配置文件设置窗口。
在文件file:// 和 localhost 之间切换
让我们尝试使用profile在file://和localhost之间切换。
您可以从“Manage Profile 管理配置文件”列表按钮添加配置文件。添加「Remote」配置文件并尝试以下设置。
MyLoadPrefix条目已添加 并设置如下。
- Default6:file://[AssetPath.ProjectPath]/ServerData/[UnityEditor.EditorUserBuildSettings.activeBuildTarget]
- 由于API获取项目路径不标准,我们自己准备(见下文)
- Remote: http://localhost/[UnityEditor.EditorUserBuildSettings.activeBuildTarget]
可以通过在 [] 中包含配置文件的值来使用静态变量。如果要从脚本控制配置文件的值,可以通过自己编写静态属性来编写它。
由于我们需要获取项目的路径以获取ServerData目录的路径,因此我们实现了以下属性。
public static class AssetPath { public static string ProjectPath { get { return System.IO.Directory.GetCurrentDirectory(); } } } |
设置配置文件后,让我们将组LoadPrefix更改为MyLoadPrefix。
在此状态下,如果从左上角的菜单中选择配置文件,则可以看到“Load Prefix”切换。如果运行它并使用Profiler进行检查,则可以检查负载源是否正常切换。
带标签的批量装载
标签可以附加到资产和地址,例如“加载指定了特定标签的所有资产”。
尝试并按如下方式加载试用版中的「girls」标签。
将以下代码放在适当的位置。
Addressables.PreloadDependencies("girls", null); .Completed += op => { Addressables.LoadAsset<Sprite>("girl_01"); }; |
您可以看到已标记资产的AssetBundle立即加载和卸载。
在此状态下加载 girl_01 会从下载的AssetBundle加载AssetBundle,因为已经下载了AssetBundle。
它可能用于将标签附加到您要预先下载的AssetBundle并使用它加载它。
内容目录的工作原理
使用构建的AssetBundle,您会注意到有一个名为 catalog_1.json 的有意义的文件。
我猜这是内容目录的标识。但是,我也记得有一个名为 catalog_1.hash 的神秘文件。
所以我们将看一下内容目录的机制。
内容目录的内容
总之,每个文件的标识如下。
- catalog_1.json:具有JSONUtility的序列化内容目录(ResourceLocationList)
- catalog_1.hash:上面json的MD5哈希值
内容是这样的。 (Json正在塑造正在缩小的东西,以便于查看)
catalog_1.json { "locations":[ { "m_address":"SampleScene", "m_guid":"2cda990e2423bbf4892e6590ba056729", "m_type":0, "m_typeName":"UnityEngine.SceneManagement.Scene", "m_internalId":"Scenes/SampleScene", "m_provider":"UnityEngine.ResourceManagement.SceneProvider", "m_labelMask":0, "m_dependencies":[ ], "m_isLoadable":true }, { "m_address":"0", "m_guid":"2cda990e2423bbf4892e6590ba056729", "m_type":1, "m_typeName":"UnityEngine.SceneManagement.Scene", "m_internalId":"Scenes/SampleScene", "m_provider":"UnityEngine.ResourceManagement.SceneProvider", "m_labelMask":0, "m_dependencies":[ ], "m_isLoadable":true }, { "m_address":"defaultlocalgroup_assets_boy_01.bundle", "m_guid":"", "m_type":0, "m_typeName":"UnityEngine.AssetBundle", "m_internalId":"file:///Users/user/Addressable Test/ServerData/StandaloneOSX/defaultlocalgroup_assets_boy_01.bundle", "m_provider":"UnityEngine.ResourceManagement.RemoteAssetBundleProvider", "m_labelMask":0, "m_dependencies":[ ], "m_isLoadable":false }, { "m_address":"boy_01", "m_guid":"7276a10699bf545ab88993a205239a28", "m_type":0, "m_typeName":"UnityEngine.Texture2D", "m_internalId":"Assets/AddressableAssets/boys/boy_01.png", "m_provider":"UnityEngine.ResourceManagement.BundledAssetProvider", "m_labelMask":0, "m_dependencies":[ "defaultlocalgroup_assets_boy_01.bundle" ], "m_isLoadable":true } //~中略~// ], "labels":[ "default", "girls" ] } |
catalog_1.hash ac03d2db95e6f339f366f0cc39218c87 |
内容目录加载处理
作为一个流程我觉得如下。
- 在启动时使用 RuntimeInitializeOnLoadMethod 调用加载过程
- 去阅读远程 catalog_1.hash
- 与本地缓存的哈希值进行比较并加载 catalog_1.json
- 如果哈希值与本地缓存中的相同,就Load
- 如果哈希值不同,请从远程加载它并将其缓存在 Application.persistentDataPath 下
通过哈希值完成缓存的点就是重点。除非更新,否则仅对哈希值进行通信。
关于解析内容目录本身的位置
我理解加载内容目录的机制,但是如何解决内容目录本身的位置?我认为它是否似乎有疑问。
有关放置内容目录的位置的信息存储在 ResourceManagerRuntimeData 中。 ResourceManagerRuntimeData在构建时生成,在json处序列化,并放在StreamingAssets下。
换句话说,在构建应用程序时,有必要预先生成 ResourceManagerRuntimeData。
如果您使用UnityEditor.AddressableAssets.BuildScript.BuildPlayer,ResourceManagerRuntimeData将预先构建,然后构建应用程序。
(在编辑器中运行时,每次自动生成读取)
ResourceManagerRuntimeData的位置已成为 StreamingAssets/ResourceManagerRuntimeData_settings.json 在跳动决定的,你必须加载内容目录之前加载它。
实际内容如下,您可以看到内容目录的位置已标记。
ResourceManagerRuntimeData_settings.json { "settingsHash":"13f9f4a40ed6bec5d9ad350892e96fdf4", "catalogLocations":{ "locations":[ { "m_address":"0_RemoteCatalog_102d3183f9bcc41febf5fd015f404569", "m_guid":"", "m_type":0, "m_typeName":"", "m_internalId":"http://localhost/StandaloneOSX/catalog_{ContentVersion}.json", "m_provider":"UnityEngine.AddressableAssets.ContentCatalogProvider", "m_labelMask":1, "m_dependencies":[ "LocalCatalogHash102d3183f9bcc41febf5fd015f404569", "RemoteCatalogHash102d3183f9bcc41febf5fd015f404569" ], "m_isLoadable":true }, { "m_address":"LocalCatalogHash102d3183f9bcc41febf5fd015f404569", "m_guid":"", "m_type":0, "m_typeName":"", "m_internalId":"{UnityEngine.Application.persistentDataPath}/catalog_{ContentVersion}.hash", "m_provider":"UnityEngine.ResourceManagement.TextDataProvider", "m_labelMask":0, "m_dependencies":[ ], "m_isLoadable":false }, { "m_address":"RemoteCatalogHash102d3183f9bcc41febf5fd015f404569", "m_guid":"", "m_type":0, "m_typeName":"", "m_internalId":"http://localhost/StandaloneOSX/catalog_{ContentVersion}.hash", "m_provider":"UnityEngine.ResourceManagement.TextDataProvider", "m_labelMask":0, "m_dependencies":[ ], "m_isLoadable":false } ], "labels":[ ] }, "usePooledInstanceProvider":false, "profileEvents":true, "contentVersion":"1", "assetCacheSize":25, "assetCacheAge":5.0, "bundleCacheSize":5, "bundleCacheAge":5.0 } |
http://localhost/StandaloneOSX/catalog_{ContentVersion}.json ,因为它包含了自己在ResourceManagerRuntimeData ContentVersion,内容目录,除非你建立在当前形势下的应用程序不会允许你改变的负载目的地 。
我觉得我能在这里改变一下。
最后
我认为目前可以使用的功能受到相当大的限制。 。
至今以来的AssetBundle关系可能的麻烦要解决大分,我想期待正式发布7!
就个人而言,我认为我想要以下功能。
- 能够在节点的基础上设置地址,如AssetBundleGraphTool
- 一种功能,可以在任意时间轻松执行构建
- 它很麻烦,因为它是在未经许可的情况下构建的,并且内容目录已更新
- 能够在加载时任意改变AssetBundle的路径模式(Load Suffix like)
- 我想在最后添加版本号和哈希
- AssetBundle缓存支持
- 如果您自己编写Provider,似乎可以处理它,但我希望您按标准进行响应
我很高兴与AssetBundleGraphTool整合......我很高兴......
总之 在Unity 2018.2 已经可以测试它了!!!!