Unity2017的新版图集 & 自带图集实现TP的Polygon布局

Unity2017的新版图集 & 自带图集实现TP的Polygon布局

升级到2017的团队应该都注意到了,打开原来的Sprite Packer会有这样一个提示

只有重新选择Legacy Sprite Packer才能继续使用原来的图集功能

那么,它新增的“不是Legacy”的图集功能在哪里呢?

在2017,图集的概念被恢复了。准确的说,是把虚拟的图集配置变成了实际存在的Asset。


实际上,整个图集系统并没有太大的变化,更改的只是用户界面。你还是可以使用(也只能使用)原来分散的Sprite文件,在没有图集的情况下拼合UI或者2D内容。图集还是可以在项目的末期再考虑,更改一个Sprite的图集所属并不需要修改使用过它的预制。

这个图集Asset其实只是一个打包配置文件,在打包之前磁盘里并不会存在这样一个图集纹理,打包时才会生成图集纹理并替换所有相关Sprite的内容。一切都和以前一样。这次只是项目配置文件的Asset化。


不过经过这样的更改,操作上则稍微有了一些便利:

1.你不需要再写编辑器脚本给目录下的文件设置sprite tag,新的Sprite Atlas设置包含内容的时候支持目录,你只需要把目录设置成Sprite Atlas的项目即可。

2.可以统一地设置整个图集的压缩纹理格式,而不是到每个Sprite上设置并保证他们一致。

3.Allow Rotation(允许旋转)和Tight Packing(按OutLine紧密打包)被拆分成了两个选项,所以你可以选择不“紧密打包”但是“允许旋转”。另外,现在的这个设置也比以前直观很多,我打赌很多人之前根本不知道Unity有这两个功能。

4.Sprite Atlas提供了GetSprites的API,所以可以从Atlas直接取到它全部Sprite的实例了。做Sprite内容的切换(不同颜色的框,VIP等级图片)就不用自己保存一个Sprite列表了,直接引用图集Asset用GetSprite(name)就可以。是的,Sprite Name的概念也恢复了。

5.AB分配的时候只需要设置图集的AssetBoundle所属,Sprite不需要设置。具体的机理我也没弄懂,总之设不设Sprite,结果都一样的。把图集加入一个Ab,等于把其包含的所有Sprite都加入这个Ab。


值得一提的是,由于图集设置变成了“图集包含特定Sprite”,所以,一个Sprite现在可以被放置到多个图集里了。但这并不是利好,而是干扰,因为加载Sprite时使用哪个图集还是固定的,另一个图集里的资源将永远不会被取到。

当出现这种一对多情况的时候,我的测试结果是,会优先选择名字排序靠前的Sprite Atlas。

不要出现这种情况就好了。


这次图集更新增加了两个(并没有什么用的)新功能:

1.延迟加载图集内容。你可以把图集的Inclube In Build(这个命名有干扰,不要按字面理解)取消勾选,这样在加载Sprite的时候并不会自动加载图集内容,而是会抛出一个事件,并临时显示一个透明图。

U2D.SpriteAtlasManager.atlasRequesteddocs.unity3d.com

你可以立刻加载图集并通过事件给你的回调回传回去,这样看上去就和以前没有区别

void RequestAtlas(string tag, System.Action<SpriteAtlas> callback)
{
    var sa = Resources.Load<SpriteAtlas>(tag);
    callback(sa);
}

也可以稍微等待一段时候再回传,比如使用异步加载减缓IO压力。

但要注意的是,现在这个功能至少编辑器里测试是无效的,Unity Answer一堆人也在说这个事儿,我试的结果是在发布版本虽然事件会触发,但即使不做任何事情图片也能显示出来,回调回去也不知道有啥作用。不知道什么时候才会修正。


2.另一个则是与其配套的Variant Atlas,类似于继承,指定一个Sprite Atlas并使用它的全部内容。

像刚才的情况,你就可以通过tag加后缀,加载一个对应的Variant Atlas来替换原来的Atlas。

但现在这个Variant Atlas只有一个缩放图片的功能,增加Sprite,替换Sprite什么的都没有,非常鸡肋,好像也只能做高低清UI替换了。

希望未来的版本可以增加这部分的功能,比如通过实时替换Sprite Atlas来切换多语言版本。


这个新版Atlas相比以前,缺点如下:

安卓下ETC1+alpha的自动拆分图集失效了,怎么搞都搞不回来。

然后就是,无法通过TAG来给单个Sprite设置“紧密布局”或者“旋转布局”,只能勉强设置OutLine为外边框来解决。

说白了,就是缺功能。

毕竟新功能并没多少用处,个人觉得没必要换。










其实下面的内容我应该单发一篇文章,但感觉太频繁也不太好,就追加在这里了。

第三方一直有个叫TexturePacker的插件,因为比较老也一直在更新,很多人都在用。但怎么说呢,其实在同一时间段里,TexturePacker和Unity图集的功能一直是差不太多的,像Polygon这个TP的付费功能,其实本来就是跟Unity抄的。

但是因为并不是所有人都会升级Unity版本,现在还有一堆人坚守NGUI呢,所以Unity后续更新的功能就跟没有一样,也没人讨论,造成了这样一个假象。

