Addressables优化catalog文件

unity使用Addressables进行资源管理很方便但是有一个很值得诟病的问题, 那就是catalog文件过大, 对于一个持续运营的项目来说catalog文件可能超过10M, 导致初始化Addressables时间较长, 峰值内存上涨明显.

本文通过分析catalog文件中主要的内容占用和内存开销, 提出一些可行的(已实践)方案.

一. catalog相关的开销分析

catalog 开销主要来自两个部分:

  1. 初始化Addressables 的时间开销, 使用Addressables需要确保catalog 加载完成, 对于部分网络情况不佳的用户造成卡顿较长的体验;
  2. 加载catalog文件的内存开销, 使用json格式的Addressables加载时会产生两倍于自身大小的内存开销.

加载catalog的时间开销和网络情况密切相关, 实际测试下来一个1Mcatalog文件经过传输和加载到内存, 整体时间超过1s. 主要的开销来源是文件的大小.

内存上的开销主要是加载文本本身和反序列化数据之后的结果上的内存开销.

1. catalog 本身大小开销分析

catalog文件本身是一个json文件, 主要结构如下:

本身实际是ContentCatalogDatajson序列化结果. 其中包含的数据是以下几类数据:

属性 数据含义
m_LocatorId Addressables支持多catalog,所以添加了区分不同catalog的ID
m_InstanceProviderData Instantiate方法使用的Provider的序列化结果
m_SceneProviderData 加载Scene使用的Provider序列化数据
m_ResourceProviderData 加载资源使用的Provider的序列化结果
m_ProviderIds Provider的类名称
m_InternalIds 资源的路径列表, 是AssetPathBase64加密结果
m_KeyDataString Key的二进制序列化结果
m_BucketDataString
m_EntryDataString 资源的Entry的序列化结果,包含资源的类型,资源的以来等
m_resourceTypes 资源的类型
m_InternalIdPrefixes InternalId列表的前缀优化数据

json文件里面能看到其中比较大的主要是m_KeyDataString m_InternalIds, m_EntryDataString 分别指向了资源的路径, 资源的key和资源的加载信息其中每一段的数据的大小如下:

scss 复制代码
catalog.json 16.30MB (3.20MB compressed)
43282 internalIds
86296 Keys:5.88MB
86296 Buckets:2.76MB
106176 Entries:3.78MB
731 Extras:802.29KB

2. catalog 加载内存开销分析

// TODO

二. 可行的catalog优化方案

1.剔除locations

上面分析中发现很多的开销是来在Locations也就是资源的数量, 如果假如到catalog中的资源数量较多, 自然catalog会明显增大. 那么什么是需要添加到catalog中的什么是不需要的呢? 这里项目不一样判断的方法也不一样, 这里设计了一个简单的配置正则项去筛选需要加到Catalog中的Locations用于过滤需要保留的locations. 经过过滤之后能减少2/3的location数量, 能减少大约1/3的大小.

剔除Location可能会导致label丢失, 比如你给一个路径下的资源都标记为lableA,但是所有的资源Location被删除了导致label丢失. 使用这个方案请确保所有的通过Addressables加载的资源确实被保留了.

代码如下

csharp 复制代码
public class LocationFilterConfig : ScriptableObject
{
    protected virtual void Init()
    {
    }
​
    // 用于子类实现自己的忽略功能
    protected virtual bool IsIgnored(ContentCatalogDataEntry entry)
    {
        return false;
    }
    
    // 过滤的接口
    public void FilterLocation(List<ContentCatalogDataEntry> locations, ref List<ContentCatalogDataEntry> output)
    {
        Init();
        foreach(ContentCatalogDataEntry location in locations)
        {
            if(!location.ResourceType.IsAssignableFrom(typeof(IAssetBundleResource)) && IsIgnored(location))
            {
                continue;
            }
            output.Add(location);
        }
    }
}
// 具体实现
public class ResConfigLocationFilterConfig : LocationFilterConfig
{
    [SerializeField]
    private string[] m_includedAssets;
​
    private Regex[] m_includedAssetsRegex;
    private HashSet<string> m_internalIds;
    
