图集打包算法_UGUI的图集处理⽅式-SpriteAtlas的前世今⽣
最糟糕的是⼈们在⽣活中经常受到错误志向的阻碍⽽不⾃知,真到摆脱了那些阻碍时才能明⽩过来。 —— 歌德
说到UGUI的图集初学者可能觉得没什么难度,包括我刚开始接触的时候也是,甚⾄你在开发的时候只需要把图⽚导⼊到项⽬中,拖拖拽拽就能做出能⽤的东西来。因为UGUI刚出的时候就打出了“Unity会⾃动帮你维护图集”的旗号。可现实真的是这样的吗?要解释这个问题就需要从Unity4.6说起了,那我们来捋⼀下!
Sprite Packer
散图的加载问题
这功能是从Unity4.6版本随着UGUI的问世⼀起发布的,我们在做开发的时候只需要把图⽚导⼊⼯程,设置⼀下,然后在通过tag标签Unity 就会⾃动打成图集。但是,这种⽅法有个问题,就是我们在运⾏时⽆法通过代码取出某张图集中的⼀张⼩图。
所以后来开发者们做了⼀套prefab引⽤sprite的解决⽅案,做法就是:建⼀个空prefab挂⼀个⾃定义脚本,脚本⾥有⼀个sprite数组,我们在编辑时把要打的图集中的sprite添加到这个sprite数组中,然后在把prefab打成bundle,这样这个bundle中的脚本就会有atlas中的Sprite的引⽤,我们在脚本⾥写⼀个GetSprite⽅法即可。这样我们在运⾏时加载完bundle后就可以通过脚本取出atlas⾥的sprite。
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
public List<Sprite> sprites;
public Sprite GetSprite(string spriteName)
{
return sprites.Find(s => s.name.Equals(spriteName));
}
}
但是,讲真,就这么个函数Unity提供⼀个API很难嘛!这个问题导致从Unity4.6到Unity2017.1每次做图集动态加载的时候都要⽤上⾯的⽅法搞⼀遍。当然要是仅仅是这点⼯作量也就忍了,重要的是这么做还会影响到打包、依赖的管理。打包依赖⼜是个Unity的⼤坑。。。⼀个坑跳到了另⼀个更深的坑。。。当然也是有解决办法的,打包依赖问题我们以后再专门讲,这⾥就不多说了。
图集的拼接算法问题
SpritePacker的TightPackerPolicy(紧凑),根本就不能⽤!SpritePacker打图集时有两个选项:默认、紧凑,然⽽使⽤过你会知道其实这个“紧凑”选项是不能⽤的!!打出来的图会串,你⽤⼀张Image指定了⼀张Sprite A,运⾏时你会发现如果⽤“紧凑”打出来的图集会把A的范围算错,表现就是显⽰的
图⽚不是A,可能只包括A的⼀部分,其他部分会显⽰别的图⽚,其实就是取A这张图⽚的时候位置没算对最终你会发现也只能使⽤”默认”⽅式打图集了。如果想优化图集的空间利⽤率,只有⼀个办法就是⾃⼰写打图集的排列算法,当然Unity也提供了这个接⼝!
Sprite Atlas
延迟绑定
SpriteAtlas是2017.1版本以后更新的⼀个新功能,它可以把图⽚“⼿动”打成⼀张图集。如果你觉得这是Unity的⼀个新功能或者⼀个强化图集的功能那你就错了,如果仅仅是打张图集也没必要单独搞这么个玩意⼉,其实这也是Unity在“还SpritePacker的帐”,当然我们上⾯也讲到了,可以肯定的⼀点是SpritePacker在商业项⽬中:是能⽤的!但是不可避免的你会为了填补SpritePacker的⼀些坑。
SpritePacker时代“图集”我们是看不到的,因为设计者的初衷是想让开发者完全不⽤考虑图集的事。但是,这跟游戏开发时的动态加载图集时⽭盾的!我们要动态加载就必须要知道atlas名字和sprite名,这样才能在运⾏时动态的到⼀张sprite!所以U3D程序员必须要清楚的知道你当前界⾯⽤的是哪atlas的哪个sprite。但是按照SpritePacker的做法他的初衷应该是想把图集⼲掉(⼲掉的意思让开发者不⽤关⼼),只需要考虑sprite即可,但是这是⾏不通的!
SpriteAtlas其实就是为了解决上⾯说的问题⽽发布的功能。通过它我们可以将atlas和UIprefab“解耦”。同时,在Unity编辑器状态下我们仍然可以利⽤未打成SpriteAtlas的Sprite进⾏开发。等开发完成我们再发布成SpriteAtlas。Unity提供了所谓“延迟绑定”(late bind)的技术来让我们实现这个功能。具体做法如下:
在Unity2017.1之后,UI prefab的Bundle在被加载的时候会有⼀个回调函数被调⽤:
SpriteAtlasManager.atlasRequested,这个委托的定义是这样的:void RequestLateBindingAtlas(string atlasName, System.Action output);
(代码不严格,当成伪码看就⾏))
SpriteAtlasManager.atlasRequested += (atlasName, output) => {
string path;//省略,atlas bundle加载路径
AssetBundle bundle;//省略,加载atlas的asset bundle
var atlas = bundle.LoadAsset<SpriteAtlas>(path + "/" + atlasName);
output(atlas);
}
我们需要在运⾏时把atlas绑定到当前的prefab中,这个绑定是通过第⼀个参数"atlasName"关联的。如果prefab中引⽤了多张atlas他会被调⽤多次,每次都会把atlasName传过来,由“⽤户”决定加载哪张atlas。最后调⽤output(atlas)把图集传给prefab。
延迟绑定的缺陷(bug)
Include in Build选项
根据实测,这个选项的含义是:选,打包时不考虑依赖。不选,打包时考虑依赖。
举例:
⽂件夹"A"存放着要使⽤的图⽚,SpriteAtlas⽂件“A_Atlas”为⽂件A下所有图⽚打成的图集,Prefab⽂件“A_Prefab”引⽤了A下的图⽚。
此时,
如果选择Include in Build,那么打包的时A_Prefab打出来的bundle⽂件会包含A下⾯的图⽚,A_Atlas打出来的bundle也会包含A下⾯的图⽚,也就是说此时会打包双份资源。这是我们不想看到的。
如果不选Include in Build,只有A_Atlas打出来的bundle会包含A下的图⽚,⽽A_Prefab不会包含A下的图⽚。这样资源只打包了⼀份,是我们想要的结果。
但是,
有个地⽅需要注意就是,如果A_Atlas第⼀次勾选了Include in Build,打包后A_Prefab和A_Atlas的bundle都会包含图⽚资源,但是如果此时再把A_Atlas的Include in Build的勾去掉重新打包,Unity只会把A_Atlas的bundle重打,因为Unity认为你只修改了A_Atlas⽂件,⽽A_Prefab被认为”没有更改”,所以之前打进去的图⽚资源依然会存在,正确的做法应该是:如果Include in Build修改了,那么打包的时候所有⽤到这个SpriteAtlas的prefab也要重新打包(删掉bundle⽂件即可)
Unity编辑器状态下运⾏UI全部⽩图(bug)
这应该是Unity的⼀个bug,在这⾥分享⼀下!
在我们的实测中发现:在加载资源的时候如果使⽤延迟绑定的⽅式显⽰UI,在UnityEditor模式下UI的prefab加载显⽰后所有图⽚都是⽩图!但是发布到设备中能正确显⽰。
莫⾮延迟绑定只能运⾏时使⽤?
Texture Packer
Texture Packer最终的解决⽅案,当然这也是很早以前就使⽤的⽅法了。如果Unity能把这件事做好,我相信⼤家都不想⾃⼰单独⽤第三⽅软件去搞图集。TexturePacker具体功能和使⽤⽅法就不多说了,⽹上相关教程有很多。
这⾥我只说⼀下TexturePacker解决了Unity的哪些问题:
1.图集打包算法,选择很多,也更专业。
2.动态加载图⽚,简单明了。编辑时加载:AssetDatabase.LoadAllAssetsAtPath()或者
unity 教程AssetDatabase.LoadAllAssetRepresentationsAtPath();运⾏时加载:AssetBundle.LoadAllAssets()
3.图集的依赖关系,很直观。
当然也有缺点:
需要维护TexturePacker⼯程⽂件。
总结⼀下:
SpritePacker,商业项⽬可⽤,需要⾃⼰做点东西“填坑”。
SpriteAtlas,商业项⽬可⽤,他解决了SpritePacker的⼩问题,但是⾃⼰有带来了新问题,如果“⽩图”的bug解决不了,可能需要单独写⼀套编辑时的加载流程。感觉和SpritePacker填的坑差不多!
TexturePacker,商业项⽬可以,相对来说⽐较“健康”,不⽤额外写代码填坑。
从本⽂还可以看出⼀点,Unity对UI的重视程度还是⽐较⼩的!
就说到这⾥吧!Have a good day!