Unity SpriteAtlas实战使用

前言

图集计划使用sprite atlas,但是看了网上资料用于实战的有点少。自己总结下。Unity 版本2019.4.9f1

图集设置-Sprite Packer Mode

在这里插入图片描述
Disabled:就是不会生成图集。
Enable For Builds(Legacy Sprite Packer):打包时生效(打AB包时,会生成图集),针对于旧版本的spritePacker生效。
Always Enabled(Legacy Sprite Packer):一直生效,就是打包和PC运行时会生效,针对于旧版本的spritePacker生效。
Enable For Builds:打包时生效(打AB包时,会生成图集),针对SpriteAtlas生效,我们的重点
Always Enabled:一直生效,就是打包和PC运行时会生效,针对SpriteAtlas生效

白话理解

生效的意思就是会不会生成图集,生成图集就意味着可以合批,降低DC。这里就会有个比较关心的问题,PC下运行时,如果生成图集的生成时机是在启动时生成(选择AlwaysEnabled模式),所以如果PC下运行时,每次启动游戏都会动态生成图集,如果图集比较多的项目,可能会比较慢,但是毕竟他是PC,提高配置吧。

效果对比

AlwaysEnabled:DC为2
在这里插入图片描述

Enable For Builds:DC为4
在这里插入图片描述

Atlas中的Include in Build

这个选项就是平时所有的LateBind的功能开关。不勾选的时候,当此图集第一次被使用时会触发SpriteAtlasManager.atlasRequested函数,和SpriteAtlasManager.atlasRegistered函数。注意,这里的第一次,比如两个预制体A,B都引用了图集C,当使用A时会触发此函数,此时我们将C加载到内存,当使用B时,不会再次触发此函数,所以这个函数并不能做图集的内存管理。举例说明:
测试代码:

public class AtlasTest : MonoBehaviour
{
    public Image image;
    private AssetBundle m_atalsTestAb; 
    void Start()
    {
        SpriteAtlasManager.atlasRequested += (string name, Action<SpriteAtlas> action) =>
        {
            if (m_atalsTestAb==null)
            {
                m_atalsTestAb = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "atlastest"));
            }
            SpriteAtlas spriteAtlas= m_atalsTestAb.LoadAsset<SpriteAtlas>(name);
            action(spriteAtlas);
            Debug.LogError(name+"   "+ spriteAtlas);
        };
        SpriteAtlasManager.atlasRegistered += (a) =>
        {
            Debug.LogError("Regist:"+a);
        };
    }
    AssetBundle fassetBundle;
    AssetBundle prefabtestassetBundle;
    private void OnGUI()
    {
        if (GUI.Button(new Rect(50,50,100,50),"加载图集"))
        {
            if (m_atalsTestAb == null)
            {
                m_atalsTestAb = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "atlastest"));
            }
        }//
        if (GUI.Button(new Rect(50, 110, 100, 50), "加载预制体"))
        {
            if (prefabtestassetBundle==null)
            {
                prefabtestassetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "prefabtest"));
            }
            Object prefab= prefabtestassetBundle.LoadAsset("prefabTest");
            if (prefab is GameObject go)
            {
                GameObject.Instantiate(go).transform.SetParent(this.transform,false);
            }
        }
        if (GUI.Button(new Rect(50, 170, 100, 50), "加载预制体F"))
        {
            if (fassetBundle == null)
            {
                fassetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "f"));
            }
            Object prefab = fassetBundle.LoadAsset("InScenes");
            if (prefab is GameObject go)
            {
                GameObject.Instantiate(go).transform.SetParent(this.transform, false);
            }
        }
        if (GUI.Button(new Rect(50, 230, 100, 50), "加载图片"))
        {

        }
        GUI.Label(new Rect(50, 290, 700, 50), "MM:" + Profiler.GetTotalAllocatedMemoryLong().ToString());//

    }
}

1.PC模式下,不勾选IncludeinBuild,SpritePackerModel=Enable For Builds

在这里插入图片描述
在这里插入图片描述
可以看到,PC模式中,内存中确实生成了一个图集,但是显示异常。SpriteAtlasManager.atlasRequested只有在第一次使用时被调用,之后的使用,不再调用。即使图集的AB被卸载后,重新生成也不再调用,只有当使用AB被卸载后,再次加载这个AB会调用,所以此函数的触发时机为载入内存时(即:AssetBundleLoadAsset时),而非clone时

2.真机模式下,不勾选IncludeinBuild,SpritePackerModel=Enable For Builds

真机模式,显示正常,内存正常。SpriteAtlasManager.atlasRequested的调用同上。
图集被卸载后:
在这里插入图片描述
这个是真的坑,sprite的管理,已经不是在跟随Atlas的生命周期走了。不过万幸的是,当所使用的GameObject被Destroy的时候,其对应的sprite会自动被卸载,这对于不需要动态更换sprite的image来说,我们不需要做额外的内存管理,至于需要动态替换的sprite,请向下看。另外,PC跑Bundle的情况下sprite是无法卸载干净的,这个真机不一样,所以做内存分析的时候,最好还是真机。

3.PC模式下,不勾选IncludeinBuild,SpritePackerModel=Always Enabled

