Version: 2020.3
Sprite Atlas 版本 2(实验性)
Sprite Shape Renderer

Sprite Packer

Sprite Packer 已弃用

请注意 Sprite Packer 在 Unity 2020.1 和更新版本中已弃用,并且将不再作为精灵打包模式的可用选项。现有已使用 Sprite Packer 的项目仍然可以继续使用它,但是在 2020.1 以后创建的任何新项目在打包纹理时将默认使用 Sprite Atlas 系统。

简介

在设计精灵图形时,可以方便地为每个角色使用单独的纹理文件。但是,精灵纹理的很大一部分通常会被图形元素之间的空白空间所占据,而此空间将导致运行时浪费视频内存。为了获得最佳性能,最好将几个精灵纹理中的图形紧密地打包在一个称为图集的纹理中。Unity 提供了一个 Sprite Packer 实用程序来自动执行从各个精灵纹理生成图集的过程。

Unity 在后台处理精灵图集纹理的生成和使用,这样用户就不需要进行手动分配。可选择在进入运行模式时或在构建期间打包图集,而图集一旦生成,便会从图集获取精灵对象的图形。

用户需要在纹理导入器 (Texture Importer) 中指定一个打包标签 (Packing Tag),从而为该纹理的精灵启用打包。

使用 Sprite Packer

The Sprite Packer is disabled by default but you can configure it from the Editor window (menu: Edit > Project Settings, then select the Editor category). The Sprite packing mode can be changed from Disabled to Enabled for Builds (packing is used for builds but not Play mode) or Always Enabled (packing is enabled for both Play mode and builds).

如果打开 Sprite Packer 窗口(菜单:__Window__ > 2D > Sprite Packer__)并单击左上角的 Pack__ 按钮,则会显示图集内包含的纹理排列结果。

如果在 Project 面板中选择一个精灵,该精灵也会突出显示以显示其在图集内的位置。轮廓实际上是渲染网格轮廓,还定义了用于紧密打包的区域。

Sprite Packer 窗口顶部的工具栏有许多影响打包和查看的控件。Pack 按钮可启动打包操作,但如果图集自上次打包后没有更改,则不会强制进行任何更新。(按__自定义 Sprite Packer__ 中所述实现自定义打包策略时,将出现一个相关的 Repack 按钮)。View AtlasPage # 菜单允许选择在窗口中显示哪个图集的哪个页面(如果没有足够的空间来容纳最大纹理大小内的所有精灵,则单个图集可能拆分成多个“页面”)。页面编号旁边的菜单用于选择对图集使用的“打包策略”(见下文)。在工具栏的右侧有两个控件用于缩放视图以及在图集的颜色和 Alpha 显示之间切换。

打包策略

Sprite Packer 使用__打包策略__来决定如何将精灵分配到图集。您可以创建自己的打包策略(见下文),但也可以随时使用 Default Packer PolicyTight Packer PolicyTight Rotate Enabled Sprite Packer Policy 选项。通过使用这些策略,纹理导入器中的 Packing Tag 属性可直接选择将精灵打包到的图集的名称,因此具有相同打包标签 (Packing Tag) 的所有精灵将打包到同一图集内。然后,根据纹理导入设置对图集进行进一步排序,使图集符合用户为源纹理进行的任何设置。具有相同纹理压缩设置的精灵将尽可能分组到相同的图集。

  • DefaultPackerPolicy 将默认使用矩形打包方式,除非在 Packing Tag 中指定了“[TIGHT]”(即,将打包标签设置为“[TIGHT]Character”将允许紧密打包)。
  • TightPackerPolicy 将默认使用紧密打包(如果精灵具有紧密网格)。如果在 Packing Tag 中指定了“[RECT]”,则将执行矩形打包方式(即,将打包标签设置为“[RECT]UI_Elements”将强制进行矩形打包)。
  • TightRotateEnabledSpritePackerPolicy 将默认使用紧密打包(如果精灵具有紧密网格)并会启用精灵旋转。如果在 Packing Tag 中指定了 “[RECT]”,则将执行矩形打包方式(即,将打包标签设置为 “[RECT]UI_Elements” 将强制进行矩形打包)。

自定义 Sprite Packer

DefaultPackerPolicy 选项足以满足大多数用途,但如果需要,也可以实施自己的自定义打包策略。为此,需要实现编辑器脚本中的一个类的 UnityEditor.Sprites.IPackerPolicy 接口。此接口需要以下方法:

  • GetVersion - 返回打包策略的版本值。如果对策略脚本进行了修改,则应该更换版本,并且此策略将保存到版本控制系统。
  • OnGroupAtlases - 在此处实现打包逻辑。在 PackerJob 上定义图集,并从给定的 TextureImporter 分配精灵。

