版本: 2022.3
语言: 英语
解决不同的精灵图集场景问题
准备精灵打包器遗留打包资源以在弃用后工作

精灵打包器

在设计 精灵二维图形对象。如果你习惯于在3D环境中工作,精灵本质上只是标准的纹理,但在开发过程中,为了提高效率和方便性,精灵纹理的合并和管理有一些特殊的技术。 更多信息
术语表中查看
图形时,为每个角色使用单独的纹理文件是很方便的。然而,精灵纹理的一个重要部分常常被元素之间的空白空间占据,这部分空间将在运行时导致视频内存浪费。为了最佳性能,最好将多个精灵纹理紧密打包在一个称为图集的单个纹理中。Unity提供了一个 精灵打包器 工具来自动从单个精灵纹理生成图集。

Unity在后端处理图集纹理的生成和使用,以便用户无需手动分配。图集可以在进入玩 modo 模式或构建期间选择性地打包,一旦生成,精灵对象的图形将从图集中获取。

用户必须在纹理导入器中指定打包标签,以启用该纹理精灵的打包。

警告:请注意,精灵打包器 已在 Unity 2020.1 及更新版本中弃用,并且从 Unity 2020.1 开始不再作为选项出现在 精灵打包器模式 中。从 2020.1 开始创建的所有新项目将默认使用 精灵图集由几个较小的纹理组成的纹理。也称为纹理图集、图像精灵、精灵表或打包纹理。 更多信息
术语表中查看
系统进行纹理打包。有关如何继续使用使用遗留 精灵打包器 打包的资源的说明,请参阅 为弃用后继续使用遗留精灵打包器打包的资源做准备

使用精灵打包器

精灵打包器默认情况下是禁用的,但您可以从 编辑器 窗口(菜单: 编辑 > 项目设置,然后选择 编辑器 类别)中配置它。可以将精灵打包模式从 禁用 更改为 只对构建启用以打包(打包用于构建但不在玩 modo 模式)或 始终启用(打包对于玩 modo 模式和构建都启用)。

如果您打开精灵打包器窗口(菜单: 窗口 > 2D > 精灵打包器),然后点击左上角的 打包 按钮,您将看到图集中打包的纹理的布局。

如果您在项目面板中选择一个精灵,它也会被高亮以显示其在图集中的位置。轮廓实际上是渲染的 网格Unity的主要图形原语。网格构成了您3D世界的很大一部分。Unity支持三角形或四边形多边形网格。Nurbs、Nurms、Subdiv曲面必须转换为多边形。更多信息
术语表中查看
outline,同时也定义了用于紧密排列的区域。

Sprite Packer窗口顶部的 工具栏Unity编辑器顶部的一排按钮和基本控件,允许您以各种方式与编辑器交互(例如缩放、平移)。更多信息
术语表中查看
中有一些影响排列和查看的控制。**排列**按钮启动排列操作,但如果自最后排列以来图集没有变化,则不会强制更新。 (重新排列按钮将在您实现以下说明的自定义排列策略时出现:自定义Sprite Packer)。**查看图集**和**页面编号**菜单允许您选择窗口中显示的是哪个图集的哪个页面(如果最大的纹理大小不足以放置所有精灵,则单个图集可以拆分为多个“页面”)。页面编号旁边的菜单选择用于图集的“排列策略”(见下文)。工具栏右侧有两个控件,用于缩放视图以及切换图集的颜色和alpha显示。

排列策略

