前言
图集计划使用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和图集一起打包,毕竟内存问题才是问题,其他的都可以用其他方式实现。
|