반응형
애니메이터 사용 중, 애니메이터 상태 변환 완료 이후 시점에 처리가 필요한 경우가 있습니다. 아래의 스크립트를 추가하면, 확장 함수로 편하게 사용 가능합니다.
우리가 필요로 하는 기능
animator.SetTrigger("Die");
캐릭터가 죽고 나서 보여지는 애니메이션 이후, 해당 상태가 완전히 끝나고 나서 처리가 필요하지만, 이는 유니티에서 지원하지 않습니다. 보통은 AnimationEvent 달아 주어 처리를 하거나, 특정을 직접 입력하여 처리를 합니다.
animator.SetTrigger("Die", () =>
{
// 스테이트가 종료된 이후 호출되는 구간
this.gameObject.SetActive(false);
});
위와 같이, 애니메이션 종료 이후 타이밍을 얻게 되며, 추가 적인 처리가 가능해집니다.
스크립트
AnimatorEventReceiever.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class AnimatorEventExtension
{
private static AnimatorEventReceiever AttachReceiever(ref Animator animator)
{
AnimatorEventReceiever receiever = animator.gameObject.GetComponent<AnimatorEventReceiever>();
if (receiever == null) receiever = animator.gameObject.AddComponent<AnimatorEventReceiever>();
return receiever;
}
public static void SetInteger(this Animator animator, string name, int value, Action onFinished)
{
animator.SetInteger(name, value);
AttachReceiever(ref animator).OnStateEnd(onFinished);
}
public static void SetInteger(this Animator animator, int id, int value, Action onFinished)
{
animator.SetInteger(id, value);
AttachReceiever(ref animator).OnStateEnd(onFinished);
}
public static void SetFloat(this Animator animator, string name, float value, Action onFinished)
{
animator.SetFloat(name, value);
AttachReceiever(ref animator).OnStateEnd(onFinished);
}
public static void SetFloat(this Animator animator, int id, float value, Action onFinished)
{
animator.SetFloat(id, value);
AttachReceiever(ref animator).OnStateEnd(onFinished);
}
public static void SetBool(this Animator animator, string name, bool value, Action onFinished)
{
animator.SetBool(name, value);
AttachReceiever(ref animator).OnStateEnd(onFinished);
}
public static void SetBool(this Animator animator, int id, bool value, Action onFinished)
{
animator.SetBool(id, value);
AttachReceiever(ref animator).OnStateEnd(onFinished);
}
public static void SetTrigger(this Animator animator, string name, Action onFinished)
{
animator.SetTrigger(name);
AttachReceiever(ref animator).OnStateEnd(onFinished);
}
public static void SetTrigger(this Animator animator, int hashid, Action onFinished)
{
animator.SetTrigger(hashid);
AttachReceiever(ref animator).OnStateEnd(onFinished);
}
}
[RequireComponent(typeof(Animator))]
public class AnimatorEventReceiever : MonoBehaviour
{
#region Inspector
public List<AnimationClip> animationClips = new List<AnimationClip>();
#endregion
private Animator _animator = null;
private Dictionary<string, List<Action>> _startEvnets = new Dictionary<string, List<Action>>();
private Dictionary<string, List<Action>> _endEvents = new Dictionary<string, List<Action>>();
private Coroutine _coroutine = null;
private bool _isPlayingAnimator = false;
public void OnStateEnd(Action onFinished)
{
if (_coroutine != null)
StopCoroutine(_coroutine);
_coroutine = StartCoroutine(OnStateEndCheck(onFinished));
}
public IEnumerator OnStateEndCheck(Action onFinished)
{
_isPlayingAnimator = true;
while (true)
{
yield return new WaitForEndOfFrame();
if (!_isPlayingAnimator)
{
// 다음 애니메이션 클립이 재생되는지 1프레임 더 기다림
yield return new WaitForEndOfFrame();
if (!_isPlayingAnimator) break;
}
}
onFinished?.Invoke();
}
private void Awake()
{
// 애니메이터 내에 있는 모든 애니메이션 클립의 시작과 끝에 이벤트를 생성한다.
_animator = GetComponent<Animator>();
for (int i = 0; i < _animator.runtimeAnimatorController.animationClips.Length; i++)
{
AnimationClip clip = _animator.runtimeAnimatorController.animationClips[i];
animationClips.Add(clip);
AnimationEvent animationStartEvent = new AnimationEvent();
animationStartEvent.time = 0;
animationStartEvent.functionName = "AnimationStartHandler";
animationStartEvent.stringParameter = clip.name;
clip.AddEvent(animationStartEvent);
AnimationEvent animationEndEvent = new AnimationEvent();
animationEndEvent.time = clip.length;
animationEndEvent.functionName = "AnimationEndHandler";
animationEndEvent.stringParameter = clip.name;
clip.AddEvent(animationEndEvent);
}
}
/// <summary>
/// 각 클립 별 시작 이벤트
/// </summary>
/// <param name="name"></param>
private void AnimationStartHandler(string name)
{
if (_startEvnets.TryGetValue(name, out var actions))
{
for (int i = 0; i < actions.Count; i++)
{
actions[i]?.Invoke();
}
actions.Clear();
}
_isPlayingAnimator = true;
}
/// <summary>
/// 클립 별 종료 이벤트
/// </summary>
/// <param name="name"></param>
private void AnimationEndHandler(string name)
{
if (_endEvents.TryGetValue(name, out var actions))
{
for (int i = 0; i < actions.Count; i++)
{
actions[i]?.Invoke();
}
actions.Clear();
}
_isPlayingAnimator = false;
}
}
예제
animator.SetTrigger("Trigger Parameter Name", () =>
{
// 애니메이터 상태 변환 완료 이후 처리
});
위와 같이 사용하면, 기존 함수와 동일한 방법에서 확장함수로 언제든 사용 가능합니다. 주의하실 점으로는 Loop되고 있는 애니메이션 클립에 걸리면 이벤트를 돌려주지 않습니다. 무한으로 돌고 있으니 애니메이터 상태 변화는 계속 일어나고 있으므로 안 주는 부분 인지바랍니다.
호출 시, 인스턴스하게 생성되는 컴포넌트와 이벤트 키
확장 함수 사용 시, Animator 컴포넌트가 있는 오브젝트와 클립에서는 아래와 같은 변화가 일어납니다.
1. 컴포넌트를 붙인다.
2. 각 애니메이션 클립에 이벤트키 생성
반응형
'Utils' 카테고리의 다른 글
NGUI Custom Extension Tool (0) | 2022.03.24 |
---|---|
Unity, 화면 스크린 캡쳐 (0) | 2022.03.05 |
C#, DateTime 이번 달의 첫날, 말일 구하기 (2) | 2022.01.25 |
C#, 커스텀 밴치마크 라이브러리 (함수 테스트 도구) (2) | 2022.01.16 |
유니티, 코루틴 최적화(Coroutine Yield Instruction) (0) | 2021.12.23 |
댓글