DefaultPackerPolicy 默认使用矩形打包(请参阅 SpritePackingMode)。如果正在创建纹理空间效果或想要使用不同的网格来渲染精灵,此设置将非常有用。自定义策略可覆盖此设置,并改用紧密打包。

  • 仅在选择自定义策略时才会启用 Repack 按钮。
    • 除非 TextureImporter 元数据或所选的 PackerPolicy 版本值发生变化,否则不会调用 OnGroupAtlases。
    • 仅在实施自定义策略时才应使用 Repack 按钮。
  • 可使用 TightRotateEnabledSpritePackerPolicy 自动对精灵打包并旋转。
    • SpritePackingRotation 是保留供未来 Unity 版本使用的类型。

其他

  • 图集缓存在 Project\Library\AtlasCache 中。
    • 删除此文件夹再启动 Unity 将强制对图集重新打包。这样做时必须关闭 Unity。
  • 启动时不加载图集缓存。
    • 重新启动 Unity 后首次打包时必须选中所有纹理。此操作可能需要一些时间,具体取决于项目中的纹理总数。
    • 仅加载必需的图集。
  • 默认最大图集大小为 2048x2048。
  • 如果设置了 PackingTag,则不会压缩纹理,这样 SpritePacker 就可以获取原始像素值,然后对图集进行压缩。

DefaultPackerPolicy

using System;
using System.Linq;
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;


public class DefaultPackerPolicySample : UnityEditor.Sprites.IPackerPolicy
{
        protected class Entry
        {
            public Sprite            Sprite;
        public UnityEditor.Sprites.AtlasSettings settings;
            public string            atlasName;
            public SpritePackingMode packingMode;
            public int               anisoLevel;
        }

        private const uint kDefaultPaddingPower = 3; // 适用于基础和两个 Mip 级别。

        public virtual int GetVersion() { return 1; }
        protected virtual string TagPrefix { get { return "[TIGHT]"; } }
        protected virtual bool AllowTightWhenTagged { get { return true; } }
        protected virtual bool AllowRotationFlipping { get { return false; } }

    public static bool IsCompressedFormat(TextureFormat fmt)
    {
        if (fmt >= TextureFormat.DXT1 && fmt <= TextureFormat.DXT5)
            return true;
        if (fmt >= TextureFormat.DXT1Crunched && fmt <= TextureFormat.DXT5Crunched)
            return true;
        if (fmt >= TextureFormat.PVRTC_RGB2 && fmt <= TextureFormat.PVRTC_RGBA4)
            return true;
        if (fmt == TextureFormat.ETC_RGB4)
            return true;
        if (fmt >= TextureFormat.ATC_RGB4 && fmt <= TextureFormat.ATC_RGBA8)
            return true;
        if (fmt >= TextureFormat.EAC_R && fmt <= TextureFormat.EAC_RG_SIGNED)
            return true;
        if (fmt >= TextureFormat.ETC2_RGB && fmt <= TextureFormat.ETC2_RGBA8)
            return true;
        if (fmt >= TextureFormat.ASTC_RGB_4x4 && fmt <= TextureFormat.ASTC_RGBA_12x12)
            return true;
        if (fmt >= TextureFormat.DXT1Crunched && fmt <= TextureFormat.DXT5Crunched)
            return true;
        return false;
    }

