スプライトのグラフィックスをデザインする場合、キャラクター毎にテクスチャファイルを分けた方が作業をしやすくなります。ただし、スプライトテクスチャには、グラフィック要素の間に空白部分が多く、ランタイムに無駄なビデオメモリを消費する結果になります。パフォーマンスを最適化するためには、いくつかのスプライトテクスチャからのグラフィックスをアトラスと呼ばれる 1 つのテクスチャに隙間をあまり空けずひとまとめにするのが最善です。Unity は、個々のスプライトテクスチャからアトラスを自動的に生成するユーティリティ、スプライトパッカー を提供します。
Unity がスプライトからテクスチャアトラスの生成と処理を行うため、ユーザーは手動で処理を行う必要はありません。アトラスは必要に応じて再生モードへの移行やビルド時にパック可能であり、スプライトオブジェクトのグラフィックスがアトラスから生成され、取得できます。
ユーザーはテクスチャインポーターで Packing Tag を指定して、そのテクスチャのスプライトのパックを有効にする必要があります。
スプライトパッカーはデフォルトで無効になっていますが、エディター 設定 (Edit -> Project Settings -> Editor) で制御できます。スプライトのパックモードは Disabled から、Enabled for Builds (パックはビルドにのみ使い、再生モードには使えません)、または、Always Enabled (再生モードとビルド両方にパックが使えます) に変更できます。
Sprite Packer ウィンドウ (Window > 2D > Sprite Packer) を開き、左上隅の Pack ボタンをクリックすると、アトラスにパックされたテクスチャが表示されます。
Project ウィンドウでスプライトを選択すると、アトラス内の位置を示すためにハイライトされます。輪郭は実際に描画されるメッシュの輪郭で、同時に Tight packing (タイトパック、隙間があまりないパック) に使用される領域を定義します。
Sprite Packer ウィンドウ上部にあるツールバーには、パックと表示のためのコントロールがいくつか有ります。Pack ボタンを押すとパック操作を開始しますが、アトラスが最後にパックされてから変更が無ければ、何も更新されません (下記の スプライトパッカーのカスタマイズ で説明していますが、カスタムパックポリシーを実装している場合は Repack ボタンが表示されます)。メニューの View Atlas と Page では、どのアトラスのどのページをウインドウに表示するか、選択することができます (最大テクスチャサイズにすべてのスプライトを格納するための十分なスペースがない場合は、1 つのアトラスが複数のページに分割される場合があります)。ページ番号の横にあるドロップダウンで、アトラスの “パックポリシー” を選択できます (後述)。ツールバーの右にある 2 つのコントロールでは、ビューをズームしたり、表示するアトラスのカラーとアルファの表示を切り替えることができます。
スプライトパッカーがアトラスにスプライトをどのように割り当てるかを決めるために、 パックポリシー を使用します。独自のパックポリシーを作成することも可能ですが (下記参照)、 Default Packer Policy、Tight Packer Policy、Tight Rotate Enabled Sprite Packer Policy オプションがすでに用意されているので、いつでも使う事ができます。これらのポリシーでは テクスチャインポーター の Packing Tag プロパティーからスプライトをパックするアトラスの名前を直接選択します。したがって、同じ Packing Tag を持つスプライトは全て、同じアトラスにパックされます。その後、アトラスはソーステクスチャの設定と一致するように、テクスチャのインポート設定によりソートされます。テクスチャ圧縮設定が同じスプライトは、可能であれば、同じアトラスにまとめられます。
たいていは DefaultPackerPolicy オプションで十分ですが、必要に応じて独自のカスタムパックポリシーを実装することができます。そのためには、エディタースクリプトのクラスに UnityEditor.Sprites.IPackerPolicy インターフェースを実装する必要があります。このインターフェースには次のメソッドが必要です。
DefaultPackerPolicy はデフォルトでは矩形パック (SpritePackingMode を参照) を使用します。これは、テクスチャ空間のエフェクトを行なったり、スプライトをレンダリングするために別のメッシュを使用したい場合に便利です。カスタムポリシーはこれをオーバーライドし、代わりに Tight Packing を使用できます。
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; // 基本と 2 ミップレベル向け
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;
// Compressed Format のためだけに、後に Grouping の Compression Quality を使用します。そうでない場合は、空のままにしておきます。
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 level を使用します
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;
// パックタグが [RECT] を含まないかぎり、TightPackerPolicy は矩形でないスプライトを隙間なくパックします。
class TightRotateEnabledSpritePackerPolicySample : DefaultPackerPolicySample
{
protected override string TagPrefix { get { return "[RECT]"; } }
protected override bool AllowTightWhenTagged { get { return false; } }
protected override bool AllowRotationFlipping { get { return true; } }
}