Unity2017的新spriteAtlas

发表评论 阅读评论

Unity2017带来了新的SpriteAtlas工具,该工具可以方便的将碎图打包成纹理集,相比于之前SpritePacker工具,SpriteAtlas将统一的管理纹理集,不必再到每个碎图上查看被打包到了哪个纹理集,并且解除了sprite使用者和纹理集的强依赖关系。

创建纹理集

sprite atlas

  1. 设置 Editor Settings 中 Sprite Packer 的 Mode 为 Always Enabled
  2. 通过 Project 面板的右键创建 Sprite Atlas
  3. 选中创建的 Sprite Atlas, 并在 Inspector 面板的 Objects for Packing部分添加 sprite
  4. 点击 Pack Preview 按钮预览合并的纹理集
  5. 当 UI 使用这些 Sprite 时,将自动使用打包的纹理集中的 sprite

注意: 一定要将包含的 Image 的 Texture Type 设置成 Sprite(2D and UI), 否则不能真正包含到 Sprite Atlas 里。

工作流

一定不能将原始纹理集从 Unity 的 Assets 目录中移除,否则将不能正常显示。 即使是被打包到了Asset bundle里也不能删掉原始的纹理集。

新纹理集在编辑器和发布后的Player中表现稍有不同。

这里先说在编辑器中最简单的使用方式。

  1. 勾选纹理集的 Inspector 面板中的 Include in Build 选项,否则需要在SpriteAtlasManager.atlasRequested回调中手动填充纹理集
  2. 将 sprite 拖到 Image 的 Source Image 属性上
  3. 点击运行,正常显示

上面的方式相当于将所有内容都打包到该场景中,对于只有少量资源的小项目可以接受。但是有太多资源就不能这么做了,必须要将这些资源分散到不同的 Asset bundle 中。

引入AssetBundle

将不同的纹理集及 UI 分离到 Asset bundle 中,那么流程基本不变,只是需要注册 SpriteAtlasManager.atlasRequested 事件,并处理。

类似下面的代码

using UnityEngine;
using UnityEngine.U2D;

public class SpriteAtlasManager : MonoBehaviour {
    void Awake () {
        // 注册事件
        SpriteAtlasManager.atlasRequested += OnAtlasRequested;
    }

    private void OnAtlasRequested(string tag, Action action) {
        // 加载纹理集
        // unity2017.1版本:当收到回调后必须立刻用纹理集填充
        // SpriteAtlas atlas = LoadAtlas(tag);
        // action(atlas);
        // 2018.2.1f1版本:可以异步加载纹理集,只需向action回调填充纹理集就可以了
        // 当纹理集没有被填充前,Image等组件将显示为默认的白色纹理
        // 一旦纹理填充后,Image等组件将自动显示为正确的纹理
        StartCoroutine(DoLoadAsset(action, tag));
    }

    private IEnumerator DoLoadAsset(Action action, string tag)
    {
        yield return new WaitForSeconds(3);
        var ab = AssetBundle.LoadFromFileAsync(getpath(tag));
        yield return ab;

        print("DoloadAsset frame:" + Time.frameCount);
        var sa = ab.assetBundle.LoadAsset(tag);
        print("sa: " + sa);
        action(sa);
    }
}

在 Unity 编辑器中,点击运行,会立刻请求所有的纹理集。
而Player中运行时,则于我们预期的一致,只回调使用到的纹理集。

