[Basic] 애니메이션 블랜드 트리와 서브 스테이트 머신



애니메이터창에 너무 많은 스테이트가 있으면 거미줄이 복잡하게 펼쳐진 것 처럼 되어서 한눈에 파악하기도 어렵고 한정된 애니메이션으로 여러 상황에 유연하게 대처하기가 어렵다. 예를들어 위, 아래, 좌, 우로만 움직이는 애니메이션이 있다고 치면 대각선으로 일 때 부자연스러움이 보일 것이다. 그래서 각 대각선 별로 애니메이션을 만들고 또 사이마다 애니메이션을 만들고 너무 비효율적이면 사실 상 불가능하다. 그래서 필요한 것이 블랜드 트리다. 블랜드 트리에 대해서 설명하겠다.

블랜드 트리

블랜드 트리 사용법을 알아보자. 우선 애니메이터 창 빈 곳에 마우스 우클릭을하고 create state -> From new blend tree를 선택하면 다음과 같은 스테이트 하나가 생긴다.

image

우리는 움직임에 대한 블랜드 틀리를 만들 것이니 이름을 Move로 바꿔주고 스테이트를 더블 클릭해주면 다음과같은 화면이 나온다.

image

그리고 저 무브 스테이트를 클릭하면

image

다음과 같이 인스펙터창이 변하는데 Blend Type을 우리는 움직임에 대한 블랜드 트리를 만드니 2차원이면 충분하다 그래서 2D로 바꿔준다.

image

그럼 파라미터를 2개 넣을 수 있게 되는데 상,하에 대한 파라미터 하나 좌,우에 대한 파라미터 하나를 만들어서 넣어주자 애니메이터창 왼쪽에 파라미터에서 추가하기 누르고 Float형으로 DirX, DirZ를 만들어준다. 그 다음 Move 스테이트의 파라미터로 넘겨주자.

image

그럼 무브 스테이트에 우리가 만들었던 변수들의 값을 조절할 수 있게 변한다. 그 다음 인스펙터창에서 모션이 비어있다고 뜨튼데 플러스버튼을 누르고 Add Motion Field를 선택해준다. 우리는 4방향의 애니메이션이 있으니 4개 만들어 주면 된다.

image

4개 추가하고 각 모션에 움직에 대한 애니메이션을 넣어주면 다음과 같은 화면이 되는데 인스펙터창에서 Pos X와 Pos Y에 우리가 만들어 둔 DirX,DirZ가 들어간다고 생각하면 된다. 여기서 Pos Y를 z축이라고 생각하자.

image

그럼 다음과 같이 인스펙터 창이 변하게 된다.

image

그럼 우리는 분명 4가지의 애니메이션만 있었는데 Move 스테이트의 DirX,DirZ값으로 그 사잇값을 표현할 수 있게 되었다.즉, 대각선 이동 애니메이션은 없는데 우리는 더 자연스러운 움직임을 연출할 수 있게 된 것이다. 이제 스크립트로 플레이어가 움직일 수 있게하고 그에 맞게 애니메이션이 재생되도록 스크립트를 작성해보자.

 void Update()
    {
        float dirX = Input.GetAxisRaw("Horizontal");
        float dirZ = Input.GetAxisRaw("Vertical");

        Vector3 dir = new Vector3(dirX, 0, dirZ); //플레이어가 움직일 방향


        isMove = false; //처음에는 움직이고 있지 않으니 처음에는 false

        if (dir != Vector3.zero) //만약 플레이어가 움직이고 있다면
        {
            isMove = true; 
            transform.Translate(dir.normalized * moveSpeed * Time.deltaTime);//그 방향으로 speed만큼 위치를 옮겨라
        }
        anim.SetBool("Move", isMove); //Move 스테이트에 파라미터를 넘김

        //블랜드 트리에 입력할 파라미터들의 값을 넘김
        anim.SetFloat("DirX", dirX); 
        anim.SetFloat("DirZ", dirZ);
    }

코드가 어려운 것은 없다.

블래드 트리는 이정도로 설명을 마치고 이제 서브 스테이트 머신에 대해서 설명하겠다.

서브 스테이트 머신

이 기능 또한 복잡하게 얽힌 애니메이션들을 정리 할때 사용하기 좋다. 예를 들면 플레이가 콤보어택을 한다해도 서브 스테이트 머신이면 간단하게 만들 수 있고 애니메이터 창도 깔끔하다. 우선 여기서 들어 볼 에시는 플레이거 점프를 하였을 때 이다.

우선 애니메이터 빈 곳에 마우스 우클릭을 해서 서브 스테이트 머신을 추가해준다.