Polygon其实就是Unity的Tight Packing,新版的设置位置上面说了,旧版则在这里

但你要直接设置通常是没有效果的,这是因为不管Polygon和Tight Packing都是根据Sprite的OutLine区域来实现紧密布局的,而Polygon默认的网格复杂度比Unity的高。Unity默认生成的OutLine通常都是这样的:

你需要拖动滑条增加结点数量

也提供了方便的网格编辑功能

处理好之后是可以实现和TexturePacker一样的效果的


但是Polygon这种功能是需要谨慎使用的,因为随便乱用的画,由于UI是以方框显示的,允许按OutLine合并图集会出现这样的串图情况:

Before
After


这是无需解释的必然的结果。想不出现这种情况,必须以OutLine为依据生成Mesh来显示UI元素。不管是UGUI和NGUI都没有提供这个功能,所以搞这种紧密图集的都需要自己实现一个按Mesh显示图片的组件。

我之前也写过了【UGUI】Image功能扩展:使用Sprite网格,镜像,挖洞,自定义碰撞

核心代码:

void GenerateSpriteSprite(VertexHelper vh, bool shouldPreserveAspect)
{
    Rect r = GetPixelAdjustedRect();
    var size = new Vector2(overrideSprite.rect.width, overrideSprite.rect.height);
    Bounds bounds = overrideSprite.bounds;

    if (shouldPreserveAspect)
    {
        PreserveAspect(ref r, size);
    }

    float w = r.width / bounds.size.x;
    float h = r.height / bounds.size.y;
    Vector4 v = new Vector4(-w * bounds.center.x,
        -h * bounds.center.y,
        w,
        h);

    Color32 color32 = color;
    vh.Clear();
    var vertices = overrideSprite.vertices;
    var uv = overrideSprite.uv;
    int count = vertices.Length;
    for (int i = 0; i < count; i++)
    {
        Vector2 vert = vertices[i];
        vh.AddVert(new Vector3(v.x + vert.x * v.z, v.y + vert.y * v.w, 0), color32, uv[i]);
    }
    var triangles = overrideSprite.triangles;
    count = triangles.Length;
    for (int i = 0; i < count; i += 3)
    {
        vh.AddTriangle(triangles[i], triangles[i + 1], triangles[i + 2]);
    }
}


但这种做法其实不推荐大量使用,因为和Mesh不同,你可以理解成显示模型时系统对其做了大量优化,所以即使上W顶点也没啥关系。但是UI里的顶点数首先会增加网格合并的负担,ReBuild时执行的代码量也确实上升了,也是C#代码,处理不好很容易造成加载界面的卡顿。

绝对不能全部UI组件都用这种方式显示,负优化是不能容忍的。

处理起来也简单。只有个别几个调整过网格的组件用这个特殊组件,其他的还是用普通的Image,为了防止这些组件相互串纹理,一种方式是设置Mesh Type为Full Rect(新版同样有效)

但这会导致原始图片外边的透明区域部分也不会被删除,所以还是在Packing Tag上加上[RECT]前缀更好,也可以反过来设置取消Tight图集,并在需要Tight的时候加上[TIGHT]前缀。

Unity - Manual: Sprite Packer

直接设置Sprite的OutLine为外部的整个方框也是一个办法。


当然,我不得不承认,虽然功能是相近的,但TexturePacker的布局算法确实要比Unity的好。Unity只是靠着“可调整”这一点把生成Mesh的劣势补回来的。而在普通的布局上,Unity对“旋转”这一点处理的也一直不如TexturePacker,会导致空间上的浪费,比如像这样:

本来红框的这个物体竖着摆是能放下的,Unity自己判断不出来,但是TP可以。

这时候就只能手动改下源图把它竖过来(Window10的默认图片查看工具就可以)

就能正确处理了。

但主要的区别其实也就是这个。用Unity的图集要尽量避免出现这种狭长的内容,发现上图的情况旋转一下长条物体,通常都可以解决。

(我再测试了一下,应该是Unity只能判断Flip而不能判断Rotate。从来没看到成功自动旋转90度的情况)


Unity这个图集毕竟在工作流上还是有很大优势的,TP毕竟要专门的软件,操作次数比Unity多。如果界面元素需要经常修改,或者说,界面是美术自己拼的话,自带图集会比TP方便很多。由于Sprite的链接机制,Unity这套更换图集的成本也更小,管理也省心, 有一些缺点是可以接受的。

就别提TP的Polygon要付费这一点了。


但是Unity的自动图集不暴露图集纹理,导致无法手动拆分自动图集通道,这点就比较“无解”,这也是部分团队不得不使用TP的理由。虽然安卓上,Unity提供了很方便的ETC1+alpha拆通道模式,但是实际用的时候,IOS上PVR的透明图质量极差也是一个问题。需要拆通道的并不仅仅是安卓平台,而Unity却把拆通道的功能限制在了安卓上,新版图集还把这个功能无视了……

所以为了UI的质量,拆图集通道这件事还是非做不可。


其实我已经搞得差不多了。做下扫尾工作应该就能发布。

编辑于 2018-01-15