본문 바로가기

게임개발

[Unity] 픽셀 아트 플립북 애니메이션 구현 실험 1

2D 픽셀 그래픽으로 게임을 개발하고 있다보니 플립북 애니메이션을 어떻게 처리해야할지가 정말 난감하였다.

게다가 수 백만의 몬스터를 만들어서 넣을 계획이기 때문에 반복 노가다를 줄일 수 있는 확장성이 필요했다.

기존의 애니메이션 클립은 스프라이트 시트를 바꿀 때마다 직접 생성해줘야하는 어려움이 있었고

그를 해결하기 위해 스프라이트 라이브러리 에셋(새창 링크)을 사용해봤지만

이 역시도 스프라이트 별로 라이브러리를 직접 생성해야했고 수동으로 넣어줘야했다.

그래서 자동으로 자르고 라이브러리를 만드는 스크립트를 짜서 자르는 것까진 성공(직접 만든 소스 깃허브 링크)했지만

스프라이트 라이브러리 만들 생각하니 이건 아니다 싶어 다음과 같은 방식을 고려했다.

 

  1. Shader Graph 사용
  2. Unirx 사용 - (기존 애니메이션 클립에서 스프라이트를 직접 바꾸는 방식을 rx로)

 

일단 두 가지 방식을 생각해봤고 각각의 성능 비교를 해보려고 한다.

이 포스트에서는 셰이더 그래프를 사용하고 테스트 환경은 Unity 2021 lts이다.

아래는 폴리톤이라는 몬스터로 지금 제작하고 있는 게임에 첫 등장하는 몬스터이다.
이 이미지를 사용해서 작업하려고 한다. fps는 초당 8장이 넘어가도록 설정했다.

 

Shader Graph를 이용해 플립북 애니메이션을 구현한 결과이다.

<셰이더 그래프와 사용된 코드(더보기로 펼쳐서 볼 수 있습니다)>

더보기
플립북 애니메이션을 만들어 주는 셰이더. 한 칸에 32px의 정사각형으로 이루어진&nbsp; 8*4 스프라이트 시트를 자르지 않고 사용했다.
using System.Collections.Generic;
using System.Diagnostics;
using TMPro;
using UnityEngine;
using Random = Unity.Mathematics.Random;

public class ShaderTest : MonoBehaviour
{
    public GameObject prefab;
    public int TestSize=100000;
    public TMP_Text text;
    public List<Renderer> obj;
    public int state=1;
    private static readonly int State = Shader.PropertyToID("_State");

    public void OnGenerate()
    {
        Stopwatch sw = new();
        Random random = new(1234);
        sw.Start();
        for (int i = 0; i < 100000; i++)
            obj.Add(Instantiate<GameObject>(prefab, 
            	new Vector3(random.NextFloat(-4,4), random.NextFloat(-4,4), 0f),
                Quaternion.identity).GetComponent<Renderer>()); 
        sw.Stop();
        text.SetText($"result: {sw.ElapsedMilliseconds}ms");
    }

    public void OnTransition()
    {
        Stopwatch sw = new();
        sw.Start();
        foreach (var o in obj)
        {
            o.material.SetInt(State,state);
        }
        sw.Stop();
        text.SetText($"result: {sw.ElapsedMilliseconds}ms");
    } 
}

 

  • 10만개 생성 결과 

 

 

 

  • 10만개의 프리팹을 Meterial.SetInt로 셰이더 내부 변수를 바꿔 트랜지션한 결과.(생성시 Renderer를 미리 캐싱해놓았음)

 

 

 

  • 두 번째 이후의 트랜지션은 10배정도 빨라졌다. 머터리얼 내 변수를 캐싱한 것인지 힙에 할당 된 메모리도 늘어난 것 같고 접근이 빨라진 것 같다. 혹시나해서 트랜지션을 4가지로 계속 돌려가며 했는데도 일정하게 유지됐다.

 

성능은 꽤 괜찮았다. 

 

빌드 후 테스트한 장면(윈도우 빌드로 Mono, ILLCPP 둘 다 해봤으나 별 차이가 없었다.)

메모리의 경우 생성시에는 500메가 트랜지션 이후에는 약 1.7기가 정도를 잡아먹었다.

 

현재 이 방법으로 계속 작업하고 있었는데 가장 큰 문제점이 애니메이션 컨트롤이 너무 까다롭다는 것이다.

그래서 Unirx의 상태머신 오픈소스를 개조해 적용했는데

트랜지션은 잘되나 애니메이션을 전환했을 때 정확하게 해당하는 부분의 첫프레임이 나타나게 하기가 어려웠다.

그리고 딱 한 번만 애니메이션 되고 멈추게 하기도 상당히 까다롭다.

게다가 다른 셰이더를 같이 입히기도 곤란했다. (불가능한 건 아니지만 확실히 어려워졌음)

 

다음 포스팅에서는 순수하게 Unirx+SpriteRenderer의 값 바꾸기로 해보려고 한다.