✅ 기능
게임을 하다 보면 여러 NPC나 몹들이 특정한 행동을 취하지 않고 가만히 있거나, 앉아있는 등 Idle 상태로 있는 경우를 많이 볼 수 있습니다. 이러한 상태에서 해당 캐릭터가 항상 같은 Idle 애니메이션을 무한 반복하고 있어도 큰 문제는 없습니다. 하지만 서있는 Idle 같은 경우 가끔씩 기지개를 켜거나, 두리번거리는 등, 여러 가지의 애니메이션을 섞어서 제공하면 플레이어 입장에서는 더욱 생동감 넘치게 느낄 수 있습니다.
이번 글에서는 아주 간단하게 Idle애니메이션 등 여러 종류의 애니메이션을 랜덤으로 플레이하도록 기능을 구현하고 정리해보았습니다.
✅ 해당 기능의 장점
1. 애니메이션의 랜덤재생을 초기에 설정한 후 더 이상 런타임 중에 접근하여 제어할 필요가 없습니다.
2. 애니메이션 각 클립의 길이를 모두 획득하여 애니메이션이 반복재생되거나 시간초에 대해 고려할 필요가 없습니다.
3. 스크립트를 등록한 후 몇개의 변수만 설정해주면 모든 세팅이 끝나 매우 간단하게 설정할 수 있습니다.
✅ 응용 기능
이번 기능은 StateMachineBehaviour이라는 기능을 사용하여 구현하였습니다.
StateMachineBehaviour은 애니메이터의 스테이트 머신에서 사용할 수 있는 스크립트입니다.
✅ 흐름도
블렌드 트리에서 스테이트 머신이 최초로 실행되면 AlreadyExecuted가 false인 상태에서 초기화를 하며 true가 됩니다. 그 후부터는 스테이트 머신이 실행이 되어도 초기화는 되지 않으며 랜덤 클립을 재생하도록 하였습니다.
✅ 사용 예시
✅ 구현
📑 BlendTreeRandomAnimation.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BlendTreeRandomAnimation : StateMachineBehaviour
{
/// <summary>
/// 블렌드에서 사용하는 파라미터의 이름
/// </summary>
[Header("Parameter name used in blend tree")][SerializeField] private string mStateParameterName;
/// <summary>
/// 블렌드 하는 시간
/// </summary>
[Header("Takes to switch to another clip")][SerializeField] private float mBlendDuration = 0.5f;
/// <summary>
/// 각 클립들의 시간
/// </summary>
[Space(50)][Header("Times in the order you put the clips in")][SerializeField] float[] mClipLengths;
/// <summary>
/// 애니메이터 블렌더
/// </summary>
private AnimatorBlender mAnimBlender;
/// <summary>
/// 최초 1회 실행하기위해 구분
/// </summary>
bool mIsAlreadyExecuted = false;
/// <summary>
/// 현재 딜레이 시간
/// </summary>
private float mCurrentDelay;
/// <summary>
/// 현재 재생중인 클립의 인덱스 번호
/// </summary>
private int mCurrentClipIndex;
private void RefreshClip()
{
mCurrentClipIndex = Random.Range(0, mClipLengths.Length);
mCurrentDelay = mClipLengths[mCurrentClipIndex];
}
private void PlayUpdatedClip(Animator animator)
{
RefreshClip();
mAnimBlender.BlendLerp(animator, mStateParameterName, mCurrentClipIndex, mBlendDuration);
}
// 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)
{
//최초 한번만 실행하도록 한다
if (mIsAlreadyExecuted) { return; }
//애니메이터블렌더 찾기
mAnimBlender = animator.GetComponent<AnimatorBlender>();
//클립 재조정
RefreshClip();
//실행 완료
mIsAlreadyExecuted = true;
}
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
mCurrentDelay -= Time.deltaTime;
if (mCurrentDelay < 0f) { PlayUpdatedClip(animator); }
}
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
//override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
//
//}
// OnStateMove is called right after Animator.OnAnimatorMove()
//override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that processes and affects root motion
//}
// OnStateIK is called right after Animator.OnAnimatorIK()
//override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that sets up animation IK (inverse kinematics)
//}
}
[SerializeField] private string mStateParameterName;
- 블렌드 트리에서 사용할 파라미터의 이름입니다.
[SerializeField] float[] mClipLengths;
- 블렌드 트리에 들어가있는 애니메이션 클립들의 길이를 넣는 배열입니다.
- 인스펙터에서 각 클립의 길이를 넣으면 됩니다.
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
- StateMachineBehaviour을 생성하면 자동으로 오버 로딩되는 함수들 중 하나입니다. 해당 스테이트 머신이 실행되면 이 함수가 실행됩니다. 내부 구현에서 mIsAlreadyExecuted를 사용하여 mIsAlreadyExecuted가 false(기본값) 일 때만 최초 한번 실행되어 변수들이 초기화됩니다.
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
- StateMachineBehaviour을 생성하면 자동으로 오버 로딩되는 함수들 중 하나입니다.
- 스테이트 머신이 플레이 중일 때 매 프레임마다 호출되며 이 함수에서는 현재 남은 시간을 체크하며 남은 시간이 0보다 작아질 경우(끝난 경우) 다른 애니메이션으로 재생하도록 하였습니다.
private void RefreshClip()
- 재생할 다음 클립을 무작위로 설정하고, 설정된 클립의 길이를 시간초에 저장해둡니다.
private void PlayUpdatedClip(Animator animator)
- 클립을 업데이트합니다. 애니메이터 블렌더 클래스에 접근하여 코루틴을 사용하여 애니메이션을 블렌드 하도록 합니다.
- StateMachineBehaviour를 상속받은 클래스에서는 코루틴을 실행할 수 없어 해당 오브젝트에 MonoBehaviour를 상속받은 클래스(AnimatorBlender)를 생성하여 코루틴을 실행할 수 있도록 했습니다.
📑 AnimatorBlender.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AnimatorBlender : MonoBehaviour
{
/// <summary>
/// 현재 클립에서 다른 클립으로 블렌드
/// </summary>
/// <param name="animator">애니메이션 컨트롤러</param>
/// <param name="parameterName">블렌드 트리에서 사용하는 파라미터 이름</param>
/// <param name="toAnimState">설정한 애니메이션 스테이트</param>
/// <param name="duration">몇초동안 블렌드를 할지 설정, -1인경우에는 블렌드하지않고 즉시 toAnimState로 설정</param>
public void BlendLerp(Animator animator, string parameterName, float toAnimState, float duration)
{
if(duration == -1)
{
animator.SetFloat(parameterName, toAnimState);
return;
}
StartCoroutine(SetState(animator, parameterName, toAnimState, duration));
}
/// <summary>
/// 코루틴을 사용하여 블렌드
/// </summary>
private IEnumerator SetState(Animator animator, string parameterName, float toAnimState, float duration)
{
float process = 0;
float currentState = animator.GetFloat(parameterName);
while (true)
{
animator.SetFloat(parameterName, Mathf.Lerp(currentState, toAnimState, process));
process += Time.deltaTime / duration;
if (process > 1.0f)
{
animator.SetFloat(parameterName, toAnimState);
yield break;
}
yield return null;
}
}
}
public void BlendLerp(Animator animator, string parameterName, float toAnimState, float duration)
- 애니메이션의 클립을 블렌드 하기 위해 코루틴을 사용할 때 필요한 MonoBehaviour을 상속받은 클래스입니다.
- BlendTreeRandomAnimation에서 호출되며 코루틴을 실행합니다.
- duration이 -1인 경우에는 블렌드 하지 않고 즉시 다음 클립으로 설정합니다.
✅ 사용
- 블렌드 트리가 있는 스테이트 머신에 BlendTreeRandomAnimation를 추가합니다.
- 블렌드 트리에서 사용할 파라미터 변수 이름을 지정합니다. (변수 이름은 무조건 파라미터 이름이어야 합니다)
- 블렌드 시간은 원하는대로 설정합니다. -1인경우에는 블렌드하지않고 즉시 다른 클립으로 전환됩니다.
- mClipLength 배열에 자신이 넣을 클립의 각 시간만큼 순서에 맞게 시간을 적어줍니다.
- 블렌드 트리에 블렌드 하고 싶은 애니메이션들을 넣습니다.
- Threshold는 각각 1의 배수로 설정합니다. 인덱스 번호와 일치시키기 위함입니다.
- 코루틴을 사용하여 블렌드를 하기 위해 AnimatorBlender.cs를 캐릭터 오브젝트에 추가합니다.
- 애니메이터가 있는 오브젝트에 추가해야 합니다.
- 플레이하여 블렌드가 제대로 적용되는지 확인합니다.
✅ 도움받은 곳(Reference)
해당 기능을 생각하고 있었지만, 기존에 구현되어있던 것들 중 제 기준에 만족스러운 구현되어있는 것을 찾을 수 없었습니다. 여러 레퍼런스를 참고하여 구현했습니다.
'unity game modules' 카테고리의 다른 글
[유니티] 인벤토리 시스템(1) - 인벤토리 관리자 (0) | 2022.12.26 |
---|---|
[유니티] 3D World, Sprite Ordering Layer (0) | 2022.12.23 |
[유니티] 편리하게 소리를 담당하는 사운드 매니저 구현 (0) | 2022.11.21 |
[유니티] NavMesh를 응용하여 '내비게이션(경로 시각화)' 만들기 (0) | 2022.10.22 |
[유니티] 파일브라우저(FileExplorer)를 이용하여 로컬 파일 저장/읽기 (SimpleFileBrowser) (0) | 2022.10.22 |