[Unity/C#]코루틴(Coroutine)

강동현·2024년 2월 25일
0

Unity/C#

목록 보기
21/26

선행지식

  • 동기 vs 비동기
  • (I)Enumerator/(I)Enumerable

Main 루틴

  • 프로그램의 주 실행 흐름으로 순차적으로 명령을 수행
  • 프로세스가 종료될 때까지 실행

Sub 루틴

  • 메인 루틴에서 호출되는 메서드
  • 특정 작업(함수) 수행 후, 함수 종료 시 메인 루틴으로 복귀

코루틴 : Coroutine

  • 메인 루틴독립적으로 여러 프레임에 걸쳐 실행되는 별도 실행 흐름
  • 비동기처럼(실제론 싱글스레드 동기) 작업이 수행되도록 여러 프레임에 분산해주는 특별한 메소드
  • yield return을 통해 작업을 일시 중단(제어권 양보)한 뒤 다른 프레임/시간에 제어권을 넘겨받아 재개(중단된 곳부터)하는 방식
  • 중지 / 재개 / 반복 제어
  • [★]게임오브젝트 비활성화/파괴되면 코루틴 종료
  • IEnumerator 반환
public class Test : MonoBehaviour
{
	//Main 루틴
	void Start()
    {
    	Func();
    }
    //Sub 루틴
   	void Func()
    {
    	...
    }
    //Co 루틴
    IEnumerator Co(){
    	yield return null;
    }
}

코루틴 작동 원리[심화]

  • 코루틴은 IEnumerator를 반환
  • 왜 yield return ...이 IEnumerator를 반환하는가?
    1. yield return 문이 선언된 매서드는 C# 컴파일러에 의해 자동으로 상태 머신으로 변환됨
    1. 상태 머신IEnumerator 인터페이스를 상속하는 Enumerator 클래스를 생성
    • Object Current property
    • MoveNext();
    • Reset()
    • Dispose()
    • 위 사항 모두 자동 구현
  • 때문에, 자동적으로 Enumerator를 얻어와 실행히키는 것
  • StartCoroutine() 같은 경우 내부적으로 while(enumerator.MoveNext()) { ... }를 돌림
  • 이를 통해 코루틴이 수행됨

코루틴 사용법

1. MonoBehaviour 상속

  • 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)을 통해 호출
//방식1: string 접근[비추천]
StartCoroutine("FadeIn");
//방식2: 함수 객체(=Pred=함수 포인터) 접근[추천]
StartCoroutine(FadeIn());
//방식3: Coroutine 변수에 저장 및 사용[★추천]
Coroutine co = StartCoroutine(FadeIn());
StartCoroutine(co);

4. yield return (조건) 사용

  • 제어권넘긴다(양보=yield)
  • 조건 달성 시, 재개
  • yield return을 만나는 순간, 프레임 변경가 발생
IEnumerator Count()
{
    //프레임X
    Debug.Log("1");
    yield return new WaitForSeconds(1);
    //제어권을 넘김 = 대기
    //일정 조건 달성 시 재개
    //프레임Y
    Debug.Log("1");
    yield return new WaitForSeconds(1);
    //제어권을 넘김 = 대기
    gameObject.SetActive(false);
    //Destroy(gameObject);
    //일정 조건 달성 시 재개
    //프레임Z
    Debug.Log("1");
    yield return new WaitForSeconds(1);
    //제어권을 넘김 = 대기
    //일정 조건 달성 시 재개
    //프레임W
}

5. 코루틴 종료 방법 : StopCoroutine

  • StopCoroutine
//방식1: string 접근[비추천]
StopCoroutine("FadeIn");
//방식2: 함수 객체(=Pred=함수 포인터) 접근[추천]
StopCoroutine(FadeIn());
//방식3: Coroutine 변수에 저장 및 사용[★추천]
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);

  • 다음 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(){
	...
    //time > 10이 될때까지 대기
	yield return new WaitUntil(() => time > 10);
}

7. yield return new WaitWhile(Func*);

  • 조건이라면 대기(거짓일 때까지 대기 후 실행)
IEnumerator CoroutineTest(){
	...
    //time > 10인 동안 대기
	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로 대체하는 경우도 존재
      • UpdateGarbage 생성 X
  • 자주 사용하는 YieldInstruction캐싱을 통해 재활용해야 함
public class CoroutineTest : MonoBehaviour
{
    //캐싱
    WaitForFixedUpdate wf = new WaitForFixedUpdate();
    IEnumerator Start()
    {
    	...
        yield return wf;//캐싱
    }
    //Unity 내장함수는 yield return을 통한 지연 호출이 가능
    //물리법칙 - OnCollision 시리즈
    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);
...
  • 이 번거로운 과정을 극복하는 방법
      1. Util.csstatic readonly Dictionary<시간,WaitForSeconds객체> 자료구조를 생성
      1. 해당 자료구조에 접근하는 전역 함수 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()
    {
    	//0.5초 후에 Delay 함수 실행
    	Invoke(nameof(Delay), 0.5f);
    }
    void Delay()
    {
    	print("출력");
    }
}
  • InvokeRepeating
public class Test : MonoBehaviour
{
	void Start()
    {
    	//0.5초 후에 Delay 함수 실행
        //후에 1초 간격으로 Delay 함수 실행
    	InvokeRepeating(nameof(Delay), 0.5f, 1f);
    }
    void Delay()
    {
    	print("출력");
    }
}
  • CancelInvoke
public class Test : MonoBehaviour
{
	void Start()
    {
    	//매개변수가 있는 경우, 해당 Func의 인보크 종료
    	CancelInvoke(nameof(Delay));
        //매개변수가 없는 경우, 해당 스크립트의 진행중인 Invoke를 모두 종료
        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;
    }
}
profile
GAME DESIGN & CLIENT PROGRAMMING

0개의 댓글