Posted in: Unity, 프로젝트 When the Spring comes

18. 근접형 기본 몬스터 애니메이션 스크립트로 제작

위 사진은 플레이어 8방향 이동 & 근접 공격 애니메이션 관계도(?)이다. 지옥이 따로 없었다. 이와 같은 막노동을 또 겪고 싶지 않았기에 몬스터 애니메이션은 스크립트로 제작하기로 마음먹었다.

아무리 스크립트로 애니메이션을 제작한다고 하더라도 유니티 에디터가 지원하는 Animator 컴포넌트를 최대한 활용하는 편이 좋다고 생각했다. “그러면 코드도 훨씬 깔끔해지지 않을까?”하고 생각했다.

그러기 위해선 애니메이션을 적용할 게임 오브젝트에 Animator 컴포넌트를 연결하고 Animator Controller를 연결해야 한다. 그리고 원하는 애니메이션 State를 만들어놔야 한다.

우리는 몬스터 애니메이션은 4방향으로 제작할 계획이기 때문에 플레이어보단 State가 훨씬 적다.

Animator.Play("Want State");

코드로 애니메이션을 재생하는 것은 위 메서드가 핵심이다. 원하는 State를 특정 상황에 맞추어서 재생시키면 된다.

중요한 것은 어떤 상황에 무슨 조건을 주는 것이냐는 건데, 내가 한 방법을 말해보고자 한다.

public void Direction(float dir)
{
    if(dir > -45 && dir <= 45)//Left
        directionVec = new Vector3(-2.0f, 0, 0);

    if(dir > -135 && dir <= -45)//Up
        directionVec = new Vector3(0, 0, 2.0f);
    
    if(dir >= -180 && dir <= -135)//Right
        directionVec = new Vector3(2.0f, 0, 0);
    if(dir > 135 && dir <= 180)
        directionVec = new Vector3(2.0f, 0, 0);

    if(dir > 45 && dir <= 135)//Down
        directionVec = new Vector3(0, 0, -2.0f);

    if(dir == 0)//Idle
        directionVec = new Vector3(0, 0, 0);    

    if(directionVec != new Vector3(0, 0, 0))
        lookAtDirection = directionVec;
}

몬스터가 이동하는 방향을 4방향으로 판별하였다. 이동할 때마다 각도를 구한 다음 해당 각도에 따라 원하는 방향을 대입한다.

그리고 중요한 것이 이동을 멈추면 velocity = new Vector3(0, 0, 0)로 캐릭터 이동을 제어하는데 방향은 그대로 바라보는 것이 중요하다.

나는 몬스터의 바라보는 방향과 이동 속도로 이동 애니메이션을 제어하였다. 이동 속도가 0이면 바라보는 방향의 Idle 상태 애니메이션을 출력하였고 이동 중이라면 Moving 상태 애니메이션을 출력하는 것이다.

private void FixedUpdate() {
    Anime();
}

