【Unity|Unity Addressables资源管理系统】
文章目录
- Addressable Asset System(可寻址资产系统)
-
- 1. 背景
- 2. Addressable系统介绍
- 3. Addressable系统优势:
- 4. Addressable系统与AssetBundle的区别
- Addressable系统的使用
-
- 1. 安装
- 2. Addressables管理窗口
- 3. AddressableAssetSettings系统设置
- 4. 资源组设置
- 5. 标记资源
- 6. 资源打包
- 7. 加载资源
- 8. 更新资源包
- 9. 热更新代码和方案
- 10. 资源自动分组
- 11. 内存管理
- 12. Addressable打包粒度
Addressable Asset System(可寻址资产系统) 1. 背景 ??在开始介绍Addressable系统之前,我们先来回顾下传统的几种资源加载方式:
- 直接引用: 使用直接引用是最简单快捷的,但不可动态加载。
- Resources资源管理: Resources文件夹下所有文件都会打包到安装包,无论资源是否被使用,这样就会导致包体过大;Resources没办法做热更新资源;Resources加载资源时,对资源路径要求严格。
- AssetBundle资源管理: 可热更,但管理难度大。
3. Addressable系统优势:
- 快速迭代:
使用Addressable在开发前期就进入快速开发的阶段,无论使用任何你喜欢的资源管理技术,都能快速切换到Addressable系统中。几乎不需要修改代码。 - 依赖管理:
系统不仅返回请求内容,还返回该内容所有依赖项,以便在返回内容之前加载所有网格、着色器、动画等。 - 内存管理:
Addressable不仅仅能加载资源,同时也能卸载资源。 - 内容打包:
Addressable系统自动管理了所有复杂的依赖连接,所以即使资源移动了或是重新命名了,系统依然能够高效地找到准确的依赖进行打包。当你需要将打包的资源从本地移到服务器上面,Addressable系统也能轻松做到。 相较于传统的资产加载方式(Resources/AssetBundle),Addressable系统拥有了完备的可视化编辑窗口以及内存管理。
- Addressable系统只需要资产的地址就可以从任意位置加载,而AssetBundle需要从指定bundle包中加载资源。
- Addressable系统使用引用计数自动管理内存卸载,而AssetBundle需要开发者手动管理。
- Addressable系统自动管理依赖关系,而AssetBundle需要开发者自己管理依赖关系,维护起来比较困难。
- 可寻址资源系统默认的所有加载操作都是异步操作,可以添加事件监听,而AssetBundle则有同步和异步加载。
文章图片
文章图片
2. Addressables管理窗口 ????Window > Asset Management > Addressables > Groups进入使用界面
文章图片
Addressable设置:
????资源默认分为Built In Data和LocalGroup(Default)两组,前者包含一些内置资源,不能改动,后者可以进行添加或删除资源。
- Create: 创建新的资源分组,也可以在Addressables Group窗口中右键 Create New Group > Packed Assets创建新的资源组。
- Profile: Profile的作用主要是用于指定项目中需要用到的几个地址,我们可以在这个窗口中点击Create->Profile创建Profile,每个Profile中都包含4个默认的变量,我们也可以点击Create->Variable扩展变量。
- Tools:
- Profiles: 用于给整个Addressable设置范围(配置四种路径的具体值)之后可以给每个组的Build,Load选择路径种类,则根据Profile的选取确定。
- Labels: 添加删除标签Label。
- Analyze: Analyze是一个工具,收集有关您的项目的可寻址布局的信息。在某些情况下,Analyze可能会采取适当的措施来清理项目的状态。在其他情况下,Analyze纯粹是一种信息工具,使您可以对可寻址布局做出更明智的决策。
文章图片
- Hosting Services: Addressables系统自带的一个资源服务,可以指定一个目录存放远程资源,然后通过连接这个服务器,来更新资源。
- Event Viewer: 可以用于监测,调试。前提是在Inspect System Settings窗口勾选发送消息(由于消耗性能所以默认关闭)。
????使用Addressables事件查看器可监视所有Addressables系统操作的资源的引用计数。在Unity中选择Window > Asset Management > Event Viewer。
????请注意,事件查看器只关心引用计数,而不关心内存消耗。在"Asset"栏下列出每一帧中,可以看到每一个资源的如下信息:
????FPS: 每秒帧数。
????MonoHeap: 内存使用的总量。
????Event Counts: 每帧事件总数。
文章图片
????可以单击左箭头和右箭头逐帧观察,或者单击Current跳转到最新的帧。按+按钮展开一行以获得更多详细信息。
????事件查看器中显示的信息与在Play Mode Script中选择的游戏模式有关。 使用事件查看器时,应避免使用 Use Asset Database模式,因为它不考虑资产之间的任何依赖关系。使用Simulate Groups 或Use Existing Build模式,但是后者更适合于事件查看器,因为它可以更准确地监视资源的引用计数。 - Check for Content Updata Restrictions: 更新静态资源组。
- Play Mode Script:
- Use Asset Database(fastest): 允许你在游戏流程中快速运行游戏。它直接通过Asset Database加载Asset ,这会在不需要分析器或assetBundle创建的情况下进行快速迭代。
- Simulate Groups(advanced): 在不创建assetBundles的情况下分析布局和依赖项的内容。asset 通过ResourceManager从assetDataBase加载,就假装它们是通过包加载的一样。若要查看游戏期间bundles加载或卸载的时间,请在Addressables事件查看器窗口Tools > Event Viewer
- Use Exiting Build(requires built groups): 最接近于已部署的应用程序生成,但它要求你将数据作为单独的步骤进行构建。如果不修改Asset,则此模式是最快的,因为它在进入Play模式时不处理任何数据。必须通过选择Build>New Build>Default Build Script,或者在游戏脚本中使用AddressableAssetSettings.BuildPlayerContent()方法,在Addressables组窗口(Window>Asset Management>Addressable>group>group)中构建此模式的内容。
- Build:
- New Build: 当所有资源都已准备就绪,点击此项打包。
- Update a Previous Build: 资源更新时,点击此项更新资源包。
- Clear Build: 清除已经Build的资源,再次运行游戏需要重新Build。
文章图片
Addressable系统的基础配置:
- Disable Catalog Update On Startup: 默认是没有勾选的,没有勾选,那么每次AA系统初始化的时候,会自动更新catalog文件,勾选上,将不会自动更新catalog文件,也就意味着不会自动更新资源.AA系统的初始化会在任意接口第一次调用时初始化,也可以主动调用Addressables.InitializeAsync()初始化.
- Build Remote Catalog: 默认没有勾选,只有勾选上才会创建catalog在指定目录,以后客户端才可以下载这个catalog来进行对比更新.
- Build Path: 资源打包后存放的地址。
- Load Path: 资源加载地址。
- Send Profiler Events: 调试用,允许加载资源的时候发送事件给EventViewer,可以通过这个工具查看资源的使用情况
- Log Runtime Exception: 输出加载资源时的异常,开启时如果资源加载发生异常,会直接抛出.如果关闭,我们也可以通过加载资源时返回的句柄,来获取到异常信息.同时我们也可以给项目添加宏ADDRESSABLES_LOG_ALL,来查看更多的日志信息
文章图片
- Build Path: 资源打包后存放的地址。
- Load Path: 资源加载地址。
- Advanced Options(高级设置):
- Asset Bundle Compression: 当前.bundle文件的压缩方式,支持LZ4和LZMA压缩。
- Include in Build: 是否被打包,默认是勾选的,若不勾选当前组不打包。
- Requset Timeout: 设置UnityWebRequest在超时的秒数超过后尝试中止。(仅适用于远程资源包)
- Bundle Mode: 控制捆绑包的打包方式。PackTogether将一个分组打包成一个资源包;Pack Separately每一个资源打成一个包,Pack Together By Label将Lable相同资源打成一个资源包。
- Content Update Restriction:
- Update Restriciton:
- Cannot Change Post Release: 静态Group,在发布后,无法被修改,只能通过Check for Content Updata Restrictions做增量更新,创建一个新的资源分组.
- Can Change Post Release: 非静态Group,在发布后,允许修改,更新时做覆盖更新。
- Update Restriciton:
文章图片
????这里有两种方式将资源标记成可寻址的,在安装好可寻址资源包后,你可以在属性面板进行标记或者将其拖拽到管理窗口指定分组上。在资源的属性窗口上,点击Address复选框并为资源设置唯一标识符。
注:如果我们标记的资产在Resources文件夹下时,Addressable系统会提示你讲资产移出Resources文件夹。6. 资源打包 ????配置好资源分组,根据需要设置Play Mode Script,再通过Build > New Build > Default Build Script打包测试。
文章图片
????本地资源打包路径: Library/com.unity.addressables/StreamingAssetsCopy/aa/Android/
????远程资源打包路径: ServerData/Android/
文章图片
????同时远程目录下会生成有.hash和.json文件,.hash文件内只包含一个catalog文件的Hash值,用于客户单检测catalog更新时,通过对比这个hash值,判断是否有catalog更新,json文件内包含每个Ab包的hash值和地址。
????使用Build > Update a previous Build 更新资源包时,需要选择一个bin文件(android环境为例,Anroid/.bin),这个bin文件记录了所有Ab包之间的依赖关系和分组信息,Addressable系统通过这个bin文件管理依赖。
7. 加载资源 ????使用AssetReference加载资源:
[SerializeField] private AssetReference m_AssetReference;
privatevoid Start() {
m_AssetReference.LoadAssetAsync();
}
????使用Addressables加载单个资源:
private void OnResLoadAsset(string key)
{
Addressables.LoadAssetAsync(key).Completed += OnCompleteLoad;
}
private void OnCompleteLoad(AsyncOperationHandle asyncOperationHandle)
{
GameObject go = GameObject.Instantiate(asyncOperationHandle.Result);
}
private void OnResInstantiate(string key)
{
Addressables.InstantiateAsync(key);
}
???? 加载多个资源:
private void OnResLoadAsset(string key,string lable)
{
Addressables.LoadAssetsAsync(new List
注:第三个参数,MergeMode查找资源的合并模式,以传入的参数是new List{key,label}为例小结:
- Node或UseFirst时,会取第一个key查询到的资源
- Union时,取并集
- Intersection时,取交集
(1)加载资源时,若加载资源指定的类型与资源类型不一致,Addressable系统找不到该资源,则抛出异常,无法加载资源,前提:系统设置勾选了Send Profiler Events。
(2)使用标签管理,同一个资源的地址和标签可以相同,当有多个资源标签相同,Addressable系统会返回第一个满足条件的资源。
(3)若资源的地址名称与下一个资源的标签相同,返回还是第一个资源,Addressable系统会对比资源的地址和标签,若都不相同,才会继续向下查找
8. 更新资源包 ????热更新资源包修改后,需要对资源重新打包
????Check for Content Update Restrictions: 针对是静态资源组,既是Update Restriciton属性为Cannot Change Post Release值。点击后弹出选择之前打包资源组生成的bin文件,点击“Apply Changes”应用更改,增加或修改的资源会被移动到新建Content Update分组。
文章图片
????Update a Previous Build: 动态资源组更新时,执行该操作,同样需要选择bin文件,系统会自动生成一个新的AB包。
文章图片
9. 热更新代码和方案 ????核心代码
public IEnumerator CheckForContentUpdate(List
- 方案一:
????从服务器获取更新资源标签。
注:动态资源更新,旧资源会被覆盖,动态资源组中有一个资源需要更新,热更时会将整个资源组都下载下来
,因此合理划分资源分组十分重要,减少重复下载以及打包粒度(多个资源需要相同的材质、贴图等资源)。
- 方案二:
????使用官方提供的Addressables.CheckForCatalogUpdates()方法检查目录,获取需要更新的目录。 - 方案三:
????暴力获取所有Key,使用官方的Addressables.GetDownloadSizeAsync(Key)检查资源是否需要更新,从而获取需要更新的所有Key值。
????资源更新具体实现:
????????使用官方提供的Addressables.GetDownloadSizeAsync(Key)方法获取所有需要更新的资源地址或标签集合,再通过Addressables.DownloadDependenciesAsync(Key, Addressables.MergeMode.Union, false)下载更新的资源。
????编辑状态创建一个新的菜单,并创建一个asset文件,配置需要标记的资源文件夹,可同时标记多个资源,配置如下:
文章图片
public static void AutoSetGroup(string groupName, string lableName, string assetPath, bool isSimplied = false)
{
var set = AddressableAssetSettingsDefaultObject.Settings;
AddressableAssetGroup Group = set.FindGroup(groupName);
if (Group == null)
{
Group = set.CreateGroup(groupName, false, false, false, new List
{ set.DefaultGroup.Schemas[0], set.DefaultGroup.Schemas[1]}, typeof(SchemaType));
}
string Guid = AssetDatabase.AssetPathToGUID(assetPath);
//获取指定路径下资源的 GUID(全局唯一标识符)
AddressableAssetEntry asset = set.CreateOrMoveEntry(Guid, Group);
if (isSimplied)
{
asset.address = Path.GetFileNameWithoutExtension(assetPath);
}
else
{
asset.address = assetPath;
}
asset.SetLabel(lableName, true, true);
}
????具体思路:
????????根据文件夹路径,获取该文件夹下的所有资源的路径信息,调用添加分组接口,检查是否存在当前分组,若无,则创建AddressableAssetGroup类型分组对象,设置默认状态,使用AssetDatabase.AssetPathToGUID获取当前路径资源的GUID,通过AddressableAssetSettingsDefaultObject.Settings.CreateOrMoveEntry(),创建AddressableAssetEntry对象,既勾选了addressable,再去简化资源地址和设置标签。
11. 内存管理 资源加载
????Addressables.LoadAssetAsync();单个资源
????Addressables.LoadAssetsAsync();多个资源
????Addressables.LoadSceneAsync();场景的加载
????GamoeObject实例化加载
????Addressables.InstantiateAsync();实例化加载
????GameObject.Instantiate();Unity提供实例化方法
资源卸载
????Addressables.UnloadSceneAsync();场景的卸载
????Addressables.Release();释放资源,参数是资源或????AsyncOperationHandle句柄
????Addressables.ReleaseInstance();销毁Addressable系统创建的实例
注:Addressables.InstantiateAsync()和其他加载调用的另一个区别就是有一个可选的trackHandle参数,当设置为false时,就必须通过AsyncOperationHandle句柄来释放资源,而不能再通过AsyncOperationHandle.Result加载资源释放了。引用计数问题: 资源卸载,可手动和自动。
????手动卸载,Addressable系统加载和卸载资源都是成对存在的,使用Addrsssables.Release()或Addressables.ReleaseInstance()方法卸载资源,减少引用计数。当资源的引用计数为0时,该资源就准备好卸载了,并减少了所有依赖项的引用计数。
????自动卸载,包含它的场景关闭时允许自动清理。
卸载问题
????若使用Addressables.ReleaseInstance()传入的实例并不是Addressables系统API创建的,或者是通过句柄创建实例,系统会检测到并返回false,以指示该方法无法释放指定的实例。在这种情况下,实例不会被销毁。
Addressables.LoadAsset()和Addressables.InstantiateAsync()讨论
????Addressables.InstantiateAsync()有一些相关的开销,所以如果需要在每一帧中实例化数百次相同的对象,可以考虑通过Addressables.LoadAsset()方法加载,然后通过GameObject.Instantiate()实例化。缺点是Addressables系统不知道您创建了多少实例,如果管理不当,可能会导致内存问题。例如,一个Prefab引用了一个加载不正确或者已经卸载的纹理,会导致渲染问题(或更糟)。这类问题很难跟踪,因为您可能不会立即触发内存卸载 。?
清除内存
????不再被引用的资源并不一定意味着资源产已被卸载。一个常见的应用场景涉及到一个资源包中包含多个资源。例如:
?????您有三个资源(“树”,“坦克”,“牛”)在同一个资源包(“东西”)。
?????当“树”加载时,“树”的ref-count +1,“东西”的ref-count +1。
?????稍后,当“坦克”加载时,“树”和“坦克”的ref-count均为1,并且“东西”包的ref-count为2。
?????如果你释放“树”,它的ref-count就会变成0。
????在这个例子中,“树”资源实际上并没有被卸载。您可以加载资源包或其部分内容,但不能部分卸载资源包。在包本身完全卸载之前,所有资产都不会卸载。这个规则的例外是Resources.UnloadUnusedAssets,在上述场景中执行此方法将导致树卸载。因为Addressables系统不能识别这些事件, 只反映Addressables的ref-counts (不完全反映内存中存在的内容)。注意,如果您选择使用Resources.UnloadUnusedAssets,这是一个非常慢的操作,应该只在一个不会显示任何游戏内容的屏幕调用(比如加载屏幕)。
12. Addressable打包粒度 ????我们在使用Addressable系统时,需要考虑的是:需要多少个Group?这个Group里面放什么资源?打包方式是Pack Together 或者 Pack Together By Label 或者 Pack Separately? 很显然,这个跟使用Assetbundle是一样的,需要开发人员自己来规划。这不是因为Addressable不够强大,而是这是跟具体项目有关,每个项目的情况各不相同。
推荐阅读
- Unity|Unity通用框架搭建(五)——基于Addressable一键打包工具
- Unity3D|【游戏开发高阶】从零到一教你Unity使用ToLua实现热更新(含Demo工程 | LuaFramework | 增量 | HotUpdate)
- Unity3D|《学Unity的猫》——第十三章(Unity使用Animator控制动画播放,皮皮猫打字机游戏)
- Unity3D|【游戏开发实战】Unity UGUI实现循环复用列表,显示巨量列表信息,含Demo工程源码
- Unity3D|(完结)Unity游戏开发——新发教你做游戏(七)(Animator控制角色动画播放)
- C#小游戏|飞行棋(C#)
- C#|C# 集合---哈希表(Hashtable)
- Delphi|60岁还在写代码的程序员大师,Delphi、C#、TypeScript之父Anders Hejlsberg(安德斯·海尔斯伯格)(编程符合10000小时定律)
- javascript|SegmentFault 创始人祁宁对话 C# 之父 Anders Hejlsberg