애니메이터 트러블슈팅
플레이어 캐릭터가 이동 중에 무기를 스왑하면, 해당 무기에 따라 다른 달리기 애니메이션이 재생된다.
ex) 기본 달리기, 두손으로 대검을 들고 달리기, 양손에 쌍검을 들고 달리기
스왑 시, 다른 애니메이션을 재생하기 때문에 캐릭터의 다리나 머리의 위치가 달라지게 됐다.
빠르게 스왑을 반복하면 다음과 같이 보기 좋지 않은 현상이 발생했다.
이 현상을 해결하기 위해 가장 먼저 떠오른 방법은 두 가지 정도였다.
결과부터 말하자면, 위 두 방법이 아니라 다른 방법으로 해결했다.
가장 먼저 Animator의 State의 프로퍼티 중 하나인 Cycle Offset
을 이용해서 구현해보려헀다.
이 녀석인데, Parameter를 체크해서 애니메이터 파라미터를 넣어줄 수 있다.
애니메이션을 실행하기 전에 이전 AnimationStateInfo의 NormalizeTime을 받아와서 파라미터를 Set한 후 다음 애니메이션을 재생하게 했는데, 결과적으로 동작하지 않았다.
공식문서를 찾아보니 0.0f ~ 1.0f의 값만 유효하게 사용할 수 있고 이외의 값은 무시된다는 것 같지만, 디버깅 과정에서 0.1 ~ 0.2 정도의 값임에도 내가 원하는 대로 동작하지 않았다. (정확히는 아예 동작을 하지 않는 것처럼 느껴졌다.)
그래서 다음 방법으로 Transition Offset을 바꿔보려 헀다.
애니메이터의 트랜지션 프로퍼티인 이 녀석이다.
값을 0.5로 놓고 돌려보니까, 이번엔 애니메이션의 절반째부터 애니메이션을 재생했다. 내가 원하던 기능이다.
근데 문제는 저 offset을 런타임에서 스크립트로 내가 원하는 값으로 설정할 수 있냐는 것인데,,
아무래도 방법이 없는 것 같다.
방금 찾아보니 Animator.CrossFade()
를 이용해서 동적으로 트랜지션을 만들 수 있다는 것 같긴한데 ,,
동적으로 생성하면 무기를 스왑할 때마다 동적으로 생성해야한다는 것 같은데, 굳이 동적으로 생성하면서 (성능에 영향이 있을 수도 있을 것 같다.) 이 방법을 고수할 필요는 없을 것 같았다.
2번의 경우는 되게 심플한 이유로 채택하지 않았다.
현재 PlayerStateMachine
클래스가 애니메이션들을 실행할 때 사용하는 코드들을 레이어를 나눴을 때 똑같이 사용할 수 있을지가 고민이었다.
막상 구현을 시작하면 예상치 못했던 사이드이펙트가 발생할 수도 있어서, 일단은 보류하고 다른 방법을 찾기로 했다.
그러던 중 떠오른 세 번째 방법은 블렌드 트리였다.
유니티에서 제공하는 Blend Tree
는 여러 애니메이션 Motion이나 또 다른 블렌드 트리를 하나의 Animator State로 묶어서 사용할 수 있게 해준다.
하나의 블렌드 트리로 묶여진 애니메이션들은 특정 float값을 이용해서 서로 블렌드된다.
우선, 달리기와 관련된 애니메이션들을 하나의 블렌드 트리로 묶었다.
그런 다음, 각 애니메이션마다 하나의 Animator Parameter들을 할당해줬다.
기본적으로 블렌드 트리는 실수 값을 사용하기 때문에 0 ~ 1사이의 값을 가진다면 애니메이션이 서로 섞이지만, 나는 실수 값을 0, 1만 사용하여 bool 변수처럼 사용하기로 했다.
이렇게 하면, 기본 달리기 float값일 1이고 다른 나머지 값이 0일 때는 기본 달리기 애니메이션을,
대검 달리기 float값이 1이고 다른 나머지 값이 0일 때는 대검 달리기 애니메이션이 재생된다.
또, 하나의 애니메이션으로 묶어놨기 때문에, 달리던 도중 무기를 스왑해도 애니메이션 클립의 재생 시간이 초기화되지 않는다!!
블렌드 트리 설정을 마쳤으니, 기존 PlayerGroundState
클래스에서 도구 스위칭 시 실행되던 이벤트 메서드를 수정한다.
수정 전
protected virtual void OnChangedEquipTool(QuickSlot quickSlot)
{
WeaponItemData hand = quickSlot.itemSlot.itemData as WeaponItemData;
if (hand != null)
{
_stateMachine.Player.Animator.SetBool(_stateMachine.Player.AnimationData.EquipTwoHandedToolParameterHash, hand.isTwoHandedTool && quickSlot.itemSlot.equipped);
_stateMachine.Player.Animator.SetBool(_stateMachine.Player.AnimationData.EquipTwinToolParameterHash, hand.isTwinTool && quickSlot.itemSlot.equipped);
}
else
{
_stateMachine.Player.Animator.SetBool(_stateMachine.Player.AnimationData.EquipTwoHandedToolParameterHash, false);
_stateMachine.Player.Animator.SetBool(_stateMachine.Player.AnimationData.EquipTwinToolParameterHash, false);
}
}
수정 후
protected virtual void OnChangedEquipTool(QuickSlot quickSlot)
{
WeaponItemData hand = quickSlot.itemSlot.itemData as WeaponItemData;
if (hand != null)
{
_stateMachine.Player.Animator.SetBool(_stateMachine.Player.AnimationData.EquipTwoHandedToolParameterHash, hand.isTwoHandedTool && quickSlot.itemSlot.equipped);
_stateMachine.Player.Animator.SetBool(_stateMachine.Player.AnimationData.EquipTwinToolParameterHash, hand.isTwinTool && quickSlot.itemSlot.equipped);
_stateMachine.Player.Animator.SetFloat(_stateMachine.Player.AnimationData.BlendEquipDefaultToolParameterHash, hand.isTwoHandedTool && quickSlot.itemSlot.equipped || hand.isTwinTool && quickSlot.itemSlot.equipped ? 0f : 1f);
_stateMachine.Player.Animator.SetFloat(_stateMachine.Player.AnimationData.BlendEquipTwoHandedToolParameterHash, hand.isTwoHandedTool && quickSlot.itemSlot.equipped ? 1f : 0f);
_stateMachine.Player.Animator.SetFloat(_stateMachine.Player.AnimationData.BlendEquipTwinToolParameterHash, hand.isTwinTool && quickSlot.itemSlot.equipped ? 1f : 0f);
}
else
{
_stateMachine.Player.Animator.SetBool(_stateMachine.Player.AnimationData.EquipTwoHandedToolParameterHash, false);
_stateMachine.Player.Animator.SetBool(_stateMachine.Player.AnimationData.EquipTwinToolParameterHash, false);
_stateMachine.Player.Animator.SetFloat(_stateMachine.Player.AnimationData.BlendEquipDefaultToolParameterHash, 1f);
_stateMachine.Player.Animator.SetFloat(_stateMachine.Player.AnimationData.BlendEquipTwoHandedToolParameterHash, 0f);
_stateMachine.Player.Animator.SetFloat(_stateMachine.Player.AnimationData.BlendEquipTwinToolParameterHash, 0f);
}
}
기존에 사용하던 bool값 파라미터와 삼항연산자를 이용해서 각 파라미터에 알맞는 값을 넣어줬다.
블렌드 트리를 사용하니까, 애니메이터의 상태가 많이 깔끔해졌다.