병렬로 작업을 처리 하고 싶을 때는 코루틴을 사용한다
유니티 - 코루틴
MSDN - yield
MSDN - IEnumerator
return이 아닌 yield 키워드를 사용한다. yield를 만나면 일시 중지되고 다른 코드를 실행하거나, 유니티에게 제어권을 반납한다. 다음 업데이트에서 중단점부터 이어서 실행멀티 스레드가 아닌 싱글 스레드IEnumerator Routine()
{
Debug.Log(1);
yield return 1;
Debug.Log(2);
yield return 2;
Debug.Log(3);
yield return 3;
Debug.Log(4);
yield return 4;
Debug.Log(5);
yield return 5;
} IEnumerator를 반환형으로 함수를 구성IEnumerator Routine()
{
Debug.Log(0);
yield return new WaitForSeconds(1f);
Debug.Log(1);
yield return new WaitForSeconds(1f);
Debug.Log(2);
yield return new WaitForSeconds(1f);
Debug.Log(3);
yield return new WaitForSeconds(1f);
Debug.Log(4);
yield return new WaitForSeconds(1f);
Debug.Log(5);
}public class coroutineJumper : MonoBehaviour
{
[SerializeField] Rigidbody rigid;
[SerializeField] float jumpPower;
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
StartCoroutine(routine: ());
}
}
IEnumerator Routine()
{
Debug.Log("점프 대기 시작!");
yield return new WaitForSeconds(2f);
Debug.Log("점프!");
rigid.AddForce(Vector3.up*jumpPower, ForceMode.Impulse);
}
}
StartCoroutine 함수의 반환형은 Coroutine형이다StartCoroutine을 통해 실행
Coroutine coroutine;
private void OnEnable()
{
coroutine = StartCoroutine(Routine());
}
StopCoroutine을 통해 코루틴 종료StopAllCoroutines을 통해 진행중인 모든 코루틴 종료private void OnDisable()
{
StopCoroutine(coroutine);
StopAllCoroutines();
}public class coroutineTester : MonoBehaviour
{
private Coroutine countDownCoroutine;
[SerializeField] int count;
private void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
countDownCoroutine = StartCoroutine(Routine());
}
if(Input.GetKeyDown(KeyCode.Escape))
{
StopCoroutine(countDownCoroutine);
}
}
IEnumerator Routine()
{
for (int i = 0; i < count; i++)
{
Debug.Log(i);
yield return new WaitForSeconds(1f);
}
}
}

