C# Start例程/收益回报模式在Unity中究竟是如何工作的?
我理解协同程序的原理。我知道如何让标准的C# Start例程/收益回报模式在Unity中究竟是如何工作的?,c#,unity3d,coroutine,C#,Unity3d,Coroutine,我理解协同程序的原理。我知道如何让标准的startcroutin/yield return模式在C#in Unity中工作,例如,通过startcroutin调用一个返回IEnumerator的方法,并在该方法中执行一些操作,执行yield return new WaitForSeconds(1)等待一秒钟,然后执行其他操作 我的问题是:幕后到底发生了什么?startcroutine真正做什么?IEnumerator返回的是什么WaitForSeconds?startcroutine如何将控制权
startcroutin
/yield return
模式在C#in Unity中工作,例如,通过startcroutin
调用一个返回IEnumerator
的方法,并在该方法中执行一些操作,执行yield return new WaitForSeconds(1)代码>等待一秒钟,然后执行其他操作
我的问题是:幕后到底发生了什么?startcroutine
真正做什么?IEnumerator
返回的是什么WaitForSeconds
?startcroutine
如何将控制权返回到被调用方法的“其他内容”部分?所有这些是如何与Unity的并发模型相互作用的(在这个模型中,许多事情同时进行,而不使用协同路由)?下面的第一个标题是对这个问题的直接回答。后面的两个标题对日常程序员更有用
协同程序的实现细节可能很枯燥
在和其他地方解释了协同程序。这里我将从实际的角度提供一些细节<代码>IEnumerator
,收益率
等是用于统一中的不同目的的
简单地说,IEnumerator
声称拥有一组可以逐个请求的值,有点像列表。在C#中,带有返回IEnumerator
的签名的函数不必实际创建并返回一个,但可以让C#提供一个隐式IEnumerator
。然后,该函数可以通过yield return
语句,在将来以惰性方式提供返回的IEnumerator
的内容。每次调用方从该隐式IEnumerator
请求另一个值时,该函数将一直执行到下一个yield return
语句,该语句提供下一个值。作为此操作的副产品,函数将暂停,直到请求下一个值
在Unity中,我们不使用这些函数来提供未来的值,而是利用函数暂停的事实。由于这种利用,关于Unity中的协同程序的很多事情都没有意义(IEnumerator
与任何事情有什么关系?什么是产生的
?为什么新的WaitForSeconds(3)
?等等)。“在引擎盖下”发生的情况是,startcroutine()
使用您通过IEnumerator提供的值来决定何时请求下一个值,这决定了您的协同程序何时将再次取消暂停
您的Unity游戏是单线程(*)
协程是而不是线程。有一个统一的主循环,您编写的所有函数都被同一个主线程按顺序调用。您可以通过放置while(true)来验证这一点在任何函数或协同程序中编写>代码。它将冻结整个事件,甚至是Unity编辑器。这是所有东西都在一个主线程中运行的证据。凯在上述评论中提到的这一点也是一个很好的资源
(*)Unity从一个线程调用函数。因此,除非您自己创建线程,否则您编写的代码是单线程的。当然,Unity会使用其他线程,如果愿意,您可以自己创建线程
游戏程序员协作程序的实用描述
基本上,当您调用start例程(MyCoroutine())
时,它与对MyCoroutine()
的常规函数调用完全相同,直到第一个返回X
,其中X
类似于null
,新的WaitForSeconds(3)
,start例程(另一个协同例程())
,中断
等。这是当它开始与函数不同时。Unity将该函数“暂停”在该收益返回X
行,继续处理其他事务,并且一些帧经过,当时间再次到来时,Unity将在该行之后恢复该函数。它记住函数中所有局部变量的值。例如,通过这种方式,可以使for
循环每两秒循环一次
Unity何时恢复协同程序取决于X
中的收益率X
。例如,如果使用返回新的WaitForSeconds(3)代码>,3秒后恢复。如果使用了yield return startcroutine(anothercorroutine())
,则在anothercorroutine()
完成后,它将恢复,这使您能够及时嵌套行为。如果只使用返回null代码>,它将在下一帧恢复 我最近对此进行了深入研究,在这里写了一篇文章,这篇文章揭示了内部结构(有密集的代码示例)、底层接口以及如何将其用于协同路由
为此目的使用集合枚举器对我来说仍然有点奇怪。这与枚举器的设计目的正好相反。枚举数的点是每次访问时返回的值,而协程的点是值返回之间的代码。在这个上下文中,实际返回的值是没有意义的
经常引用的链接已失效。由于评论和答案中提到了这一点,我将在这里发布文章的内容。此内容来自
详细介绍Unity3D协同程序
游戏中的许多过程都发生在多帧的过程中。你有“密集”的进程,比如寻路,每一帧都很努力,但是会被分割到多个帧,这样就不会对帧率造成太大的影响。你有一些“稀疏”的过程,比如游戏触发,它们在大多数帧中都不起作用,但偶尔会被要求做一些关键的工作。这两者之间有各种各样的过程
无论何时,当您创建一个将在多个帧上进行的过程时–没有multith
function LongComputation()
{
while(someCondition)
{
/* Do a chunk of work */
// Pause here and carry on next frame
yield;
}
}
IEnumerator LongComputation()
{
while(someCondition)
{
/* Do a chunk of work */
// Pause here and carry on next frame
yield return null;
}
}
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;
}
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; }
}
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 y;
if(something)
y = null;
else if(somethingElse)
y = new WaitForEndOfFrame();
else
y = new WaitForSeconds(1.0f);
yield return y;
IEnumerator DoSomething()
{
/* ... */
}
IEnumerator DoSomethingUnlessInterrupted()
{
IEnumerator e = DoSomething();
bool interrupted = false;
while(!interrupted)
{
e.MoveNext();
yield return e.Current;
interrupted = HasBeenInterrupted();
}
}
IEnumerator UntilTrueCoroutine(Func fn)
{
while(!fn()) yield return null;
}
Coroutine UntilTrue(Func fn)
{
return StartCoroutine(UntilTrueCoroutine(fn));
}
IEnumerator SomeTask()
{
/* ... */
yield return UntilTrue(() => _lives < 3);
/* ... */
}
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;
}
void func()
IEnumerator func()
Time.deltaTime
yield return new WaitForSeconds();
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));
}
}
public void UpdateLives(int currentlives)
{
if (currentlives < 1)
{
_gameOver.gameObject.SetActive(true);
StartCoroutine(GameOver());
}
}