image

그럼 다음과 같은 스테이트가 나오는데 스테이트의 이름을 점프로 바꿔주고 더블 클릭해서 안으로 들어가보자

image

그럼 다음과 같이 애니메이션이 없는 빈 애니메이터 창이 나오는데 우리가 만들어 둔 Jump,Fall,Land 애니메이션을 추가해보자. 그다음 점프 애니메이션은 현재 작동되고 있는 애니메이션이 뭐든 상관없이 재생되어야 하므로 Any State에서 트랜지션을 연결해준다.

image

트랜지션을 다음과 같이 연결해준다 순서는 당연히 점플, 폴, 랜드이다. 그리고 나서 이제 다시 Base Layer의 Idle상태로 가야하기 때문에 Base Layer에 트랜지션을 연결하고 Idle 스테이트로 연결해준다. 그리고 각 트랜지션마다 파라미터를 통해 제어를 해준다. 그러고 나서 Basr Layer로 가보면

image

다음과 같이 깔끔하게 정리된 모습을 볼 수 있다. 이제 점프도 스크립트로 제어 해보겠다. 아래의 코드는 플레이어의 움직임과 점프를 제어하는 스크립트이다.

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

public class Test : MonoBehaviour
{
    private Animator anim;
    private Rigidbody rigid; //점프했을 때 AddForce사용하기 위해
    private BoxCollider col; //레이를 쏠 때 거리를 콜라이더 기준으로 삼기 위해

    [SerializeField]
    private float moveSpeed;
    [SerializeField]
    private float jumpForce;
    [SerializeField]
    private LayerMask layerMask; //특정 레이어와 닿았을 때만 고려하기 위해

    private bool isMove;
    private bool isJump;
    private bool isFall;
    void Start()
    {
        anim=GetComponent<Animator>(); 
        rigid=GetComponent<Rigidbody>();
        col=GetComponent<BoxCollider>();    
    }

    void Update()
    {
        TryWalk();
        TryJump();
    }
    void TryWalk()
    {
        float dirX = Input.GetAxisRaw("Horizontal");
        float dirZ = Input.GetAxisRaw("Vertical");

        Vector3 dir = new Vector3(dirX, 0, dirZ); //플레이어가 움직일 방향


        isMove = false; //처음에는 움직이고 있지 않으니 처음에는 false

        if (dir != Vector3.zero) //만약 플레이어가 움직이고 있다면
        {
            isMove = true;
            transform.Translate(dir.normalized * moveSpeed * Time.deltaTime);//그 방향으로 speed만큼 위치를 옮겨라
        }
        anim.SetBool("Move", isMove); //Move 스테이트에 파라미터를 넘김

        //블랜드 트리에 입력할 파라미터들의 값을 넘김
        anim.SetFloat("DirX", dirX);
        anim.SetFloat("DirZ", dirZ);
    }
    void TryJump()
    {
        if(Input.GetKeyDown(KeyCode.Space) && !isJump) //스페이스바 눌르고 점프상태가 아닐 때
        {
            isJump = true;
            rigid.AddForce(Vector3.up*jumpForce,ForceMode.Impulse);
            anim.SetTrigger("Jump");
        }
        if (isJump)
        {
            if (rigid.velocity.y <=-0.1f  && !isFall) //점프중이고 떨어지는중이 아니고 현재 아래로 떨어지고 있다면
            {
                isFall = true;
                anim.SetTrigger("Fall");
            }
            if (rigid.velocity.y < 0) //현재 아래로 떨어지고 있다면 
            {
                Debug.DrawRay(transform.position, -transform.up, Color.green, 0.1f);
                RaycastHit hit;     //내 위치에서 -> 아래 방향으로 , hit정보 저장하고 ,거리는 나의 박스콜라이더의 y축의 반에 +0.1f만큼,특정 레이어만
                if (Physics.Raycast(transform.position, -transform.up, out hit, col.bounds.extents.y + 0.1f, layerMask))//레이를 쏜다.
                {
                    isJump = false;
                    isFall = false;
                    anim.SetTrigger("Land");
                }
            }
        }
       
    }
}

그다지 어려운 내용은 없지만 아직까지는 항상 레이에 관한 함수를 사용할 때 파라미터가 많아서 헷갈린다. 이번 포스팅을 요약하면 애니메이터 컴포넌트로 애니메이션을 효과적으로 관리 할 때 더욱 효과적으로 관리하기 위해 블랜드 틀리와 서브 스테이트 머신을 사용한다는 것이다. 익혀두면 좋을 것 같다.




© 2022. by KSC

Powered by sora