IEnumerator , yield 키워드
Unity/API2019. 4. 23. 02:50
IEnumerator : 제너릭이 아닌 컬렉션을 단순반복할 수 있도록 지원하는 인터페이스
while 문과 foreach문을 사용하기 편하게 Coroutine으로 사용하는 인터페이스다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | public class App : MonoBehaviour { public Character hero; public Character monster; private Animation anim; private Character target; private float attackRange = 0.5f; private bool isStart; void Start() { StartCoroutine(this.hero.Move(new Vector3(5, 0, 5))); this.monster.Position(new Vector3(5, 0, 5)); StartCoroutine(this.monster.Attack(this.hero)); } private void Awake() { this.anim = this.gameObject.GetComponent<Animation>(); } public void Position(Vector3 position) { this.transform.position = position; } public IEnumerator Move(Vector3 tPos) { this.anim.Play("run@loop"); this.isStart = true; while(true) { if (this.isStart == true) { this.transform.LookAt(tPos); var dir = (tPos - this.transform.position).normalized; var speed = 1; this.transform.position += dir * speed * Time.deltaTime; var distance = Vector3.Distance(tPos, this.transform.position); if (attackRange > distance) { this.anim.Play("idle@loop"); this.isStart = false; if (distance <= 0.1f && this.isStart == true) { anim.Play("idle@loop"); Debug.Log("이동완료"); break; } } } yield return null; } } public IEnumerator Attack(Character target) { this.anim.Play("idle@loop"); this.target = target; while (true) { var distance = Vector3.Distance(this.transform.position, target.transform.position); if (attackRange > distance) { this.transform.LookAt(target.transform.position); anim.Play("attack_sword_01"); break; } yield return null; } } } | cs |
예를들면 전에 사용했던 코루틴을 사용하여 이동과 공격하는 인터페이스를 만든 부분이다.
while문 안에는 yield 키워드가 들어가며 기능은 "한 프레임마다" 반복이라고 생각하면 편하다.
코루틴을 사용할 때에는 StartCoroutine(); 을 사용해야 한다.
이것이 번거롭다면 미리 메서드를 생성해 두는 것도 좋은 방법이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public void Move(Vector3 tPos) { StartCoroutine(this.MoveImpl(tPos)); } private IEnumerator MoveImpl(Vector3 tPos) { this.anim.Play("run@loop"); this.isStart = true; while(true) { if (this.isStart == true) { this.transform.LookAt(tPos); var dir = (tPos - this.transform.position).normalized; var speed = 1; this.transform.position += dir * speed * Time.deltaTime; var distance = Vector3.Distance(tPos, this.transform.position); if (attackRange > distance) { this.anim.Play("idle@loop"); this.isStart = false; if (distance <= 0.1f && this.isStart == true) { anim.Play("idle@loop"); Debug.Log("이동완료"); break; } } } yield return null; } } | cs |
예시이다.
위와 같이 메서드를 사용하여 외부에서는 Move메서드를 호출하면 코루틴을 사용하기 용이하다.
(여기서 주의할 점)
코루틴은 재대로 종료하지 않을 시 작업이 겹치므로 다음과 같은 코딩을 하면 좋다.
1 2 3 4 5 6 7 8 | public void Move(Vector3 tPos) { if (routine != null) { StopCoroutine(this.routine); } this.routine = StartCoroutine(this.MoveImpl(tPos)); } | cs |
지역변수로 private Coroutine routine;을 선언하며 지역변수에 값을 넣어주며 그 값이 null이 아닐경우 중지시킨다.
참조 : https://docs.microsoft.com/ko-kr/dotnet/api/system.collections.ienumerator?view=netframework-4.7.2
참조 : https://www.slideshare.net/jungsoopark104/ienumerator
yield 키워드 : 호출자(Caller)에게 컬렉션 데이타를 하나씩 리턴할 때 사용
yield return : 각 요소를 따라 반환 , while문에서 쓴 이유 = 한 프레임마다 다시 돌리기 위함이다.
yield break : 리턴을 중지하고 Iteration 루프를 빠져나올 때 사용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //1번 public IEnumerator test() { //1번째 프레임 진입 yield return null; // 첫 프레임은 여기서 종료 //2번째 프레임 진입 Debug.Log("Hello world"); // 2번째 프레임에서 실행 // 함수내에 yield가 더이상 없기 때문에 종료. } //2번. public IEnumerator test2() { // 1번째 프레임 진입 Debug.Log("Hello world"); // 1번째 프레임에서 실행 yield return null; // 1번째 프레임에서 yield 시킴 // 2번째 프레임 진입 // 더이상 실행할 게 없으므로 종료. } | cs |
참조 : http://www.devkorea.co.kr/bbs/board.php?bo_table=m03_qna&wr_id=77246