Version: 2022.1
언어: 한국어
다양한 스프라이트 아틀라스 시나리오 확인
스프라이트 패커 모드

스프라이트 패커

스프라이트 패커 지원 중단

Please note that Sprite Packer is deprecated for Unity 2020.1 and newer, and will no longer be available as an option from Sprite Packer Modes. Existing Projects already using Sprite Packer will still be able to continue using it, however any new Project created in 2020.1 onwards will default to the Sprite Atlas system when packing Textures.

개요

When designing Sprite graphics, it is convenient to work with a separate Texture file for each character. However, a significant portion of a Sprite Texture will often be taken up by the empty space between the graphic elements and this space will result in wasted video memory at runtime. For optimal performance, it is best to pack graphics from several Sprite textures tightly together within a single Texture known as an atlas. Unity provides a Sprite Packer utility to automate the process of generating atlases from the individual Sprite textures.

Unity가 내부적으로 스프라이트 아틀라스 텍스처의 생성 및 사용을 처리하기 때문에 사용자가 수동으로 할당할 필요가 없습니다. 플레이 모드에 들어갈 때 또는 빌드 중에 아틀라스를 패킹하는 옵션이 있으며 스프라이트 오브젝트용 그래픽스는 일단 아틀라스가 생성되면 아틀라스로부터 얻어오게 됩니다.

텍스처의 스프라이트를 패킹하도록 하려면 텍스처 임포터의 패킹 태그를 지정해야 합니다.

스프라이트 패커 사용

The Sprite Packer is disabled by default but you can configure it from the Editor window (menu: Edit > Project Settings, then select the Editor category). The Sprite packing mode can be changed from Disabled to Enabled for Builds (packing is used for builds but not Play mode) or Always Enabled (packing is enabled for both Play mode and builds).

If you open the Sprite Packer window (menu: Window > 2D > Sprite Packer) and click the Pack button in the top-left corner, you will see the arrangement of the textures packed within the atlas.

프로젝트 패널에서 스프라이트를 선택하면 아틀라스에서도 해당 포지션이 표시됩니다. 아웃라인은 실제로는 렌더 메시 아웃라인이며 타이트 패킹에 사용되는 영역 또한 정의합니다.

The toolbar at the top of the Sprite Packer window has a number of controls that affect packing and viewing. The Pack buttons initiates the packing operation but will not force any update if the atlas hasn’t changed since it was last packed. (A related Repack button will appear when you implement a custom packing policy as explained in Customizing the Sprite Packer below). The View Atlas and Page # menus allow you to choose which page of which atlas is shown in the window (a single atlas may be split into more than one “page” if there is not enough space for all Sprites in the maximum Texture size). The menu next to the page number selects which “packing policy” is used for the atlas (see below). At the right of the toolbar are two controls to zoom the view and to switch between color and alpha display for the atlas.

패킹 정책

The Sprite Packer uses a packing policy to decide how to assign Sprites into atlases. It is possible to create your own packing policies (see below) but the Default Packer Policy, Tight Packer Policy and Tight Rotate Enabled Sprite Packer Policy options are always available. With these policies, the Packing Tag property in the Texture Importer directly selects the name of the atlas where the Sprite will be packed and so all Sprites with the same packing tag will be packed in the same atlas. Atlases are then further sorted by the Texture import settings so that they match whatever the user sets for the source textures. Sprites with the same Texture compression settings will be grouped into the same atlas where possible.

  • DefaultPackerPolicy will use rectangle packing by default unless “[TIGHT]” is specified in the Packing Tag (i.e. setting your packing tag to “[TIGHT]Character” will allow tight packing).
  • TightPackerPolicy will use tight packing by default if Sprite have tight meshes. If “[RECT]” is specified in the Packing Tag, rectangle packing will be done (i.e. setting your packing tag to “[RECT]UI_Elements” will force rect packing).
  • TightRotateEnabledSpritePackerPolicy will use tight packing by default if Sprite have tight meshes and will enable rotation of Sprites. If “[RECT]” is specified in the Packing Tag, rectangle packing will be done (i.e. setting your packing tag to “[RECT]UI_Elements” will force rect packing).

스프라이트 패커 커스터마이징

The DefaultPackerPolicy option is sufficient for most purposes but you can also implement your own custom packing policy if you need to. To do this, you need to implement the UnityEditor.Sprites.IPackerPolicy interface for a class in an editor script. This interface requires the following methods:

  • GetVersion - 패커 정책의 버전 값을 반환합니다. 정책 스크립트에 수정이 가해질 경우 버전이 업데이트되어야 하며 이 정책은 버전 관리에 저장됩니다.
  • OnGroupAtlases - 패킹 로직을 여기에 구현합니다. 패커 작업에 아틀라스를 정의하고 주어진 TextureImporters에서 스프라이트를 할당해야 합니다.

DefaultPackerPolicy는 디폴트로 사각 패킹을 사용합니다(SpritePackingMode 참조). 텍스처 공간 효과를 사용하거나 스프라이트 렌더링에 다른 메시를 사용하고자 할 때 유용합니다. 커스텀 정책에서는 이를 오버라이드하여 타이트 패킹을 대신 사용할 수 있습니다.

  • 리팩 버튼은 커스텀 정책이 선택된 경우에만 활성화됩니다.
    • OnGroupAtlases는 TextureImporter 메타데이터 또는 선택된 PackerPolicy 버전 값이 변하지 않는 이상 호출되지 않습니다.
    • 커스텀 정책에서 작업 시에는 리팩 버튼을 사용해야 합니다.
  • 스프라이트는 TightRotateEnabledSpritePackerPolicy 옵션의 경우 자동으로 회전하여 패킹됩니다.
    • SpritePackingRotation은 향후 Unity 버전에서 사용하기 위해 예약된 타입입니다.

기타

  • 아틀라스는 Project\Library\AtlasCache에 캐시됩니다.
    • 이 폴더를 삭제하고 Unity를 시작하면 아틀라스가 강제로 리패킹됩니다. Unity는 반드시 종료된 상태여야 합니다.
  • 아틀라스 캐시는 시작할 때 로드되지 않습니다.
    • 모든 텍스처는 Unity가 재시작된 후 최초로 패킹될 때 반드시 체크되어야 합니다. 이 동작은 프로젝트 내의 전체 텍스처 수에 따라 시간이 다소 걸릴 수도 있습니다.
    • 필요한 아틀라스만 로드됩니다.
  • 디폴트 최대 아틀라스 크기는 2048x2048입니다.
  • PackingTag가 설정되어 있으면 텍스처는 압축되지 않으며 이에 따라 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; } } }


  • Sprite Packer deprecated in 2020.1 NewIn20191
다양한 스프라이트 아틀라스 시나리오 확인
스프라이트 패커 모드
Copyright © 2023 Unity Technologies
优美缔软件(上海)有限公司 版权所有
"Unity"、Unity 徽标及其他 Unity 商标是 Unity Technologies 或其附属机构在美国及其他地区的商标或注册商标。其他名称或品牌是其各自所有者的商标。
公安部备案号:
31010902002961