Sprite Packer使用一个**排列策略**来确定如何将精灵分配到图集中。您可以创建自己的排列策略(见下文),但**默认排列策略**、**紧密排列策略**和**启用旋转的紧密排列策略**选项始终可用。在这些策略下,Texture Importer中的**排列标记**属性直接选择精灵将打包到的图集的名称,因此具有相同排列标记的所有精灵都将打包到同一个图集中。然后,图集将根据纹理导入设置进一步排序,以匹配用户为原始纹理设置的任何内容。具有相同纹理**压缩**设置(在Texture CompressionAnimation CompressionAudio CompressionBuild Compression中描述)的精灵将尽可能地分组到同一个图集中。

  • 默认排列策略将使用矩形排列,除非**排列标记**中指定了[TIGHT](即,将您的排列标记设置为[TIGHT]Character将允许紧密排列)。
  • 紧密排列策略将默认使用紧密排列,如果精灵具有紧密网格。如果**排列标记**中指定了[RECT],则将执行矩形排列(即,将您的排列标记设置为[RECT]UI_Elements将强制矩形排列)。
  • 启用旋转的紧密排列策略将默认使用紧密排列,如果精灵具有紧密网格,并将启用旋转精灵。如果**排列标记**中指定了[RECT],则将执行矩形排列(即,将您的排列标记设置为[RECT]UI_Elements将强制矩形排列)。

自定义Sprite Packer

对于大多数目的,**默认排列策略**选项就已足够,但如果您需要,您还可以实现自己的自定义排列策略。为此,您需要在编辑器脚本中的一个类中实现UnityEditor.Sprites.IPackerPolicy接口。此接口需要以下方法

  • GetVersion - 返回您的打包策略的版本值。如果策略脚本进行了修改且策略被保存到 版本控制一个用于管理文件变化的系统。您可以结合使用Unity和一些常见版本控制工具,包括Perforce、Git、Mercurial和PlasticSCM。 更多信息
    请查阅术语表
  • OnGroupAtlases - 在此处实现您的打包逻辑。在PackerJob上定义图集,并分配从给定的TextureImporters中来的Sprite。

默认打包策略默认使用 rect 打包(参见 SpritePackingMode)。如果您在进行纹理空间效果或想要使用不同的网格来渲染Sprite,这将很有用。自定义策略可以覆盖这一点,并改为使用紧密打包。

  • 仅在选择了自定义策略时,重新打包按钮将被启用。
    • 除非TextureImporter元数据或选定的PackerPolicy版本值发生变化,否则不会调用OnGroupAtlases。
    • 当您正在处理自定义策略时,请使用重新打包按钮。
  • Sprite可以自动使用TightRotateEnabledSpritePackerPolicy旋转打包。
    • SpritePackingRotation是一个为未来Unity版本保留的类型。

其他

  • 图集缓存在“项目\库\AtlasCache”中。
    • 删除此文件夹然后启动Unity将强制重新打包图集。此时必须关闭Unity。
  • 启动时不会加载图集缓存。
    • 在Unity重启后,必须检查所有纹理以进行首次打包。此操作可能需要一些时间,具体时间取决于项目中纹理的总数。
    • 只加载所需的图集。
  • 默认最大图集大小为2048x2048。
  • 当设置PackingTag时,纹理将不会进行压缩,这样SpritePacker就可以捕捉原始的 像素计算机图像中最小的单位。像素的大小取决于您的屏幕分辨率。屏幕像素上的像素照明被计算。 更多信息
    请查阅术语表
    的值,然后在图集上进行压缩。

默认打包策略

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; // Good for base and two mip levels.

        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;
                    // Use Compression Quality for Grouping later only for Compressed Formats. Otherwise leave it Empty.
                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);
            }

            // First split Sprites into groups based on atlas name
            var atlasGroups =
                from e in entries
                group e by e.atlasName;
            foreach (var atlasGroup in atlasGroups)
            {
                int page = 0;
                // Then split those groups into smaller groups based on texture settings
                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;
                    // Use the highest aniso level from all entries in this atlas
                    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;
        }
}

严格打包策略

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 TightPackerPolicySample : DefaultPackerPolicySample
{
        protected override string TagPrefix { get { return "[RECT]"; } }
        protected override bool AllowTightWhenTagged { get { return false; } }
        protected override bool AllowRotationFlipping { get { return false; } }
}

锁定旋转启用SpritePacker策略

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; } }
}

附加资源

解决不同的精灵图集场景问题
准备精灵打包器遗留打包资源以在弃用后工作