有时在 Unity 中等待 Coroutine 完成
Sometimes wait for Coroutine to complete in Unity
我正在开发一个在 Unity 中保存游戏状态的系统。当前结构如下所示:
public void SaveGame() {
StopAllCoroutines();
if (source != null)
{
source.Cancel();
}
StartCoroutine(DoSave());
}
protected IEnumerator DoSave(){
/// Code that prepares a bunch of stuff necessary to save the game goes here
foreach (item in itemsToSave)
{
// Code that adds 'item' to a data structure in memory that is accessible by later functions goes here //
yield return new WaitForSeconds(0.05f);
}
source = new CancellationTokenSource();
WriteFiles(source.Token);
}
private async Task WriteFiles(CancellationToken token)
{
Task task = Task.Run(() => GenerateSaveFiles(), token);
// (A canceled task will raise an exception when awaited).
await task;
}
private void GenerateSaveFiles()
{
/// Functions that actually write to disk go here
/// E.g. File.WriteAllText();
/// This function has access to the place in memory where 'items' were being added to in the DoSave() foreach loop, so it just dumps that memory storage to disk
}
我这样设置是为了可以在单独的线程中异步保存并跟踪它的状态(例如,是否失败,以及错误消息)。
SaveGame()
然后在不同的事件(如拾取物品、杀死敌人等)时围绕我的代码库调用以实时保存游戏。这对我 99% 的用例都适用,但有些情况下我想调用 SavGame()
并等待它完成,然后再做任何其他事情。例如,如果玩家死亡,我想将状态写入磁盘而不给其他任何事情(例如在不同场景中重生玩家)发生的机会。
我研究了回调之类的一些东西(例如 https://codesaying.com/action-callback-in-unity/),但我不确定如何针对我的主要 SaveGame()
任务的用例正确设置它 运行 异步,但其他时候可以等待它完成,或者至少让调用 SaveGame()
的方法知道它何时完成。
只需做一个任何人都可以访问的简单布尔值 属性:
public bool IsSavingGame {
get; private set;
}
// ...
private IEnumerator SaveGameCoroutine(){
IsSavingGame = true;
// Your save functionality.
IsSavingGame = false;
}
例如,如果你想在场景保存时拒绝加载任何场景,你可以编写这个脚本:
// or you can singleton it
[SerializeField]
private SaveManager yourSaveManger
public void LoadScene(){
StartCoroutine(LoadSceneCoroutine());
}
private IEnumerator LoadSceneCoroutine(){
while (yourSaveManger.IsSavingGame){
// Show a loading or saving screen or something
yield return null;
}
SceneManager.LoadScene("Your Scene Name");
}
好的,经过大量的阅读和努力,我能够让它发挥作用。由于我使用 Coroutine 的设置以及嵌套在其中的 Task 和其他 Coroutines,因此有很多事情会妨碍。
我的解决方案是 two-fold
- 对于协程 (
DoSave()
) 和嵌套在其中的任何其他协程,我传递了一个参数 waitForSaveComplete
如果为真,我不会 运行 [=] 中的任何 yield 语句14=] 或其嵌套协程。因此,在我的问题示例中,我将所有 yield 语句包装成这样:
if (!waitForSaveComplete)
{
yield return new WaitForSeconds(0.05f);
}
我还有一些同步嵌套协程,我用同样的方式包装了它们的 yield 语句。
接下来,我把嵌套协程的调用改成了:
yield return StartCoroutine(MySynchronizedCoroutine());
至:
if(!waitForSaveComplete) {
yield return StartCoroutine(MySynchronizedCoroutine(waitForSavComplete));
} else
{
// IMPORTANT: Make sure MySynchronizedCoroutine also uses
// waitForSaveComplete to wrap its own yield statements
// or else this won't work!!!
StartCoroutine(MySynchronizedCoroutine(waitForSavComplete));
}
像这样包装 yield 语句是可行的,因为这是协程暂停并放弃控制的地方,直到满足相应的 Wait
条件。到那时它是 'blocking' 鉴于我不确定为什么我花了这么长时间才想到这个。
- 对于任务,我在异步情况下使用了
WaitUntil
以确保它在协程本身发生任何其他事情之前完成。在 waitForSaveComplete
的情况下,我什至没有启动任务,而是 运行ning GenerateSaveFiles()
直接像这样
if (waitForSaveComplete)
{
GenerateSaveFiles();
} else
{
Task writeTask = WriteFiles(source.Token);
yield return new WaitUntil(() => writeTask.IsCompleted);
}
再一次,另一个 'duh' 时刻,只是不喜欢.. 在不需要的情况下开始任务。
这似乎可行,但也有点复杂,因为嵌套协同程序似乎是滋生可怕、难以跟踪错误的沃土。唉,我的异步编程经验非常有限,所以我没有更好的设计或实现想法。
我正在开发一个在 Unity 中保存游戏状态的系统。当前结构如下所示:
public void SaveGame() {
StopAllCoroutines();
if (source != null)
{
source.Cancel();
}
StartCoroutine(DoSave());
}
protected IEnumerator DoSave(){
/// Code that prepares a bunch of stuff necessary to save the game goes here
foreach (item in itemsToSave)
{
// Code that adds 'item' to a data structure in memory that is accessible by later functions goes here //
yield return new WaitForSeconds(0.05f);
}
source = new CancellationTokenSource();
WriteFiles(source.Token);
}
private async Task WriteFiles(CancellationToken token)
{
Task task = Task.Run(() => GenerateSaveFiles(), token);
// (A canceled task will raise an exception when awaited).
await task;
}
private void GenerateSaveFiles()
{
/// Functions that actually write to disk go here
/// E.g. File.WriteAllText();
/// This function has access to the place in memory where 'items' were being added to in the DoSave() foreach loop, so it just dumps that memory storage to disk
}
我这样设置是为了可以在单独的线程中异步保存并跟踪它的状态(例如,是否失败,以及错误消息)。
SaveGame()
然后在不同的事件(如拾取物品、杀死敌人等)时围绕我的代码库调用以实时保存游戏。这对我 99% 的用例都适用,但有些情况下我想调用 SavGame()
并等待它完成,然后再做任何其他事情。例如,如果玩家死亡,我想将状态写入磁盘而不给其他任何事情(例如在不同场景中重生玩家)发生的机会。
我研究了回调之类的一些东西(例如 https://codesaying.com/action-callback-in-unity/),但我不确定如何针对我的主要 SaveGame()
任务的用例正确设置它 运行 异步,但其他时候可以等待它完成,或者至少让调用 SaveGame()
的方法知道它何时完成。
只需做一个任何人都可以访问的简单布尔值 属性:
public bool IsSavingGame {
get; private set;
}
// ...
private IEnumerator SaveGameCoroutine(){
IsSavingGame = true;
// Your save functionality.
IsSavingGame = false;
}
例如,如果你想在场景保存时拒绝加载任何场景,你可以编写这个脚本:
// or you can singleton it
[SerializeField]
private SaveManager yourSaveManger
public void LoadScene(){
StartCoroutine(LoadSceneCoroutine());
}
private IEnumerator LoadSceneCoroutine(){
while (yourSaveManger.IsSavingGame){
// Show a loading or saving screen or something
yield return null;
}
SceneManager.LoadScene("Your Scene Name");
}
好的,经过大量的阅读和努力,我能够让它发挥作用。由于我使用 Coroutine 的设置以及嵌套在其中的 Task 和其他 Coroutines,因此有很多事情会妨碍。
我的解决方案是 two-fold
- 对于协程 (
DoSave()
) 和嵌套在其中的任何其他协程,我传递了一个参数waitForSaveComplete
如果为真,我不会 运行 [=] 中的任何 yield 语句14=] 或其嵌套协程。因此,在我的问题示例中,我将所有 yield 语句包装成这样:
if (!waitForSaveComplete)
{
yield return new WaitForSeconds(0.05f);
}
我还有一些同步嵌套协程,我用同样的方式包装了它们的 yield 语句。
接下来,我把嵌套协程的调用改成了:
yield return StartCoroutine(MySynchronizedCoroutine());
至:
if(!waitForSaveComplete) {
yield return StartCoroutine(MySynchronizedCoroutine(waitForSavComplete));
} else
{
// IMPORTANT: Make sure MySynchronizedCoroutine also uses
// waitForSaveComplete to wrap its own yield statements
// or else this won't work!!!
StartCoroutine(MySynchronizedCoroutine(waitForSavComplete));
}
像这样包装 yield 语句是可行的,因为这是协程暂停并放弃控制的地方,直到满足相应的 Wait
条件。到那时它是 'blocking' 鉴于我不确定为什么我花了这么长时间才想到这个。
- 对于任务,我在异步情况下使用了
WaitUntil
以确保它在协程本身发生任何其他事情之前完成。在waitForSaveComplete
的情况下,我什至没有启动任务,而是 运行ningGenerateSaveFiles()
直接像这样
if (waitForSaveComplete)
{
GenerateSaveFiles();
} else
{
Task writeTask = WriteFiles(source.Token);
yield return new WaitUntil(() => writeTask.IsCompleted);
}
再一次,另一个 'duh' 时刻,只是不喜欢.. 在不需要的情况下开始任务。
这似乎可行,但也有点复杂,因为嵌套协同程序似乎是滋生可怕、难以跟踪错误的沃土。唉,我的异步编程经验非常有限,所以我没有更好的设计或实现想法。