주의
- 해당 방법으론는 멈출 수 없다
if(Input.GetKeyDown(KeyCode.Escape)) { StopCoroutine(Routine()); }
- 실행중인 함수를 객체로 등록해서 중지 해야 한다
if(Input.GetKeyDown(KeyCode.Escape)) { StopCoroutine(countDownCoroutine); }
null로 바꿔주는 작업을 해야한다 countDownCoroutine = null;
StopCoroutine()은 터진다. null 체크를 해서 작업하자public class coroutineTester : MonoBehaviour
{
private Coroutine countDownCoroutine;
[SerializeField] int count;
private void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
countDownCoroutine = StartCoroutine(Routine());
}
if(Input.GetKeyDown(KeyCode.Escape))
{
if (countDownCoroutine != null)
{
StopCoroutine(countDownCoroutine);
}
}
}
IEnumerator Routine()
{
for (int i = 0; i < count; i++)
{
Debug.Log(i);
yield return new WaitForSeconds(1f);
}
countDownCoroutine = null;
}
}
new WaitForSeconds() 를 쓰는 것도 메모리가 새로 계속 생성된다. 이것또한 객체 생성이기 때문이다. 메모리 사용을 줄이기 위해 아예 객체로 하나 지정하는 것이 좋다private WaitForSeconds waitSeconds;
[SerializeField] float seconds;
private void Awake()
{
waitSeconds = new WaitForSeconds(seconds);
}
IEnumerator Routine()
{
for (int i = 0; i < count; i++)
{
Debug.Log(i);
yield return waitSeconds;
}
countDownCoroutine = null;
}
코루틴은 반복작업 중 지연처리를 정의하여 작업의 진행 타이밍을 지정할 수 있음
yield return null;yield return new WaitForSeconds(1f); yield return new WaitForSecondsRealtime(1f); yield return new WaitForFixedUpdate(); yield return new WaitForEndOfFrame(); yield return new WaitUntil(() => 조건);
yield return new WaitUntil(() => Input.GetKeyDown(KeyCode.Space));public class coroutineShooter : MonoBehaviour
{
public GameObject bulletPrefab;
public Transform muzzlePoint;
private GameObject bulletOut;
private Coroutine fireCoroutine;
[SerializeField] float repeatTime;
[Range(1, 50)]
public float speed;
[SerializeField] float coolTime;
private float currentShootTime;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && fireCoroutine == null)
{
fireCoroutine = StartCoroutine(FireRoutine());
}
if (Input.GetKeyUp(KeyCode.Space)&&fireCoroutine != null)
{
StopCoroutine(fireCoroutine);
fireCoroutine = null;
}
}
void Fire()
{
//currentShootTime = coolTime;
bulletOut = Instantiate(bulletPrefab, muzzlePoint.position, muzzlePoint.rotation);
Rigidbody rig = bulletOut.GetComponent<Rigidbody>();
rig.velocity = muzzlePoint.forward * speed;
}
IEnumerator FireRoutine()
{
WaitForSeconds delay = new WaitForSeconds(repeatTime);
while (true)
{
Fire();
yield return delay;
}
}
}
Space 키를 꾹 누르고있으면 포탄이 지정한 repeatTime 마다 날아간다Coroutine이 끝나고 삭제된다private Coroutine chargeCoroutine;
private float bulletSpeed;
void Update()
{
//차지공격
if(Input.GetKeyDown(KeyCode.Space))
{
if(chargeCoroutine== null)
{
chargeCoroutine = StartCoroutine(ChargeRoutine());
}
}
}
void Fire(float bulletSpeed)
{
bulletOut = Instantiate(bulletPrefab, muzzlePoint.position, muzzlePoint.rotation);
Rigidbody rig = bulletOut.GetComponent<Rigidbody>();
rig.velocity = muzzlePoint.forward * bulletSpeed;
}
IEnumerator ChargeRoutine()
{
float timer = 0;
while (true)
{
timer += Time.deltaTime * 50;
yield return null;
if (Input.GetKeyUp(KeyCode.Space))
{
break;
}
}
// mathf : 수학 함수 기능
bulletSpeed = Mathf.Clamp(speed+timer, speed, speed * 5);
Fire(bulletSpeed);
chargeCoroutine = null;
}
mathf 클래스의 함수 Clamp로 최솟값, 최댓값을 설정할 수 있다ChargeRoutine()이 실행된다int playerHealth = 50;
playerHealth +=20;playerHealth -= 10;50 + 20 - 10 = 60 일것이다. 멀티 스레드에서는 얘기가 다르다playerHealth = 50 + 20;
= 70;playerHealth = 50 - 10;
= 40;40이 저장된다. 한 프레임 안에서 동시에 작업이 수행 되다보니, 각 스레드가 같은 변수 값을 들고 가서 연산하다보니 이런 문제가 생긴다코루틴은 싱글 스레드에서 작동한다공유자원 문제를 해결한 멀티스레드 방식은 뮤텍스와 세마포어를 이용해 해결한다. 근데 이 방식을 쓰면, 시간 리소스에 차이가 없어진다. 즉 멀티 스레드일 이유가 사라진다...게임 오브젝트가 각각 멀티 스레드로 계산 된다고 해보자. 그러다 서로 좌표가 겹칠 수도 있다. 양자역학과 같은 현상이 생기는거다물리 처리, 애니메이션, 후처리 등 순차적으로 진행해야 되는 작업들이 있다. 그렇다보니 멀티 스레드로는 해결하기 난해한 부분들이 생긴다.CPU를 싱글 코어만 사용하는 이유이기도 하다. 대표적인 것이 마인크래프트 자바 에디션. 베드락 에디션 에서는 멀티코어를 사용하여 청크를 불러온다