显示正常。其他数据与1一致。出乎意料之外的是,空场景的游戏启动,并没有出现预期的将编辑器下所有的atlas都生成一份图集,我想说Profiler这块真的跟吃了屎似的,有时候有资源中的图集,有时候没有。心烦。。。
但是细心的你会发现,AlwaysEnabled到底做了啥,打开工程目录下的Library/AtlasCache,发现当为AlwaysEnabled时,图集替换时会再次目录生成一份Cache,之后使用的为这个cache,这大概就是AlwaysEnabled主要做的事情吧

4.Include in Build研究

官方定义

Include in Build Check this box to include the Sprite Atlas Asset in the current build. This option is enabled by default.
勾选时spriteatlas会在打包时,将图集中的Assets包含进去。如果将Atlas打成AB包,这个勾不勾确实看不到bundle包有什么变化,而且bundle中asset的数量也是一致的。从包体和被依赖的情况来看,这个勾选不被影响。

对SpriteAtlasManager.atlasRequested影响

当加载使用到图集的预制体时。

Include不勾选不勾选勾选勾选
图集bundle未加载已加载未加载已加载
atalsRequested触发触发触发触发不触发

从这个触发机制来看,如果勾选了Include in Build是会打包时记录上这种依赖关系,如果使用时内存中已经存在此图集,则会直接进行索引。反之如果没有,则会触发atlasRequested函数,进行图集的请求。所以如果我们要依赖于atalsRequested函数进行某种操作时。要注意这个勾选情况。

对AssetBundle的影响

勾选了会触发到依赖检索,比如sprite sa,在atlas A和B中分别被使用,且AB设置include in build,此时打包对sa设置包名,对A,B不设置包名,进行打包,此时会把AB都打进包里,如果不设置不会打到包里,所以如果设置了,就会检索依赖关系的感觉。

5.图集打AssetBundle的影响

1.打包时,只是打包图集,不打包散图。

AssetBundle大小:684KB,且如果AssetBundle A如果依赖某个sprite是无法检测,A于这个图集的依赖关系的。
测试情况加载N个prefabA,N个prefab B,单独加载某个sprite,加载代码如下

 SpriteAtlas atlas = m_atalsTestAb.LoadAsset<SpriteAtlas>("myFirst");
 atlas.GetSprite("ui_sim_ftxr_db01_NEW");

在这里插入图片描述
分析:问题1,两个prefab其实引用的是同一个的图集的同一个sprite,但是Asset的sprite是两份的sprite的内存,好消息是这里的sprite会随着prefab的AssetBundle的卸载和销毁而被清除,这样只要我们做好assetbundle的管理就不需要关系这部分sprite的内存。但是确实也生成了多份的sprite的内存。
问题2:对于单独加载出来的sprite,每次都是clone出来的一份实例,且这个实例的内存并不会随着图集的assetbundle的卸载而被清除,需要destroy或者使用Resources.UnloadUnusedAssets()才会被清除,众所周知Resources.UnloadUnusedAssets()调用耗时严重,所以这样使用的sprite容易造成内存的增长。

2.打包时,将sprites和atlas都放到Assetbundle中。

AssetBundle大小:685KB,可以检测到其他AssetBundle和这个图集的依赖关系。此时使用加载某个sprite的加载代码如下

m_atalsTestAb.LoadAsset<Sprite>("ui_sim_ftxr_db01_NEW");

在这里插入图片描述
哇!!清爽,单独加载sprite也不会造成内存泄漏,同一个sprite的内存只有一份。但是如此打包之后,就不会再次触发SpriteAtlasManager.atlasRequested函数,但是如果我们是依赖加载的话,也不需要这个函数,如果想用这个函数做本地化,这种打包方案就会被否掉,但是本地化的方案可以使用其他方式,比如可以在初始化依赖关系是,改变本地化的依赖路径等等…
个人分析:按照理解,Include in Build应该是实现这个功能,不知道是版本的bug还是我的理解错误,目前没看到Include in Build起到了多大的作用。

打包图集时,只打包散图,不设置atlas的bundlename

假如图集A中包含sprite–sa,图集B包含sa,图集C为A的变体。也就是说两个图集使用了相同的资源,因为只打包sa,图集不设置

如果使用方式2的打包方式,则需要将sa复制一份。

Atlas Variant测试–Include in Build

个人总结

SpiteAtlas使图集使用更加灵活,但是这个使用属实有点脑袋疼,两年前使用的时候bug一堆,现在一看,感觉有些地方还莫名其妙。如果使用将atlas和sprites打成一个包,貌似内存管理更加方便,且内存更加少,但是带来的问题就是美术在两个图集共同使用一个sprite的时候将会变得额外复杂,因为当两个图集共享一个sprite的时候,我们没有办法为sprite同时设置两个bundleName,就需要美术制作的复制一份图片,虽然复制了一份但是我们不需要担心assetbundle大小增加和内存的增加,因为都是应用合成texture图集的。但是如果我们需要依赖SpriteAtlasManager.atlasRequested函数做某些功能,这是无法实现的。个人建议还是使用sprite和图集一起打包,毕竟内存问题才是问题,其他的都可以用其他方式实现。

  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘培玉--大王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值