    protected override void Init()
    {
        m_includedAssetsRegex = null;
        if (m_includedAssets != null)
        {
            m_includedAssetsRegex = new Regex[m_includedAssets.Length];
            for(int i = 0;i < m_includedAssets.Length;i++)
            {
                m_includedAssetsRegex[i] = new Regex(m_includedAssets[i]);
            }
        }
    }
    // 使用正则项对资源路径进行过滤
    protected override bool IsIgnored(ContentCatalogDataEntry entry)
    {
        if (m_includedAssetsRegex != null)
        {
            foreach (Regex regex in m_includedAssetsRegex)
            {
                if(regex.IsMatch(entry.InternalId))
                {
                    return false;
                }
            }
        }
        return true;
    }
}

2.剔除location 类型

对于同一个资源, 存在多个Entry, 其中不同的key, 不同的资源类型都会生成一个新的数据, 所以会导致同一个资源数据多很多. 这里主要是剔除key和资源类型:

2.1 剔除key

剔除key可能会导致加载资源时无法找到对应的资源而加载失败, 确保你的剔除手段是合理的

剔除key的方法很简单, 在AssetGroup里面勾选是否包含部分信息.其中分别包含的信息是是否需要使用Addresses加载资源, 是否会使用AssetReference加载资源以及是否会通过Label加载资源

2.2 剔除资源类型

对于一个资源来说, 他的资源类型有很多, 在Catalog中会生成不同类型的Entry, 比如TextAsset的资源不仅仅会存在一个TextAssetEntry, 也会产生UnityEngine.Object资源的Entry. 这里也能按照类型剔除部分的资源类型生成的Entry.

csharp 复制代码
[CreateAssetMenu(fileName = "TypeLocationFilterConfig", menuName = "Addressables/Location Filter/Type")]
public class LocationFilterConfigType : LocationFilterConfig
{
    [SerializeField]
    private string[] m_types;
​
    protected override bool IsIgnored(ContentCatalogDataEntry entry)
    {
        string typeName = entry.ResourceType.FullName;
        foreach(string type in m_types)
        {
            if(type == typeName)
            {
                return true;
            }
        }
        return false;
    }
}

3.剔除依赖

可能会导致资源加载失败, 请记得实现检查工具

依赖的数据是catalog中很大的部分, 尤其是对于依赖关系复杂的项目, 依赖的数据可能会比真实数据还多, 这里推荐使用编辑器下的资源依赖去替换使用Assetbundle计算出来的依赖关系, 减少资源的依赖数量

三.总结

Addressables是一个广泛使用的资源管理库, 使用默认的方式能确保完全不会出问题, 但是不同项目有不同的实际情况, 在做库开发时适用性应该要大于效率,但是项目实际使用的情况下应当是效率大于适用.

相关推荐
evamango6 天前
《Unity Shader入门精要》二、渲染流水线
unity3d
Thomas游戏开发7 天前
Unity3D Boehm GC原理解析
前端框架·unity3d·游戏开发
Thomas游戏开发7 天前
Unity3D 文件夹注释工具
前端框架·unity3d·游戏开发
Thomas_YXQ8 天前
Unity3D游戏内存优化指南
游戏·unity·职场和发展·性能优化·蓝桥杯·游戏引擎·unity3d
Thomas_YXQ9 天前
Unity URP法线贴图实现教程
开发语言·unity·性能优化·游戏引擎·unity3d·贴图·单一职责原则
一名用户9 天前
unity实现梦日记式传送组件
后端·c#·unity3d
留待舞人归1 个月前
【Unity3D优化】优化多语言字体包大小
游戏·unity·游戏引擎·unity3d·优化
留待舞人归1 个月前
【Unity优化】提高热更新和打包速度
游戏·unity·游戏引擎·unity3d·优化
Thomas游戏开发1 个月前
Unity3D 自动化游戏框架设计
前端框架·unity3d·游戏开发
一名用户1 个月前
unity实现自定义粒子系统
c#·unity3d·游戏开发