Game Development, 게임개발/개발

Coroutine,코루틴(IEnumerator, StartCoroutine, yield 등 )에 대한 모든 것 [Unity]

게임이 더 좋아 2021. 3. 30. 07:04
반응형
728x170

 

 

 

 

모든 것을 자세히 알기 전에 코루틴(Coroutine)이라는 것에 대해서 알고 가야 한다.

A coroutine is a function that can suspend its execution (yield) until the given YieldInstruction finishes.

 

쉽게 말하면 잠시 ~할 시간을 준다. 다시 말해서 Interval을 준다.

YieldInstruction이 끝날 때까지 Interval을 주는 것이다.

 


 

근데 왜 쓰느냐?

 

우선 대표적으로는 Single function임에도 시간이 오래걸리는 함수를 실행한다면

그 실행동안은 게임이 frozen 된 것 같이 보이겠지? 그래서 코루틴으로 그것을 해결할 수 있고

또한 Timer같이 일정 시간 이후에 쓰이게 할 수 있다.

 

 

코루틴(Coroutine)은 항상 실행되는 것이 아니라 필요한 상황에서만 발생시킬수 있다.

즉, Update 문 밖에서 쓰일 수 있다!

 

**모든 조건문을 Update()에다 넣는다면 아무리 빨리한다 하더라도.. 불필요한 조건문까지 읽는 상황이 나오기 때문에 성능이 조금이라도 내려가겠지?

-> 매 프레임 연산되는 양을 줄여서 효율적으로 컴퓨터 자원 사용 가능케함.

 

 


그럼 언제쓰느냐?

 

아까 Interval, 시간을 벌어준다고 말했다.

코루틴(Coroutine)은 대개 WaitForSecond 시간을 벌고(대기) 일시적으로 사용될 작업들이나 비동기 작업에 사용이 된다.

 

사실 어떤 작업이나 조건이 될 때까지 기다리는 건 마찬가지 아니냐? 라고 할 수 있다.

 

**yield return의 뜻은 코루틴이 가진 control을 다시 Unity에게 준다고 보면 되겠다.

yield return 뒤에 나오는 interval 만큼 코드 동작을 중지하고 제어권을 유니티에게 준다.

 


 

어떻게 쓰느냐??

 

Coroutine 메서드를 실행시킬 땐 일반 메서드 call 하듯이 그렇게 부를 수 없다. 

 

StartCoroutine을 필요로 한다.

 

원형을 보고가자.

 

public Coroutine StartCoroutine(IEnumerator routine);

public Coroutine StartCoroutine(string methodName, object value = null);

 

**StartCoroutine은 IEnumerator 반환을 원함 -> IEnumerator로 코루틴을 이용하는 이유

Coroutine의 이름으로도 호출 가능 -> StartCoroutine("Name") 가능 - (StopCoroutine("Name")) 가능

 

**코루틴(Coroutine)은 Start() 메서드 실행 중 또는 이후에 GameObject가 active = true 상태에서 호출 가능

-> 즉, Start() 이전의 Awake()에서 실행되거나 GameObject가 active = false 일 때 정상적으로 작동하지 않는다.

 

**코루틴을 서브 update()의 형태처럼 Infinite Loop를 적용하는 경우가 있는데 반복문 바깥에 yield return을 쓰는 경우가 있다. (하지만 에디터에선 오류로 발견되지 않는다)

이 경우 오류가 발생한다. -> 반복문 안에는 제어권을 돌려주는 yield return이 없기 때문이다.

 

반복문 안에만 써넣으면 되느냐?? 그것도 아니다.

예를 들어, 코루틴 반복문 안에 yield return null 이 들어가 있다면, 이 코루틴은 다음 프레임에서 다시 이 반복문을 실행하게 된다.

 만약, 코루틴 안에 무한반복하는 반복문이 들어간다면 유니티는 1프레임 안에서 무한루프 작업을 해야 하니 유니티가 통째로 멈춰 버리는 사태가 발생할 수 있다.

 코루틴 안에 계속해서 반복하는 함수를 넣고 싶다면, yield 문을 통해 매 프레임, 혹은 매 초마다 반복을 하도록 하는 것이 내 건강에 좋다.

 

 


 

yield return이 쓰이는 방법

 

