docs(coroutine): 更新协程系统文档

- 重构 Core 协程系统文档,优化概述和核心概念说明
- 新增 Godot 协程系统集成文档
- 添加协程系统使用教程
- 更新等待指令说明,包括时间、条件、Task 和事件等待
- 补充协程控制、快照查询和生命周期管理相关内容
- 修正代码示例和 API 使用说明
This commit is contained in:
GeWuYou 2026-04-05 15:06:53 +08:00
parent 1c41c57d72
commit 03346fbfe7
3 changed files with 332 additions and 1074 deletions

View File

@ -1,61 +1,95 @@
--- ---
title: 协程系统 title: 协程系统
description: 协程系统提供基于 IEnumerator<IYieldInstruction>调度、等待和组合能力可与事件、Task、命令与查询集成 description: 基于 IEnumerator<IYieldInstruction>协程调度系统支持时间等待、阶段等待、Task 桥接、事件等待与运行时快照查询
--- ---
# 协程系统 # 协程系统
## 概述 ## 概述
GFramework 的 Core 协程系统基于 `IEnumerator<IYieldInstruction>` 构建,通过 `CoroutineScheduler` `GFramework.Core.Coroutine` 提供一个宿主无关的协程内核。它围绕 `CoroutineScheduler` 工作,统一处理:
统一推进协程执行。它适合处理分帧逻辑、时间等待、条件等待、Task 桥接,以及事件驱动的异步流程。
协程系统主要由以下部分组成: - `IEnumerator<IYieldInstruction>` 形式的协程推进
- 时间等待、条件等待、Task 等待与事件等待
- 标签、分组、暂停、恢复与终止
- 取消令牌、完成状态查询与运行快照
- 调度阶段语义,例如默认更新、固定更新和帧结束
- `CoroutineScheduler`:负责运行、更新和控制协程 Core 协程本身不依赖任何具体引擎;阶段语义是否真实成立,取决于宿主是否为调度器提供了匹配的执行阶段。
- `CoroutineHandle`:用于标识协程实例并控制其状态
- `IYieldInstruction`:定义等待行为的统一接口
- `Instructions`:内置等待指令集合
- `CoroutineHelper`:提供常用等待与生成器辅助方法
- `Extensions`:提供 Task、组合、命令、查询和 Mediator 场景下的扩展方法
## 核心概念 ## CoroutineScheduler
### CoroutineScheduler ### 基础创建
`CoroutineScheduler` 是协程系统的核心调度器。构造时需要提供 `ITimeSource`,调度器会在每次 `Update()` 时读取时间增量并推进所有活跃协程。
```csharp ```csharp
using GFramework.Core.Abstractions.Coroutine; using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Coroutine; using GFramework.Core.Coroutine;
ITimeSource timeSource = /* 你的时间源实现 */; ITimeSource scaledTimeSource = /* 游戏时间 */;
var scheduler = new CoroutineScheduler(timeSource); ITimeSource realtimeTimeSource = /* 真实时间,可选 */;
var handle = scheduler.Run(MyCoroutine()); var scheduler = new CoroutineScheduler(
scaledTimeSource,
realtimeTimeSource: realtimeTimeSource,
executionStage: CoroutineExecutionStage.Update);
// 在你的主循环中推进协程 var handle = scheduler.Run(MyCoroutine(), tag: "bootstrap", group: "loading");
// 在宿主主循环中推进协程
scheduler.Update(); scheduler.Update();
``` ```
如果需要统计信息,可以启用构造函数的 `enableStatistics` 参数。 构造参数中最重要的两个语义是:
### CoroutineHandle - `realtimeTimeSource`
- 如果提供,`WaitForSecondsRealtime` 会使用它的 `DeltaTime`
- 如果不提供,实时等待会退化为使用默认时间源
- `executionStage`
- `Update`:默认阶段
- `FixedUpdate`:固定步阶段
- `EndOfFrame`:帧结束阶段
`CoroutineHandle` 用于引用具体协程,并配合调度器进行控制: ### 控制与完成状态
```csharp ```csharp
var handle = scheduler.Run(MyCoroutine(), tag: "gameplay", group: "battle"); using var cts = new CancellationTokenSource();
if (scheduler.IsCoroutineAlive(handle)) var handle = scheduler.Run(
{ LoadResources(),
scheduler.Pause(handle); tag: "loading",
scheduler.Resume(handle); group: "bootstrap",
scheduler.Kill(handle); cancellationToken: cts.Token);
}
scheduler.Pause(handle);
scheduler.Resume(handle);
scheduler.Kill(handle);
var completionStatus = await scheduler.WaitForCompletionAsync(handle);
``` ```
### IYieldInstruction 协程的最终结果由 `CoroutineCompletionStatus` 表示:
- `Completed`
- `Cancelled`
- `Faulted`
- `Unknown`
### 快照与可观测性
```csharp
if (scheduler.TryGetSnapshot(handle, out var snapshot))
{
Console.WriteLine(snapshot.State);
Console.WriteLine(snapshot.WaitingInstructionType);
Console.WriteLine(snapshot.ExecutionStage);
}
var allSnapshots = scheduler.GetActiveSnapshots();
```
快照适合做诊断、调试面板和运行中状态检查。
## IYieldInstruction
协程通过 `yield return IYieldInstruction` 表达等待逻辑: 协程通过 `yield return IYieldInstruction` 表达等待逻辑:
@ -67,91 +101,40 @@ public interface IYieldInstruction
} }
``` ```
## 基本用法
### 创建简单协程
```csharp
using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Coroutine.Instructions;
public IEnumerator<IYieldInstruction> SimpleCoroutine()
{
Console.WriteLine("开始");
yield return new Delay(2.0);
Console.WriteLine("2 秒后");
yield return new WaitOneFrame();
Console.WriteLine("下一帧");
}
```
### 使用 CoroutineHelper
`CoroutineHelper` 提供了一组常用等待和生成器辅助方法:
```csharp
using GFramework.Core.Coroutine;
public IEnumerator<IYieldInstruction> HelperCoroutine()
{
yield return CoroutineHelper.WaitForSeconds(1.5);
yield return CoroutineHelper.WaitForOneFrame();
yield return CoroutineHelper.WaitForFrames(10);
yield return CoroutineHelper.WaitUntil(() => isReady);
yield return CoroutineHelper.WaitWhile(() => isLoading);
}
```
除了直接返回等待指令,`CoroutineHelper` 也可以直接生成可运行的协程枚举器:
```csharp
scheduler.Run(CoroutineHelper.DelayedCall(2.0, () => Console.WriteLine("延迟执行")));
scheduler.Run(CoroutineHelper.RepeatCall(1.0, 5, () => Console.WriteLine("重复执行")));
using var cts = new CancellationTokenSource();
scheduler.Run(CoroutineHelper.RepeatCallForever(1.0, () => Console.WriteLine("持续执行"), cts.Token));
```
### 控制协程状态
```csharp
var handle = scheduler.Run(LoadResources(), tag: "loading", group: "bootstrap");
scheduler.Pause(handle);
scheduler.Resume(handle);
scheduler.Kill(handle);
scheduler.KillByTag("loading");
scheduler.PauseGroup("bootstrap");
scheduler.ResumeGroup("bootstrap");
scheduler.KillGroup("bootstrap");
var cleared = scheduler.Clear();
```
## 常用等待指令 ## 常用等待指令
### 时间与帧 ### 时间与帧
```csharp ```csharp
yield return new Delay(1.0); yield return new Delay(1.0);
yield return new WaitForSecondsScaled(1.0);
yield return new WaitForSecondsRealtime(1.0); yield return new WaitForSecondsRealtime(1.0);
yield return new WaitOneFrame(); yield return new WaitOneFrame();
yield return new WaitForNextFrame(); yield return new WaitForNextFrame();
yield return new WaitForFrames(5); yield return new WaitForFrames(5);
yield return new WaitForEndOfFrame();
yield return new WaitForFixedUpdate(); yield return new WaitForFixedUpdate();
yield return new WaitForEndOfFrame();
``` ```
语义说明:
- `Delay``WaitForSecondsScaled`
- 使用调度器默认时间源推进
- `WaitForSecondsRealtime`
- 优先使用调度器的 `realtimeTimeSource`
- `WaitForFixedUpdate`
- 仅在 `CoroutineExecutionStage.FixedUpdate` 调度器中推进
- `WaitForEndOfFrame`
- 仅在 `CoroutineExecutionStage.EndOfFrame` 调度器中推进
如果宿主没有提供匹配阶段,这类阶段型等待不会自然完成。
### 条件等待 ### 条件等待
```csharp ```csharp
yield return new WaitUntil(() => health > 0); yield return new WaitUntil(() => health > 0);
yield return new WaitWhile(() => isLoading); yield return new WaitWhile(() => isLoading);
yield return new WaitForPredicate(() => hp >= maxHp); yield return new WaitForPredicate(() => hp >= maxHp);
yield return new WaitForPredicate(() => isBusy, waitForTrue: false);
yield return new WaitUntilOrTimeout(() => connected, timeoutSeconds: 5.0); yield return new WaitUntilOrTimeout(() => connected, timeoutSeconds: 5.0);
yield return new WaitForConditionChange(() => isPaused, waitForTransitionTo: true); yield return new WaitForConditionChange(() => isPaused, waitForTransitionTo: true);
``` ```
@ -159,27 +142,14 @@ yield return new WaitForConditionChange(() => isPaused, waitForTransitionTo: tru
### Task 桥接 ### Task 桥接
```csharp ```csharp
using System.Threading.Tasks;
using GFramework.Core.Coroutine.Extensions; using GFramework.Core.Coroutine.Extensions;
Task loadTask = LoadDataAsync(); Task loadTask = LoadDataAsync();
yield return loadTask.AsCoroutineInstruction(); yield return loadTask.AsCoroutineInstruction();
var handle = scheduler.StartTaskAsCoroutine(LoadDataAsync());
``` ```
也可以将 `Task` 转成协程枚举器后直接交给调度器:
```csharp
var coroutine = LoadDataAsync().ToCoroutineEnumerator();
var handle1 = scheduler.Run(coroutine);
var handle2 = scheduler.StartTaskAsCoroutine(LoadDataAsync());
```
- `AsCoroutineInstruction()` 适合已经处在某个协程内部,只需要在当前位置等待 `Task` 完成的场景。
- `ToCoroutineEnumerator()` 适合需要把 `Task` 先转换成 `IEnumerator<IYieldInstruction>`,再传给 `scheduler.Run(...)`
`Sequence(...)` 或其他只接受协程枚举器的 API。
- `StartTaskAsCoroutine()` 适合已经持有 `CoroutineScheduler`,并希望把 `Task` 直接作为一个顶层协程启动的场景。
### 等待事件 ### 等待事件
```csharp ```csharp
@ -188,236 +158,52 @@ using GFramework.Core.Coroutine.Instructions;
public IEnumerator<IYieldInstruction> WaitForEventExample(IEventBus eventBus) public IEnumerator<IYieldInstruction> WaitForEventExample(IEventBus eventBus)
{ {
using var waitEvent = new WaitForEvent<PlayerDiedEvent>(eventBus); using var wait = new WaitForEvent<PlayerJoinedEvent>(eventBus);
yield return waitEvent;
var eventData = waitEvent.EventData;
Console.WriteLine($"玩家 {eventData!.PlayerId} 死亡");
}
```
为事件等待附加超时:
```csharp
public IEnumerator<IYieldInstruction> WaitForEventWithTimeoutExample(IEventBus eventBus)
{
using var waitEvent = new WaitForEvent<PlayerJoinedEvent>(eventBus);
var timeoutWait = new WaitForEventWithTimeout<PlayerJoinedEvent>(waitEvent, 5.0f);
yield return timeoutWait;
if (timeoutWait.IsTimeout)
Console.WriteLine("等待超时");
else
Console.WriteLine($"玩家加入: {timeoutWait.EventData!.PlayerName}");
}
```
等待两个事件中的任意一个:
```csharp
public IEnumerator<IYieldInstruction> WaitForEitherEvent(IEventBus eventBus)
{
using var wait = new WaitForMultipleEvents<PlayerReadyEvent, PlayerQuitEvent>(eventBus);
yield return wait; yield return wait;
if (wait.TriggeredBy == 1) Console.WriteLine(wait.EventData?.PlayerName);
Console.WriteLine($"Ready: {wait.FirstEventData}");
else
Console.WriteLine($"Quit: {wait.SecondEventData}");
} }
``` ```
### 协程组合 ## CoroutineHelper
等待子协程完成: `CoroutineHelper` 提供一组常用简写:
```csharp
yield return CoroutineHelper.WaitForSeconds(1.5);
yield return CoroutineHelper.WaitForOneFrame();
yield return CoroutineHelper.WaitForFrames(10);
yield return CoroutineHelper.WaitUntil(() => isReady);
yield return CoroutineHelper.WaitWhile(() => isLoading);
```
也可以直接生成可运行的协程枚举器:
```csharp
scheduler.Run(CoroutineHelper.DelayedCall(2.0, () => Console.WriteLine("延迟执行")));
scheduler.Run(CoroutineHelper.RepeatCall(1.0, 5, () => Console.WriteLine("重复执行")));
```
## 协程组合
```csharp ```csharp
public IEnumerator<IYieldInstruction> ParentCoroutine() public IEnumerator<IYieldInstruction> ParentCoroutine()
{ {
Console.WriteLine("父协程开始");
yield return new WaitForCoroutine(ChildCoroutine()); yield return new WaitForCoroutine(ChildCoroutine());
Console.WriteLine("子协程完成");
} }
private IEnumerator<IYieldInstruction> ChildCoroutine() private IEnumerator<IYieldInstruction> ChildCoroutine()
{ {
yield return CoroutineHelper.WaitForSeconds(1.0); yield return new Delay(1.0);
Console.WriteLine("子协程执行");
} }
``` ```
等待多个句柄全部完成: 如果需要等待多个顶层协程句柄,可以结合 `WaitForAllCoroutines``ParallelCoroutines(...)` 使用。
```csharp ## 建议
public IEnumerator<IYieldInstruction> WaitForMultipleCoroutines(CoroutineScheduler scheduler)
{
var handles = new List<CoroutineHandle>
{
scheduler.Run(LoadTexture()),
scheduler.Run(LoadAudio()),
scheduler.Run(LoadModel())
};
yield return new WaitForAllCoroutines(scheduler, handles); - 普通游戏时间等待优先使用 `Delay``WaitForSecondsScaled`
- 只有宿主提供真实时间源时再使用 `WaitForSecondsRealtime`
Console.WriteLine("所有资源加载完成"); - 只有宿主显式区分阶段时才使用 `WaitForFixedUpdate``WaitForEndOfFrame`
} - 需要对接生命周期或外部取消时,优先传入 `CancellationToken`
``` - 需要诊断线上状态时,优先使用 `TryGetSnapshot(...)``GetActiveSnapshots()`
### 进度等待
```csharp
public IEnumerator<IYieldInstruction> LoadingWithProgress()
{
yield return CoroutineHelper.WaitForProgress(
duration: 3.0,
onProgress: progress => Console.WriteLine($"加载进度: {progress * 100:F0}%"));
}
```
## 扩展方法
### 组合扩展
`CoroutineComposeExtensions` 提供链式顺序组合能力:
```csharp
using GFramework.Core.Coroutine.Extensions;
var chained =
LoadConfig()
.Then(() => Console.WriteLine("配置加载完成"))
.Then(StartBattle());
scheduler.Run(chained);
```
### 协程生成扩展
`CoroutineExtensions` 提供了一些常用的协程生成器:
```csharp
using GFramework.Core.Coroutine.Extensions;
var delayed = CoroutineExtensions.ExecuteAfter(2.0, () => Console.WriteLine("延迟执行"));
var repeated = CoroutineExtensions.RepeatEvery(1.0, () => Console.WriteLine("tick"), count: 5);
var progress = CoroutineExtensions.WaitForSecondsWithProgress(3.0, p => Console.WriteLine(p));
scheduler.Run(delayed);
scheduler.Run(repeated);
scheduler.Run(progress);
```
顺序或并行组合多个协程:
```csharp
var sequence = CoroutineExtensions.Sequence(LoadConfig(), LoadScene(), StartBattle());
scheduler.Run(sequence);
var parallel = scheduler.ParallelCoroutines(LoadTexture(), LoadAudio(), LoadModel());
scheduler.Run(parallel);
```
### Task 扩展
`TaskCoroutineExtensions` 提供了三类扩展:
- `AsCoroutineInstruction()`:把 `Task` / `Task<T>` 包装成等待指令
- `ToCoroutineEnumerator()`:把 `Task` / `Task<T>` 转成协程枚举器
- `StartTaskAsCoroutine()`:直接通过调度器启动 Task 协程
### 命令、查询与 Mediator 扩展
这些扩展都定义在 `GFramework.Core.Coroutine.Extensions` 命名空间中。
### 命令协程
```csharp
using GFramework.Core.Coroutine.Extensions;
public IEnumerator<IYieldInstruction> ExecuteCommand(IContextAware contextAware)
{
yield return contextAware.SendCommandCoroutineWithErrorHandler(
new LoadSceneCommand(),
ex => Console.WriteLine(ex.Message));
}
```
如果命令执行后需要等待事件:
```csharp
public IEnumerator<IYieldInstruction> ExecuteCommandAndWaitEvent(IContextAware contextAware)
{
yield return contextAware.SendCommandAndWaitEventCoroutine<LoadSceneCommand, SceneLoadedEvent>(
new LoadSceneCommand(),
evt => Console.WriteLine($"场景加载完成: {evt.SceneName}"),
timeout: 5.0f);
}
```
### 查询协程
`SendQueryCoroutine` 会同步执行查询,并通过回调返回结果:
```csharp
public IEnumerator<IYieldInstruction> QueryPlayer(IContextAware contextAware)
{
yield return contextAware.SendQueryCoroutine<GetPlayerDataQuery, PlayerData>(
new GetPlayerDataQuery { PlayerId = 1 },
playerData => Console.WriteLine($"玩家名称: {playerData.Name}"));
}
```
### Mediator 协程
如果项目使用 `Mediator.IMediator`,还可以使用 `MediatorCoroutineExtensions`
```csharp
public IEnumerator<IYieldInstruction> ExecuteMediatorCommand(IContextAware contextAware)
{
yield return contextAware.SendCommandCoroutine(
new SaveArchiveCommand(),
ex => Console.WriteLine(ex.Message));
}
```
## 异常处理
调度器会在协程抛出未捕获异常时触发 `OnCoroutineException`
```csharp
scheduler.OnCoroutineException += (handle, exception) =>
{
Console.WriteLine($"协程 {handle} 异常: {exception.Message}");
};
```
如果协程等待的是 `Task`,也可以通过 `WaitForTask` / `WaitForTask<T>` 检查任务异常。
## 常见问题
### 协程什么时候执行?
协程在调度器的 `Update()` 中推进。调度器每次更新都会先更新 `ITimeSource`,再推进所有活跃协程。
### 协程是多线程的吗?
不是。协程本身仍由调用 `Update()` 的线程推进,通常用于主线程上的分帧流程控制。
### `Delay``CoroutineHelper.WaitForSeconds()` 有什么区别?
两者表达的是同一类等待语义。`CoroutineHelper.WaitForSeconds()` 只是 `Delay` 的辅助构造方法。
### 如何等待异步方法?
在现有协程里等待 `Task` 时,优先使用 `yield return task.AsCoroutineInstruction()`;如果要把 `Task` 单独交给调度器启动,使用
`scheduler.StartTaskAsCoroutine(task)`;如果中间还需要传给只接受协程枚举器的 API则先调用 `task.ToCoroutineEnumerator()`
## 相关文档
- [事件系统](/zh-CN/core/events)
- [CQRS](/zh-CN/core/cqrs)
- [协程系统教程](/zh-CN/tutorials/coroutine-tutorial)

View File

@ -2,41 +2,30 @@
## 概述 ## 概述
GFramework 的协程系统由两层组成 `GFramework.Godot.Coroutine` 在 Core 协程内核之上提供 Godot 宿主集成,负责把 Godot 的不同更新循环映射为真实的协程阶段语义
- `GFramework.Core.Coroutine` 提供通用调度器、`IYieldInstruction` 和一组等待指令。 - `Segment.Process`
- `GFramework.Godot.Coroutine` 提供 Godot 环境下的运行入口、分段调度以及节点生命周期辅助方法。 - `Segment.ProcessIgnorePause`
- `Segment.PhysicsProcess`
- `Segment.DeferredProcess`
Godot 集成层的核心入口包括: 它同时补充了以下宿主能力
- `RunCoroutine(...)` - 节点归属协程运行入口
- `Timing.RunGameCoroutine(...)` - 节点退树自动终止
- `Timing.RunUiCoroutine(...)` - Godot 真实时间源
- `Timing.CallDelayed(...)` - 句柄控制与快照查询
- `CancelWith(...)`
协程本身使用 `IEnumerator<IYieldInstruction>` ## 启动协程
## 主要能力 ### 直接运行枚举器
- 在 Godot 中按不同更新阶段运行协程
- 等待时间、帧、条件、Task 和事件总线事件
- 显式将协程与一个或多个 `Node` 的生命周期绑定
- 通过 `CoroutineHandle` 暂停、恢复、终止协程
- 将命令、查询、发布操作直接包装为协程运行
## 基本用法
### 启动协程
```csharp ```csharp
using System.Collections.Generic;
using GFramework.Core.Abstractions.Coroutine; using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Coroutine.Instructions; using GFramework.Core.Coroutine.Instructions;
using GFramework.Godot.Coroutine; using GFramework.Godot.Coroutine;
using Godot;
public partial class MyNode : Node public partial class DemoNode : Node
{ {
public override void _Ready() public override void _Ready()
{ {
@ -45,242 +34,131 @@ public partial class MyNode : Node
private IEnumerator<IYieldInstruction> Demo() private IEnumerator<IYieldInstruction> Demo()
{ {
GD.Print("开始执行"); GD.Print("start");
yield return new Delay(1.0);
yield return new Delay(2.0);
GD.Print("2 秒后继续执行");
yield return new WaitForEndOfFrame(); yield return new WaitForEndOfFrame();
GD.Print("当前帧结束后继续执行"); GD.Print("done");
} }
} }
``` ```
`RunCoroutine()` 默认在 `Segment.Process` 上运行,也就是普通帧更新阶段 默认情况下,`RunCoroutine()` 会在 `Segment.Process` 上运行
除了枚举器扩展方法,也可以直接使用 `Timing` 的静态入口: ### 以 Node 作为生命周期所有者运行
更推荐的方式是以节点为入口运行协程:
```csharp ```csharp
Timing.RunCoroutine(Demo()); public override void _Ready()
Timing.RunGameCoroutine(GameLoop());
Timing.RunUiCoroutine(MenuAnimation());
```
### 显式绑定节点生命周期
可以使用 `CancelWith(...)` 将协程与一个或多个节点的生命周期关联。
```csharp
using System.Collections.Generic;
using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Coroutine.Instructions;
using GFramework.Godot.Coroutine;
using Godot;
public partial class MyNode : Node
{ {
public override void _Ready() this.RunCoroutine(LongRunningTask(), Segment.Process, tag: "ui-blink");
{
LongRunningTask()
.CancelWith(this)
.RunCoroutine();
}
private IEnumerator<IYieldInstruction> LongRunningTask()
{
while (true)
{
GD.Print("tick");
yield return new Delay(1.0);
}
}
} }
``` ```
`CancelWith` 目前有三种重载: 这会自动把协程登记为该节点归属协程,并在节点退出场景树时终止它。
- `CancelWith(Node node)` 你仍然可以继续使用 `CancelWith(...)` 包装已有枚举器;它适合把一个协程显式绑定到多个节点生命周期。
- `CancelWith(Node node1, Node node2)`
- `CancelWith(params Node[] nodes)`
`CancelWith(...)` 内部通过 `Timing.IsNodeAlive(...)` 判断节点是否仍然有效。只要任一被监视的节点出现以下任一情况,包装后的协程就会停止继续枚举: ## Segment 与阶段语义
- 节点引用为 `null` Godot 层会把不同 segment 映射为不同的 `CoroutineExecutionStage`
- Godot 实例已经失效或已被释放
- 节点已进入 `queue_free` / `IsQueuedForDeletion()`
- 节点已退出场景树,`IsInsideTree()` 返回 `false`
这意味着协程不只会在节点真正释放时停止;节点一旦退出场景树,下一次推进时也会停止。 - `Segment.Process`
- 对应默认更新阶段
## Segment 分段 - 场景树暂停时不会推进
- `Segment.ProcessIgnorePause`
Godot 层通过 `Segment` 决定协程挂在哪个调度器上: - 同样对应默认更新阶段
- 场景树暂停时仍会推进
```csharp - `Segment.PhysicsProcess`
public enum Segment - 对应固定更新阶段
{ - `WaitForFixedUpdate` 会在这里真实完成
Process, - `Segment.DeferredProcess`
ProcessIgnorePause, - 对应帧结束阶段
PhysicsProcess, - `WaitForEndOfFrame` 会在这里真实完成
DeferredProcess
}
```
- `Process`:普通 `_Process` 段,场景树暂停时不会推进。
- `ProcessIgnorePause`:同样使用 process delta但即使场景树暂停也会推进。
- `PhysicsProcess`:在 `_PhysicsProcess` 段推进。
- `DeferredProcess`:通过 `CallDeferred` 在当前帧之后推进,场景树暂停时不会推进。
示例: 示例:
```csharp ```csharp
UiAnimation().RunCoroutine(Segment.ProcessIgnorePause); this.RunCoroutine(PhysicsRoutine(), Segment.PhysicsProcess);
PhysicsRoutine().RunCoroutine(Segment.PhysicsProcess); this.RunCoroutine(UiAnimation(), Segment.ProcessIgnorePause);
``` ```
如果你更偏向语义化入口,也可以直接使用: ## 时间等待语义
Godot 集成层为每个调度器同时提供了两套时间源:
- 缩放时间
- 来自 `_Process` / `_PhysicsProcess` 的帧增量
- 真实时间
- 来自 Godot 单调时钟,不受时间缩放和暂停影响
因此:
- `Delay` / `WaitForSecondsScaled` 使用宿主帧增量
- `WaitForSecondsRealtime` 使用真实时间
这意味着 UI 或暂停菜单中的协程可以安全使用 `WaitForSecondsRealtime` 保持真实计时。
## 生命周期管理
### 自动归属
```csharp ```csharp
Timing.RunGameCoroutine(GameLoop()); var handle = this.RunCoroutine(LoadAvatar(), tag: "avatar");
Timing.RunUiCoroutine(MenuAnimation());
``` ```
### 延迟调用 ### 手动绑定多个节点
`Timing` 还提供了两个延迟调用快捷方法: ```csharp
LongRunningTask()
.CancelWith(this, panelNode)
.RunCoroutine();
```
### 主动清理
```csharp
Timing.KillCoroutine(handle);
Timing.KillCoroutines(this);
Timing.KillCoroutines("avatar");
Timing.KillAllCoroutines();
```
## 调试与查询
```csharp
if (Timing.TryGetCoroutineSnapshot(handle, out var snapshot))
{
GD.Print(snapshot.ExecutionStage);
GD.Print(snapshot.WaitingInstructionType);
}
var ownedCount = Timing.GetOwnedCoroutineCount(this);
```
实例级计数器:
- `Timing.Instance.ProcessCoroutines`
- `Timing.Instance.ProcessIgnorePauseCoroutines`
- `Timing.Instance.PhysicsCoroutines`
- `Timing.Instance.DeferredCoroutines`
## 延迟调用
```csharp ```csharp
Timing.CallDelayed(1.0, () => GD.Print("1 秒后执行")); Timing.CallDelayed(1.0, () => GD.Print("1 秒后执行"));
Timing.CallDelayed(1.0, () => GD.Print("节点仍然有效时执行"), this); Timing.CallDelayed(1.0, () => GD.Print("节点仍然有效时执行"), this);
``` ```
第二个重载会在执行前检查传入节点是否仍然存活。 第二个重载内部使用节点归属语义,因此节点退树后不会再触发动作。
## 常用等待指令
以下类型可直接用于 `yield return`
### 时间与帧
```csharp
yield return new Delay(1.0);
yield return new WaitForSecondsRealtime(1.0);
yield return new WaitOneFrame();
yield return new WaitForNextFrame();
yield return new WaitForFrames(5);
yield return new WaitForEndOfFrame();
```
说明:
- `Delay` 是最直接的秒级等待。
- `WaitForSecondsRealtime` 常用于需要独立计时语义的协程场景。
- `WaitOneFrame``WaitForNextFrame``WaitForEndOfFrame` 用于帧级调度控制。
### 条件等待
```csharp
yield return new WaitUntil(() => health > 0);
yield return new WaitWhile(() => isLoading);
```
### Task 等待
```csharp
using System.Threading.Tasks;
using GFramework.Core.Coroutine.Extensions;
Task loadTask = LoadSomethingAsync();
yield return loadTask.AsCoroutineInstruction();
```
也可以先把 `Task` 转成协程枚举器,再直接运行:
```csharp
LoadSomethingAsync()
.ToCoroutineEnumerator()
.RunCoroutine();
```
- 已经在一个协程内部时,优先使用 `yield return task.AsCoroutineInstruction()`,这样可以直接把 `Task` 嵌入当前协程流程。
- 如果要把一个现成的 `Task` 当作独立协程入口交给 Godot 协程系统运行,再使用
`task.ToCoroutineEnumerator().RunCoroutine()`
### 等待事件总线事件
可以通过事件总线等待业务事件:
```csharp
using System.Collections.Generic;
using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Coroutine.Instructions;
private IEnumerator<IYieldInstruction> WaitForGameEvent(IEventBus eventBus)
{
using var wait = new WaitForEvent<PlayerSpawnedEvent>(eventBus);
yield return wait;
var evt = wait.EventData;
}
```
如需为事件等待附加超时控制,可结合 `WaitForEventWithTimeout<TEvent>`
## 协程控制
协程启动后会返回 `CoroutineHandle`,可用于控制运行状态:
```csharp
var handle = Demo().RunCoroutine(tag: "demo");
Timing.PauseCoroutine(handle);
Timing.ResumeCoroutine(handle);
Timing.KillCoroutine(handle);
Timing.KillCoroutines("demo");
Timing.KillAllCoroutines();
```
如果希望在场景初始化阶段主动确保调度器存在,也可以调用:
```csharp
Timing.Prewarm();
```
## 与 IContextAware 集成 ## 与 IContextAware 集成
`GFramework.Godot.Coroutine` 还提供了一组扩展方法,用于把命令、查询和通知直接包装成协程: Godot 层还提供以下扩展方法,用于把命令、查询和通知直接包装成协程并交给 Timing 调度:
- `RunCommandCoroutine(...)` - `RunCommandCoroutine(...)`
- `RunCommandCoroutine<TResponse>(...)` - `RunCommandCoroutine<TResponse>(...)`
- `RunQueryCoroutine<TResponse>(...)` - `RunQueryCoroutine<TResponse>(...)`
- `RunPublishCoroutine(...)` - `RunPublishCoroutine(...)`
这些方法会把异步操作转换为协程,并交给 `RunCoroutine(...)` 调度执行。 这些 API 仍然可以与 `Segment`、节点归属和标签控制一起使用。
例如:
```csharp
public void StartCoroutines(IContextAware contextAware)
{
contextAware.RunCommandCoroutine(
new EnterBattleCommand(),
Segment.Process,
tag: "battle");
contextAware.RunQueryCoroutine(
new LoadPlayerQuery(),
Segment.ProcessIgnorePause,
tag: "ui");
}
```
这些扩展适合在 Godot 节点或控制器中直接启动和跟踪业务协程。
## 相关文档
- [Godot 概述](./index.md)
- [Godot 扩展方法](./extensions.md)
- [信号扩展](./signal.md)
- [事件系统](../core/events.md)

View File

@ -1,6 +1,6 @@
--- ---
title: 使用协程系统 title: 使用协程系统
description: 学习如何使用协程系统实现异步操作和时间控制 description: 学习如何在 GFramework 中创建调度器、运行协程并结合时间、阶段、Task 与生命周期管理实现常见异步流程。
--- ---
# 使用协程系统 # 使用协程系统
@ -9,590 +9,184 @@ description: 学习如何使用协程系统实现异步操作和时间控制
完成本教程后,你将能够: 完成本教程后,你将能够:
- 理解协程的基本概念和执行机制 - 创建并驱动 `CoroutineScheduler`
- 创建和启动协程 - 编写 `IEnumerator<IYieldInstruction>` 协程
- 使用各种等待指令控制协程执行 - 区分缩放时间、真实时间与阶段等待
- 在架构组件中使用协程 - 使用句柄、取消令牌和快照查询控制协程
- 实现常见的游戏逻辑(延迟执行、循环任务、事件等待) - 在 Godot 中把协程绑定到节点生命周期
## 前置条件
- 已安装 GFramework.Core NuGet 包
- 了解 C# 基础语法和迭代器IEnumerator
- 阅读过[快速开始](/zh-CN/getting-started/quick-start)
- 了解[生命周期管理](/zh-CN/core/lifecycle)
## 步骤 1创建第一个协程 ## 步骤 1创建第一个协程
首先,让我们创建一个简单的协程来理解基本概念。
```csharp ```csharp
using GFramework.Core.Abstractions.Coroutine; using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Coroutine; using GFramework.Core.Coroutine;
using GFramework.Core.Coroutine.Instructions; using GFramework.Core.Coroutine.Instructions;
namespace MyGame.Systems public sealed class TutorialLoop
{ {
public class TutorialSystem : AbstractSystem private readonly CoroutineScheduler _scheduler;
public TutorialLoop(ITimeSource timeSource)
{ {
protected override void OnInit() _scheduler = new CoroutineScheduler(timeSource);
{ }
// 启动协程
this.StartCoroutine(MyFirstCoroutine());
}
/// <summary> public void Start()
/// 第一个协程示例 {
/// </summary> _scheduler.Run(MyFirstCoroutine(), tag: "tutorial");
private IEnumerator<IYieldInstruction> MyFirstCoroutine() }
{
Console.WriteLine("协程开始执行");
// 等待 1 秒 public void Tick()
yield return CoroutineHelper.WaitForSeconds(1.0); {
_scheduler.Update();
}
Console.WriteLine("1 秒后执行"); private IEnumerator<IYieldInstruction> MyFirstCoroutine()
{
Console.WriteLine("协程开始");
// 等待 1 帧 yield return new Delay(1.0);
yield return CoroutineHelper.WaitForOneFrame(); Console.WriteLine("1 秒后");
Console.WriteLine("下一帧执行"); yield return new WaitOneFrame();
Console.WriteLine("下一帧");
// 等待 5 帧 yield return new WaitForFrames(3);
yield return CoroutineHelper.WaitForFrames(5); Console.WriteLine("3 帧后");
Console.WriteLine("5 帧后执行");
}
} }
} }
``` ```
**代码说明** 关键点
- 协程方法返回 `IEnumerator<IYieldInstruction>` - 协程返回类型必须是 `IEnumerator<IYieldInstruction>`
- 使用 `yield return` 返回等待指令 - 调度器不会自动运行,你必须在宿主主循环中调用 `Update()`
- `this.StartCoroutine()` 扩展方法启动协程 - `Run(...)` 返回 `CoroutineHandle`,后续控制都依赖这个句柄
- `WaitForSeconds` 等待指定秒数
- `WaitForOneFrame` 等待一帧
- `WaitForFrames` 等待多帧
## 步骤 2实现生命值自动恢复 ## 步骤 2控制协程生命周期
让我们实现一个实用的功能:玩家生命值自动恢复。
```csharp ```csharp
using GFramework.Core.Abstractions.Model; using var cts = new CancellationTokenSource();
using GFramework.Core.Abstractions.Property;
using GFramework.Core.Model;
namespace MyGame.Models var handle = _scheduler.Run(
HealthRegenerationCoroutine(),
tag: "regen",
group: "player",
cancellationToken: cts.Token);
_scheduler.Pause(handle);
_scheduler.Resume(handle);
// 外部取消会在下一次 Update 时生效
cts.Cancel();
var status = await _scheduler.WaitForCompletionAsync(handle);
Console.WriteLine(status);
```
如果你需要观察运行中状态:
```csharp
if (_scheduler.TryGetSnapshot(handle, out var snapshot))
{ {
public class PlayerModel : AbstractModel Console.WriteLine(snapshot.State);
{ Console.WriteLine(snapshot.WaitingInstructionType);
// 当前生命值
public BindableProperty<int> Health { get; } = new(100);
// 最大生命值
public BindableProperty<int> MaxHealth { get; } = new(100);
// 是否启用自动恢复
public BindableProperty<bool> AutoRegenEnabled { get; } = new(true);
private CoroutineHandle? _regenHandle;
protected override void OnInit()
{
// 启动生命值恢复协程
StartHealthRegeneration();
}
/// <summary>
/// 启动生命值恢复
/// </summary>
public void StartHealthRegeneration()
{
// 如果已经在运行,先停止
if (_regenHandle.HasValue)
{
this.StopCoroutine(_regenHandle.Value);
}
// 启动新的恢复协程
_regenHandle = this.StartCoroutine(HealthRegenerationCoroutine());
}
/// <summary>
/// 停止生命值恢复
/// </summary>
public void StopHealthRegeneration()
{
if (_regenHandle.HasValue)
{
this.StopCoroutine(_regenHandle.Value);
_regenHandle = null;
}
}
/// <summary>
/// 生命值恢复协程
/// </summary>
private IEnumerator<IYieldInstruction> HealthRegenerationCoroutine()
{
while (true)
{
// 等待 1 秒
yield return CoroutineHelper.WaitForSeconds(1.0);
// 检查是否启用自动恢复
if (!AutoRegenEnabled.Value)
continue;
// 如果生命值未满,恢复 5 点
if (Health.Value < MaxHealth.Value)
{
Health.Value = Math.Min(Health.Value + 5, MaxHealth.Value);
Console.WriteLine($"生命值恢复: {Health.Value}/{MaxHealth.Value}");
}
}
}
}
} }
``` ```
**代码说明** ## 步骤 3区分时间等待
- 使用 `while (true)` 创建无限循环协程
- 保存协程句柄以便后续控制
- 使用 `StopCoroutine` 停止协程
- 协程中可以访问类成员变量
## 步骤 3实现技能冷却系统
接下来实现一个技能冷却系统,展示如何使用协程管理时间相关的游戏逻辑。
```csharp ```csharp
using GFramework.Core.System; private IEnumerator<IYieldInstruction> CooldownCoroutine()
using System.Collections.Generic;
namespace MyGame.Systems
{ {
public class SkillSystem : AbstractSystem // 使用宿主默认时间
{ yield return new Delay(2.0);
// 技能冷却状态
private readonly Dictionary<string, bool> _skillCooldowns = new();
/// <summary> // 使用真实时间,需要调度器提供 realtimeTimeSource
/// 使用技能 yield return new WaitForSecondsRealtime(2.0);
/// </summary>
public bool UseSkill(string skillName, double cooldownTime)
{
// 检查是否在冷却中
if (_skillCooldowns.TryGetValue(skillName, out var isOnCooldown) && isOnCooldown)
{
Console.WriteLine($"技能 {skillName} 冷却中...");
return false;
}
// 执行技能
Console.WriteLine($"使用技能: {skillName}");
// 启动冷却协程
this.StartCoroutine(SkillCooldownCoroutine(skillName, cooldownTime));
return true;
}
/// <summary>
/// 技能冷却协程
/// </summary>
private IEnumerator<IYieldInstruction> SkillCooldownCoroutine(string skillName, double cooldownTime)
{
// 标记为冷却中
_skillCooldowns[skillName] = true;
Console.WriteLine($"技能 {skillName} 开始冷却 {cooldownTime} 秒");
// 等待冷却时间
yield return CoroutineHelper.WaitForSeconds(cooldownTime);
// 冷却结束
_skillCooldowns[skillName] = false;
Console.WriteLine($"技能 {skillName} 冷却完成");
}
/// <summary>
/// 带进度显示的技能冷却
/// </summary>
private IEnumerator<IYieldInstruction> SkillCooldownWithProgressCoroutine(
string skillName,
double cooldownTime)
{
_skillCooldowns[skillName] = true;
// 使用 WaitForProgress 显示冷却进度
yield return CoroutineHelper.WaitForProgress(
duration: cooldownTime,
onProgress: progress =>
{
Console.WriteLine($"技能 {skillName} 冷却进度: {progress * 100:F0}%");
}
);
_skillCooldowns[skillName] = false;
Console.WriteLine($"技能 {skillName} 冷却完成");
}
}
} }
``` ```
**代码说明** 建议:
- 使用字典管理多个技能的冷却状态 - 普通游戏逻辑优先使用 `Delay`
- 每个技能使用独立的协程管理冷却 - 暂停菜单、真实倒计时、网络超时等场景使用 `WaitForSecondsRealtime`
- `WaitForProgress` 可以在等待期间执行回调
- 协程结束后自动清理冷却状态
## 步骤 4等待事件触发 ## 步骤 4使用阶段等待
实现一个等待玩家完成任务的系统,展示如何在协程中等待事件。 只有宿主为调度器提供了匹配阶段时,阶段等待才会真实生效:
```csharp ```csharp
using GFramework.Core.Abstractions.Events; var fixedScheduler = new CoroutineScheduler(
fixedTimeSource,
executionStage: CoroutineExecutionStage.FixedUpdate);
private IEnumerator<IYieldInstruction> PhysicsCoroutine()
{
yield return new WaitForFixedUpdate();
Console.WriteLine("下一次固定步到达");
}
```
同理,`WaitForEndOfFrame` 需要运行在 `CoroutineExecutionStage.EndOfFrame` 的调度器上。
## 步骤 5等待 Task
```csharp
using GFramework.Core.Coroutine.Extensions;
private IEnumerator<IYieldInstruction> LoadCoroutine()
{
var task = LoadDataAsync();
yield return task.AsCoroutineInstruction();
Console.WriteLine("Task 已完成");
}
```
如果你已经持有调度器,也可以直接把 `Task` 作为顶层协程启动:
```csharp
var handle = _scheduler.StartTaskAsCoroutine(LoadDataAsync());
```
## 步骤 6在 Godot 中绑定 Node 生命周期
```csharp
using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Coroutine.Instructions; using GFramework.Core.Coroutine.Instructions;
using GFramework.Godot.Coroutine;
using Godot;
namespace MyGame.Systems public partial class DemoNode : Node
{ {
// 任务完成事件 public override void _Ready()
public record QuestCompletedEvent(int QuestId, string QuestName) : IEvent;
public class QuestSystem : AbstractSystem
{ {
/// <summary> // 推荐:节点作为所有者运行协程
/// 开始任务并等待完成 this.RunCoroutine(BlinkCoroutine(), Segment.ProcessIgnorePause, tag: "blink");
/// </summary> }
public void StartQuest(int questId, string questName)
private IEnumerator<IYieldInstruction> BlinkCoroutine()
{
while (true)
{ {
this.StartCoroutine(QuestCoroutine(questId, questName)); Visible = !Visible;
} yield return new WaitForSecondsRealtime(0.5);
/// <summary>
/// 任务协程
/// </summary>
private IEnumerator<IYieldInstruction> QuestCoroutine(int questId, string questName)
{
Console.WriteLine($"任务开始: {questName}");
// 获取事件总线
var eventBus = this.GetService<IEventBus>();
// 等待任务完成事件
var waitEvent = new WaitForEvent<QuestCompletedEvent>(
eventBus,
evt => evt.QuestId == questId // 过滤条件
);
yield return waitEvent;
// 获取事件数据
var completedEvent = waitEvent.EventData;
Console.WriteLine($"任务完成: {completedEvent.QuestName}");
// 发放奖励
GiveReward(questId);
}
/// <summary>
/// 带超时的任务
/// </summary>
private IEnumerator<IYieldInstruction> TimedQuestCoroutine(
int questId,
string questName,
double timeLimit)
{
Console.WriteLine($"限时任务开始: {questName} (时限: {timeLimit}秒)");
var eventBus = this.GetService<IEventBus>();
// 等待事件,带超时
var waitEvent = new WaitForEventWithTimeout<QuestCompletedEvent>(
eventBus,
timeout: timeLimit,
predicate: evt => evt.QuestId == questId
);
yield return waitEvent;
if (waitEvent.IsTimeout)
{
Console.WriteLine($"任务超时失败: {questName}");
}
else
{
Console.WriteLine($"任务完成: {questName}");
GiveReward(questId);
}
}
private void GiveReward(int questId)
{
Console.WriteLine($"发放任务 {questId} 的奖励");
} }
} }
} }
``` ```
**代码说明** `DemoNode` 退出场景树时,上面的协程会被自动终止。
- `WaitForEvent` 等待特定事件触发 如果你需要绑定多个节点,可以继续使用:
- 可以使用 `predicate` 参数过滤事件
- `WaitForEventWithTimeout` 支持超时机制
- 通过 `EventData` 属性获取事件数据
## 步骤 5协程组合与嵌套
实现一个复杂的游戏流程,展示如何组合多个协程。
```csharp ```csharp
namespace MyGame.Systems BlinkCoroutine()
{ .CancelWith(this, anotherNode)
public class GameFlowSystem : AbstractSystem .RunCoroutine();
{
/// <summary>
/// 游戏开始流程
/// </summary>
public void StartGame()
{
this.StartCoroutine(GameStartSequence());
}
/// <summary>
/// 游戏开始序列
/// </summary>
private IEnumerator<IYieldInstruction> GameStartSequence()
{
Console.WriteLine("=== 游戏开始 ===");
// 1. 显示标题
yield return ShowTitle();
// 2. 加载资源
yield return LoadResources();
// 3. 初始化玩家
yield return InitializePlayer();
// 4. 播放开场动画
yield return PlayOpeningAnimation();
Console.WriteLine("=== 游戏准备完成 ===");
}
/// <summary>
/// 显示标题
/// </summary>
private IEnumerator<IYieldInstruction> ShowTitle()
{
Console.WriteLine("显示游戏标题...");
yield return CoroutineHelper.WaitForSeconds(2.0);
Console.WriteLine("标题显示完成");
}
/// <summary>
/// 加载资源
/// </summary>
private IEnumerator<IYieldInstruction> LoadResources()
{
Console.WriteLine("开始加载资源...");
// 并行加载多个资源
var loadTextures = LoadTexturesCoroutine();
var loadAudio = LoadAudioCoroutine();
var loadModels = LoadModelsCoroutine();
// 等待所有资源加载完成
yield return new WaitForAllCoroutines(
this.GetCoroutineScheduler(),
loadTextures,
loadAudio,
loadModels
);
Console.WriteLine("所有资源加载完成");
}
private IEnumerator<IYieldInstruction> LoadTexturesCoroutine()
{
Console.WriteLine(" 加载纹理...");
yield return CoroutineHelper.WaitForSeconds(1.0);
Console.WriteLine(" 纹理加载完成");
}
private IEnumerator<IYieldInstruction> LoadAudioCoroutine()
{
Console.WriteLine(" 加载音频...");
yield return CoroutineHelper.WaitForSeconds(1.5);
Console.WriteLine(" 音频加载完成");
}
private IEnumerator<IYieldInstruction> LoadModelsCoroutine()
{
Console.WriteLine(" 加载模型...");
yield return CoroutineHelper.WaitForSeconds(0.8);
Console.WriteLine(" 模型加载完成");
}
private IEnumerator<IYieldInstruction> InitializePlayer()
{
Console.WriteLine("初始化玩家...");
yield return CoroutineHelper.WaitForSeconds(0.5);
Console.WriteLine("玩家初始化完成");
}
private IEnumerator<IYieldInstruction> PlayOpeningAnimation()
{
Console.WriteLine("播放开场动画...");
yield return CoroutineHelper.WaitForSeconds(3.0);
Console.WriteLine("开场动画播放完成");
}
/// <summary>
/// 获取协程调度器
/// </summary>
private CoroutineScheduler GetCoroutineScheduler()
{
// 从架构服务中获取
return this.GetService<CoroutineScheduler>();
}
}
}
``` ```
**代码说明**
- 使用 `yield return` 调用其他协程实现嵌套
- `WaitForAllCoroutines` 并行执行多个协程
- 协程可以像函数一样组合和复用
- 清晰的流程控制,避免回调嵌套
## 完整代码
### GameArchitecture.cs
```csharp
using GFramework.Core.Architecture;
namespace MyGame
{
public class GameArchitecture : Architecture
{
public static IArchitecture Interface { get; private set; }
protected override void Init()
{
Interface = this;
// 注册 Model
RegisterModel(new PlayerModel());
// 注册 System
RegisterSystem(new TutorialSystem());
RegisterSystem(new SkillSystem());
RegisterSystem(new QuestSystem());
RegisterSystem(new GameFlowSystem());
}
}
}
```
### 测试代码
```csharp
using MyGame;
using MyGame.Systems;
// 初始化架构
var architecture = new GameArchitecture();
architecture.Initialize();
await architecture.WaitUntilReadyAsync();
// 测试技能系统
var skillSystem = architecture.GetSystem<SkillSystem>();
skillSystem.UseSkill("火球术", 3.0);
await Task.Delay(1000);
skillSystem.UseSkill("火球术", 3.0); // 冷却中
await Task.Delay(3000);
skillSystem.UseSkill("火球术", 3.0); // 冷却完成
// 测试任务系统
var questSystem = architecture.GetSystem<QuestSystem>();
questSystem.StartQuest(1, "击败史莱姆");
// 模拟任务完成
await Task.Delay(2000);
var eventBus = architecture.GetService<IEventBus>();
eventBus.Publish(new QuestCompletedEvent(1, "击败史莱姆"));
// 测试游戏流程
var gameFlowSystem = architecture.GetSystem<GameFlowSystem>();
gameFlowSystem.StartGame();
```
## 运行结果
运行程序后,你将看到类似以下的输出:
```
协程开始执行
1 秒后执行
下一帧执行
5 帧后执行
使用技能: 火球术
技能 火球术 开始冷却 3.0 秒
技能 火球术 冷却中...
技能 火球术 冷却完成
使用技能: 火球术
任务开始: 击败史莱姆
任务完成: 击败史莱姆
发放任务 1 的奖励
=== 游戏开始 ===
显示游戏标题...
标题显示完成
开始加载资源...
加载纹理...
加载音频...
加载模型...
模型加载完成
纹理加载完成
音频加载完成
所有资源加载完成
初始化玩家...
玩家初始化完成
播放开场动画...
开场动画播放完成
=== 游戏准备完成 ===
```
**验证步骤**
1. 协程按预期顺序执行
2. 技能冷却系统正常工作
3. 事件等待功能正确
4. 并行加载资源成功
## 下一步 ## 下一步
恭喜!你已经掌握了协程系统的基本用法。接下来可以学习: - Core 侧更完整的 API 说明见 [Core 协程系统](/zh-CN/core/coroutine)
- Godot 集成细节见 [Godot 协程系统](/zh-CN/godot/coroutine)
- [实现状态机](/zh-CN/tutorials/state-machine-tutorial) - 使用协程实现状态转换
- [资源管理最佳实践](/zh-CN/tutorials/resource-management) - 在协程中加载资源
- [使用事件系统](/zh-CN/core/events) - 协程与事件系统集成
## 相关文档
- [协程系统](/zh-CN/core/coroutine) - 协程系统详细说明
- [事件系统](/zh-CN/core/events) - 事件系统详解
- [生命周期管理](/zh-CN/core/lifecycle) - 组件生命周期
- [System 层](/zh-CN/core/system) - System 详细说明