[Unity] Coroutine Manager
코루틴은 유니티에서 비동기적인 작업이나 순차적인 시간 작업을 효율적으로 처리하기 위해서 사용하는 방식이다. 코루틴을 활용하면 실행을 잠시 멈췄다가 다시 이어 실행할 수 있다는 특징이 있다.
코루틴 매니저를 만드는 이유
여러가지 이유가 있지만, 가장 큰 이유는 특정 오브젝트가 활성화 비활성화가 반복되는 경우 (예시 오브젝트 풀링) 코루틴을 사용하게 되면 문제가 생긴다.
코루틴은 비활성화된 오브젝트 상대로는 작동하지 않기 때문에 풀링되는 오브젝트의 경우 중간에 코루틴이 동작하지 않는 치명적인 문제가 발생할 수 있다.
그리고 코루틴을 매니저를 통해 한곳에서 관리해준다면, 매번 코루틴 함수를 만들고 start를 하는 귀찮음에서도 벗어날 수 있고, 문제 생겼을 경우 참조로 빠르게 버그를 찾을 수 있기 때문이다.
또한 코루틴을 여러개 동시에 사용할 경우 Dictionary를 통해 직관성있게 관리해줄 수 있으며 가독성 면에서도 장점이 있기 때문에 코루틴 매니저를 만들어 사용하게 되었다.
코루틴 매니저 코드
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CoroutineManager : MonoBehaviour
{
private Dictionary<string, Coroutine> _coroutinesDic = new Dictionary<string, Coroutine>();
public void RunCoroutine(IEnumerator coroutine)
{
string coroutineName = nameof(coroutine); // 메서드 이름을 자동으로 추출
if (_coroutinesDic.ContainsKey(coroutineName))
{
StopCoroutine(coroutineName);
}
// 코루틴 실행
Coroutine coroutineInstance = StartCoroutine(CoroutineWrapper(coroutine));
_coroutinesDic[coroutineName] = coroutineInstance;
}
// 코루틴
private IEnumerator CoroutineWrapper(IEnumerator coroutine)
{
yield return StartCoroutine(coroutine);
OnCoroutineFinished(nameof(coroutine));
}
// 강제로 종료해야 하는 경우 사용
public void StopActiveCoroutine(string coroutineName)
{
if (_coroutinesDic.ContainsKey(coroutineName))
{
StopCoroutine(_coroutinesDic[coroutineName]);
_coroutinesDic.Remove(coroutineName);
}
}
// 코루틴이 완료되면 딕셔너리에서 제거
private void OnCoroutineFinished(string id)
{
if (_coroutinesDic.ContainsKey(id))
{
_coroutinesDic.Remove(id);
}
}
}
처음에는 매개변수에 함수 이름을 그대로 들고와서 dictionary의 id값으로 만들거나 하였는데, 타이핑 오타로 인한 문제가 발생할 수 있기 때문에 nameof를 이용해 함수 이름 자체를 string으로 변환하여 id값에 넣어줬다.
또한 wrapper 함수를 이용해 coroutine이 시작하고, 끝날 때 dictionary에서 제거하여 코루틴이 남아있지 않게 관리하였다.
그럼에도 StopActiveCoroutine함수를 만들어둔 이유는 특정 구간에서 강제로 멈춰야 할 필요가 있는 경우에 사용하기 위해 만들어 두었다.
private void OnClick()
{
GameManager.Instance.Coroutine.RunCoroutine(SpawnMonsterCo());
}
이렇게 싱글턴으로 만들어둔 코루틴 매니저를 호출하여 사용하면 된다.
주의점
코루틴은 MonoBehaviour 를 상속받아야 사용할 수 있다. 그래서 GameManager에서 싱글톤으로 생성할때 아래와 같이 new를 하지 않고 그냥 만들어 줘야 한다.
private CoroutineManager _coroutine;
public CoroutineManager Coroutine { get { return _coroutine; } }
그렇다면 오브젝트에 스크립트를 어떻게 붙여서 사용해야 할까?
GameManger를 싱글턴으로 생성한다면, 그 자식으로 코루틴 매니저 오브젝트를 생성해주면 된다.
protected override void Awake()
{
base.Awake();
EnsureManager(ref _coroutine, gameObject.transform);
Init();
}
private void EnsureManager<T>(ref T manager, Transform parent) where T : Component
{
manager = FindObjectOfType<T>();
if (manager == null)
{
GameObject managerObject = new GameObject($"@{typeof(T).Name}");
managerObject.transform.SetParent(parent);
manager = managerObject.AddComponent<T>();
}
}
위와 같이 오브젝트를 생성하여 SetParent를 통해 붙여주고, 컴포넌트도 붙여주는 모습이다.