Unity - StateMachineBehavior와 Animation Transition
의도치 않은 동작 발생
몬스터 애니메이터를 만지다가 원치 않은 동작이 발생한다.
영역 밖으로 나가면 나오고, 들어오면 숨는 그런 기능을 만들려고 했는데
애니메이션이 부자연스럽게 전이가 된다.
(원래는 슝~ 하고 들어갔다 나왔다가 해야 함)
몬스터의 AnimatorController FSM
왜 그런지 이유를 찾다 보니
애니메이션 전이를 위해 해주고 있던 파라미터의 SetTrigger()
가 한 번이 아닌 여러 번 호출되고 있었다.
SetTrigger()
가 여러 번 호출되면서 Idle -> Hide로 이미 상태가 전이됐음에도
Hide -> Hide로 또다시 애니메이션 전이를 발생시키고 있는 것이었다.
참고 : 상태 머신 동작 (StateMachineBehavior)
내가 진행하고 있는 유니티 프로젝트에서는 유니티에서 제공하는 AnimatorController와 StateMachineBehavior를 이용해 FSM을 설계한다.
상태 머신 동작 - Unity 3D Docs
생명주기에서의 상태 머신- Unity 3D Docs
해결 방법
Idle State의 StateMachineBehaviour은 대충 이렇게 생겼다.
(근데 왜 Behaviour? Behavior 아닌가?)
(프로젝트의 코드가 아닌 임시 코드)
using UnityEngine;
public class IdleState : StateMachineBehaviour
{
[SerializeField] private LayerMask _targetLayer;
private BoxCollider2D _detectBoxCollider;
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
_detectBoxCollider = animator.GetComponent<BoxCollider2D>();
}
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
Collider2D rayHit = Physics2D.OverlapBox(_detectBoxCollider.transform.position,
_detectBoxCollider.bounds.size, 0f, _targetLayer);
if (rayHit)
{
Debug.Log("SetTrigger Hide");
animator.SetTrigger("Hide");
}
}
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
}
}
내 프로젝트에서도 위 코드와 마찬가지로 OnStateUpdate()에서 SetTrigger("Hide")
를 통해 애니메이션을 전이시키는데
분명 유니티의 생명주기 상 한 번만 호출되어야 할 SetTrigger()가 왜 여러 번 호출되지 생각하던 찰나에 떠오른 것이
Transition Duration
이었다.
Idle 애니메이션이 Hide 애니메이션으로 전이되는 과정에서
Idle 애니메이션이 끝나고 난 이후에도 0.15초 동안 Hide 애니메이션과 함께 재생되는 것으로 간주하기 때문에
위와같은 문제가 발생한 것이었다.
이 현상에 대해 로그를 찍어 확인해 보면
Enter Idle
- Idle 애니메이션 시작Hide SetTrigger
- Hide 애니메이션으로 전이Enter Hide
- Hide 애니메이션 시작Hide SetTrigger
와Hide의 Update ~~~~~~~~
- Idle과 Hide 애니메이션이 동시에 실행Exit Idle
- Idle 애니메이션 종료 (0.15초 경과)
이렇게 Idle 애니메이션, Hide 애니메이션이 동시에 실행되는 상황이었고,
애니메이션에 종속적인 StateMachineBehaviour
가 두 상태에서 실행되며 발생한 문제였다.
즉 Idle State의 OnStateUpdate()와 Hide State의 OnStateUpdate()가 동시에 호출되고 있는 것이다.
객체가 FSM에서 한 상태만을 가지고 있을거라고 예상한 것과 달리
Animation의 Transition Duration을 이용하면 두 상태를 동시에 가지고 있을 수 있었다.
만약 Transition Duration을
0으로 맞춘다면, 위와 같이
애니메이션이 동시에 실행될 일이 없기 때문에 한 번의 SetTrigger("Hide")
가 실행된다.
프로젝트의 해결 방안
Transition Duration
을 0.15초였을 때는 애니메이션이 자연스럽게 보간 되어 전이됐는데
이제 0초로 하면 툭툭 끊기듯이 애니메이션이 전이된다.
이 문제는 어떻게 해결할 것인가..?
protected override void Update()
{
base.Update();
if (CautionEvaluator)
{
if (!CautionEvaluator.IsTargetWithinCautionRange())
{
if (CurrentStateIs<Monster_HideState>())
{
Debug.Log("SetTrigger Idle");
Animator.SetTrigger("Idle");
return;
}
}
else
{
if (CurrentStateIs<Monster_IdleState>())
{
Debug.Log("SetTrigger Hide");
Animator.SetTrigger("Hide");
return;
}
}
}
}
각각의 State가 아닌 Monobehavior의 Update()에서 현재 상태 체크 후 SetTrigger()
해준다.
이제 자연스럽게 잘 동작하는 것을 볼 수 있다.
플레이어 FSM에 이어 몬스터 FSM을 설계하는데 몬스터 FSM은 다양한 종류의 몬스터를 고려해야 해서 더 복잡한 것 같다.
각 State에서의 스크립트와 MonoBehavior의 스크립트(게임 오브젝트의 컴포넌트에서 실행되는 스크립트)를 적절히 잘 사용하도록 노력하자.
라고 생각했지만 아니었다
위의 방식대로 하면 일시적으로 문제를 해결할 수 있었지만
본질적인 원인인 Animation이 Transition
되면서 2개의 State를 동시에 가질 수 있다는 점이 계속해서 발목을 잡았다.
StateMachineBehaviour와 MonoBehavior를 함께 사용하는 방식이 단순한 FSM에서는 괜찮을지도 모르겠지만 나의 경우는 아니었다.
몬스터는 StateMachineBehavior를 상속받아 FSM을 설계했지만,
플레이어는 순수 C# 스크립트로 SMB를 상속받지 않고 사용했는데,
이 방식으로 다시 재작업해야 한다는 생각이 들었다.
즉 StateMachineBehaviour를 직접 제작하면서 수동으로 Animation 전환에 따른 ChangeState를 해줘야 한다는 것이며,
나는 이제 StateMachineBase와
StateBase
이 두 클래스를 기반으로 FSM 설계를 다시 하러 간다.
댓글남기기