注意事项

  1. 总是启用 Always Enabled,默认是 Disabled,菜单路径:
    Edit > Project Settings > Editor > Sprite Packer > Mode
  2. 纹理集 Inspector 面板上的 Include in Build 选项,仅作用于编辑器中。如果勾选就在点击运行时自动加载纹理集,否则不自动加载,并触发SpriteAtlasManager.atlasRequested事件。
  3. 纹理集中的图片的纹理类型需要设置为 Sprite (2D and UI
  4. 编辑器中的原始纹理集一定不能删除。
  5. 不同电脑上的纹理集的.meta文件中的hash会不一样,建议只在一台电脑上打包assetbundle,其他电脑忽略.meta文件的变化。不要提交。

结尾

新的纹理集相比旧版本的SpritePacker,最大的改进是解除了纹理及与UI的强引用关系,只使用tag来关联,使得应用多套资源更方便。

Unity 2017.1是新纹理集的第一个版本,虽然还有一些不太方便的地方,但是相信在后面的版本迭代中会越来越好。

2018/07/29更新

unity2018.2.1f1已经完美支持Late Binding,也就是可以在收到SpriteAtlas.atlasRequested回调后延迟填充纹理集,避免同步加载造成卡顿。

看到留言说Prefab中使用纹理集会造成资源重复打包,在AssetBundleBrower中确实看到资源被自动打包到不同的包,但是从打包后的bundle大小来看,应该是AssetBundleBrower的误报。

以上测试均使用unity2018.2.1f1版本。

SpriteAtlas测试项目 https://github.com/litefeel/UnitySpriteAtlasTest

标签: ,

  1. litefeel | | #1

    @qixiang_xie
    只需要同一个图集就可以了,只是会分成多张图。

  2. qixiang_xie | #2

    spriteatlas图集里的图片总大小超过2048会自动分图集?还是要自己手动拆分不能超过2048?一个目录下直接弄成一个图集,但目录下的图片总大小会超过2048,就要手动建两个图集?

  3. 一青年 | #3

    @litefeel
    谢谢谢谢,万分感谢。这下彻底搞懂了,占用了你这么多时间,谢谢谢谢

  4. litefeel | | #4

    @小青年
    1. 这里纠正下Build的意思, Build指的是用到spriteAtlas的资源,比如prefab,也就是说如果勾选了Include in Build,那么总是把该atlas打包到目标assetbundle中,而不是apk中。
    https://forum.unity.com/threads/about-include-in-build-behaviour.481433/#post-3138664

    2. 勾选Include in Build后,通过assetbundle加载来的Image/SpriteRenderer不能显示,并触发SpriteAtlasManager.atlasRequested,这个可能是bug,也可能设计如此。

    https://docs.unity3d.com/2018.3/Documentation/Manual/SpriteAtlas.html
    最新版的文档里专门增加了这句话:”Note that unchecking this option causes any packed Assets to not be rendered during Play Mode.“

    https://unity3d.com/unity/beta-download
    2D: Image components not displaying sprite data when instantiated from a prefab and trigger a SpriteAtlas request (1038179)
    unity2018.3beta版的release note,在 Fixes 段中有上面一句话,可能是说修复了不显示并触发事件的问题,但是我在unity2018.3.0b5上测试的结果,勾选了Include in Build后打包,仍然是不显示,并会触发SpriteAtlasManager.atlasRequested

    所以目前来看,最好的方式是,不要勾选Include in Build,并在SpriteAtlasManager.atlasRequested中加载spriteAtlas

  5. 小青年 | #5

    @litefeel
    谢谢你这次的指教,我这次atlas打AB包删了重打,确实测试出来结果不一样了,大部分与您和官方文档讲的现象是相符的,但是还有两个情况不太符合(且我反复确认了两三遍)。
    一个是Android真机,勾选Include in Build,不注册atlasRequested,加载带Image的UI预制体,结果却是不显示,并警告SpriteAtlasManager.atlasRequested wasn't listened to while RGB requested.(这让我有点意外。不过却也和我们项目中开发遇到的状况是一样的)。
    另一个是Android真机,勾选Include in Build,注册atlasRequested,加载带Image的UI预制体,结果是正常显示图片,但是会触发atlasRequested事件。(反复确认了两次……,和您说的不太一样。不过编辑器上的情况是正常的,显示图片但不触发事件)。
    我的结论这两个状况是Unity在这两个版本中的BUG,我测试的版本是2017.3.0f3,项目开发的是2018.2.0f5.

  6. 一青年 | #6

    @litefeel
    哦~~~对,我就很奇怪测出来和你和官方的不一样。原来时我取消Include in build的时候没有删除AB再重新打AB,而是直接重新打AB。那我回去再重新删除AB再重新打。

  7. litefeel | | #7

    @小青年
    再次阅读了unity的官方文档 https://docs.unity3d.com/Manual/SpriteAtlas.html

    Include in build: Always includes the Atlas Asset in the build.

    这里的build应该是 build player的build,也就是说会打到apk里,同时也总是打到build里(不论编辑器还是player),所以勾选Include in build后就不会触发SpriteAtlas.atlasRequested事件了。

    1. 勾选 Include in build 不会触发 SpriteAtlas.atlasRequested事件,否则会触发,并需要处理。
    2. SpriteAtlas.atlasRequested的触发是在加载了含有SpriteAtlas的AssetBundle后(编辑器是一开始运行时)
    3. 测试不勾选Include in build时,需要重新build apk, 删除Asset Bundle并重新打包AssetBundle

  8. 小青年 | #8

    @litefeel
    有一些不解,请教一下,下面是我用测试项目测的
    测试记录:unity2017.3.0f3
    SpriteAtlas打AB包:
    UnityEditor,勾选Include in Build,不注册atlasRequested,加载带Image的UI预制体,正常显示
    UnityEditor,不勾选Include in Build,不注册atlasRequested,加载带Image的UI预制体,不显示显示,并警告SpriteAtlasManager.atlasRequested wasn't listened to while RGB requested.

    UnityEditor,勾选Include in Build,注册atlasRequested,加载带Image的UI预制体,正常显示,
    但不触发atlasRequested事件。
    UnityEditor,不勾选Include in Build,注册atlasRequested,加载带Image的UI预制体,正常显示,并触发atlasRequested事件。

    Android真机,勾选Include in Build,不注册atlasRequested,加载带Image的UI预制体,正常显示
    Android真机,不勾选Include in Build,不注册atlasRequested,加载带Image的UI预制体,正常显示显示图片(奇怪)

    Android真机,勾选Include in Build,注册atlasRequested,不加载带Image的UI预制体,正常显示图片,不触发atlasRequested事件。
    Android真机,不勾选Include in Build,注册atlasRequested,不加载带Image的UI预制体,正常显示图片,但也不触发atlasRequested事件(奇怪)

    下面是我们开发项目遇到的问题,我们采取的修复方法;
    我们项目Unity2018在大量UI(勾了Include in Build)I的时候有时候会出现显示错误,注册事件后恢复正常。

    怎么看都感觉很奇怪?特别是2017测试记录里两个我以为不会正常显示的地方都正常显示了。以及我们开发时,按理论来说也应该显示正常才对

  9. litefeel | | #9

    @小青年
    atlasRequest+= 这个就是做Atlas后绑定用的,没有他,打包后不能正常运行。

  10. 小青年 | #10

    @litefeel 对,我的意思就是就是想你一项给atlasRequest+=我们自己的方法才好

  11. litefeel | | #11

    @touyi
    编辑器要么在最开始运行时触发SpriteAtlasManager.atlasRequested,要么就不触发。

    @小青年
    SpriteAtlasManager.atlasRequested是Unity自己调的,不用手动调用。

  12. 小青年 | #12

    我们的项目是,2018.2.5f,我们勾选了include in build ,但是仍然会出现sprite显示出错的问题。但是我们调用了SpriteAtlasManager.atlasRequested才改善了这个问题。这个好像跟楼主讲的有点不一样……不过,我们还没尝试去掉inculd in build再调用这个回调看看。

  13. touyi | #13

    感谢楼主 我在编辑器里面运行没法触发回调 或者可能添加回调的时间比较晚了 但是打包出来却没有问题 这是怎么回事呢?

  14. litefeel | | #14

    @liudq
    Late Binding 没有加载纹理集并填充前,Image会显示默认的白色纹理

  15. litefeel | | #15

    @小小搬运工

    @yyh9488
    注意两点:
    1. EditorSettings中SpritePacker设置为AlwaysEnabled
    2. SpriteAtlas纹理集的取消勾选Include In Build
    3. 如果纹理集已经被重复打包了,需要先删除所有的ab包,重新打包
    如果以上还是不行,建议升级unity版本,我测试的2018.2.1f1没有问题

  16. litefeel | | #16

    @开山怪
    1. 依赖关系里确实没有atlas的依赖,因为ab的依赖是以ab包为单位的,如果依赖了就不能做变体了,比如不同分辨率的资源。
    2. AB包时不要勾选Include In Build
    3. 其实是AB包加载完成后,就会触发该AB所引用的所有的atlas的tag回调,但是实例化prefab后再填充纹理集也可以
    4. 不能勾选Include in Build,否则不能触发SpriteAtlasManager.atlasRequested

  17. 开山怪 | #17

    我用得unity2018.1.5f1
    1、我现在遇到的问题是prefab打包后,manifest依赖关系里面没有图集,我现在是在prefab加载前把所有图集都先加载到内存中缓存起来(这明显不符合热更的需求)。
    2、SpriteAtlasManager.atlasRequested中调用action回调后,prefabe并没有填充,我在edtor中勾选Include in Build,在edtor中运行是没问题的。
    3、SpriteAtlasManager.atlasRequested中tag只返回一个图集的名称,我prefabe使用了多个图集怎么处理?
    4、我一系列调试后,现在SpriteAtlasManager.atlasRequested不触发了,整个人都不好了

  18. 小小搬运工 | #18

    谢谢分享,心中有个疑问。我用了一些图,用2017图集打包成assetbundle atlas1,我有一个prefab里面包含了该图集中的一张图,这个prefab打包成assetbundle prefab1,最后打包的时候发现prefab1的manifest文件里有对atlas1的依赖。但是prefab1的文件大小比较大。看起来是把atlas1包含进去了。这点有办法解决嘛?

  19. yyh9488 | #19

    @Dinko 感觉很鸡肋不删除打包进去就算了,运行的时候还加载到内存中
    导致运行内存很大

  20. liudq | #20

    @liudq
    我自己做的测试,Awake时,注册了atlasRequested回调并加载有SpriteAtlas的AB包,在回调里,填入AB包里的SpriteAtlas,这样测试是正常可以显示出Sprite的。但如果,我只是在Awake里注册回调,并隔几秒再执行加载AB包,就不显示Sprite。请问这种情况如何处理?

  21. liudq | #21

    感谢分享,想问下,都有哪些情况会触发atlasRequested回调?

  22. litefeel | | #22

    @Dinko
    没有发现会重复打包的情况,你可以比对下assetbundle的大小

  23. Dinko | #23

    请问一下,

    之前一直用的散图,最后包很大,想用sprite atlas打包做压缩,但是打包成atlas之后,散图不删除的话,还是会被打到包里面。这样反而会因为多出了atlas多一些资源。

    删除了散图的话,又不能正常显示,这个应该怎么处理?

    理论上来说,atlas应该直接替换散图才对啊

  24. litefeel | | #24

    @花底滑
    1. 注册SpriteAtlasManager.atlasRequested一定要在加载要用的spriteAtlas的AB之前
    2. OnAtlasRequested 中一定要同步填充 spriteAtlas,不能异步

  25. 花底滑 | #25

    谢谢您的分享,我是这样用的
    1、将散图资源做成一个spritearlas,将这个spritearlas打包成AB,散图资源不打AB包
    2、导出window版本,在window版本上,通过代码读取spritearlas的AB资源,并通过名字来获取里面的图片信息,显示之,能正常显示,达成目的,此时AB资源中只有spritearlas,没有散图资源。
    3、第2步是测试通过代码读取spritearlas的图片资源并显示,可行。但是不通过代码读取,如有一个image,上面直接给sprite一个图片,注册了SpriteAtlasManager.atlasRequested之后仍然显示不了(非编辑器模式,window包下),请问这种情况如何处理?

  1. 本文目前尚无任何 trackbacks 和 pingbacks.
回到顶部