나는 코 루틴의 원리를 이해합니다. 나는 표준 얻을하는 방법을 알고 StartCoroutine
/의 yield return
예를 호출하는 방법은 반환, 유니티에서 C #에서 작업에 패턴을 IEnumerator
통해 StartCoroutine
, 그 방법으로 일을 할 yield return new WaitForSeconds(1);
, 잠깐만 후 다른 일을 할 수 있습니다.
내 질문은 : 무대 뒤에서 실제로 무슨 일이 일어나고 있습니까? StartCoroutine
실제로 무엇을 합니까? 어떻게 IEnumerator
되어 WaitForSeconds
반환? StartCoroutine
호출 된 메소드의 “다른 것”부분으로 제어를 리턴 하는 방법은 무엇입니까? 이 모든 것이 Unity의 동시성 모델과 어떻게 상호 작용합니까 (코 루틴을 사용하지 않고 많은 일이 동시에 진행되고 있는가)?
답변
종종 참조 상세 링크 에서 Unity3D 코 루틴 이 죽었습니다. 의견과 답변에서 언급되었으므로 기사의 내용을 여기에 게시 할 것입니다. 이 내용은 이 거울 에서 나온 것입니다 .
Unity3D 코 루틴 디테일
게임의 많은 프로세스는 여러 프레임 과정에서 발생합니다. 경로 찾기와 같은 ‘밀집한’프로세스가있어 각 프레임마다 효과가 있지만 여러 프레임으로 분할되어 프레임 속도에 너무 큰 영향을 미치지 않습니다. 대부분의 프레임을 수행하지 않지만 때로는 중요한 작업을 수행해야하는 게임 플레이 트리거와 같은 ‘스파 스’프로세스가 있습니다. 그리고 둘 사이에 여러 프로세스가 있습니다.
멀티 스레딩없이 여러 프레임에서 발생하는 프로세스를 만들 때마다 작업을 프레임 당 실행할 수있는 청크로 분할하는 방법을 찾아야합니다. 중앙 루프가있는 알고리즘의 경우 상당히 분명합니다. 예를 들어 A * 패스 파인더는 노드 목록을 반영구적으로 유지하도록 구성 할 수 있으며 시도하지 않고 열린 프레임에서 소수의 노드 만 처리합니다. 한 번에 모든 작업을 수행합니다. 대기 시간을 관리하기 위해 약간의 균형이 이루어져야합니다. 결국 프레임 속도를 초당 60 또는 30 프레임으로 고정하는 경우 프로세스는 초당 60 또는 30 단계 만 수행하므로 프로세스가 중단 될 수 있습니다. 전체적으로 너무 길다. 깔끔한 디자인은 한 수준에서 가장 작은 작업 단위를 제공 할 수 있습니다. 예 : 단일 A * 노드를 처리하고 작업을 더 큰 청크로 그룹화하는 방법 위에 배치합니다. 예를 들어 A * 노드를 X 밀리 초 동안 계속 처리합니다. (일부 사람들은 이것을 ‘타임 슬라이싱’이라고 부릅니다.)
그럼에도 불구하고 이러한 방식으로 작업을 분할하면 한 프레임에서 다음 프레임으로 상태를 전송해야합니다. 반복 알고리즘을 중단하는 경우 반복에서 공유되는 모든 상태와 다음에 수행 할 반복을 추적하는 수단을 유지해야합니다. 일반적으로 그렇게 나쁘지는 않습니다. ‘A * 패스 파인더 클래스’의 디자인은 상당히 분명합니다. 그러나 다른 경우도 있습니다. 때로는 프레임마다 다른 종류의 작업을 수행하는 긴 계산에 직면하게됩니다. 상태를 캡처하는 객체는 한 프레임에서 다음 프레임으로 데이터를 전달하기 위해 유지되는 반 유용한 ‘로컬’의 큰 혼란으로 끝날 수 있습니다. 그리고 드문 프로세스를 처리하는 경우 작업이 언제 완료되어야하는지 추적하기 위해 작은 상태 머신을 구현해야하는 경우가 종종 있습니다.
여러 프레임에서이 모든 상태를 명시 적으로 추적하지 않고 동기화 및 잠금 등을 멀티 스레드하고 관리하는 대신 함수를 단일 코드 덩어리로 작성하는 것만으로도 깔끔하지 않습니까? 기능이 ‘일시 정지’되어야하는 특정 장소를 표시하고 나중에 계속 수행합니까?
Unity는 다른 여러 환경 및 언어와 함께 코 루틴 형식으로 제공합니다.
어떻게 보이나요? “Unityscript”(자바 스크립트)에서 :
function LongComputation()
{
while(someCondition)
{
/* Do a chunk of work */
// Pause here and carry on next frame
yield;
}
}
C #에서 :
IEnumerator LongComputation()
{
while(someCondition)
{
/* Do a chunk of work */
// Pause here and carry on next frame
yield return null;
}
}
그들은 어떻게 작동합니까? Unity Technologies에서 근무하지 않는다고 빨리 말씀 드리겠습니다. Unity 소스 코드를 보지 못했습니다. 나는 Unity의 코 루틴 엔진의 내장을 본 적이 없다. 그러나 그들이 설명하려고하는 것과 근본적으로 다른 방식으로 그것을 구현했다면, 나는 매우 놀랄 것입니다. UT 출신의 누군가가 실제로 어떻게 작동하는지에 대해 이야기하고 싶다면 그것이 좋을 것입니다.
큰 실마리는 C # 버전입니다. 먼저 함수의 반환 유형은 IEnumerator입니다. 둘째, 성명서 중 하나는 수익률 반환입니다. 즉 수익률은 키워드 여야하고 Unity의 C # 지원은 바닐라 C # 3.5이므로 바닐라 C # 3.5 키워드 여야합니다. 실제로 MSDN 에는 ‘반복자 블록’이라는 것이 있습니다. 무슨 일이야?
첫째,이 IEnumerator 유형이 있습니다. IEnumerator 형식은 시퀀스에서 커서처럼 작동하여 두 가지 중요한 멤버를 제공합니다. Current는 현재 커서가있는 요소를 제공하는 속성 인 Current와 시퀀스의 다음 요소로 이동하는 함수 인 NextNext ()입니다. IEnumerator는 인터페이스이므로 이러한 멤버가 구현되는 방식을 정확하게 지정하지 않습니다. MoveNext ()는 하나의 toCurrent를 추가하거나 파일에서 새 값을로드하거나 인터넷에서 이미지를 다운로드하여 해시하여 Current에 새 해시를 저장할 수 있습니다. 순서에있는 요소와 두 번째 요소와 완전히 다른 요소입니다. 원하는 경우이를 사용하여 무한 시퀀스를 생성 할 수도 있습니다. MoveNext ()는 시퀀스에서 다음 값을 계산합니다 (값이 없으면 false를 반환 함).
일반적으로 인터페이스를 구현하려면 클래스를 작성하고 멤버를 구현하는 등의 작업을 수행해야합니다. 반복자 블록은 번거 로움없이 IEnumerator를 구현하는 편리한 방법입니다. 몇 가지 규칙 만 따르면 IEnumerator 구현이 컴파일러에 의해 자동으로 생성됩니다.
반복자 블록은 (a) IEnumerator를 반환하고 (b) yield 키워드를 사용하는 일반 함수입니다. 수익률 키워드는 실제로 무엇을합니까? 시퀀스의 다음 값이 무엇인지 또는 더 이상 값이 없음을 선언합니다. 코드에서 수익률 반환 X 또는 수익률 중단이 발생하는 지점은 IEnumerator.MoveNext ()가 중지해야하는 지점입니다. yield return X는 MoveNext ()가 true를 반환하고 Current에 값 X가 할당되도록하는 반면 yield break는 MoveNext ()가 false를 반환하도록합니다.
자, 여기 속임수가 있습니다. 시퀀스에서 반환 된 실제 값이 무엇인지는 중요하지 않습니다. MoveNext ()를 반복해서 호출하고 Current를 무시할 수 있습니다. 계산은 여전히 수행됩니다. MoveNext ()가 호출 될 때마다 반복자 블록은 실제로 생성되는 표현식에 관계없이 다음 ‘yield’문으로 실행됩니다. 따라서 다음과 같이 작성할 수 있습니다.
IEnumerator TellMeASecret()
{
PlayAnimation("LeanInConspiratorially");
while(playingAnimation)
yield return null;
Say("I stole the cookie from the cookie jar!");
while(speaking)
yield return null;
PlayAnimation("LeanOutRelieved");
while(playingAnimation)
yield return null;
}
실제로 작성한 것은 긴 null 값 시퀀스를 생성하는 반복자 블록이지만 중요한 것은 값을 계산하는 작업의 부작용입니다. 다음과 같은 간단한 루프를 사용하여이 코 루틴을 실행할 수 있습니다.
IEnumerator e = TellMeASecret();
while(e.MoveNext()) { }
또는 더 유용하게 다른 작업과 혼합 할 수 있습니다.
IEnumerator e = TellMeASecret();
while(e.MoveNext())
{
// If they press 'Escape', skip the cutscene
if(Input.GetKeyDown(KeyCode.Escape)) { break; }
}
지금까지 살펴본 바와 같이 각 yield return 문은 null 블록과 같은 식을 제공하여 반복자 블록이 실제로 IEnumerator.Current에 할당 할 항목을 갖도록해야합니다. 긴 null 시퀀스는 유용하지 않지만 부작용에 더 관심이 있습니다. 우리 아닌가요?
실제로 우리는 그 표현으로 할 수있는 편리한 일이 있습니다. 널 (null)을 산출하고 무시하는 대신 더 많은 작업이 필요할 것으로 예상되는 것을 산출하면 어떻게 될까요? 애니메이션이나 사운드가 재생을 마치거나 특정 시간이 지난 후에도 계속하고 싶은 시간이 많이있을 것입니다. while (playingAnimation) yield는 null을 반환합니다. 구조는 약간 지루합니다.
Unity는 YieldInstruction 기본 유형을 선언하고 특정 종류의 대기를 나타내는 몇 가지 구체적 파생 유형을 제공합니다. 지정된 시간이 지나면 코 루틴을 다시 시작하는 WaitForSeconds가 있습니다. WaitForEndOfFrame이 있는데, 나중에 동일한 프레임의 특정 지점에서 코 루틴을 다시 시작합니다. 코 루틴 유형 자체가 있습니다. 코 루틴 A가 코 루틴 B를 생성하면 코 루틴 B가 완료 될 때까지 코 루틴 A를 일시 중지합니다.
이것은 런타임 관점에서 어떤 모습입니까? 내가 말했듯이, 나는 Unity에서 일하지 않으므로 코드를 본 적이 없다. 하지만 다음과 같이 보일 수 있습니다.
List<IEnumerator> unblockedCoroutines;
List<IEnumerator> shouldRunNextFrame;
List<IEnumerator> shouldRunAtEndOfFrame;
SortedList<float, IEnumerator> shouldRunAfterTimes;
foreach(IEnumerator coroutine in unblockedCoroutines)
{
if(!coroutine.MoveNext())
// This coroutine has finished
continue;
if(!coroutine.Current is YieldInstruction)
{
// This coroutine yielded null, or some other value we don't understand; run it next frame.
shouldRunNextFrame.Add(coroutine);
continue;
}
if(coroutine.Current is WaitForSeconds)
{
WaitForSeconds wait = (WaitForSeconds)coroutine.Current;
shouldRunAfterTimes.Add(Time.time + wait.duration, coroutine);
}
else if(coroutine.Current is WaitForEndOfFrame)
{
shouldRunAtEndOfFrame.Add(coroutine);
}
else /* similar stuff for other YieldInstruction subtypes */
}
unblockedCoroutines = shouldRunNextFrame;
다른 경우를 처리하기 위해 더 많은 YieldInstruction 하위 유형을 추가 할 수있는 방법을 상상하는 것은 어렵지 않습니다. 예를 들어 WaitForSignal ( “SignalName”) YieldInstruction을 지원하여 신호에 대한 엔진 레벨 지원을 추가 할 수 있습니다. YieldInstructions를 더 추가하면 코 루틴 자체가보다 표현력이 향상 될 수 있습니다. 새로운 수익률 반환 WaitForSignal ( “GameOver”)은 (! Signals.HasFired ( “GameOver”))보다 반환하기가 더 좋습니다. 엔진에서 수행하는 것이 스크립트에서 수행하는 것보다 빠를 수 있다는 사실.
명백하지 않은 결과 두 가지이 모든 것에 대해 사람들이 때때로 내가 지적해야한다고 생각하지 못하는 몇 가지 유용한 것들이 있습니다.
첫째, 수익률 반환은 표현식 (어떤 표현식이든)을 산출하는 것이며 YieldInstruction은 일반 유형입니다. 즉, 다음과 같은 작업을 수행 할 수 있습니다.
YieldInstruction y;
if(something)
y = null;
else if(somethingElse)
y = new WaitForEndOfFrame();
else
y = new WaitForSeconds(1.0f);
yield return y;
특정 라인 yield yield new WaitForSeconds (), yield return new WaitForEndOfFrame () 등은 일반적이지만 실제로는 고유 한 형태가 아닙니다.
둘째, 이러한 코 루틴은 반복자 블록 일 뿐이므로 원하는 경우 직접 반복 할 수 있습니다. 엔진을 사용하지 않아도됩니다. 나는 전에 코 루틴에 인터럽트 조건을 추가하기 위해 이것을 사용했다 :
IEnumerator DoSomething()
{
/* ... */
}
IEnumerator DoSomethingUnlessInterrupted()
{
IEnumerator e = DoSomething();
bool interrupted = false;
while(!interrupted)
{
e.MoveNext();
yield return e.Current;
interrupted = HasBeenInterrupted();
}
}
셋째, 다른 코 루틴에서 생산할 수 있다는 사실은 마치 엔진에서 구현 한 것처럼 성능 적으로는 아니지만 자체 YieldInstructions를 구현할 수있게 해줍니다. 예를 들면 다음과 같습니다.
IEnumerator UntilTrueCoroutine(Func fn)
{
while(!fn()) yield return null;
}
Coroutine UntilTrue(Func fn)
{
return StartCoroutine(UntilTrueCoroutine(fn));
}
IEnumerator SomeTask()
{
/* ... */
yield return UntilTrue(() => _lives < 3);
/* ... */
}
그러나, 나는 이것을 정말로 추천하지 않을 것입니다 – 코 루틴을 시작하는 비용은 내가 좋아하는 데 약간 무겁습니다.
결론 이것이 Unity에서 Coroutine을 사용할 때 실제로 일어나는 일 중 일부를 분명히하기를 바랍니다. C #의 반복자 블록은 그루비 한 작은 구성이며, Unity를 사용하지 않더라도 동일한 방식으로 활용하는 것이 유용 할 수 있습니다.
답변
아래 첫 번째 제목은 질문에 대한 정답입니다. 이 두 제목은 일상적인 프로그래머에게 더 유용합니다.
코 루틴의 아마도 지루한 구현 세부 사항
코 루틴은 Wikipedia 와 다른 곳 에서 설명됩니다 . 여기에서는 실용적인 관점에서 몇 가지 세부 정보를 제공합니다. IEnumerator
, yield
등은 Unity에서 다소 다른 목적으로 사용되는 C # 언어 기능 입니다.
간단히 말해서, 같은 IEnumerator
값을 하나씩 요청할 수있는 값 모음이 있다고 주장합니다 List
. C #에서 서명을 반환하는 함수는 IEnumerator
실제로 생성하고 반환 할 필요는 없지만 C #에서 암시 적을 제공하도록 할 수 있습니다 IEnumerator
. 그러면 함수는 명령문을 IEnumerator
통해 미래에 리턴 된 내용을 게으른 방식으로 제공 할 수 있습니다 yield return
. 호출자가 암시 적에서 다른 값을 요청할 때마다 IEnumerator
함수는 다음 값 까지 실행됩니다.yield return
명령문을 다음 값을 제공합니다. 이로 인해 다음 값이 요청 될 때까지 함수가 일시 중지됩니다.
Unity에서는 미래의 값을 제공하기 위해 이러한 값을 사용하지 않으며 함수가 일시 중지된다는 사실을 이용합니다. 이 악용으로 인해 Unity의 코 루틴에 대한 많은 것들이 이해가되지 않습니다 (무엇 IEnumerator
과 관련이 있습니까? 무엇인가 yield
? 왜 new WaitForSeconds(3)
인가? 등). “후드에서”발생하는 것은 IEnumerator를 통해 제공 한 값 StartCoroutine()
은 다음 값을 요청하는 시점을 결정 하는 데 사용되며 이는 코 루틴이 다시 일시 중지되지 않는 시점을 결정합니다.
Unity 게임은 단일 스레드입니다 (*)
코 루틴은 스레드 가 아닙니다 . Unity에는 하나의 메인 루프가 있으며 작성한 모든 함수는 동일한 메인 스레드에서 순서대로 호출됩니다. while(true);
함수 또는 코 루틴 에 배치하여이를 확인할 수 있습니다 . Unity 에디터까지 모든 것을 고정시킵니다. 이것은 모든 것이 하나의 메인 스레드에서 실행된다는 증거입니다. Kay가 위의 의견에서 언급 한이 링크 는 훌륭한 리소스입니다.
(*) Unity는 하나의 스레드에서 함수를 호출합니다. 따라서 스레드를 직접 만들지 않으면 작성한 코드는 단일 스레드입니다. 물론 Unity는 다른 스레드를 사용하므로 원하는 경우 직접 스레드를 만들 수 있습니다.
게임 프로그래머를위한 코 루틴의 실용적 설명
전화 할 때 기본적으로 StartCoroutine(MyCoroutine())
, 정확히에 정기적으로 함수 호출처럼 MyCoroutine()
처음 할 때까지, yield return X
어디에서, X
같은이며 null
, new WaitForSeconds(3)
, StartCoroutine(AnotherCoroutine())
, break
이 함수에서 다른 시작 할 때입니다, 등. Unity는 해당 yield return X
라인 에서 바로 작동하는 다른 기능을 “일시 정지”하고 다른 비즈니스와 함께 진행하며 일부 프레임은지나갑니다. 함수의 모든 지역 변수에 대한 값을 기억합니다. 이런 식으로, for
예를 들어 2 초마다 반복되는 루프를 가질 수 있습니다 .
Unity가 코 루틴을 다시 시작할 때의 내용 X
에 따라 다릅니다 yield return X
. 예를 yield return new WaitForSeconds(3);
들어을 사용한 경우 3 초가 지나면 다시 시작됩니다. 를 사용한 경우 작업이 완전히 완료된 yield return StartCoroutine(AnotherCoroutine())
후 재개되므로 AnotherCoroutine()
동작을 제 시간에 중첩 할 수 있습니다. 를 방금 사용한 경우 yield return null;
다음 프레임에서 바로 다시 시작됩니다.
답변
더 간단하지 못했습니다.
유니티 (및 모든 게임 엔진)는 프레임 기반 입니다.
전체 요점, 유니티의 모든 영역은 프레임 기반이라는 것입니다. 엔진은 “각 프레임”을 수행합니다. (애니메이션, 오브젝트 렌더링, 물리학 등)
당신은 .. “아, 훌륭합니다. 엔진이 매 프레임마다 무언가를하도록하려면 어떻게해야합니까? 엔진이 프레임에서 그런 작업을하도록 어떻게 지시합니까?”
정답은 …
그것이 바로 “코 루틴”의 목적입니다.
그렇게 간단합니다.
그리고 이것을 고려하십시오 ….
“업데이트”기능을 알고 있습니다. 간단히 말해서, 당신이 거기에 넣은 것은 매 프레임마다 수행 됩니다 . 그것은 코 루틴-수율 구문과 문자 그대로 완전히 동일합니다.
void Update()
{
this happens every frame,
you want Unity to do something of "yours" in each of the frame,
put it in here
}
...in a coroutine...
while(true)
{
this happens every frame.
you want Unity to do something of "yours" in each of the frame,
put it in here
yield return null;
}
아무런 차이가 없습니다.
각주 : 모두가 지적했듯이 Unity 에는 스레드가 없습니다 . Unity 또는 모든 게임 엔진의 “프레임”은 어떤 식 으로든 스레드에 전혀 연결되어 있지 않습니다.
코 루틴 / 수율은 Unity에서 프레임에 액세스하는 방법입니다. 그게 다야. (실제로 Unity에서 제공하는 Update () 함수와 완전히 동일합니다.) 이것으로 모든 것이 간단합니다.
답변
-이 최근에 발굴이 여기에 게시물을 작성한 http://eppz.eu/blog/understanding-ienumerator-in-unity-3d/ (고밀도 코드 예제) 내부에 광명을 비춰 그 기본 – IEnumerator
, 인터페이스를 코 루틴에 사용되는 방법.
이 목적으로 컬렉션 열거자를 사용하면 여전히 조금 이상하게 보입니다. 열거자가 설계된 느낌의 반대입니다. 열거 자의 포인트는 모든 액세스에서 반환되는 값이지만 코 루틴 포인트는 값이 반환되는 코드입니다. 이 문맥에서 실제 반환 값은 의미가 없습니다.
답변
Unity에서 자동으로 얻는 기본 함수는 Start () 함수와 Update () 함수이므로 Coroutine은 본질적으로 Start () 및 Update () 함수와 같은 함수입니다. 이전 함수 func ()는 코 루틴을 호출 할 때와 같은 방식으로 호출 할 수 있습니다. Unity는 분명히 코 루틴에 대해 특정 기능과 다른 경계를 설정했습니다. 한가지 차이점은
void func()
당신은 쓰기
IEnumerator func()
코 루틴을 위해. 그리고 같은 방법으로 코드 라인을 사용하여 일반 함수에서 시간을 제어 할 수 있습니다
Time.deltaTime
코 루틴에는 시간을 제어 할 수있는 방법에 대한 특정 핸들이 있습니다.
yield return new WaitForSeconds();
이것이 IEnumerator / Coroutine 내부에서 수행 할 수있는 유일한 방법은 아니지만 코 루틴이 사용하는 유용한 기능 중 하나입니다. 코 루틴의 다른 특정 사용법을 배우려면 Unity의 스크립팅 API를 조사해야합니다.
답변
StartCoroutine은 IEnumerator 함수를 호출하는 방법입니다. 단순한 void 함수를 호출하는 것과 비슷하지만 IEnumerator 함수에서 사용한다는 점이 다릅니다. 이 유형의 함수는 특별한 yield 함수 를 사용할 수 있으므로 고유 합니다. 무언가를 반환해야합니다. 내가 아는 한. 여기에 간단한 깜박임 게임 Over text method in unity를 작성했습니다.
public IEnumerator GameOver()
{
while (true)
{
_gameOver.text = "GAME OVER";
yield return new WaitForSeconds(Random.Range(1.0f, 3.5f));
_gameOver.text = "";
yield return new WaitForSeconds(Random.Range(0.1f, 0.8f));
}
}
그런 다음 IEnumerator 자체에서 호출했습니다.
public void UpdateLives(int currentlives)
{
if (currentlives < 1)
{
_gameOver.gameObject.SetActive(true);
StartCoroutine(GameOver());
}
}
보시다시피 StartCoroutine () 메서드를 어떻게 사용했는지 알 수 있습니다. 어떻게 든 도움이 되었기를 바랍니다. 나는 나 자신이다. 그래서 당신이 나를 고치거나 이해한다면, 어떤 유형의 피드백이라도 좋을 것이다.