请注意 Sprite Packer 在 Unity 2020.1 和更新版本中已弃用,并且将不再作为精灵打包模式的可用选项。现有已使用 Sprite Packer 的项目仍然可以继续使用它,但是在 2020.1 以后创建的任何新项目在打包纹理时将默认使用 Sprite Atlas 系统。
在设计精灵图形时,可以方便地为每个角色使用单独的纹理文件。但是,精灵纹理的很大一部分通常会被图形元素之间的空白空间所占据,而此空间将导致运行时浪费视频内存。为了获得最佳性能,最好将几个精灵纹理中的图形紧密地打包在一个称为图集的纹理中。Unity 提供了一个 Sprite Packer 实用程序来自动执行从各个精灵纹理生成图集的过程。
Unity 在后台处理精灵图集纹理的生成和使用,这样用户就不需要进行手动分配。可选择在进入运行模式时或在构建期间打包图集,而图集一旦生成,便会从图集获取精灵对象的图形。
用户需要在纹理导入器 (Texture Importer) 中指定一个打包标签 (Packing Tag),从而为该纹理的精灵启用打包。
默认情况下已禁用 Sprite Packer,但可从 Editor 窗口(菜单:Edit > Project Settings,然后选择 Editor 类别)中对其进行配置。精灵打包模式可从 Disabled 更改为 Enabled for Builds(打包用于构建但不用于运行模式)或 Always Enabled(运行模式和构建都启用打包)。
如果打开 Sprite Packer 窗口(菜单:Window > 2D > Sprite Packer)并单击左上角的 Pack 按钮,则会显示图集内包含的纹理排列结果。
如果在 Project 面板中选择一个精灵,该精灵也会突出显示以显示其在图集内的位置。轮廓实际上是渲染网格轮廓,还定义了用于紧密打包的区域。
Sprite Packer 窗口顶部的工具栏有许多影响打包和查看的控件。Pack 按钮可启动打包操作,但如果图集自上次打包后没有更改,则不会强制进行任何更新。(按自定义 Sprite Packer 中所述实现自定义打包策略时,将出现一个相关的 Repack 按钮)。View Atlas 和 Page # 菜单允许选择在窗口中显示哪个图集的哪个页面(如果没有足够的空间来容纳最大纹理大小内的所有精灵,则单个图集可能拆分成多个“页面”)。页面编号旁边的菜单用于选择对图集使用的“打包策略”(见下文)。在工具栏的右侧有两个控件用于缩放视图以及在图集的颜色和 Alpha 显示之间切换。
Sprite Packer 使用打包策略来决定如何将精灵分配到图集。您可以创建自己的打包策略(见下文),但也可以随时使用 Default Packer Policy、Tight Packer Policy 或 Tight Rotate Enabled Sprite Packer Policy 选项。通过使用这些策略,纹理导入器中的 Packing Tag 属性可直接选择将精灵打包到的图集的名称,因此具有相同打包标签 (Packing Tag) 的所有精灵将打包到同一图集内。然后,根据纹理导入设置对图集进行进一步排序,使图集符合用户为源纹理进行的任何设置。具有相同纹理压缩设置的精灵将尽可能分组到相同的图集。
DefaultPackerPolicy 选项足以满足大多数用途,但如果需要,也可以实施自己的自定义打包策略。为此,需要实现编辑器脚本中的一个类的 UnityEditor.Sprites.IPackerPolicy 接口。此接口需要以下方法:
DefaultPackerPolicy 默认使用矩形打包(请参阅 SpritePackingMode)。如果正在创建纹理空间效果或想要使用不同的网格来渲染精灵,此设置将非常有用。自定义策略可覆盖此设置,并改用紧密打包。
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;
}
}
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; } }
}
```` 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; } } }
Did you find this page useful? Please give it a rating:
Thanks for rating this page!
What kind of problem would you like to report?
Thanks for letting us know! This page has been marked for review based on your feedback.
If you have time, you can provide more information to help us fix the problem faster.
Provide more information
You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see:
You've told us there are code samples on this page which don't work. If you know how to fix it, or have something better we could use instead, please let us know:
You've told us there is information missing from this page. Please tell us more about what's missing:
You've told us there is incorrect information on this page. If you know what we should change to make it correct, please tell us:
You've told us this page has unclear or confusing information. Please tell us more about what you found unclear or confusing, or let us know how we could make it clearer:
You've told us there is a spelling or grammar error on this page. Please tell us what's wrong:
You've told us this page has a problem. Please tell us more about what's wrong:
Thank you for helping to make the Unity documentation better!
Your feedback has been submitted as a ticket for our documentation team to review.
We are not able to reply to every ticket submitted.