Version: 2017.1
Sprite Editor: Edit Outline
Sorting Group

Sprite Packer

Cuando diseñe gráficas de Sprite, es conveniente trabajar con un archivo de textura separado por cada personaje. Sin embargo, una parte significativa de una textura de sprite a menudo será ocupado por el espacio vacío entre los elementos gráficos y este espacio va a resultar en memoria de video gastada en el tiempo de ejecución. Para un rendimiento optimo, lo mejor es empacar gráficas de varias texturas de sprite muy juntas dentro de una misma textura conocida como un atlas. Unity proporcionar una utilidad Sprite Packer para automatizar el proceso de generar atlases del las texturas de sprite individuales.

Unity maneja la generación y uso de texturas sprite atlas detrás de escenas para que el usuario no tenga que hacer ninguna asignatura manual. El atlas puede ser opcionalmente empacado al entrar en el modo de reproducción durante una construcción y las gráficas para un objeto sprite serán obtenidas del atlas una vez haya sido generadas.

Los usuarios son requeridos especificar una etiqueta de empaque (Packing Tag) en el importador de texturas(Texture Importer) para habilitar el empacamiento de Sprites de esa textura.

Usando el Sprite Packer

El Sprite Packer es deshabilitado por defecto pero usted lo puede configurar desde los Ajustes del Editor (menú: Edit -> Project Settings -> Editor). El modo Sprite Packing puede ser cambiado de Disabled a Enabled for Builds (i.e. el empaque es utilizado para construcciones pero no para el modo de reproducción) o Always Enabled (i.e. el empaque es habilitado para ambos modo de reproducción y construcciones).

Si usted abre la ventana del Sprite Packer (menú: Window -> Sprite Packer) y hace click en el botón Pack en la esquina superior izquierda, usted vera los arreglos de las texturas empacadas dentro del atlas.

Si usted selecciona un sprite en el panel de proyecto, esto también será subrayado para mostrar su posición en el atlas. El esquema es en realidad el esquema del mesh renderizado y también define el área utilizado para tight packing.

La barra de herramientas en la parte superior de la ventana del Sprite Packer tiene una serie de controles que afectan el empacamiento y la visualización. Los botones Pack inician la operación de empacar pero no obligará ninguna actualización si el atlas no ha cambiado desde la última vez que fue empacada. (Un botón relacionado Repack aparecerá cuando usted implemente una política personalizada de empacamiento como es explicado en Customizing the Sprite Packer a continuación). Los menús View Atlas y Page # le permiten a usted escoger qué página de que atlas es mostrada en la ventana (un solo atlas puede ser dividido a más de una “página” si hay el espacio suficiente para todos los sprites en el tamaño máximo de textura). El menú junto al número de página selecciona qué “parking policy” es utilizada para el atlas (ver abajo). A la derecha de la barra de herramientas están dos controles para enfocar la vista y cambiar entre una visualización en color y alpha para el atlas.

La Política de Empacamiento (Packing Policy)

El Sprite Packer utiliza una packing policy para decidir cómo asignar sprites a atlases. Es posible crear sus propias políticas de empaquetamiento (packing) (ver abajo) pero las opciones Default Packer Policy, Tight Packer Policy y Tight Rotate Enabled Sprite Packer Policy siempre están disponibles. Con estas políticas, la propiedad Packing Tag en el Texture Importer directamente selecciona el nombre del atlas dónde el sprite será empaquetado por lo que todos los sprites con la misma tag (etiqueta) de packing (empaquetamiento) serán agrupados en el mismo atlas. Los atlases luego son ordenados por los texture import settings para que coincidan con lo que sea que el usuario configura como las texturas fuentes. Los sprites con los mismos ajustes de compresión de textura serán agrupados al mismo atlas dónde sea posible.

  • DefaultPackerPolicy va a utilizar un empacamiento rectangular por defecto al menos que “[TIGHT]” (apretado) sea especificado en el Packing Tag (i.e. ajustando su etiqueta de empacamiento (packing tag) a “[TIGHT]Character” le permitirá agrupar de manera apretada).
  • TightPackerPolicy va a utilizar tight parking por defecto si el Sprite tiene meshes tight (apretados). Si “[RECT]” es especificado en el Packing Tag, un empacamiento rectangular será hecho (i.e. ajustando su etiqueta de empacamiento a “[RECT]UI_Elements” va a obligar a rect packing).
  • TightRotateEnabledSpritePackerPolicy utilizará tight packing (empaquetamiento apretado) por defecto si el sprite tiene meshes apretados y permitirá la rotación de los sprites. Si “[RECT]” se específica en el Packing Tag, un empaquetamiento rectangular será hecho (i.e configurar su packing tag a “[RECT]UI_Elements” va a obligar a un rect packing).

Personalizando el Sprite Packer

La opción DefaultPackerPolicy es suficiente para la mayoría de propósito pero usted también puede implementar su propia política personalizada de empacamiento si usted necesita. Para hacer esto, usted necesita implementar la interfaz UnityEditor.Sprites.IPackerPolicy para una clase en el script del editor. Esta interfaz requiere los siguientes métodos:

  • GetVersion - devuelve el valor de la versión de su política de empacamiento. La versión será aumentada si las modificaciones son hechas al script de políticas y esta política es guardada al version control.
  • OnGroupAtlases - implemente su lógica de empacamiento aquí. Define atlases en el PackerJob y asigna Sprites de los TextureImporters.

La DefaultPackerPolicy utiliza rect parking (mire SpritePackingMode) por defecto. Este es útil si usted está haciendo efectos texture-space o le gustaría utilizar un diferente mesh para renderizar el sprite. Unas políticas personalizadas pueden anular esto en vez de utilizar tight packing.

  • El botón Repack solamente está habilitado cuándo una política personalizada es seleccionada.
    • OnGroupAtlases no será llamado al menos de que la metadata TextureImporter o los valores de la versión del PackerPolicy seleccionado hayan cambiado.
    • Utilice el botón Repack cuando trabaje en su política personalizada.
  • Los sprites pueden ser empacados girados con TightRotateEnabledSpritePackerPolicy de manera automática.
    • SpritePackingRotation es un tipo reservado para futuras versiones de Unity.

Otros

  • Atlases están en caché en Project\Library\AtlasCache.
    • Borrar esta carpeta y luego ejecutar Unity obligará los atlases a ser re-empacados. Unity debe estar cerrado para que se haga esto.
  • El cache del Atlas no es cargado al comienzo.
    • Todas las texturas deben ser revisadas cuando se empaquen la primera vez después de que Unity se re-inicie. Esta operación puede tomar algún tiempo dependiendo en el número total de texturas en el proyecto.
    • Solo se cargan los atlases requeridos.
  • El tamaño predeterminado del atlas es 2048x2048.
  • Cuando PackingTag es activado, la textura no será comprimida para que el SpritePacker puede coger los valores original de píxel y luego comprimir en el atlas.

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 empacará firmemente Sprites no rectangulares a menos que su etiqueta de embalaje contenga "[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 empacará firmemente Sprites no rectangulares a menos que su etiqueta de packing contenga "[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; } }
}

Sprite Editor: Edit Outline
Sorting Group