Unity Sprite Packer

2018-10-19 20:26 更新

在设计sprite图形时,可以方便地为每个角色使用单独的纹理文件。然而,sprite纹理的很大一部分通常将被图形元素之间的空白空间占据,这个空间会在运行时浪费视频内存。为了获得最佳性能,最好将图形从几个sprite纹理紧密地组合在一个称为图集的单一纹理中。Unity提供了一个Sprite Packer实用工具,用于自动化从单个sprite纹理生成图层的过程。

Unity处理幕后纹理图案纹理的生成和使用,使用户无需手工分配。图谱可以选择在进入播放模式或构建过程中打包,并且sprite物体的图形将在生成后从地图集获取。

用户需要在纹理导入器中指定包装标签,才能打包该纹理的Sprites。

使用Sprite Packer

默认情况下,Sprite Packer已禁用,但可以从编辑器设置(菜单:编辑( Edit)- >项目设置(Project Settings) - >编辑(Editor))进行配置。sprite包装模式可以从禁用到启用(即打包用于构建但不是播放模式)或始终启用(即打包打开播放模式和构建)。

如果打开Sprite Packer窗口(菜单:Window - > Sprite Packer),然后单击左上角的Pack按钮,您将看到在图集中包装的纹理的排列。

SpritePackerMain

如果您在“项目(Project)”面板中选择一个sprite,这也将突出显示其在图集中的位置。轮廓实际上是渲染网格轮廓,并且还定义了用于紧密包装的区域。

Sprite Packer窗口顶部的工具栏有一些影响打包和查看的控件。该Pack按钮启动压缩操作,但不会强迫任何更新,如果寰并没有改变,因为它是最后包装。(实现自定义打包封装时,会显示相关的重新打包(Repack)按钮,如下面的自定义Sprite包Customizing the Sprite Packer 中所述)。在查看图集View AtlasPage #菜单允许您选择窗口中显示哪些地图集的页面(如果没有足够的空间用于最大纹理大小的所有sprite,单个地图集可能会被分割成多个“页面Page”)。页码旁边的菜单选择哪个Packing Policy用于地图册atlas(见下文)。工具栏右侧有两个缩放视图的控件,并在图集的颜色和alpha显示之间进行切换。

打包封装 | Packing Policy

prite Packer使用打包封装来决定如何将sprite分配到atlases中。可以创建自己的packing policies(见下文),但默认打包程序策略Default Packer Policy,紧密封装程序策略(Tight Packer Policy)和紧密旋转启用的Sprite打包程序策略选项始终可用。通过这些策略,纹理导入器中的打包标签属性直接选择精灵将要包装的地图集的名称,所有相同包装标签的所有精灵将被包装在同一个图集中。然后通过纹理导入设置对Atlases进行进一步排序,使其与用户为源纹理设置的任何一致。具有相同纹理压缩设置的Sprites将尽可能地分组到相同的图集中。

  • DefaultPackerPolicy将默认使用矩形包装,除非在“ 包装标签(Packing Tag) ”中指定“[TIGHT]”(即将包装标签设置为“[TIGHT] Character”将允许紧密包装)。
  • 如果Sprite有紧密的网格,默认情况下,TightPackerPolicy将使用紧密包装。如果在“ Packing Tag ”中指定了“[RECT]” ,则矩形包装将被完成(即将包装标签Packing Tag设置为“[RECT] UI_Elements”将强制直接包装)。
  • TightRotateEnabledSpritePackerPolicy将默认使用紧密打包,如果Sprite具有紧密的网格,并且将允许精灵旋转。如果在“ 包装标签 ”中指定了“[RECT]” ,则矩形包装将被完成(即将包装标签设置为“[RECT] UI_Elements”将强制直接包装)。

定制Sprite Packer

DefaultPackerPolicy选项足以满足大多数目的,但你也可以实现自己的定制包装的政策,如果你需要。为此,您需要在编辑器脚本中为类实现UnityEditor.Sprites.IPackerPolicy接口。此界面需要以下方法:

  • GetVersion - 返回打包程序策略的版本值。如果对策略脚本进行了修改,则该版本应该被触发,并且该策略被保存到版本控制中。
  • OnGroupAtlases - 在这里实现你的打包逻辑。定义PackerJob上的地址集,并从给定的TextureImporters中分配Sprites。

DefaultPackerPolicy默认使用rect打包(请参阅SpritePackingMode)。如果您正在进行纹理空间效果或者想使用不同的网格渲染Sprite,这将非常有用。自定义策略可以覆盖这一点,而是使用紧密包装。

  • 重新启动按钮仅在选择自定义策略时启用。
    • OnGroupAtlases不会被调用,除非TextureImporter元数据或所选的PackerPolicy版本值发生变化。
    • 在使用自定义策略时使用“重新包装”按钮。
  • Sprite可以自动旋转TightRotateEnabledSpritePackerPolicy。
    • SpritePackingRotation是未来Unity版本的保留类型。

其他

  • 地图集缓存在Project \ Library \ AtlasCache中。
    • 删除此文件夹,然后启动Unity将强制重新打包地图集。这样做必须关闭Unity。
  • Atlas缓存在启动时未加载。
    • 在Unity重新启动后,首次打包时,必须检查所有纹理。此操作可能需要一些时间, 具体取决于项目中纹理的总数。
    • 只载入所需的地图集。
  • 默认最大图集大小为2048x2048。
  • 当设置了PackingTag时,Texture将不会被压缩,以便SpritePacker可以获取原始像素值,然后对图集进行压缩。

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; } }
}
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号