Coroutine - 코루틴 : 개념파트
Coroutine은 Unity에서 지원하는 멀티태스킹 방법중에 하나이다. python에서도 비슷한 기법이 있다. generator expression에서 yield를 이용해서 어떤 특정한 값을 리턴하게 만드는 것이다. 그럼 yield문에서 당장의 컨트롤은 그 함수 밖으로 나가지만, 컴퓨터는 그 함수가 어디까지 진행되었는지를 기억해둔다. 다시 함수를 불렀을 때, yield문의 다음 줄을 시행해서 동시에 어떤 작업을 하는 것처럼 보이게 만드는 것이다.
이를 그림으로 표현하면 다음과 같다 :
코루틴이 실행되는 것을 이미지를 이용해서 이렇게 정리할 수 있다.
1. Coroutine(Functon)이 불러 코루틴으로 쓰일 수 있는 함수가 불린다.
2. Coroutine은 yield문이 실행될 때 까지 쭉~ 실행된다.
-> 그림에서는 init 부터 while문 안의 yield Delay(1)까지 진행할 것이다.
3. yield문이 실행되면서 컨트롤이 다시 Animate()로 옮겨가며, 이때 Coroutine이 어디까지 진행되었는지를 기억한다.(함수가 정말로 종료되는게 아니라 일시정지를 했다고 생각하면 편하다.)
---------------------------------실제로 애니메이션이 일어나는 부분 시작
4. yield의 Delay(1)이기 때문에, 1밀리초 동안 그외 작업을 진행한다.
->여기에서 다양한 함수가 있을 수 있다. Start, Update일 수도.
5. 다시 Coroutine 함수로 돌아와서 while문에 있는 것이 실행되기 시작한다.
6. 전부 실행이 끝났으면 다시 yield문을 실행한다.
7. 4~6을 그렇게 while문의 조건이 false가 될때 까지 반복한다.
---------------------------------실제로 애니메이션이 일어나는 부분 끝
8. while문의 조건이 false(그림에서는 애니메이션이 끝남)이 되었으므로 마지막 뒤처리를 하고, 코루틴 함수를 종료한다.
이는 개념적으로는 멀티스레딩 / 차일드 프로세싱 등과는 많이 다른 성격을 가졌는데, 그 이유는 실제로 싱글 프로세스 / 싱글 스레드로 진행하기 때문이다. 다만 그것과 비슷한 동작을 수행할 수 있을 뿐인 것으로 알고 있다. 따라서 Java의 Thread 혹은 Android의 Handler와는 좀 다른 성격을 지닐 것으로 예측된다.
사실, 이러나 저러나 이론을 적어 놓았지만, 사용방법은 이와 전혀 딴판인 경우도 많다. c#에서도 Photon등의 라이브러리가 AsyncTask가 가능한걸 보면 아예 멀티스레딩을 못하는건 아닌가보다.
Coroutine - 코루틴 : 실전파트
다른 사람은 모르겠지만, 나는 이 코루틴을 애니메이션을 만드는데 주로 이용했다. 유니티 자체에 애니메이션 기능이 있기는 있는데, 꼭 편리한 것 만은 아니고 결국 스프라이트 중노동이 필요한 경우가 많다. 그리고 도무지 중간에 컬러를 바꾼다거나 alpha를 바꾼다거나 하는 방법은 터득할 수 없었는데, 그 대안으로 coroutine을 이용했다. 런타임에 새로운 루틴을 작성하는 건 부담을 주는 것일 수도 있겠지만, 뭐 애니메이션 하나 만드는데 뭐 그리 대수랴...
1. WaitForSeconds를 이용해서 깜빡이 만들기
display를 Unity.Text라고 생각하고 봐보자. Hello!라는 글씨가 나타났다 사라질 것이다.
IEnumerator MoveToPos(Vector2 to, Vector2 from){
WaitForSeconds interval = new WaitForSeconds(1.0f);
display.text = "Hello!";
yield return interval;
display.text = "";
yield return interval;
display.text = "Hello!";
}
2. Mathf.Lerp를 이용해서 자연스러운 애니메이션 연출하기
아래의 코드는 from -> to로 자연스럽게 rectTransform의 좌표를 옮기는 코드이다.
float speed = 2.5f;
IEnumerator MoveToPos(Vector2 to, Vector2 from){
WaitForSeconds interval = new WaitForSeconds(0.001f);
Vector2 moving_pos = new Vector2(to.x, to.y);
float dx = from.x, dy = from.y;
float t = 0.0f;
is_drag_animating = true;
while(Mathf.Abs(dx - to.x) > Mathf.Epsilon || Mathf.Abs(dy - to.y) > Mathf.Epsilon){
yield return interval;
dx = Mathf.Lerp(dx, to.x, t);
dy = Mathf.Lerp(dy, to.y, t);
moving_pos.x = dx;
moving_pos.y = dy;
t += speed * Time.deltaTime;
rectTransform.anchoredPosition = moving_pos;
}
rectTransform.anchoredPosition = to;
display_offset = to;
is_drag_animating = false;
}
Mathf.Lerp는 내가 알기로 map()함수와 매우~ 유사한데, 첫 두개의 입력값으로 start와 end를 잡고, 마지막 세번째 입력은 0.0f~1.0f 사이의 실수이다. 따라서 위의 코드처럼 굳이 t에 speed * Time.deltaTime을 더할 필요는 없고, 0.01f같은 고정 (적어도 몇 배했을 때 1.0f에 도달할) 실수값을 더해도 된다. 다만 Time.deltaTime이 더 나은 점은, 렉이 걸리더라도 일정하게 오브젝트를 움직일 수 있다는 점이 아닐까... 생각해본다. (확실하지는 않음, 아래의 링크에서도 왜 굳이 이래야하는지 모르겠다고 함.)
참고하면 매우 좋은 글 :
https://gamedevbeginner.com/the-right-way-to-lerp-in-unity-with-examples/
이거 원래 1달 전인가 2달전인가 만들어 놓은 임시 저장글이었는데 이제서야 완성;; 역시 글 쓰는건 어렵다.
'프로그래밍 > Unity' 카테고리의 다른 글
[게임 출시!] 2021학년도 2학기 학기 동아리 학기 프로젝트 (0) | 2022.01.27 |
---|---|
Unity - Drag & Drop - How to make Post (2) | 2021.11.25 |
내가 유니티에선 왕초보? - 4주차 - 레이어와 Hp (0) | 2021.05.08 |
내가 유니티에선 왕초보? - 3주차 - 애니메이션 (0) | 2021.05.07 |
내가 유니티에선 왕초보? - 2주차 - 움직여보기 (0) | 2021.05.07 |