Unity

[Unity] Sprite Atlas 및 스프라이트 동적 변화

박도치 2024. 11. 25. 19:58

프로젝트 중 원래는 텍스트로 처리하던 방식을 스프라이트로 된 숫자로 변화를 줘서 만들고 싶다는 생각이 들어서 시도해 보았다.

 

플레이어의 돈이 변화할 때 Action을 통해 구독해둔 UI가 증감에 따라 함께 변하는 식으로 했는데, 텍스트의 경우 ToString() 으로 int를 string으로 변화하여 .text를 통해 전달하면 되지만, 스프라이트의 경우 이미지의 스프라이트를 동적으로 변화시켜줘야 하기 때문에 다르다.

 

 

Sprite Atlas

 

스프라이트 아틀라스는 프로젝트 세팅에서 Sprite Packer의 Mode를 Always Enable로 하면, Resource에서 Craete -> 2D 

-> SpriteAtals 항목이 생긴걸 볼 수 있다.

 

이를 생성하면 아래와 같이 보이는데, 밑에 Objects for Packing리스트에 필요한 텍스트 리스트를 담아주면 된다.

 

 

그러면 패킹한 스프라이트들이 저렇게 하나로 뭉쳐지는데, 이는 DrawCall을 최소화 하여 Batch를 줄여주는 효과를 주며 관리에 용이하고 최적화에 장점이 있다.

 

SpriteAtlas는 스크립트로는 아래처럼 불러올 수 있다.

 SpriteAtlas spriteAtlas = GameManager.Instance.Resource.Load<SpriteAtlas>($"Sprites/{containerName}");

 

Resource에 Sprites라는 폴더 내의 경로로 아틀라스를 불러올 수 있는 것이다.

 

Sprite 동적 변화

 

Sprite Atlas에 담았다면, 이를 불러와서 적용시켜줘야 하는데 spriteAtals는 GetSprite() 라는 함수가 있다. 이는 매개변수를 string으로 받으며, sprite의 이름을 알 수 있다면 해당 함수를 통해 가져올 수 있다.

 

Sprite sprite = spriteAtlas.GetSprite(spriteName);

 

 

플레이어의 돈은 다음과 같이 관리가 된다.

 

플레이어 부분

 public Action DecreaseLifeAction;
 public Action ChangeMoneyAction;

 private int _money;
 private int _life;
 
 public int Money 
 { 
     get { return _money; } 
     set 
     { 
         _money = value; 
         ChangeMoneyAction?.Invoke();
     }
 }
 void Start()
 {
     _life = 30;
     _money = 999;
 }

 

예시를 위해 직접 하드코딩으로 수치를 넣어뒀다 이 돈은 몬스터가 죽으면 올라가는 돈이며, 변화가 일어날 때 마다 구독된 Action이 호출이 일어나게끔 하여 UI와 연결해 주었다.

 

UI 부분

다음은 UI부분의 Enum이다.

    enum Images
    {
        M_One,
        M_Ten,
        M_H,
        M_K,
        M_M,
        L_One,
        L_Ten,
        L_H,
        Speed1,
        Speed2,
        Resume,
        Pause,
    }

 

enum의 개수를 추출해 (Enum.GetName()) 해당 수 만큼 Dictionary에 제네릭 타입을 키값, 개수만큼의 오브젝트를 밸류값으로 하여 Bindind을 해둔다.

 

Binding을 해두면 Get으로 어디서든 가져올 수 있게끔 구조를 해뒀는데 이를 이용해 MainScene에서 Get해둔걸 저장해둔다.

   private Dictionary<Images, Image> _imagesDic = new Dictionary<Images, Image>();

   
   private void StoreImagesInDictionary()
   {
       foreach(Images image in Enum.GetValues(typeof(Images)))
       {
        	// 바인딩하여 저장해둔 Image 오브젝트를 enum key값으로 저장
           _imagesDic[image] = GetImage((int)image);
       }
   }

 

UI 부분 스프라이트 동적 변화

이제 player측에서 가지고 있던 money 정보를 GameManger를 통해 받아서, 이를 로직 매개변수에 전달해주도록 한다.

 

    private void OnUpdateMoneyUI()
    {
        // 골드 수치 가져오기 (예: 999)
        _money = GameManager.Instance.PlayerInfo.Money.ToString();
        UpdateNumberContainerObjects(_money, Images.M_One, Images.M_M);
    }

 

  1. UpdateNumberContainerObjects에서 아까 위에서 잠시 설명해둔 SpriteAtlas를 경로를 통해 미리 로드를 한다.
  2. 그리고 Enum에 보면 M_ , L_ 두 가지로 나눠져 있는데 이는 각각 Money와 Life로 나눈 것으로 Money의 Image와 Life의 Image를 구분해 둔 것이다. 그렇기에 Money의 크기는 M_One 보다 크거나 같고, M_M 보다 작거나 같아야 하는 조건을 달아줘야 한다.
  3. 그리고 Money의 크기 (999 면 1의자리, 10의자리, 100의자리 3가지를 넣어야 하기 때문) 만큼 조건으로 반복하여 sprite를 각각 넣어주도록한다. 이때 sprite의 이름은 위 사진과 같이 0 ~ 9 로 해놨기 때문에 문제가 생기지 않는다.
  4. 마지막으로 위에서 저장해둔 Dictionary를 통해 저장해둔 enum의 key값을 통해 TryGetValue로 Image를 뱉고, 여기에 스프라이트를 옮겨주도록 하면 된다.
    private void UpdateNumberContainerObjects(string value, Images minImage, Images maxImage)
    {
        string containerName = "NumberContainer";
        
        SpriteAtlas spriteAtlas = GameManager.Instance.Resource.Load<SpriteAtlas>($"Sprites/{containerName}");

        int count = 0;

        foreach (Images image in Enum.GetValues(typeof(Images)))
        {
            if (image >= minImage && image <= maxImage)
            {
                if (value.Length > count)
                {
                    char digit = value[value.Length - 1 - count];
                    string spriteName = digit.ToString();
                    Sprite sprite = spriteAtlas.GetSprite(spriteName);

                    if (sprite != null && _imagesDic.TryGetValue(image, out Image uiImage))
                    {
                        uiImage.sprite = sprite;
                        uiImage.gameObject.SetActive(true);
                    }
                }
                else
                {
                    if (_imagesDic.TryGetValue(image, out Image uiImage))
                    {
                        uiImage.gameObject.SetActive(false);
                    }
                }
                count++;
            }
        }
    }

 

 

위에서 경로를 캐싱처리하지않았는데, 골드의 경우 많은 순환이 일어나기 때문에 매 순간 로드하면 성능적으로 문제가 생길 수 있기 때문에 캐싱하여 최적화 해 주었다.