yield return null 다음 프레임까지 대기 -> 한 프레임만 기다림
yield return new WWW(string) 웹 통신 작업이 끝날 때까지 대기
yield return new WaitForSeconds(float) 지정된 초 만큼 대기 -> 내가 넣은 숫자만큼 대기

++ WaitForSecondsRealTime -> TimeScale에 영향을 받냐 안받냐 차이임.(RealTime은 영향 안받음)


yield return new WaitForFixedUpdate() 다음 물리 프레임까지 대기 -> FixedUpdate가 끝나면 제어권을 받음
yield return new WaitForEndOfFrame() 모든 렌더링작업이 끝날 때까지 대기 -> 한 프레임의 연산이 다 끝나고 마지막에 제어권을 받음
yield return StartCoRoutine(string) 다른 코루틴이 끝날 때까지 대기
yield return new AsyncOperation 비동기 작업이 끝날 때까지 대기 ( 씬로딩 )

**TimeScale은 게임 속에서 흐르는 시간을 말함

 


 

 

추가로 Coroutine 안에서 특정 조건이 달성하면 그냥 거기서 바로 Coroutine을 멈추고 싶을 때가 있는데


그 때는 yield return이 아닌 yield break문을 쓰면 바로 그 순간부터 그 코루틴 메서드는 멈춰버린다.

 

 


 

예시를 보자

using UnityEngine;
using System.Collections;

public class CoroutinesExample : MonoBehaviour
{
    public float smoothing = 1f;
    public Transform target;


    void Start ()
    {
        StartCoroutine(MyCoroutine(target));
    }


    IEnumerator MyCoroutine (Transform target)
    {
        while(Vector3.Distance(transform.position, target.position) > 0.05f)
        {
            transform.position = Vector3.Lerp(transform.position, target.position, smoothing * Time.deltaTime);

            yield return null; // 한 프레임 기다림
        }

        print("Reached the target.");

        yield return new WaitForSeconds(3f); //3초 기다림

        print("MyCoroutine is now finished.");
    }
}

 

일반적으로 코루틴을 쓰는 예다.

위에 거는 솔직히 코루틴을 안써도 괜찮다고 느껴진다.

 

 

아래 2개는 조금 자세히 보자

 

using UnityEngine;
using System.Collections;

public class ClickSetPosition : MonoBehaviour
{
    public PropertiesAndCoroutines coroutineScript;


    void OnMouseDown ()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        Physics.Raycast(ray, out hit);

        if(hit.collider.gameObject == gameObject)
        {
            Vector3 newTarget = hit.point + new Vector3(0, 0.5f, 0);
            coroutineScript.Target = newTarget;
        }
    }
}

이 스크립트는  클릭하면 그 위치로 target의 위치를 저장하는 메서드를 가지고 있다.

 

 

그렇다면 set이 일어나는 것인데

set이 일어난 경우 Movement라는 코루틴을 멈춘다.

그리고 다시 target을 파라미터로 주면서 코루틴을 다시 시작한다.

 

"Movement" 메서드는 Lerp 방식으로 어떠한 오브젝트의 위치를 변화시키는 것인데.

이렇게 코루틴을 사용하게 되면 Update()에 넣지 않고도 

set이 일어날 때 메서드를 실행할 수 있다. 

이것이 코루틴의 강점이라고 말할 수 있다. 

Update()문에서 매 프레임 관리할 필요가 없다는 말이다.

 

 

using UnityEngine;
using System.Collections;

public class PropertiesAndCoroutines : MonoBehaviour
{
    public float smoothing = 7f;
    public Vector3 Target
    {
        get { return target; }
        set
        {
            target = value;

            StopCoroutine("Movement");
            StartCoroutine("Movement", target);
        }
    }


    private Vector3 target;


    IEnumerator Movement (Vector3 target)
    {
        while(Vector3.Distance(transform.position, target) > 0.05f)
        {
            transform.position = Vector3.Lerp(transform.position, target, smoothing * Time.deltaTime);

            yield return null;
        }
    }
}

 

 

 

 


참고자료

docs.unity3d.com/ScriptReference/YieldInstruction.html

docs.unity3d.com/ScriptReference/Coroutine.html

www.youtube.com/watch?v=5L9ksCs6MbE

docs.microsoft.com/ko-kr/dotnet/api/system.collections.ienumerable?view=netcore-3.1

docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html

 

 

 

반응형
그리드형