在设计 精灵二维图形对象。如果你习惯于在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 Compression、Animation Compression、Audio Compression、Build Compression中描述)的精灵将尽可能地分组到同一个图集中。
对于大多数目的,**默认排列策略**选项就已足够,但如果您需要,您还可以实现自己的自定义排列策略。为此,您需要在编辑器脚本中的一个类中实现UnityEditor.Sprites.IPackerPolicy接口。此接口需要以下方法
默认打包策略默认使用 rect 打包(参见 SpritePackingMode)。如果您在进行纹理空间效果或想要使用不同的网格来渲染Sprite,这将很有用。自定义策略可以覆盖这一点,并改为使用紧密打包。
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; } }
}
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; } }
}