unity에서는 코루틴을 IEnumerator
로 구현하는데, 먼저 IEnumerable
에 대해서 알아봅시다.
Index
1. IEnumerable?
2. 왜 사용하는가?
3. 유니티에서의 커스텀 코루틴
IEnumerable?
이 콜렉션은 foreachr구문 등에서 개체를 한개한개 넘겨주는 일을 한다. (※ C++에서 STL의 iterator와 비슷?) 콜렉션을 하나하나 넘겨주려면 넘겨주는 쪽이 몇번쨰인지 기억해야 하는데, 이 떄 IEnumerator을 생성해서 해결한다.
즉, IEnumerator는
- 지금 몇번쨰 까지 읽었는지(state)를 기억한다.
- MoveNext()호출 하면 다음 순번으로 이동.
- Current를 요구할 때 해당 순번의 개체를 리턴.
그러면 이런 구조로 볼 수 있겠다. IEnumerable에서 GetEnumerator()호출하면 -> IEnumerator는 Current(), MoveNext(), Reset()등 현재 객체 접근하는 구조. (※ IEnumerator가 더 iterator와 비슷하네 IEnumerable는 컬렉션 관리 같다) Enuerable 인터페이스에는 foreach 구문에서 필요한 멤버들을 약속한 IEnumerator 개체를 반환하는 GetEnumerator 메서드를 제공하고 있다.
그런데 이게 코루틴이랑 무슨관계?? -> C#은 코드를 멈출 수 있다..?? -> 진짜.
public static System.Collections.IEnumerable SomeNumbers()
{
yield return 3;
yield return 5;
yield return 8;
}
이 메서드로 리턴 받은 IEnumerable을 foreach로 돌려보면 3,5,8을 순서대로 리턴 하는데.. why?
- IEnumerable, IEnumerator클래스는 컴파일러가 코드를 보고 자동으로 만들어준다.(사용자 정의 컬렉션)
- IEnumrator가 가지는 state의 초기 값은 -1이다.
- 자동 생성된 IEnumerator에서 MoveNext() 실행하면 yield return 3; 이전까지 실행하고 state를 0으로 옮긴다.
- state가 0일 때 Current를 읽으면 3를 리턴한다.
- 마지막 부분(8)에서 MoveNext()실행 시 false를 리턴한다.
컴파일러가 다해준다?
- 컴파일러가 코드를 yield return 기준으로 나눠서 클래스를 짜준다.
- 그러므로 메서드가 부분부분 나눠서 실행
즉, IEnumerator메서드에서 IEnumerator가 자동생성 되 리턴된 IEnumerator를 받아서 MoveNext()를 실행시키면 코드 앞 부분이 실행된다.
yield return new WaitForSeconds(1);
Current()에서 WaitForSeconds를 리턴 했다면??
- Update()에서 1초가 지났는지 매프레임 마다 체크.
- 만약 1초가 지났다면 IEnumerator의 MoveNext()를 호출 한다.
- 코드 다음 부분이 실행된다.
IEnumerable -> IEnumerator -> 반복기
※ 참고로 반복기란 반복기는 컬렉션에 대해 사용자 지정 반복을 수행하는 데 사용됩니다. 반복기는 메서드 또는 get 접근자일 수 있습니다. 반복기는 yield return 문을 사용하여 한 번에 하나씩 컬렉션의 각 요소를 반환합니다. 클라이언트 코드에서 foreach 문 또는 LINQ 쿼리를 사용하여 반복기를 이용
※ 참고로 C#에서 컬렉션이란 대부분의 응용 프로그램의 경우 관련 개체의 그룹을 만들고 관리하려고 합니다. 개체를 그룹화하는 방법에는 개체 배열을 만들거나 개체 컬렉션을 만드는 두 가지가 있다.(list, IEtorable등이 컬력션에 해당, 배열은 제외) 컬렉션은 클래스이므로 해당 컬렉션에 요소를 추가하려면 먼저 클래스 인스턴스를 선언해야 합니다.(new 키워드) 나중에 컬렉션에 대해 자세히 정리하자. collection 참고 사이트
왜 사용하는가?
코루틴 활용 시 오브젝트의 property를 이용한 물체 이동이라든지, 매 프레임마다 값을 가져와 사용해야 하는 부분들을 Update함수의 의존성 없이도 물체을 움직일 수 있다는 점이다. 물론 어쩔수 없이 의존될 수도 있지만 되도록 피하는게 코드효율을 높이는데 좋고, 필요한 상황에 대해서만 값을 매 프레임마다 가져올 수 있어 되도록 코루틴으로 작성을 하자.
yield return에서 사용할 수 있는 것들
-
yield break; 코루틴을 끝낸다.
-
yield return new WaitForSecondsRealtime (float time); WaitForSeconds와 하는 역할은 동일하지만 결정적으로 다른것은, Scaled Time의 영향을 받지 않고 현실 시간 기준으로만 동작
-
yield return new WaitForFixedUpdate (); 다음 FixedUpdate() 가 실행될때까지 기다리게 됩니다. 이 FixedUpdate()는 Update()와 달리 일정한 시간 단위로 호출되는 Update() 함수라고 생각하시면 됩니다.
-
yield return new WaitForEndOfFrame (); 하나의 프레임워 완전yield return null;히 종료될 때 호출이 됩니다. Update(), LateUpdate() 이벤트가 모두 실행되고 화면에 렌더링이 끝난 이후에 호출
-
yield return null; 다음 Update() 가 실행될때까지 기다린다는 의미를 갖게 됩니다. 좀 더 정확하게는 Update()가 먼저 실행되고 null을 양보 반환했던 코루틴이 이어서 진행 됩니다. 그 다음에 LateUpdate()가 호출
-
yield return new WaitUntil (System.Func
predicate); WaitUntil에 실행하고자 하는 식을 정의해 두면 매번 Update() 와 LateUpdate() 이벤트 사이에 호출해 보고 결과값이 true면 이후로 재진행IEnumerator Example() { Debug.Log("조건만족 까지 기다리는 중..."); yield return new WaitUntil(() => frame >= 10); Debug.Log("조건만족 후"); } void Update() { if (frame <= 10) { Debug.Log("Frame: " + frame); frame++; } }
-
yield return new WaitWhile(System.Func
predicate); WaitWhile은 WaitUntil과 동일한 목적을 가지고 있지만 한가지만 다릅니다. WaitUntil은 람다식 실행 결과값이 true가 될때까지 기다린다면 WaitWhile은 false가 될때까지 기다립니다. 즉 WaitWhile은 결과가 true인 동안 계속 기다린다.IEnumerator Example() { Debug.Log("조건만족 까지 기다리는 중..."); yield return new WaitWhile(() => frame < 10); Debug.Log("조건만족 후"); } void Update() { if (frame <= 10) { Debug.Log("Frame: " + frame); frame++; } }
10프레임에 도달하면 false가 되어서 이후 진행이 되겠네요.
-
yield return StartCoroutine (IEnumerator coroutine); 코루틴 내부에서 또다른 코루틴을 호출할 수 있습니다. 물론 그 코루틴이 완료될 때까지 기다리게 됩니다. 의존성 있는 여러작업을 수행하는데에 유리하게 사용.
IEnumerator TestRoutine() { Debug.Log ("Run TestRoutine"); yield return StartCoroutine (OtherRoutine ()); Debug.Log ("Finish TestRoutine"); } IEnumerator OtherRoutine() { Debug.Log ("Run OtherRoutine #1"); yield return new WaitForSeconds (1.0f); Debug.Log ("Run OtherRoutine #2"); yield return new WaitForSeconds (1.0f); Debug.Log ("Run OtherRoutine #3"); yield return new WaitForSeconds (1.0f); Debug.Log ("Finish OtherRoutine"); }
출력결과
Run TestRoutine Run OtherRoutine #1 Run OtherRoutine #2 Run OtherRoutine #3 Finish OtherRoutine Finish TestRoutine
-
yield return new WWW(string) URL의 내용을 검색하기위한 작은 유틸리티 모듈이며, 호출 WWW(url)로 백그라운드에서 다운로드를 시작 속성을 검사하여 isDone다운로드가 완료되었는지 확인하거나 다운로드 객체를 생성하여 게임의 나머지 부분을 차단하지 않고 자동으로 기다릴 수 있습니다. 웹 서버에서 데이터를 가져 오려는 경우에 사용하십시오. 또한 웹에서 다운로드 한 이미지로 텍스처를 만들고 새로운 웹 플레이어 데이터 파일을 스트리밍하고로드하는 기능.
public class ExampleClass : MonoBehaviour { public string url = "http://images.earthcam.com/ec_metros/ourcams/fridays.jpg"; IEnumerator Start() { //스트림 관련 이므로 using쓴듯 using (WWW www = new WWW(url)) { yield return www; Renderer renderer = GetComponent<Renderer>(); renderer.material.mainTexture = www.texture; } } }
- tips : Note: URLs passed to WWW class must be ’%’ escaped.
- tips : http://, https:// and file:// protocols are supported on iPhone. ftp:// protocol support is limited to anonymous downloads only. Other protocols are not supported.
- tips : When using file protocol on Windows and Windows Store Apps for accessing local files, you have to specify file:/// (with three slashes).
- yield return new AsyncOperation 비동기 작업이 끝날 때까지 대기 ( 씬로딩 )
유니티에서의 커스텀 코루틴
-
코루틴 중간중간 확인이 필요한 값을 간단한 트릭으로 확인 가능
-
코루틴 예외에러 처리를 일괄적 처리 가능
-
참고사이트 : https://www.youtube.com/watch?v=ciDD6Wl-Evk&feature=youtu.be
public static class MonoBehaviorExt {
public static Coroutine<T> StartCoroutine<T>(this MonoBehaviour obj, IEnumerator coroutine) {
Coroutine<T> coroutineObject = new Coroutine<T>();
coroutineObejct.coroutine = obj.startCoroutine(coroutineObejct.InteranlRoutine(coroutine));
return coroutineObject;
}
}
public class Coroutine<T> {
public T Value {
get {
if(e != null) {
throw e;
}
return returnVal;
}
}
private T returnVal; //코루틴 작동 사이에 원하는 값 체크 용도.
private Exception e; //에러 캐치 용도.
public Coroutine coroutine;
public IEnumerator InternalRoutine(IEnumerator coroutine) {
while(true) {
//에러 핸들링
try {
//다음 state로 이동.
if(!coroutine.MoveNext()) {
yield break;
}
}
catch(Exception e) {
//에러 캐치
this.e = e;
yield break;
}
object yielded = coroutine.Current;
if(yielded != null && yielded.GetType() is typeof(T)) {
//TypeICareAbout 특정 타입 변수 저장
returnVal = (T)yielded;
}
else {
yield return coroutine.Current;
}
}
}
}