선행지식
동기
vs 비동기
(I)Enumerator
/(I)Enumerable
Main 루틴
- 프로그램의 주 실행 흐름으로 순차적으로 명령을 수행
- 프로세스가 종료될 때까지 실행
Sub 루틴
- 메인 루틴에서 호출되는 메서드
- 특정 작업(함수) 수행 후, 함수 종료 시 메인 루틴으로 복귀
코루틴 : Coroutine
메인 루틴
과 독립적
으로 여러 프레임
에 걸쳐 실행되는 별도 실행 흐름
비동기처럼(실제론 싱글스레드 동기)
작업이 수행되도록 여러 프레임에 분산
해주는 특별한 메소드
yield return
을 통해 작업을 일시 중단(제어권 양보)한 뒤 다른 프레임/시간
에 제어권을 넘겨받아 재개(중단된 곳부터)
하는 방식
중지
/ 재개
/ 반복
제어
- [★]
게임오브젝트
비활성화
/파괴
되면 코루틴 종료
IEnumerator
반환
public class Test : MonoBehaviour
{
void Start()
{
Func();
}
void Func()
{
...
}
IEnumerator Co(){
yield return null;
}
}
코루틴 작동 원리[심화]
- 코루틴은
IEnumerator
를 반환
- 왜 yield return ...이 IEnumerator를 반환하는가?
- yield return 문이 선언된 매서드는
C# 컴파일러
에 의해 자동으로 상태 머신
으로 변환됨
상태 머신
이 IEnumerator 인터페이스
를 상속하는 Enumerator 클래스
를 생성
- Object Current property
- MoveNext();
- Reset()
- Dispose()
- 위 사항 모두 자동 구현
- 때문에, 자동적으로 Enumerator를 얻어와 실행히키는 것
- StartCoroutine() 같은 경우 내부적으로 while(enumerator.MoveNext()) { ... }를 돌림
- 이를 통해 코루틴이 수행됨
코루틴 사용법
1. MonoBehaviour 상속
public class Test : MonoBehaviour { ... }
2. 선언 : IEnumerator
코루틴
은 IEnumerator 타입을 반환하는 함수
IEnumerator FadeIn(){
while(alpha < 1.0f)
{
alpha += Time.deltaTime / fadeTime;
image.color = new Color(1,1,1,alpha);
yield return null;
}
}
3. 호출 : StartCoroutine
- StartCoroutine(IEnumerator)을 통해 호출
StartCoroutine("FadeIn");
StartCoroutine(FadeIn());
Coroutine co = StartCoroutine(FadeIn());
StartCoroutine(co);
4. yield return (조건) 사용
제어권
을 넘긴다(양보=yield
)
조건
달성 시, 재개
yield return
을 만나는 순간, 프레임 변경
가 발생
IEnumerator Count()
{
Debug.Log("1");
yield return new WaitForSeconds(1);
Debug.Log("1");
yield return new WaitForSeconds(1);
gameObject.SetActive(false);
Debug.Log("1");
yield return new WaitForSeconds(1);
}
5. 코루틴 종료 방법 : StopCoroutine
StopCoroutine("FadeIn");
StopCoroutine(FadeIn());
Coroutine co = StartCoroutine(FadeIn());
StopCoroutine(co);
StopAllCoroutine
: 해당 스크립트
의 모든 코루틴
종료
StopAllCoroutine();
Yield
Yield
: 주도권
을 다른 프레임
에 양보
하는 것
YieldInstruction
클래스
1. yield return null;
Update의 한 프레임(=Time.deltaTime)
동안 대기 후 실행
IEnumerator CoroutineTest(){
yield return new WaitForSeconds(time);
while (time < 1.0f)
{
yield return null;
time += Time.deltaTime;
}
}
2. yield return new WaitForFixedUpdate();
FixedUpdate의 한 프레임(=Time.fixedDeltaTime)
동안 대기 후 실행
IEnumerator CoroutineTest(){
...
yield return new WaitForFixedUpdate();
}
3. yield return new WaitForEndOfFrame();
Unity Event 순서
상 렌더링이 마무리되는 거의 마지막 시점
에 호출
모든 렌더링 작업이 완료되는 프레임이 끝날때
까지 대기 후 실행
IEnumerator CoroutineTest(){
...
yield return new WaitForEndOfFrame();
}
4. yield return new WaitForSeconds(time);
IEnumerator CoroutineTest(){
...
yield return new WaitForSeconds(time);
}
IEnumerator CoroutineTest(){
...
while (time < 1.0f)
{
yield return null;
time += Time.deltaTime;
}
}
5. yield return new WaitForSecondsRealtime(time);
TimeScale
에 영향받지 않음
float time
만큼 시간이 지난 후 첫 프레임까지 대기 후 실행
IEnumerator CoroutineTest(){
...
yield return new WaitForSecondsRealtime(time);
}
6. yield return new WaitUntil(Func*);
IEnumerator CoroutineTest(){
...
yield return new WaitUntil(() => time > 10);
}
7. yield return new WaitWhile(Func*);
조건
이 참
이라면 대기
(거짓일 때까지 대기 후 실행)
IEnumerator CoroutineTest(){
...
yield return new WaitWhile(() => time > 10);
}
코루틴 체인[★]
StartCoroutine()
은 Coroutine 형식
을 반환
- 이를 이용해 코루틴 내에 코루틴을
연쇄적으로 호출 가능
간결
하면서도 유동적
인 절차적 움직임 구현
에 용이
IEnumerator Coroutine1()
{
yield return StartCoroutine(Coroutine2());
yield return StartCoroutine(Coroutine3());
print("모든 코루틴 끝남");
}
IEnumerator Coroutine2()
{
while(true){
time2 += Time.deltaTime;
if(time2 >= 2){
time2 = 2f;
break;
}
yield return null;
}
}
IEnumerator Coroutine3()
{
yield return new WaitForSeconds(3f);
}
캐싱 : 코루틴
코루틴 수행 시
마다 Garbage
가 생성된다.
yield return new
를 수행시마다 Garbage
생성
성능 저하
문제
특정 코루틴
을 반복 사용
할 필요가 있으면, Update
로 대체하는 경우도 존재
자주 사용하는 YieldInstruction
은 캐싱
을 통해 재활용
해야 함
public class CoroutineTest : MonoBehaviour
{
WaitForFixedUpdate wf = new WaitForFixedUpdate();
IEnumerator Start()
{
...
yield return wf;
}
IEnumerator OnCollisionEnter()
{
yield return wf;
}
IEnumerator OnMouseEnter()
{
yield return wf;
}
private IEnumerator OnBecameInvisible()
{
yield return wf;
}
IEnumerator CoTest()
{
yield return wf;
}
Util.cs 캐싱
- Util.cs
Util.cs 파일
엔 매우 효율적
이거나 전역적
으로 사용하는 공용 함수
를 작성해, 효율적인 작업
을 위한 개발자들의 협업 공간
으로 사용한다.
- 이런 번거로운 과정을 해결할 때 사용한다.[★]
Util.cs 클래스
에 캐싱
을 전역적
으로 만들고 생성 및 사용
- WaitForSeconds를 캐싱하는 방법은 번거롭다.
WaitForSeconds wf0.1 = new WaitForSeconds(0.1f);
WaitForSeconds wf0.2 = new WaitForSeconds(0.2f);
WaitForSeconds wf0.3 = new WaitForSeconds(0.3f);
WaitForSeconds wf0.4 = new WaitForSeconds(0.4f);
WaitForSeconds wf0.5 = new WaitForSeconds(0.5f);
...
- 이 번거로운 과정을 극복하는 방법
Util.cs
에 static readonly Dictionary<시간,WaitForSeconds객체>
자료구조를 생성
- 해당 자료구조에 접근하는
전역 함수 WaitForSecond
생성
원하는 시간의 WaitForSecond 객체
가 존재하면 반환
원하는 시간의 WaitForSecond 객체
가 없다면 생성/저장/반환
using System.Collections.Generic;
using UnityEngine;
public class Util : MonoBehaviour
{
public static readonly WaitForFixedUpdate m_WaitForFixedUpdate = new WaitForFixedUpdate();
public static readonly WaitForEndOfFrame m_WaitForEndOfFrame = new WaitForEndOfFrame();
private static readonly Dictionary<float, WaitForSeconds> m_WaitForSecondsDict = new Dictionary<float, WaitForSeconds>();
public static WaitForSeconds WaitForSecond(float waitTime)
{
WaitForSeconds wfs;
if (m_WaitForSecondsDict.TryGetValue(waitTime, out wfs)) return wfs;
else
{
wfs = new WaitForSeconds(waitTime);
m_WaitForSecondsDict.Add(waitTime, wfs);
return wfs;
}
}
}
Invoke
매개변수 없는
간단한 지연
을 구현할 땐, Coroutine
말고 Invoke
를 사용할 수도 있다.
[★]코루틴과 달리 게임 오브젝트가 비활성화
되어도 수행
됨
CancelInvoke
를 매우 적절히 사용해야 함
Invoke
public class Test : MonoBehaviour
{
void Start()
{
Invoke(nameof(Delay), 0.5f);
}
void Delay()
{
print("출력");
}
}
public class Test : MonoBehaviour
{
void Start()
{
InvokeRepeating(nameof(Delay), 0.5f, 1f);
}
void Delay()
{
print("출력");
}
}
public class Test : MonoBehaviour
{
void Start()
{
CancelInvoke(nameof(Delay));
CancelInvoke();
}
}
public class Test : MonoBehaviour
{
[SerializeField] bool attackable = true;
void Update()
{
if(attackable && Input.GetKeyDown(KeyCode.Space))
{
attackable = false;
Invoke(nameof(AttackDelay), 1f);
}
}
void AttackDelay()
{
attackable = true;
}
}