Unity Sprite Packer

2018-10-19 20:26 更新

在設(shè)計(jì)sprite圖形時(shí),可以方便地為每個(gè)角色使用單獨(dú)的紋理文件。然而,sprite紋理的很大一部分通常將被圖形元素之間的空白空間占據(jù),這個(gè)空間會(huì)在運(yùn)行時(shí)浪費(fèi)視頻內(nèi)存。為了獲得最佳性能,最好將圖形從幾個(gè)sprite紋理緊密地組合在一個(gè)稱為圖集的單一紋理中。Unity提供了一個(gè)Sprite Packer實(shí)用工具,用于自動(dòng)化從單個(gè)sprite紋理生成圖層的過(guò)程。

Unity處理幕后紋理圖案紋理的生成和使用,使用戶無(wú)需手工分配。圖譜可以選擇在進(jìn)入播放模式或構(gòu)建過(guò)程中打包,并且sprite物體的圖形將在生成后從地圖集獲取。

用戶需要在紋理導(dǎo)入器中指定包裝標(biāo)簽,才能打包該紋理的Sprites。

使用Sprite Packer

默認(rèn)情況下,Sprite Packer已禁用,但可以從編輯器設(shè)置(菜單:編輯( Edit)- >項(xiàng)目設(shè)置(Project Settings) - >編輯(Editor))進(jìn)行配置。sprite包裝模式可以從禁用到啟用(即打包用于構(gòu)建但不是播放模式)或始終啟用(即打包打開播放模式和構(gòu)建)。

如果打開Sprite Packer窗口(菜單:Window - > Sprite Packer),然后單擊左上角的Pack按鈕,您將看到在圖集中包裝的紋理的排列。

SpritePackerMain

如果您在“項(xiàng)目(Project)”面板中選擇一個(gè)sprite,這也將突出顯示其在圖集中的位置。輪廓實(shí)際上是渲染網(wǎng)格輪廓,并且還定義了用于緊密包裝的區(qū)域。

Sprite Packer窗口頂部的工具欄有一些影響打包和查看的控件。該Pack按鈕啟動(dòng)壓縮操作,但不會(huì)強(qiáng)迫任何更新,如果寰并沒(méi)有改變,因?yàn)樗亲詈蟀b。(實(shí)現(xiàn)自定義打包封裝時(shí),會(huì)顯示相關(guān)的重新打包(Repack)按鈕,如下面的自定義Sprite包Customizing the Sprite Packer 中所述)。在查看圖集View AtlasPage #菜單允許您選擇窗口中顯示哪些地圖集的頁(yè)面(如果沒(méi)有足夠的空間用于最大紋理大小的所有sprite,單個(gè)地圖集可能會(huì)被分割成多個(gè)“頁(yè)面Page”)。頁(yè)碼旁邊的菜單選擇哪個(gè)Packing Policy用于地圖冊(cè)atlas(見下文)。工具欄右側(cè)有兩個(gè)縮放視圖的控件,并在圖集的顏色和alpha顯示之間進(jìn)行切換。

打包封裝 | Packing Policy

prite Packer使用打包封裝來(lái)決定如何將sprite分配到atlases中??梢詣?chuàng)建自己的packing policies(見下文),但默認(rèn)打包程序策略Default Packer Policy,緊密封裝程序策略(Tight Packer Policy)和緊密旋轉(zhuǎn)啟用的Sprite打包程序策略選項(xiàng)始終可用。通過(guò)這些策略,紋理導(dǎo)入器中的打包標(biāo)簽屬性直接選擇精靈將要包裝的地圖集的名稱,所有相同包裝標(biāo)簽的所有精靈將被包裝在同一個(gè)圖集中。然后通過(guò)紋理導(dǎo)入設(shè)置對(duì)Atlases進(jìn)行進(jìn)一步排序,使其與用戶為源紋理設(shè)置的任何一致。具有相同紋理壓縮設(shè)置的Sprites將盡可能地分組到相同的圖集中。

  • DefaultPackerPolicy將默認(rèn)使用矩形包裝,除非在“ 包裝標(biāo)簽(Packing Tag) ”中指定“[TIGHT]”(即將包裝標(biāo)簽設(shè)置為“[TIGHT] Character”將允許緊密包裝)。
  • 如果Sprite有緊密的網(wǎng)格,默認(rèn)情況下,TightPackerPolicy將使用緊密包裝。如果在“ Packing Tag ”中指定了“[RECT]” ,則矩形包裝將被完成(即將包裝標(biāo)簽Packing Tag設(shè)置為“[RECT] UI_Elements”將強(qiáng)制直接包裝)。
  • TightRotateEnabledSpritePackerPolicy將默認(rèn)使用緊密打包,如果Sprite具有緊密的網(wǎng)格,并且將允許精靈旋轉(zhuǎn)。如果在“ 包裝標(biāo)簽 ”中指定了“[RECT]” ,則矩形包裝將被完成(即將包裝標(biāo)簽設(shè)置為“[RECT] UI_Elements”將強(qiáng)制直接包裝)。

定制Sprite Packer

DefaultPackerPolicy選項(xiàng)足以滿足大多數(shù)目的,但你也可以實(shí)現(xiàn)自己的定制包裝的政策,如果你需要。為此,您需要在編輯器腳本中為類實(shí)現(xiàn)UnityEditor.Sprites.IPackerPolicy接口。此界面需要以下方法:

  • GetVersion - 返回打包程序策略的版本值。如果對(duì)策略腳本進(jìn)行了修改,則該版本應(yīng)該被觸發(fā),并且該策略被保存到版本控制中。
  • OnGroupAtlases - 在這里實(shí)現(xiàn)你的打包邏輯。定義PackerJob上的地址集,并從給定的TextureImporters中分配Sprites。

DefaultPackerPolicy默認(rèn)使用rect打包(請(qǐng)參閱SpritePackingMode)。如果您正在進(jìn)行紋理空間效果或者想使用不同的網(wǎng)格渲染Sprite,這將非常有用。自定義策略可以覆蓋這一點(diǎn),而是使用緊密包裝。

  • 重新啟動(dòng)按鈕僅在選擇自定義策略時(shí)啟用。
    • OnGroupAtlases不會(huì)被調(diào)用,除非TextureImporter元數(shù)據(jù)或所選的PackerPolicy版本值發(fā)生變化。
    • 在使用自定義策略時(shí)使用“重新包裝”按鈕。
  • Sprite可以自動(dòng)旋轉(zhuǎn)TightRotateEnabledSpritePackerPolicy。
    • SpritePackingRotation是未來(lái)Unity版本的保留類型。

其他

  • 地圖集緩存在Project \ Library \ AtlasCache中。
    • 刪除此文件夾,然后啟動(dòng)Unity將強(qiáng)制重新打包地圖集。這樣做必須關(guān)閉Unity。
  • Atlas緩存在啟動(dòng)時(shí)未加載。
    • 在Unity重新啟動(dòng)后,首次打包時(shí),必須檢查所有紋理。此操作可能需要一些時(shí)間, 具體取決于項(xiàng)目中紋理的總數(shù)。
    • 只載入所需的地圖集。
  • 默認(rèn)最大圖集大小為2048x2048。
  • 當(dāng)設(shè)置了PackingTag時(shí),Texture將不會(huì)被壓縮,以便SpritePacker可以獲取原始像素值,然后對(duì)圖集進(jìn)行壓縮。

DefaultPackerPolicy

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

TightPackerPolicy

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

TightRotateEnabledSpritePackerPolicy

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; } }
}
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)