Unity 2D 终结手册 (4) - 精灵打包_Unity3d开发
当美术设计师在制作精灵图像时,会给每个精灵创建一个单独的文件,因为这样方便制作。但是,每个精灵纹理周围都会留有大量空白区域,这些区域不会记录任何精灵数据信息,但依然会消耗各种资源。为了达到最优性能,我们会把多个精灵放在一张图片上,并且紧紧地排列在一起,这张大图我们就叫做图集(Atlas)。Unity 中就提供了一个自动把多个精灵纹理放在一张大图上生成图集(Atlas)的工具――精灵打包器(Sprite Packer)。
Unity 会在后台处理精灵图集(Sprite Atlas)纹理的生成和使用,这样,用户就不需要手动操作了。图集(Atlas)会在进入Play模式或Build时有选择得将精灵打包,精灵对象在创建时会从图集(Atlas)中获取到图像。我们需要提前在纹理的导入设置中指定好打包标签(Packing Tag),这样才能进行打包。
精灵打包器(Sprite Packer)在默认情况下有可能是禁用的,但是可以在编辑器设置中打开(Edit -> Project Settings -> Editor)。其中精灵打包的模式(Mode)可以选择禁用(Disabled);也可以选择只在 Build 时启用(Enabled For Builds),此时进入Play模式不会启用,只在打包发布时启用;或者总是启用(Always Enabled),此时在Play模式下也会启用。
我们可以在菜单中打开精灵打包器(Sprite Packer)视图(Window -> Sprite Packer)。在视图左上角有个Pack按钮,点击之后就开始打包,但别忘了提前给精灵的导入设置中设置好打包标签(Packing Tag)。打包之后我们就能够看到多个精灵被打包在一张图集(Atlas)中,紧凑地排列在一起。
(图 1)精灵打包器窗口
如果我们在项目视图(Project)中选中了一个精灵,也会在精灵打包器(Sprite Packer)视图中高亮显示出边框,标明它在图集(Atlas)中的位置。高亮显示的边框就是渲染网格的边界,同时用来表示精灵打包后的区域。
在精灵打包器(Sprite Packer)视图的上方的工具条中,可以控制精灵的打包和显示。打包(Pack)按钮用来开始打包,但如果图集(Atlas)没有任何修改,已经是之前打包过了的,就不会有任何反应。重新打包(Repack)按钮只有当我们使用了非默认打包策略时才会可用。显示图集(View Atlas)和后面的页码(Page)菜单让我们选择哪个图集中的那一页显示在精灵打包器(Sprite Packer)视图中。如果某个图集中的精灵太多,超过了最大纹理尺寸,图集可能会被切割成多个页(Page)。页码(Page)菜单后面是打包策略(Packing Policy)菜单,这个后面再说。最右侧的两个滑杆分别用来控制视图的缩放和像素分辨率。在这两个滑杆左侧有一个像彩色条纹的按钮,用来切换显示模式,可以显示彩色或者显示透明度。
精灵打包器(Sprite Packer)通过打包策略(Packing Policy)来决定精灵如何打包进图集(Atlas)中。在精灵打包器(Sprite Packer)视图中提供给我们三种打包策略(Packing Policy):默认打包策略(Default Packer Policy)、紧密打包策略(Tight Packer Policy)和紧密可旋转打包策略(Tight Rotate Enabled Packer Policy)。除此以外,我们还可以自定义打包策略。使用这些策略时,我们在精灵的导入设置中设置的打包标签(Packing Tag)会决定精灵最终被打包进哪个图集(Atlas)中,具有相同打包标签(Packing Tag)的精灵会存放在同一张图集(Atlas)中,并且打包标签(Packing Tag)也会是图集(Atlas)的名称。图集(Atlas)会进一步根据纹理的导入设置进行分页,这样无论用户有如何设置源图像,最终都能够索引的到。具有相同纹理压缩设置的精灵会更可能分到同一个图集中。
1、默认打包策略(Default Packer Policy)默认会使用矩形对精灵进行打包,除非在精灵的打包标签(Packing Tag)中带有[TIGHT](比如,打包标签(Packing Tag)设置为了[TIGHT]Character,此时会进行紧密打包)。
2、紧密打包策略(Tight Packer Policy)默认会使用紧密打包,如果精灵的打包标签(Packing Tag)中带有[RECT](比如,打包标签(Packing Tag)设置为了[RECT]Character)此时会使用矩形进行打包。
3、紧密可旋转打包策略(Tight Rotate Enabled Packer Policy)默认会使用紧密打包,并且允许精灵进行旋转,如果精灵的打包标签(Packing Tag)中带有[RECT](比如,打包标签(Packing Tag)设置为了[RECT]Character)此时会使用矩形进行打包。
一般情况下使用默认打包策略(Default Packer Policy)即可完成我们的大部分需求,但如果需要的话也可以使用自定义的打包策略。想要实现自定义打包,我们需要在一个编辑器脚本类中实现UnityEditor.Sprites.IPackerPolicy接口。这个接口需要以下方法:
1、GetVersion(),返回你的打包策略的版本号。如果策略脚本已被修改,应该更新版本号,并且这一策略会被保存到版本控制。
2、OnGroupAtlases(),需要在这个方法中实现打包的逻辑。需要在打包过程中定义图集,并且从指定的纹理中指定精灵。
默认打包策略(Default Packer Policy)会使用矩形包围盒进行打包,如果我们要使用不同的网格渲染精灵,可以使用自定义的策略,它会使用紧密打包。
重新打包(Repack)按钮只有当我们选择了非默认打包策略时才会可用。
OnGroupAtlases()方法只有在纹理元数据发生变化或者打包策略版本发生变化时才会被调用。
当我们使用的是自定义的打包策略时也可以使用重新打包(Repack)按钮。
如果选择了紧密可旋转打包策略(Tight Rotate Enabled Packer Policy),在打包时精灵可以自动旋转以节省更多空间。
精灵打包旋转(Sprite Packing Rotation)是一个保留类型,为未来 Unity 的功能所准备的。
打包完的图集会缓存在Project\Library\AtlasCache路径中。
删除这个文件夹然后再回到 Unity 中就会自动触发重新打包。如果我们不想重新打包,就关闭 Unity 再删。
图集缓存在开始时是不会被加载的。
在 Unity 重新启动后首次打包时,会检查所有纹理。
只有需要的图集会被加载。
默认的最大图集大小是2048×2048。
当我们设置了打包标签后,纹理不会先进行压缩,这样打包器能够精确获取每像素的值。当打包到图集后才会进行压缩。
using System;using System.Linq;using UnityEngine;using UnityEditor;using System.Collections.Generic;
public class DefaultPackerPolicySample : UnityEditor.Sprites.IPackerPolicy {
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 virtual int GetVersion () {
return 1;
}
protected class Entry {
public Sprite sprite;
public UnityEditor.Sprites.AtlasSettings settings;
public string atlasName;
public SpritePackingMode packingMode;
public int anisoLevel;
}
protected virtual string TagPrefix {
get { return "[TIGHT]"; }
}
protected virtual bool AllowTightWhenTagged {
get { return true; }
}
protected virtual bool AllowRotationFlipping {
get { return false; }
}
private const uint kDefaultPaddingPower = 3;
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
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;
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;
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;
class TightRotateEnabledSpritePackerPolicySample : DefaultPackerPolicySample {
protected override string TagPrefix {
get { return "[RECT]"; }
}
protected override bool AllowTightWhenTagged {
get { return false; }
}
protected override bool AllowRotationFlipping {
get { return true; }
}
}