[Unity3D] Idle 모션의 다양화

oy Hong·2024년 5월 3일

Idle


Idle 상태에서 일정시간이 지나면 지루한 모션등의 다른 모션을 취하도록 수정해보자.

블렌드 트리 구성

일단 사용할 여러가지 Idle 모션을 블렌드 트리로 구성하고, 매개변수와 Threshord값을 설정한다.

스크립트 추가

애니메이터 State에서 스크립트를 생성한다.

스크립트

일정 시간을 Idle 상태에서 머물 경우 다른 Idle 모션으로 변경하는 스크립트를 작성해보자.

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

public class BoredBehaviour : StateMachineBehaviour
{
    // 지루해질 때까지의 시간
    public float timeUntilBored = 3;
    // 애니메이션 갯수
    public int numberOfBoredAnimations = 3;

    private bool isBored;
    // Idle 상태 시간 저장
    private float idleTime;

    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        ResetIdle(animator);
    }

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if(isBored == false)
        {
            idleTime += Time.deltaTime;

            if(idleTime > timeUntilBored)
            {
                isBored = true;
                int boredAnimation = Random.Range(1, numberOfBoredAnimations + 1);

                animator.SetFloat("BoredAnimation", boredAnimation);
            }
        }
        // 상태 정보를 이용하여 애니메이션의 정규화된 시간을 확인하여
        // 지루한 애니메이션을 1회만 재생하도록 제어
        else if (stateInfo.normalizedTime % 1 > 0.98f)
        {
            ResetIdle(animator);
        }
    }

    private void ResetIdle(Animator animator)
    {
        isBored = false;
        idleTime = 0;

        animator.SetFloat("BoredAnimation", 0);
    }
}

(stateInfo.normalizedTime % 1 > 0.98f) 사용 이유
Loop 애니메이션의 경우 normalizedTime이 0에서 시작해 계속 증가하기 때문에 0과 1사이의 값이 아니다. Loop가 계속 되면서 1이상으로 계속 늘어나게 된다.

  • % 나머지 연산자를 이용해 항상 애니메이션이 끝까지 재생되도록 한다.

문제 해결

#1 Idle 모션 끊김

Idle 모션 재생 중 다른 Bored 모션으로 전환이 이루어진다. Idle 모션이 끝난 뒤 다른 Bored 모션으로 전환하도록 수정해보자.

// 조건을 추가하여 Loop가 시작 부분에 가까워졌는지 체크한다.
if(idleTime > timeUntilBored && stateInfo.normalizedTime % 1 < 0.02f)

#2 모션 전환

모션이 전환될 때 전환이 부드럽게 이루어지지 않는 부분을 수정해보자.

// 애니메이션 재생에 dampTime을 적용한다.
animator.SetFloat("BoredAnimation", boredAnimation, 0.2f, Time.deltaTime);

#3 Idle 모션으로 되돌아갈 때

기본 Idle 모션과 3가지 Bored 모션이 블렌드 트리로 묶여있기 때문에 Bored 모션 재생 후 Idle 모션으로 돌아갈 때 3가지 Bored 모션이 조금씩 표시되면서 부자연스러운 젼환을 보여준다.

이를 해결하기 위해 bored 모션 사이에 기본 Idle 모션을 배치하여 다른 애니메이션을 거치지 않도록 함으로써 자연스러운 전환을 이끌어낼 수 있다.

스크립트 수정

관련 문제를 해결한 전체 스크립트를 보자.

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

public class BoredBehaviour : StateMachineBehaviour
{
    public float timeUntilBored = 3;
    public int numberOfBoredAnimations = 3;

    private bool isBored;
    private float idleTime;

    private int boredAnimation;

    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        ResetIdle();
    }

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if(isBored == false)
        {
            idleTime += Time.deltaTime;

            // 모션이 처음에 가까운지도 같이 체크
            if(idleTime > timeUntilBored && stateInfo.normalizedTime % 1 < 0.02f)
            {
                isBored = true;
                boredAnimation = Random.Range(1, numberOfBoredAnimations + 1);
                // 기본 Idle 모션 추가 후 올바른 bored 모션을 얻기 위한 공식
                boredAnimation = boredAnimation * 2 - 1;

                // 가장 가까운 기본 Idle 모션으로 전환
                animator.SetFloat("BoredAnimation", boredAnimation - 1);
            }
        }
        else if (stateInfo.normalizedTime % 1 > 0.98f)
        {
            ResetIdle();
        }

        // damptime 추가
        animator.SetFloat("BoredAnimation", boredAnimation, 0.2f, Time.deltaTime);
    }

    private void ResetIdle()
    {
        // bored 모션이 실행 중이라면 가장 가까운 기본 idle 모션을 재생하도록 리셋
        if(isBored)
        {
            boredAnimation--;
        }

        isBored = false;
        idleTime = 0;
    }
}

0개의 댓글