Version: 2022.1
解析不同的精灵图集情形
Sprite Packer 模式

Sprite Packer

Sprite Packer 已弃用

Please note that Sprite Packer is deprecated for Unity 2020.1 and newer, and will no longer be available as an option from Sprite Packer Modes. Existing Projects already using Sprite Packer will still be able to continue using it, however any new Project created in 2020.1 onwards will default to the Sprite Atlas system when packing Textures.

简介

When designing Sprite graphics, it is convenient to work with a separate Texture file for each character. However, a significant portion of a Sprite Texture will often be taken up by the empty space between the graphic elements and this space will result in wasted video memory at runtime. For optimal performance, it is best to pack graphics from several Sprite textures tightly together within a single Texture known as an atlas. Unity provides a Sprite Packer utility to automate the process of generating atlases from the individual Sprite textures.

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).

If you open the Sprite Packer window (menu: Window > 2D > Sprite Packer) and click the Pack button in the top-left corner, you will see the arrangement of the textures packed within the atlas.

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

The toolbar at the top of the Sprite Packer window has a number of controls that affect packing and viewing. The Pack buttons initiates the packing operation but will not force any update if the atlas hasn’t changed since it was last packed. (A related Repack button will appear when you implement a custom packing policy as explained in Customizing the Sprite Packer below). The View Atlas and Page # menus allow you to choose which page of which atlas is shown in the window (a single atlas may be split into more than one “page” if there is not enough space for all Sprites in the maximum Texture size). The menu next to the page number selects which “packing policy” is used for the atlas (see below). At the right of the toolbar are two controls to zoom the view and to switch between color and alpha display for the atlas.

打包策略

The Sprite Packer uses a packing policy to decide how to assign Sprites into atlases. It is possible to create your own packing policies (see below) but the Default Packer Policy, Tight Packer Policy and Tight Rotate Enabled Sprite Packer Policy options are always available. With these policies, the Packing Tag property in the Texture Importer directly selects the name of the atlas where the Sprite will be packed and so all Sprites with the same packing tag will be packed in the same atlas. Atlases are then further sorted by the Texture import settings so that they match whatever the user sets for the source textures. Sprites with the same Texture compression settings will be grouped into the same atlas where possible.

  • DefaultPackerPolicy will use rectangle packing by default unless “[TIGHT]” is specified in the Packing Tag (i.e. setting your packing tag to “[TIGHT]Character” will allow tight packing).
  • TightPackerPolicy will use tight packing by default if Sprite have tight meshes. If “[RECT]” is specified in the Packing Tag, rectangle packing will be done (i.e. setting your packing tag to “[RECT]UI_Elements” will force rect packing).
  • TightRotateEnabledSpritePackerPolicy will use tight packing by default if Sprite have tight meshes and will enable rotation of Sprites. If “[RECT]” is specified in the Packing Tag, rectangle packing will be done (i.e. setting your packing tag to “[RECT]UI_Elements” will force rect packing).

自定义 Sprite Packer

The DefaultPackerPolicy option is sufficient for most purposes but you can also implement your own custom packing policy if you need to. To do this, you need to implement the UnityEditor.Sprites.IPackerPolicy interface for a class in an editor script. This interface requires the following methods:

  • 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 Packer 模式