    public void OnGroupAtlases(BuildTarget target, UnityEditor.Sprites.PackerJob job, int[] textureImporterInstanceIDs)
        {
            List<Entry> entries = new List<Entry>();

            foreach (int instanceID in textureImporterInstanceIDs)
            {
                TextureImporter ti = EditorUtility.InstanceIDToObject(instanceID) as TextureImporter;

                TextureFormat desiredFormat;
                ColorSpace colorSpace;
                int compressionQuality;
                ti.ReadTextureImportInstructions(target, out desiredFormat, out colorSpace, out compressionQuality);

                TextureImporterSettings tis = new TextureImporterSettings();
                ti.ReadTextureSettings(tis);

            Sprite[] Sprites =
                AssetDatabase.LoadAllAssetRepresentationsAtPath(ti.assetPath)
                    .Select(x => x as Sprite)
                    .Where(x => x != null)
                    .ToArray();
                foreach (Sprite Sprite in Sprites)
                {
                    Entry entry = new Entry();
                    entry.Sprite = Sprite;
                    entry.settings.format = desiredFormat;
                    entry.settings.colorSpace = colorSpace;
                    // 之后仅针对压缩格式使用压缩质量进行分组。否则将其留空。
                entry.settings.compressionQuality = IsCompressedFormat(desiredFormat) ? compressionQuality : 0;
                entry.settings.filterMode = Enum.IsDefined(typeof(FilterMode), ti.filterMode)
                    ? ti.filterMode
                    : FilterMode.Bilinear;
                    entry.settings.maxWidth = 2048;
                    entry.settings.maxHeight = 2048;
                    entry.settings.generateMipMaps = ti.mipmapEnabled;
                    entry.settings.enableRotation = AllowRotationFlipping;
                    if (ti.mipmapEnabled)
                        entry.settings.paddingPower = kDefaultPaddingPower;
                    else
                        entry.settings.paddingPower = (uint)EditorSettings.SpritePackerPaddingPower;
                    #if ENABLE_ANDROID_ATLAS_ETC1_COMPRESSION
                        entry.settings.allowsAlphaSplitting = ti.GetAllowsAlphaSplitting ();
                    #endif //ENABLE_ANDROID_ATLAS_ETC1_COMPRESSION

                    entry.atlasName = ParseAtlasName(ti.SpritePackingTag);
                    entry.packingMode = GetPackingMode(ti.SpritePackingTag, tis.SpriteMeshType);
                    entry.anisoLevel = ti.anisoLevel;

                    entries.Add(entry);
                }

                Resources.UnloadAsset(ti);
            }

            // 首先根据图集名称将精灵分成几组
            var atlasGroups =
                from e in entries
                group e by e.atlasName;
            foreach (var atlasGroup in atlasGroups)
            {
                int page = 0;
                // 然后根据纹理设置将这些组分成更小的组
                var settingsGroups =
                    from t in atlasGroup
                    group t by t.settings;
                foreach (var settingsGroup in settingsGroups)
                {
                    string atlasName = atlasGroup.Key;
                    if (settingsGroups.Count() > 1)
                        atlasName += string.Format(" (Group {0})", page);

                UnityEditor.Sprites.AtlasSettings settings = settingsGroup.Key;
                    settings.anisoLevel = 1;
                    // 使用此图集中所有条目的最高 aniso 级别
                    if (settings.generateMipMaps)
                        foreach (Entry entry in settingsGroup)
                            if (entry.anisoLevel > settings.anisoLevel)
                                settings.anisoLevel = entry.anisoLevel;

                    job.AddAtlas(atlasName, settings);
                    foreach (Entry entry in settingsGroup)
                    {
                        job.AssignToAtlas(atlasName, entry.Sprite, entry.packingMode, SpritePackingRotation.None);
                    }

                    ++page;
                }
            }
        }

        protected bool IsTagPrefixed(string packingTag)
        {
            packingTag = packingTag.Trim();
            if (packingTag.Length < TagPrefix.Length)
                return false;
            return (packingTag.Substring(0, TagPrefix.Length) == TagPrefix);
        }

        private string ParseAtlasName(string packingTag)
        {
            string name = packingTag.Trim();
            if (IsTagPrefixed(name))
                name = name.Substring(TagPrefix.Length).Trim();
            return (name.Length == 0) ?"(unnamed)" : name;
        }

        private SpritePackingMode GetPackingMode(string packingTag, SpriteMeshType meshType)
        {
            if (meshType == SpriteMeshType.Tight)
                if (IsTagPrefixed(packingTag) == AllowTightWhenTagged)
                    return SpritePackingMode.Tight;
            return SpritePackingMode.Rectangle;
        }
}

TightPackerPolicy

using System;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.Sprites;
using System.Collections.Generic;

// 除非打包标签包含 "[RECT]",否则 TightPackerPolicy 将对非矩形精灵进行紧密打包。
class TightPackerPolicySample : DefaultPackerPolicySample
{
        protected override string TagPrefix { get { return "[RECT]"; } }
        protected override bool AllowTightWhenTagged { get { return false; } }
        protected override bool AllowRotationFlipping { get { return false; } }
}

TightRotateEnabledSpritePackerPolicy

```` using System; using System.Linq; using UnityEngine; using UnityEditor; using UnityEditor.Sprites; using System.Collections.Generic;

// TightPackerPolicy will tightly pack non-rectangle Sprites unless their packing tag contains “[RECT]”. class TightRotateEnabledSpritePackerPolicySample : DefaultPackerPolicySample { protected override string TagPrefix { get { return “[RECT]”; } } protected override bool AllowTightWhenTagged { get { return false; } } protected override bool AllowRotationFlipping { get { return true; } } }


  • Sprite Packer deprecated in 2020.1 NewIn20191
Sprite Atlas 版本 2(实验性)
Sprite Shape Renderer