던전앤인챈트 개발 #1 커스텀 애니메이터 제작

던전 앤 인챈트는 새로 시작하는 게임프로젝트입니다.

완성 및 출시가 궁극적 목표지만, 이 프로젝트를 통해 게임 내 기능구현에 대해 좀 더 효율적인 방법은 없는지, 더 최적화할 방법은 없는지 공부하며 개발합니다.

게임 플랫폼은 PC입니다.


1. 애니메이션 구현부터 하는 이유

이번 프로젝트는 리소스를 이미 지인으로부터 받았기 때문에 그 분이 구성 해둔 파일트리를 수정 하지 않고 그대로 쓰는 방향으로 개발 하기로 했습니다. 그런데 캐릭터 이미지 구성이 0~7번 폴더 안에 폴더 별로 8방향 이미지가 0 1 2 ... 7 8 9 파일명으로 구성되어 있습니다. 그래서 기존 유니티에서 제공하는 애니메이션 컨트롤러를 쓰게되면 방향 및 캐릭터State에 맞게 애니메이션을 바꿔주기가 어려워집니다. 물론 방향과 State마다 애니메이터를 만들어야 된다는 것도 큰 압박이구요.

 

그래서 파일주소가 "캐릭터 고유 주소/상태/방향/frame번호" 형태로 된 이미지들을 애니메이션으로 사용하기 위해 커스텀 애니메이터를 먼저 구현하기로 했습니다. 다음 글에서 테스트를 위해 idle/move/attack 세가지 상태도 구현합니다.

 

2. 애니메이터 설계

매 프레임마다 호출되는 Update() 함수에서 sprite를 업데이트 해주는 함수를 호출하고, 해당 함수는 계속 해서 다음 번호의 sprite를 Resources.Load()를 통해 가져와 SpriteRenderer의 sprite로 넘겨줍니다.

먼저 경로, state, 방향, anim번호 해당하는 변수를 만들고, set함수를 만들어줍니다.

    string animPath;
    string sprPath;
    string state;
    int direction;
    SpriteRenderer sr;
    int animNum;
    Sprite s;
    public void setPath(string aPath) {

        animPath = aPath;
    }
    public void setState(string stat) {
        state = stat;
        animNum = 0;
    }
    public void setDir(int dir) {
        direction = dir;
    }

이때, 이미지의 경로는 string.Format을 이용해 변수들이 해당 자리에 들어갈 수 있게 해주면서, + 연산자를 사용 했을때 생기는 임시 문자열에 대한 가비지 콜렉터 호출을 막아줍니다.

   const string animPathFormat = "image/{0}/{1}/{2}/{3}";//0:경로 1:state 2:방향 3:anim번호
   
   void sprUptate() {
        sprPath = string.Format(animPathFormat, animPath,state,direction,animNum);
        s = Resources.Load<Sprite>(sprPath);
        if (s == null)
        {
            animNum = 0;
            sprPath = string.Format(animPathFormat, animPath, state, direction, animNum);
            s = Resources.Load<Sprite>(sprPath);
        }
        sr.sprite = s;
        
        animNum++;
    }

 

스크립트를 게임 오브젝트에 붙여 주고, Awake()에서 임시로 경로를 부여하고 테스트합니다.

임시경로 : image/player/sword/idle/6/0 을 부여한 모습.
Resources 폴더 안에 위치해야 Resources.Load() 함수를 통해 가져올 수 있다.

풀소스:

더보기

#풀소스 첨부(본문 내용 작성 후에 수정 되었습니다.)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class myAnimator : MonoBehaviour
{
    const string animPathFormat = "image/{0}/{1}/{2}/{3}";//0:경로 1:state 2:방향 3:anim번호
    const string animPathFormat_NONE = "image/{0}/{1}";//0:경로 1:anim번호
    string animPath;
    string sprPath;
    string state;
    int direction;
    SpriteRenderer sr;
    int animNum;
    Sprite s;
    public bool hasDir = false;
    void Awake()
    {
        sr = GetComponent<SpriteRenderer>();
        animNum = 0;//anim 번호 처음으로 초기화
    }
    void Update()
    {
        if (hasDir)
            sprUptate();
        else
            sprUptate_None();
    }
    void sprUptate_None()
    {//state, 방향 필요 없음
        sprPath = string.Format(animPathFormat_NONE, animPath, animNum);
        s = Resources.Load<Sprite>(sprPath);
        if (s == null)
        {
            animNum = 0;
            sprPath = string.Format(animPathFormat_NONE, animPath, animNum);
            s = Resources.Load<Sprite>(sprPath);
        }
        sr.sprite = s;

        animNum++;
    }
    void sprUptate() {
        sprPath = string.Format(animPathFormat, animPath,state,direction,animNum);
        s = Resources.Load<Sprite>(sprPath);
        if (s == null)
        {
            animNum = 0;
            sprPath = string.Format(animPathFormat, animPath, state, direction, animNum);
            s = Resources.Load<Sprite>(sprPath);
        }
        sr.sprite = s;
        
        animNum++;
    }
    public void setPath(string aPath) {

        animPath = aPath;
    }
    public void setState(string stat) {
        state = stat;
        animNum = 0;
    }
    public void setDir(int dir) {
        direction = dir;
    }
    public bool isEnd() {
        if(hasDir)
        sprPath = string.Format(animPathFormat, animPath, state, direction, animNum+1);
        else
        sprPath = string.Format(animPathFormat_NONE, animPath, animNum+1);

        s = Resources.Load<Sprite>(sprPath);
        if (s == null)
            return true;

        return false;
    }
}

 

3. 최적화

유니티 프로파일러를 보면 Resources.Load()함수가 자체적으로 캐싱을 해서 같은 리소스를 여러번 불러와도 메모리사용량이 크게 늘어나지 않는것으로 보이지만, 자체적인 캐싱을 해보고 자체 캐싱이 더 효율적이라면 자체 캐싱을 하도록 하겠습니다. 해당 기능은 원활한 테스트를 위해 캐릭터 FSM 제작 후 제작하도록 하겠습니다.