void Anime()
{
    //Idle 상태
    if(lookAtDirection == new Vector3(0, 0, -2.0f) && !monsterMove.isMoving
        && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(IDLE_DOWN);
    if(lookAtDirection == new Vector3(-2.0f, 0, 0) && !monsterMove.isMoving
        && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(IDLE_LEFT);
    if(lookAtDirection == new Vector3(2.0f, 0, 0) && !monsterMove.isMoving
        && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(IDLE_RIGHT);
    if(lookAtDirection == new Vector3(0, 0, 2.0f) && !monsterMove.isMoving
        && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(IDLE_UP);

    //Moving 상태
    if(lookAtDirection == new Vector3(0, 0, -2.0f) && monsterMove.isMoving
        && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(MOVING_DOWN);
    if(lookAtDirection == new Vector3(-2.0f, 0, 0) && monsterMove.isMoving
        && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(MOVING_LEFT);
    if(lookAtDirection == new Vector3(2.0f, 0, 0) && monsterMove.isMoving
        && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(MOVING_RIGHT);
    if(lookAtDirection == new Vector3(0, 0, 2.0f) && monsterMove.isMoving
        && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(MOVING_UP);

    //AttackReady 상태
    if(lookAtDirection == new Vector3(0, 0, -2.0f) && monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(ATTACKREADY_DOWN);
    if(lookAtDirection == new Vector3(-2.0f, 0, 0) && monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(ATTACKREADY_LEFT);
    if(lookAtDirection == new Vector3(2.0f, 0, 0) && monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(ATTACKREADY_RIGHT);
    if(lookAtDirection == new Vector3(0, 0, 2.0f) && monsterBasicAttack.attackReadyAnime)
        ChangeAnimationState(ATTACKREADY_UP);

    //Attack 상태
    if(lookAtDirection == new Vector3(0, 0, -2.0f) && monsterBasicAttack.attackAnime)
        ChangeAnimationState(ATTACK_DOWN);
    if(lookAtDirection == new Vector3(-2.0f, 0, 0) && monsterBasicAttack.attackAnime)
        ChangeAnimationState(ATTACK_LEFT);
    if(lookAtDirection == new Vector3(2.0f, 0, 0) && monsterBasicAttack.attackAnime)
        ChangeAnimationState(ATTACK_RIGHT);
    if(lookAtDirection == new Vector3(0, 0, 2.0f) && monsterBasicAttack.attackAnime)
        ChangeAnimationState(ATTACK_UP);
}

공격 준비와 공격 상태 애니메이션은 당연히 bool 변수로 쉽게 제어할 수 있다. 이렇게 코드로 제작하면 훨씬 쉽게 제작할 수 있다.

bool 변수에 따라 바라보는 방향의 공격 준비 애니메이션과 공격 애니메이션을 적절하게 출력하면 코드로 깔끔하게 애니메이션 기능을 제작할 수 있다.

하지만 중요한 것은 이동 상태와 Idle 상태일 때 공격 상태가 아니라는 것을 확실하게 적어주는 것이 중요하다. 아니면 공격 상태일 때와 공격 준비 상태일 때도 반복해서 이동 애니메이션이 불릴 수 있다.

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

public class WildMeleeAnime : MonoBehaviour
{
    private Animator animator;
    private MonsterMove monsterMove;
    private string currentState;
    public Vector3 directionVec;
    public Vector3 lookAtDirection;

    //애니메이션 상태
    const string IDLE_DOWN = "WildMelee_Idle_Down";
    const string IDLE_UP = "WildMelee_Idle_Up";
    const string IDLE_LEFT = "WildMelee_Idle_Left";
    const string IDLE_RIGHT = "WildMelee_Idle_Right";
    const string MOVING_DOWN = "WildMelee_Moving_Down";
    const string MOVING_UP = "WildMelee_Moving_Up";
    const string MOVING_LEFT = "WildMelee_Moving_Left";
    const string MOVING_RIGHT = "WildMelee_Moving_Right";
    const string ATTACKREADY_DOWN = "WildMelee_AttackReady_Down";
    const string ATTACKREADY_UP = "WildMelee_AttackReady_Up";
    const string ATTACKREADY_LEFT = "WildMelee_AttackReady_Left";
    const string ATTACKREADY_RIGHT = "WildMelee_AttackReady_Right";
    const string ATTACK_DOWN = "WildMelee_Attack_Down";
    const string ATTACK_UP = "WildMelee_Attack_Up";
    const string ATTACK_LEFT = "WildMelee_Attack_Left";
    const string ATTACK_RIGHT = "WildMelee_Attack_Right";

    private MonsterBasicAttack monsterBasicAttack;

    private void Start() {
        animator = GetComponent<Animator>();
        monsterMove = GetComponent<MonsterMove>();
        monsterBasicAttack = GetComponent<MonsterBasicAttack>();
    }

    private void FixedUpdate() {
        Anime();
    }

    void Anime()
    {
        //Idle 상태
        if(lookAtDirection == new Vector3(0, 0, -2.0f) && !monsterMove.isMoving
            && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(IDLE_DOWN);
        if(lookAtDirection == new Vector3(-2.0f, 0, 0) && !monsterMove.isMoving
            && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(IDLE_LEFT);
        if(lookAtDirection == new Vector3(2.0f, 0, 0) && !monsterMove.isMoving
            && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(IDLE_RIGHT);
        if(lookAtDirection == new Vector3(0, 0, 2.0f) && !monsterMove.isMoving
            && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(IDLE_UP);

        //Moving 상태
        if(lookAtDirection == new Vector3(0, 0, -2.0f) && monsterMove.isMoving
            && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(MOVING_DOWN);
        if(lookAtDirection == new Vector3(-2.0f, 0, 0) && monsterMove.isMoving
            && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(MOVING_LEFT);
        if(lookAtDirection == new Vector3(2.0f, 0, 0) && monsterMove.isMoving
            && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(MOVING_RIGHT);
        if(lookAtDirection == new Vector3(0, 0, 2.0f) && monsterMove.isMoving
            && !monsterBasicAttack.attackAnime && !monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(MOVING_UP);

        //AttackReady 상태
        if(lookAtDirection == new Vector3(0, 0, -2.0f) && monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(ATTACKREADY_DOWN);
        if(lookAtDirection == new Vector3(-2.0f, 0, 0) && monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(ATTACKREADY_LEFT);
        if(lookAtDirection == new Vector3(2.0f, 0, 0) && monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(ATTACKREADY_RIGHT);
        if(lookAtDirection == new Vector3(0, 0, 2.0f) && monsterBasicAttack.attackReadyAnime)
            ChangeAnimationState(ATTACKREADY_UP);

        //Attack 상태
        if(lookAtDirection == new Vector3(0, 0, -2.0f) && monsterBasicAttack.attackAnime)
            ChangeAnimationState(ATTACK_DOWN);
        if(lookAtDirection == new Vector3(-2.0f, 0, 0) && monsterBasicAttack.attackAnime)
            ChangeAnimationState(ATTACK_LEFT);
        if(lookAtDirection == new Vector3(2.0f, 0, 0) && monsterBasicAttack.attackAnime)
            ChangeAnimationState(ATTACK_RIGHT);
        if(lookAtDirection == new Vector3(0, 0, 2.0f) && monsterBasicAttack.attackAnime)
            ChangeAnimationState(ATTACK_UP);
    }

    void ChangeAnimationState(string newState)
    {
        if(currentState == newState) return;

        animator.Play(newState);

        currentState = newState;
    }

    public void Direction(float dir)
    {
        if(dir > -45 && dir <= 45)//Left
            directionVec = new Vector3(-2.0f, 0, 0);

        if(dir > -135 && dir <= -45)//Up
            directionVec = new Vector3(0, 0, 2.0f);
        
        if(dir >= -180 && dir <= -135)//Right
            directionVec = new Vector3(2.0f, 0, 0);
        if(dir > 135 && dir <= 180)
            directionVec = new Vector3(2.0f, 0, 0);

        if(dir > 45 && dir <= 135)//Down
            directionVec = new Vector3(0, 0, -2.0f);

        if(dir == 0)//Idle
            directionVec = new Vector3(0, 0, 0);    

        if(directionVec != new Vector3(0, 0, 0))
            lookAtDirection = directionVec;
    }
}

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다