mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
docs: 添加协程系统、CQRS与Mediator、生命周期管理、资源管理系统文档
- 新增协程系统文档,介绍协程调度器、等待指令、基本用法和最佳实践 - 新增CQRS与Mediator文档,涵盖命令查询职责分离、处理器实现和管道行为 - 新增生命周期管理文档,说明同步异步初始化和销毁机制 - 新增资源管理系统文档,介绍资源加载、缓存和引用计数管理功能
This commit is contained in:
parent
ae8c3e4fc4
commit
bbb91d597a
506
docs/zh-CN/core/coroutine.md
Normal file
506
docs/zh-CN/core/coroutine.md
Normal file
@ -0,0 +1,506 @@
|
|||||||
|
---
|
||||||
|
title: 协程系统
|
||||||
|
description: 协程系统提供了轻量级的异步操作管理机制,支持时间延迟、事件等待、任务等待等多种场景。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 协程系统
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
协程系统是 GFramework 中用于管理异步操作的核心机制。通过协程,你可以编写看起来像同步代码的异步逻辑,避免回调地狱,使代码更加清晰易读。
|
||||||
|
|
||||||
|
协程系统基于 C# 的迭代器(IEnumerator)实现,提供了丰富的等待指令(YieldInstruction),可以轻松处理时间延迟、事件等待、任务等待等各种异步场景。
|
||||||
|
|
||||||
|
**主要特性**:
|
||||||
|
|
||||||
|
- 轻量级协程调度器
|
||||||
|
- 丰富的等待指令(30+ 种)
|
||||||
|
- 支持协程嵌套和组合
|
||||||
|
- 协程标签和批量管理
|
||||||
|
- 与事件系统、命令系统、CQRS 深度集成
|
||||||
|
- 异常处理和错误恢复
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### 协程调度器
|
||||||
|
|
||||||
|
`CoroutineScheduler` 是协程系统的核心,负责管理和执行所有协程:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.coroutine;
|
||||||
|
|
||||||
|
// 创建调度器(通常由架构自动管理)
|
||||||
|
var scheduler = new CoroutineScheduler(timeSource);
|
||||||
|
|
||||||
|
// 运行协程
|
||||||
|
var handle = scheduler.Run(MyCoroutine());
|
||||||
|
|
||||||
|
// 每帧更新
|
||||||
|
scheduler.Update();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 协程句柄
|
||||||
|
|
||||||
|
`CoroutineHandle` 用于标识和控制协程:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 运行协程并获取句柄
|
||||||
|
var handle = scheduler.Run(MyCoroutine());
|
||||||
|
|
||||||
|
// 检查协程是否存活
|
||||||
|
if (scheduler.IsCoroutineAlive(handle))
|
||||||
|
{
|
||||||
|
// 停止协程
|
||||||
|
scheduler.Stop(handle);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 等待指令
|
||||||
|
|
||||||
|
等待指令(YieldInstruction)定义了协程的等待行为:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IYieldInstruction
|
||||||
|
{
|
||||||
|
bool IsDone { get; }
|
||||||
|
void Update(double deltaTime);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基本用法
|
||||||
|
|
||||||
|
### 创建简单协程
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.Abstractions.coroutine;
|
||||||
|
using GFramework.Core.coroutine.instructions;
|
||||||
|
|
||||||
|
public IEnumerator<IYieldInstruction> SimpleCoroutine()
|
||||||
|
{
|
||||||
|
Console.WriteLine("开始");
|
||||||
|
|
||||||
|
// 等待 2 秒
|
||||||
|
yield return new Delay(2.0);
|
||||||
|
|
||||||
|
Console.WriteLine("2 秒后");
|
||||||
|
|
||||||
|
// 等待 1 帧
|
||||||
|
yield return new WaitOneFrame();
|
||||||
|
|
||||||
|
Console.WriteLine("下一帧");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用协程辅助方法
|
||||||
|
|
||||||
|
```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);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在架构组件中使用
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.model;
|
||||||
|
using GFramework.Core.extensions;
|
||||||
|
|
||||||
|
public class PlayerModel : AbstractModel
|
||||||
|
{
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
// 启动协程
|
||||||
|
this.StartCoroutine(RegenerateHealth());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator<IYieldInstruction> RegenerateHealth()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// 每秒恢复 1 点生命值
|
||||||
|
yield return CoroutineHelper.WaitForSeconds(1.0);
|
||||||
|
Health = Math.Min(Health + 1, MaxHealth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### 等待事件
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.coroutine.instructions;
|
||||||
|
|
||||||
|
public IEnumerator<IYieldInstruction> WaitForEventExample()
|
||||||
|
{
|
||||||
|
Console.WriteLine("等待玩家死亡事件...");
|
||||||
|
|
||||||
|
// 等待事件触发
|
||||||
|
var waitEvent = new WaitForEvent<PlayerDiedEvent>(eventBus);
|
||||||
|
yield return waitEvent;
|
||||||
|
|
||||||
|
// 获取事件数据
|
||||||
|
var eventData = waitEvent.EventData;
|
||||||
|
Console.WriteLine($"玩家 {eventData.PlayerId} 死亡");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 等待事件(带超时)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public IEnumerator<IYieldInstruction> WaitForEventWithTimeout()
|
||||||
|
{
|
||||||
|
var waitEvent = new WaitForEventWithTimeout<PlayerJoinedEvent>(
|
||||||
|
eventBus,
|
||||||
|
timeout: 5.0
|
||||||
|
);
|
||||||
|
|
||||||
|
yield return waitEvent;
|
||||||
|
|
||||||
|
if (waitEvent.IsTimeout)
|
||||||
|
{
|
||||||
|
Console.WriteLine("等待超时");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"玩家加入: {waitEvent.EventData.PlayerName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 等待 Task
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public IEnumerator<IYieldInstruction> WaitForTaskExample()
|
||||||
|
{
|
||||||
|
// 创建异步任务
|
||||||
|
var task = LoadDataAsync();
|
||||||
|
|
||||||
|
// 在协程中等待 Task 完成
|
||||||
|
var waitTask = new WaitForTask(task);
|
||||||
|
yield return waitTask;
|
||||||
|
|
||||||
|
// 检查异常
|
||||||
|
if (waitTask.Exception != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"任务失败: {waitTask.Exception.Message}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("任务完成");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadDataAsync()
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
// 加载数据...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 等待多个协程
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public IEnumerator<IYieldInstruction> WaitForMultipleCoroutines()
|
||||||
|
{
|
||||||
|
var coroutine1 = LoadTexture();
|
||||||
|
var coroutine2 = LoadAudio();
|
||||||
|
var coroutine3 = LoadModel();
|
||||||
|
|
||||||
|
// 等待所有协程完成
|
||||||
|
yield return new WaitForAllCoroutines(
|
||||||
|
scheduler,
|
||||||
|
coroutine1,
|
||||||
|
coroutine2,
|
||||||
|
coroutine3
|
||||||
|
);
|
||||||
|
|
||||||
|
Console.WriteLine("所有资源加载完成");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 协程嵌套
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public IEnumerator<IYieldInstruction> ParentCoroutine()
|
||||||
|
{
|
||||||
|
Console.WriteLine("父协程开始");
|
||||||
|
|
||||||
|
// 等待子协程完成
|
||||||
|
yield return new WaitForCoroutine(scheduler, ChildCoroutine());
|
||||||
|
|
||||||
|
Console.WriteLine("子协程完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator<IYieldInstruction> ChildCoroutine()
|
||||||
|
{
|
||||||
|
yield return CoroutineHelper.WaitForSeconds(1.0);
|
||||||
|
Console.WriteLine("子协程执行");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 带进度的等待
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public IEnumerator<IYieldInstruction> LoadingWithProgress()
|
||||||
|
{
|
||||||
|
Console.WriteLine("开始加载...");
|
||||||
|
|
||||||
|
yield return CoroutineHelper.WaitForProgress(
|
||||||
|
duration: 3.0,
|
||||||
|
onProgress: progress =>
|
||||||
|
{
|
||||||
|
Console.WriteLine($"加载进度: {progress * 100:F0}%");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Console.WriteLine("加载完成");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 协程标签管理
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用标签运行协程
|
||||||
|
var handle1 = scheduler.Run(Coroutine1(), tag: "gameplay");
|
||||||
|
var handle2 = scheduler.Run(Coroutine2(), tag: "gameplay");
|
||||||
|
var handle3 = scheduler.Run(Coroutine3(), tag: "ui");
|
||||||
|
|
||||||
|
// 停止所有带特定标签的协程
|
||||||
|
scheduler.StopAllWithTag("gameplay");
|
||||||
|
|
||||||
|
// 获取标签下的所有协程
|
||||||
|
var gameplayCoroutines = scheduler.GetCoroutinesByTag("gameplay");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 延迟调用和重复调用
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 延迟 2 秒后执行
|
||||||
|
scheduler.Run(CoroutineHelper.DelayedCall(2.0, () =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("延迟执行");
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 每隔 1 秒执行一次,共执行 5 次
|
||||||
|
scheduler.Run(CoroutineHelper.RepeatCall(1.0, 5, () =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("重复执行");
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 无限重复,直到条件不满足
|
||||||
|
scheduler.Run(CoroutineHelper.RepeatCallWhile(1.0, () => isRunning, () =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("条件重复");
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 与命令系统集成
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.coroutine.extensions;
|
||||||
|
|
||||||
|
public IEnumerator<IYieldInstruction> ExecuteCommandInCoroutine()
|
||||||
|
{
|
||||||
|
// 在协程中执行命令
|
||||||
|
var command = new LoadSceneCommand();
|
||||||
|
yield return command.ExecuteAsCoroutine(this);
|
||||||
|
|
||||||
|
Console.WriteLine("场景加载完成");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 与 CQRS 集成
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public IEnumerator<IYieldInstruction> QueryInCoroutine()
|
||||||
|
{
|
||||||
|
// 在协程中执行查询
|
||||||
|
var query = new GetPlayerDataQuery { PlayerId = 1 };
|
||||||
|
var waitQuery = query.SendAsCoroutine<GetPlayerDataQuery, PlayerData>(this);
|
||||||
|
|
||||||
|
yield return waitQuery;
|
||||||
|
|
||||||
|
var playerData = waitQuery.Result;
|
||||||
|
Console.WriteLine($"玩家名称: {playerData.Name}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **使用扩展方法启动协程**:通过架构组件的扩展方法启动协程更简洁
|
||||||
|
```csharp
|
||||||
|
✓ this.StartCoroutine(MyCoroutine());
|
||||||
|
✗ scheduler.Run(MyCoroutine());
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **合理使用协程标签**:为相关协程添加标签,便于批量管理
|
||||||
|
```csharp
|
||||||
|
this.StartCoroutine(BattleCoroutine(), tag: "battle");
|
||||||
|
this.StartCoroutine(EffectCoroutine(), tag: "battle");
|
||||||
|
|
||||||
|
// 战斗结束时停止所有战斗相关协程
|
||||||
|
this.StopCoroutinesWithTag("battle");
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **避免在协程中执行耗时操作**:协程在主线程执行,不要阻塞
|
||||||
|
```csharp
|
||||||
|
✗ public IEnumerator<IYieldInstruction> BadCoroutine()
|
||||||
|
{
|
||||||
|
Thread.Sleep(1000); // 阻塞主线程
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
✓ public IEnumerator<IYieldInstruction> GoodCoroutine()
|
||||||
|
{
|
||||||
|
yield return CoroutineHelper.WaitForSeconds(1.0); // 非阻塞
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **正确处理协程异常**:使用 try-catch 捕获异常
|
||||||
|
```csharp
|
||||||
|
public IEnumerator<IYieldInstruction> SafeCoroutine()
|
||||||
|
{
|
||||||
|
var waitTask = new WaitForTask(riskyTask);
|
||||||
|
yield return waitTask;
|
||||||
|
|
||||||
|
if (waitTask.Exception != null)
|
||||||
|
{
|
||||||
|
// 处理异常
|
||||||
|
Logger.Error($"任务失败: {waitTask.Exception.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **及时停止不需要的协程**:避免资源泄漏
|
||||||
|
```csharp
|
||||||
|
private CoroutineHandle? _healthRegenHandle;
|
||||||
|
|
||||||
|
public void StartHealthRegen()
|
||||||
|
{
|
||||||
|
_healthRegenHandle = this.StartCoroutine(RegenerateHealth());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopHealthRegen()
|
||||||
|
{
|
||||||
|
if (_healthRegenHandle.HasValue)
|
||||||
|
{
|
||||||
|
this.StopCoroutine(_healthRegenHandle.Value);
|
||||||
|
_healthRegenHandle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **使用 WaitForEvent 时记得释放资源**:避免内存泄漏
|
||||||
|
```csharp
|
||||||
|
public IEnumerator<IYieldInstruction> WaitEventExample()
|
||||||
|
{
|
||||||
|
using var waitEvent = new WaitForEvent<GameEvent>(eventBus);
|
||||||
|
yield return waitEvent;
|
||||||
|
// using 确保资源被释放
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题:协程什么时候执行?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
协程在调度器的 `Update()` 方法中执行。在 GFramework 中,架构会自动在每帧调用调度器的更新方法。
|
||||||
|
|
||||||
|
### 问题:协程是多线程的吗?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
不是。协程在主线程中执行,是单线程的。它们通过分帧执行来实现异步效果,不会阻塞主线程。
|
||||||
|
|
||||||
|
### 问题:如何在协程中等待异步方法?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
使用 `WaitForTask` 等待 Task 完成:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public IEnumerator<IYieldInstruction> WaitAsyncMethod()
|
||||||
|
{
|
||||||
|
var task = SomeAsyncMethod();
|
||||||
|
yield return new WaitForTask(task);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:协程可以返回值吗?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
协程本身不能直接返回值,但可以通过闭包或类成员变量传递结果:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private int _result;
|
||||||
|
|
||||||
|
public IEnumerator<IYieldInstruction> CoroutineWithResult()
|
||||||
|
{
|
||||||
|
yield return CoroutineHelper.WaitForSeconds(1.0);
|
||||||
|
_result = 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用
|
||||||
|
this.StartCoroutine(CoroutineWithResult());
|
||||||
|
// 稍后访问 _result
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何停止所有协程?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
使用调度器的 `StopAll()` 方法:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 停止所有协程
|
||||||
|
scheduler.StopAll();
|
||||||
|
|
||||||
|
// 或通过扩展方法
|
||||||
|
this.StopAllCoroutines();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:协程中的异常会怎样?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
协程中未捕获的异常会触发 `OnCoroutineException` 事件,并停止该协程:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
scheduler.OnCoroutineException += (handle, exception) =>
|
||||||
|
{
|
||||||
|
Logger.Error($"协程异常: {exception.Message}");
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:WaitForSeconds 和 Delay 有什么区别?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
它们是相同的,`WaitForSeconds` 是辅助方法,内部创建 `Delay` 实例:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 两者等价
|
||||||
|
yield return CoroutineHelper.WaitForSeconds(1.0);
|
||||||
|
yield return new Delay(1.0);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [事件系统](/zh-CN/core/events) - 协程与事件系统集成
|
||||||
|
- [命令系统](/zh-CN/core/command) - 在协程中执行命令
|
||||||
|
- [CQRS](/zh-CN/core/cqrs) - 在协程中执行查询和命令
|
||||||
|
- [协程系统教程](/zh-CN/tutorials/coroutine-tutorial) - 分步教程
|
||||||
613
docs/zh-CN/core/cqrs.md
Normal file
613
docs/zh-CN/core/cqrs.md
Normal file
@ -0,0 +1,613 @@
|
|||||||
|
---
|
||||||
|
title: CQRS 与 Mediator
|
||||||
|
description: CQRS 模式通过 Mediator 实现命令查询职责分离,提供清晰的业务逻辑组织方式。
|
||||||
|
---
|
||||||
|
|
||||||
|
# CQRS 与 Mediator
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
CQRS(Command Query Responsibility Segregation,命令查询职责分离)是一种架构模式,将数据的读取(Query)和修改(Command)操作分离。GFramework
|
||||||
|
通过集成 Mediator 库实现了 CQRS 模式,提供了类型安全、解耦的业务逻辑处理方式。
|
||||||
|
|
||||||
|
通过 CQRS,你可以将复杂的业务逻辑拆分为独立的命令和查询处理器,每个处理器只负责单一职责,使代码更易于测试和维护。
|
||||||
|
|
||||||
|
**主要特性**:
|
||||||
|
|
||||||
|
- 命令查询职责分离
|
||||||
|
- 基于 Mediator 模式的解耦设计
|
||||||
|
- 支持管道行为(Behaviors)
|
||||||
|
- 异步处理支持
|
||||||
|
- 与架构系统深度集成
|
||||||
|
- 支持流式处理
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### Command(命令)
|
||||||
|
|
||||||
|
命令表示修改系统状态的操作,如创建、更新、删除:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.cqrs.command;
|
||||||
|
using GFramework.Core.Abstractions.cqrs.command;
|
||||||
|
|
||||||
|
// 定义命令输入
|
||||||
|
public class CreatePlayerInput : ICommandInput
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int Level { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义命令
|
||||||
|
public class CreatePlayerCommand : CommandBase<CreatePlayerInput, int>
|
||||||
|
{
|
||||||
|
public CreatePlayerCommand(CreatePlayerInput input) : base(input) { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query(查询)
|
||||||
|
|
||||||
|
查询表示读取系统状态的操作,不修改数据:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.cqrs.query;
|
||||||
|
using GFramework.Core.Abstractions.cqrs.query;
|
||||||
|
|
||||||
|
// 定义查询输入
|
||||||
|
public class GetPlayerInput : IQueryInput
|
||||||
|
{
|
||||||
|
public int PlayerId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义查询
|
||||||
|
public class GetPlayerQuery : QueryBase<GetPlayerInput, PlayerData>
|
||||||
|
{
|
||||||
|
public GetPlayerQuery(GetPlayerInput input) : base(input) { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handler(处理器)
|
||||||
|
|
||||||
|
处理器负责执行命令或查询的具体逻辑:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.cqrs.command;
|
||||||
|
using Mediator;
|
||||||
|
|
||||||
|
// 命令处理器
|
||||||
|
public class CreatePlayerCommandHandler : AbstractCommandHandler<CreatePlayerCommand, int>
|
||||||
|
{
|
||||||
|
public override async ValueTask<int> Handle(
|
||||||
|
CreatePlayerCommand command,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var input = command.Input;
|
||||||
|
var playerModel = this.GetModel<PlayerModel>();
|
||||||
|
|
||||||
|
// 创建玩家
|
||||||
|
var playerId = playerModel.CreatePlayer(input.Name, input.Level);
|
||||||
|
|
||||||
|
return playerId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mediator(中介者)
|
||||||
|
|
||||||
|
Mediator 负责将命令/查询路由到对应的处理器:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 通过 Mediator 发送命令
|
||||||
|
var command = new CreatePlayerCommand(new CreatePlayerInput
|
||||||
|
{
|
||||||
|
Name = "Player1",
|
||||||
|
Level = 1
|
||||||
|
});
|
||||||
|
|
||||||
|
var playerId = await mediator.Send(command);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基本用法
|
||||||
|
|
||||||
|
### 定义和发送命令
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 1. 定义命令输入
|
||||||
|
public class SaveGameInput : ICommandInput
|
||||||
|
{
|
||||||
|
public int SlotId { get; set; }
|
||||||
|
public GameData Data { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 定义命令
|
||||||
|
public class SaveGameCommand : CommandBase<SaveGameInput, Unit>
|
||||||
|
{
|
||||||
|
public SaveGameCommand(SaveGameInput input) : base(input) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 实现命令处理器
|
||||||
|
public class SaveGameCommandHandler : AbstractCommandHandler<SaveGameCommand>
|
||||||
|
{
|
||||||
|
public override async ValueTask<Unit> Handle(
|
||||||
|
SaveGameCommand command,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var input = command.Input;
|
||||||
|
var saveSystem = this.GetSystem<SaveSystem>();
|
||||||
|
|
||||||
|
// 保存游戏
|
||||||
|
await saveSystem.SaveAsync(input.SlotId, input.Data);
|
||||||
|
|
||||||
|
// 发送事件
|
||||||
|
this.SendEvent(new GameSavedEvent { SlotId = input.SlotId });
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 发送命令
|
||||||
|
public async Task SaveGame()
|
||||||
|
{
|
||||||
|
var mediator = this.GetService<IMediator>();
|
||||||
|
|
||||||
|
var command = new SaveGameCommand(new SaveGameInput
|
||||||
|
{
|
||||||
|
SlotId = 1,
|
||||||
|
Data = currentGameData
|
||||||
|
});
|
||||||
|
|
||||||
|
await mediator.Send(command);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 定义和发送查询
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 1. 定义查询输入
|
||||||
|
public class GetHighScoresInput : IQueryInput
|
||||||
|
{
|
||||||
|
public int Count { get; set; } = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 定义查询
|
||||||
|
public class GetHighScoresQuery : QueryBase<GetHighScoresInput, List<ScoreData>>
|
||||||
|
{
|
||||||
|
public GetHighScoresQuery(GetHighScoresInput input) : base(input) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 实现查询处理器
|
||||||
|
public class GetHighScoresQueryHandler : AbstractQueryHandler<GetHighScoresQuery, List<ScoreData>>
|
||||||
|
{
|
||||||
|
public override async ValueTask<List<ScoreData>> Handle(
|
||||||
|
GetHighScoresQuery query,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var input = query.Input;
|
||||||
|
var scoreModel = this.GetModel<ScoreModel>();
|
||||||
|
|
||||||
|
// 查询高分榜
|
||||||
|
var scores = await scoreModel.GetTopScoresAsync(input.Count);
|
||||||
|
|
||||||
|
return scores;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 发送查询
|
||||||
|
public async Task<List<ScoreData>> GetHighScores()
|
||||||
|
{
|
||||||
|
var mediator = this.GetService<IMediator>();
|
||||||
|
|
||||||
|
var query = new GetHighScoresQuery(new GetHighScoresInput
|
||||||
|
{
|
||||||
|
Count = 10
|
||||||
|
});
|
||||||
|
|
||||||
|
var scores = await mediator.Send(query);
|
||||||
|
return scores;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注册处理器
|
||||||
|
|
||||||
|
在架构中注册 Mediator 和处理器:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
// 注册 Mediator 行为
|
||||||
|
RegisterMediatorBehavior<LoggingBehavior>();
|
||||||
|
RegisterMediatorBehavior<PerformanceBehavior>();
|
||||||
|
|
||||||
|
// 处理器会自动通过依赖注入注册
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### Request(请求)
|
||||||
|
|
||||||
|
Request 是更通用的消息类型,可以用于任何场景:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.cqrs.request;
|
||||||
|
using GFramework.Core.Abstractions.cqrs.request;
|
||||||
|
|
||||||
|
// 定义请求输入
|
||||||
|
public class ValidatePlayerInput : IRequestInput
|
||||||
|
{
|
||||||
|
public string PlayerName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义请求
|
||||||
|
public class ValidatePlayerRequest : RequestBase<ValidatePlayerInput, bool>
|
||||||
|
{
|
||||||
|
public ValidatePlayerRequest(ValidatePlayerInput input) : base(input) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现请求处理器
|
||||||
|
public class ValidatePlayerRequestHandler : AbstractRequestHandler<ValidatePlayerRequest, bool>
|
||||||
|
{
|
||||||
|
public override async ValueTask<bool> Handle(
|
||||||
|
ValidatePlayerRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var input = request.Input;
|
||||||
|
var playerModel = this.GetModel<PlayerModel>();
|
||||||
|
|
||||||
|
// 验证玩家名称
|
||||||
|
var isValid = await playerModel.IsNameValidAsync(input.PlayerName);
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notification(通知)
|
||||||
|
|
||||||
|
Notification 用于一对多的消息广播:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.cqrs.notification;
|
||||||
|
using GFramework.Core.Abstractions.cqrs.notification;
|
||||||
|
|
||||||
|
// 定义通知输入
|
||||||
|
public class PlayerLevelUpInput : INotificationInput
|
||||||
|
{
|
||||||
|
public int PlayerId { get; set; }
|
||||||
|
public int NewLevel { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义通知
|
||||||
|
public class PlayerLevelUpNotification : NotificationBase<PlayerLevelUpInput>
|
||||||
|
{
|
||||||
|
public PlayerLevelUpNotification(PlayerLevelUpInput input) : base(input) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现通知处理器 1
|
||||||
|
public class AchievementNotificationHandler : AbstractNotificationHandler<PlayerLevelUpNotification>
|
||||||
|
{
|
||||||
|
public override async ValueTask Handle(
|
||||||
|
PlayerLevelUpNotification notification,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var input = notification.Input;
|
||||||
|
// 检查成就
|
||||||
|
CheckLevelAchievements(input.PlayerId, input.NewLevel);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现通知处理器 2
|
||||||
|
public class RewardNotificationHandler : AbstractNotificationHandler<PlayerLevelUpNotification>
|
||||||
|
{
|
||||||
|
public override async ValueTask Handle(
|
||||||
|
PlayerLevelUpNotification notification,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var input = notification.Input;
|
||||||
|
// 发放奖励
|
||||||
|
GiveRewards(input.PlayerId, input.NewLevel);
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发布通知(所有处理器都会收到)
|
||||||
|
var notification = new PlayerLevelUpNotification(new PlayerLevelUpInput
|
||||||
|
{
|
||||||
|
PlayerId = 1,
|
||||||
|
NewLevel = 10
|
||||||
|
});
|
||||||
|
|
||||||
|
await mediator.Publish(notification);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pipeline Behaviors(管道行为)
|
||||||
|
|
||||||
|
Behaviors 可以在处理器执行前后添加横切关注点:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Mediator;
|
||||||
|
|
||||||
|
// 日志行为
|
||||||
|
public class LoggingBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
|
||||||
|
where TMessage : IMessage
|
||||||
|
{
|
||||||
|
public async ValueTask<TResponse> Handle(
|
||||||
|
TMessage message,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
MessageHandlerDelegate<TMessage, TResponse> next)
|
||||||
|
{
|
||||||
|
var messageName = message.GetType().Name;
|
||||||
|
Console.WriteLine($"[开始] {messageName}");
|
||||||
|
|
||||||
|
var response = await next(message, cancellationToken);
|
||||||
|
|
||||||
|
Console.WriteLine($"[完成] {messageName}");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能监控行为
|
||||||
|
public class PerformanceBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
|
||||||
|
where TMessage : IMessage
|
||||||
|
{
|
||||||
|
public async ValueTask<TResponse> Handle(
|
||||||
|
TMessage message,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
MessageHandlerDelegate<TMessage, TResponse> next)
|
||||||
|
{
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
var response = await next(message, cancellationToken);
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
var elapsed = stopwatch.ElapsedMilliseconds;
|
||||||
|
|
||||||
|
if (elapsed > 100)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"警告: {message.GetType().Name} 耗时 {elapsed}ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册行为
|
||||||
|
RegisterMediatorBehavior<LoggingBehavior<,>>();
|
||||||
|
RegisterMediatorBehavior<PerformanceBehavior<,>>();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 验证行为
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class ValidationBehavior<TMessage, TResponse> : IPipelineBehavior<TMessage, TResponse>
|
||||||
|
where TMessage : IMessage
|
||||||
|
{
|
||||||
|
public async ValueTask<TResponse> Handle(
|
||||||
|
TMessage message,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
MessageHandlerDelegate<TMessage, TResponse> next)
|
||||||
|
{
|
||||||
|
// 验证输入
|
||||||
|
if (message is IValidatable validatable)
|
||||||
|
{
|
||||||
|
var errors = validatable.Validate();
|
||||||
|
if (errors.Any())
|
||||||
|
{
|
||||||
|
throw new ValidationException(errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await next(message, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 流式处理
|
||||||
|
|
||||||
|
处理大量数据时使用流式处理:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 流式查询
|
||||||
|
public class GetAllPlayersStreamQuery : QueryBase<EmptyInput, IAsyncEnumerable<PlayerData>>
|
||||||
|
{
|
||||||
|
public GetAllPlayersStreamQuery() : base(new EmptyInput()) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 流式查询处理器
|
||||||
|
public class GetAllPlayersStreamQueryHandler : AbstractStreamQueryHandler<GetAllPlayersStreamQuery, PlayerData>
|
||||||
|
{
|
||||||
|
public override async IAsyncEnumerable<PlayerData> Handle(
|
||||||
|
GetAllPlayersStreamQuery query,
|
||||||
|
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var playerModel = this.GetModel<PlayerModel>();
|
||||||
|
|
||||||
|
await foreach (var player in playerModel.GetAllPlayersAsync(cancellationToken))
|
||||||
|
{
|
||||||
|
yield return player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用流式查询
|
||||||
|
var query = new GetAllPlayersStreamQuery();
|
||||||
|
var stream = await mediator.CreateStream(query);
|
||||||
|
|
||||||
|
await foreach (var player in stream)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"玩家: {player.Name}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **命令和查询分离**:严格区分修改和读取操作
|
||||||
|
```csharp
|
||||||
|
✓ CreatePlayerCommand, GetPlayerQuery // 职责清晰
|
||||||
|
✗ PlayerCommand // 职责不明确
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **使用有意义的命名**:命令用动词,查询用 Get
|
||||||
|
```csharp
|
||||||
|
✓ CreatePlayerCommand, UpdateScoreCommand, GetHighScoresQuery
|
||||||
|
✗ PlayerCommand, ScoreCommand, ScoresQuery
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **输入验证**:在处理器中验证输入
|
||||||
|
```csharp
|
||||||
|
public override async ValueTask<int> Handle(...)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(command.Input.Name))
|
||||||
|
throw new ArgumentException("Name is required");
|
||||||
|
|
||||||
|
// 处理逻辑
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **使用 Behaviors 处理横切关注点**:日志、性能、验证等
|
||||||
|
```csharp
|
||||||
|
RegisterMediatorBehavior<LoggingBehavior<,>>();
|
||||||
|
RegisterMediatorBehavior<ValidationBehavior<,>>();
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **保持处理器简单**:一个处理器只做一件事
|
||||||
|
```csharp
|
||||||
|
✓ 处理器只负责业务逻辑,通过架构组件访问数据
|
||||||
|
✗ 处理器中包含复杂的数据访问和业务逻辑
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **使用 CancellationToken**:支持操作取消
|
||||||
|
```csharp
|
||||||
|
public override async ValueTask<T> Handle(..., CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await someAsyncOperation(cancellationToken);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题:Command 和 Query 有什么区别?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- **Command**:修改系统状态,可能有副作用,通常返回 void 或简单结果
|
||||||
|
- **Query**:只读取数据,无副作用,返回查询结果
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Command: 修改状态
|
||||||
|
CreatePlayerCommand -> 创建玩家
|
||||||
|
UpdateScoreCommand -> 更新分数
|
||||||
|
|
||||||
|
// Query: 读取数据
|
||||||
|
GetPlayerQuery -> 获取玩家信息
|
||||||
|
GetHighScoresQuery -> 获取高分榜
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:什么时候使用 Request?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
Request 是更通用的消息类型,当操作既不是纯命令也不是纯查询时使用:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 验证操作:读取数据并返回结果,但不修改状态
|
||||||
|
ValidatePlayerRequest
|
||||||
|
|
||||||
|
// 计算操作:基于输入计算结果
|
||||||
|
CalculateDamageRequest
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:Notification 和 Event 有什么区别?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- **Notification**:通过 Mediator 发送,处理器在同一请求上下文中执行
|
||||||
|
- **Event**:通过 EventBus 发送,监听器异步执行
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Notification: 同步处理
|
||||||
|
await mediator.Publish(notification); // 等待所有处理器完成
|
||||||
|
|
||||||
|
// Event: 异步处理
|
||||||
|
this.SendEvent(event); // 立即返回,监听器异步执行
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何处理命令失败?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
使用异常或返回 Result 类型:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 方式 1: 抛出异常
|
||||||
|
public override async ValueTask<Unit> Handle(...)
|
||||||
|
{
|
||||||
|
if (!IsValid())
|
||||||
|
throw new InvalidOperationException("Invalid operation");
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式 2: 返回 Result
|
||||||
|
public override async ValueTask<Result> Handle(...)
|
||||||
|
{
|
||||||
|
if (!IsValid())
|
||||||
|
return Result.Failure("Invalid operation");
|
||||||
|
|
||||||
|
return Result.Success();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:处理器可以调用其他处理器吗?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
可以,通过 Mediator 发送新的命令或查询:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public override async ValueTask<Unit> Handle(...)
|
||||||
|
{
|
||||||
|
var mediator = this.GetService<IMediator>();
|
||||||
|
|
||||||
|
// 调用其他命令
|
||||||
|
await mediator.Send(new AnotherCommand(...));
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何测试处理器?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
处理器是独立的类,易于单元测试:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Test]
|
||||||
|
public async Task CreatePlayer_ShouldReturnPlayerId()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var handler = new CreatePlayerCommandHandler();
|
||||||
|
handler.SetContext(mockContext);
|
||||||
|
|
||||||
|
var command = new CreatePlayerCommand(new CreatePlayerInput
|
||||||
|
{
|
||||||
|
Name = "Test",
|
||||||
|
Level = 1
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var playerId = await handler.Handle(command, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(playerId, Is.GreaterThan(0));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [命令系统](/zh-CN/core/command) - 传统命令模式
|
||||||
|
- [查询系统](/zh-CN/core/query) - 传统查询模式
|
||||||
|
- [事件系统](/zh-CN/core/events) - 事件驱动架构
|
||||||
|
- [协程系统](/zh-CN/core/coroutine) - 在协程中使用 CQRS
|
||||||
461
docs/zh-CN/core/lifecycle.md
Normal file
461
docs/zh-CN/core/lifecycle.md
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
---
|
||||||
|
title: 生命周期管理
|
||||||
|
description: 生命周期管理提供了标准化的组件初始化和销毁机制,确保资源的正确管理和释放。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 生命周期管理
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
生命周期管理是 GFramework 中用于管理组件初始化和销毁的核心机制。通过实现标准的生命周期接口,组件可以在适当的时机执行初始化逻辑和资源清理,确保系统的稳定性和资源的有效管理。
|
||||||
|
|
||||||
|
GFramework 提供了同步和异步两套生命周期接口,适用于不同的使用场景。架构会自动管理所有注册组件的生命周期,开发者只需实现相应的接口即可。
|
||||||
|
|
||||||
|
**主要特性**:
|
||||||
|
|
||||||
|
- 标准化的初始化和销毁流程
|
||||||
|
- 支持同步和异步操作
|
||||||
|
- 自动生命周期管理
|
||||||
|
- 按注册顺序初始化,按逆序销毁
|
||||||
|
- 与架构系统深度集成
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### 生命周期接口层次
|
||||||
|
|
||||||
|
GFramework 提供了一套完整的生命周期接口:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 同步接口
|
||||||
|
public interface IInitializable
|
||||||
|
{
|
||||||
|
void Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDestroyable
|
||||||
|
{
|
||||||
|
void Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ILifecycle : IInitializable, IDestroyable
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步接口
|
||||||
|
public interface IAsyncInitializable
|
||||||
|
{
|
||||||
|
Task InitializeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAsyncDestroyable
|
||||||
|
{
|
||||||
|
ValueTask DestroyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IAsyncLifecycle : IAsyncInitializable, IAsyncDestroyable
|
||||||
|
{
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 初始化阶段
|
||||||
|
|
||||||
|
组件在注册到架构后会自动进行初始化:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class PlayerModel : AbstractModel
|
||||||
|
{
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
// 初始化逻辑
|
||||||
|
Console.WriteLine("PlayerModel 初始化");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 销毁阶段
|
||||||
|
|
||||||
|
当架构销毁时,所有实现了 `IDestroyable` 的组件会按注册的逆序被销毁:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
public void Destroy()
|
||||||
|
{
|
||||||
|
// 清理资源
|
||||||
|
Console.WriteLine("GameSystem 销毁");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基本用法
|
||||||
|
|
||||||
|
### 实现同步生命周期
|
||||||
|
|
||||||
|
最常见的方式是继承框架提供的抽象基类:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.model;
|
||||||
|
|
||||||
|
public class InventoryModel : AbstractModel
|
||||||
|
{
|
||||||
|
private List<Item> _items = new();
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
// 初始化库存
|
||||||
|
_items = new List<Item>();
|
||||||
|
Console.WriteLine("库存系统已初始化");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实现销毁逻辑
|
||||||
|
|
||||||
|
对于需要清理资源的组件,实现 `IDestroyable` 接口:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.Abstractions.system;
|
||||||
|
using GFramework.Core.Abstractions.lifecycle;
|
||||||
|
|
||||||
|
public class AudioSystem : ISystem, IDestroyable
|
||||||
|
{
|
||||||
|
private AudioEngine _engine;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_engine = new AudioEngine();
|
||||||
|
_engine.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destroy()
|
||||||
|
{
|
||||||
|
// 清理音频资源
|
||||||
|
_engine?.Stop();
|
||||||
|
_engine?.Dispose();
|
||||||
|
_engine = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在架构中注册
|
||||||
|
|
||||||
|
组件注册后,架构会自动管理其生命周期:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
// 注册顺序:Model -> System -> Utility
|
||||||
|
RegisterModel(new PlayerModel()); // 1. 初始化
|
||||||
|
RegisterModel(new InventoryModel()); // 2. 初始化
|
||||||
|
RegisterSystem(new AudioSystem()); // 3. 初始化
|
||||||
|
|
||||||
|
// 销毁顺序会自动反转:
|
||||||
|
// AudioSystem -> InventoryModel -> PlayerModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### 异步初始化
|
||||||
|
|
||||||
|
对于需要异步操作的组件(如加载配置、连接数据库),使用异步生命周期:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.Abstractions.lifecycle;
|
||||||
|
using GFramework.Core.Abstractions.system;
|
||||||
|
|
||||||
|
public class ConfigurationSystem : ISystem, IAsyncInitializable
|
||||||
|
{
|
||||||
|
private Configuration _config;
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
// 异步加载配置文件
|
||||||
|
_config = await LoadConfigurationAsync();
|
||||||
|
Console.WriteLine("配置已加载");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Configuration> LoadConfigurationAsync()
|
||||||
|
{
|
||||||
|
await Task.Delay(100); // 模拟异步操作
|
||||||
|
return new Configuration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 异步销毁
|
||||||
|
|
||||||
|
对于需要异步清理的资源(如关闭网络连接、保存数据):
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.Abstractions.lifecycle;
|
||||||
|
|
||||||
|
public class NetworkSystem : ISystem, IAsyncDestroyable
|
||||||
|
{
|
||||||
|
private NetworkClient _client;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_client = new NetworkClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DestroyAsync()
|
||||||
|
{
|
||||||
|
// 异步关闭连接
|
||||||
|
if (_client != null)
|
||||||
|
{
|
||||||
|
await _client.DisconnectAsync();
|
||||||
|
await _client.DisposeAsync();
|
||||||
|
}
|
||||||
|
Console.WriteLine("网络连接已关闭");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整异步生命周期
|
||||||
|
|
||||||
|
同时实现异步初始化和销毁:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class DatabaseSystem : ISystem, IAsyncLifecycle
|
||||||
|
{
|
||||||
|
private DatabaseConnection _connection;
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
// 异步连接数据库
|
||||||
|
_connection = new DatabaseConnection();
|
||||||
|
await _connection.ConnectAsync("connection-string");
|
||||||
|
Console.WriteLine("数据库已连接");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DestroyAsync()
|
||||||
|
{
|
||||||
|
// 异步关闭数据库连接
|
||||||
|
if (_connection != null)
|
||||||
|
{
|
||||||
|
await _connection.CloseAsync();
|
||||||
|
await _connection.DisposeAsync();
|
||||||
|
}
|
||||||
|
Console.WriteLine("数据库连接已关闭");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生命周期钩子
|
||||||
|
|
||||||
|
监听架构的生命周期阶段:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.Abstractions.enums;
|
||||||
|
|
||||||
|
public class AnalyticsSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
Console.WriteLine("分析系统初始化");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnArchitecturePhase(ArchitecturePhase phase)
|
||||||
|
{
|
||||||
|
switch (phase)
|
||||||
|
{
|
||||||
|
case ArchitecturePhase.Initializing:
|
||||||
|
Console.WriteLine("架构正在初始化");
|
||||||
|
break;
|
||||||
|
case ArchitecturePhase.Ready:
|
||||||
|
Console.WriteLine("架构已就绪");
|
||||||
|
StartTracking();
|
||||||
|
break;
|
||||||
|
case ArchitecturePhase.Destroying:
|
||||||
|
Console.WriteLine("架构正在销毁");
|
||||||
|
StopTracking();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartTracking() { }
|
||||||
|
private void StopTracking() { }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **优先使用抽象基类**:继承 `AbstractModel`、`AbstractSystem` 等基类,它们已经实现了生命周期接口
|
||||||
|
```csharp
|
||||||
|
✓ public class MyModel : AbstractModel { }
|
||||||
|
✗ public class MyModel : IModel, IInitializable { }
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **初始化顺序很重要**:按依赖关系注册组件,被依赖的组件先注册
|
||||||
|
```csharp
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
RegisterModel(new ConfigModel()); // 先注册配置
|
||||||
|
RegisterModel(new PlayerModel()); // 再注册依赖配置的模型
|
||||||
|
RegisterSystem(new GameplaySystem()); // 最后注册系统
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **销毁时释放资源**:实现 `Destroy()` 方法清理非托管资源
|
||||||
|
```csharp
|
||||||
|
public void Destroy()
|
||||||
|
{
|
||||||
|
// 释放事件订阅
|
||||||
|
_eventBus.Unsubscribe<GameEvent>(OnGameEvent);
|
||||||
|
|
||||||
|
// 释放非托管资源
|
||||||
|
_nativeHandle?.Dispose();
|
||||||
|
|
||||||
|
// 清空引用
|
||||||
|
_cache?.Clear();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **异步操作使用异步接口**:避免在同步方法中阻塞异步操作
|
||||||
|
```csharp
|
||||||
|
✓ public async Task InitializeAsync() { await LoadDataAsync(); }
|
||||||
|
✗ public void Initialize() { LoadDataAsync().Wait(); } // 可能死锁
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **避免在初始化中访问其他组件**:初始化顺序可能导致组件尚未就绪
|
||||||
|
```csharp
|
||||||
|
✗ protected override void OnInit()
|
||||||
|
{
|
||||||
|
var system = this.GetSystem<OtherSystem>(); // 可能尚未初始化
|
||||||
|
}
|
||||||
|
|
||||||
|
✓ public override void OnArchitecturePhase(ArchitecturePhase phase)
|
||||||
|
{
|
||||||
|
if (phase == ArchitecturePhase.Ready)
|
||||||
|
{
|
||||||
|
var system = this.GetSystem<OtherSystem>(); // 安全
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **使用 OnArchitecturePhase 处理跨组件依赖**:在 Ready 阶段访问其他组件
|
||||||
|
```csharp
|
||||||
|
public override void OnArchitecturePhase(ArchitecturePhase phase)
|
||||||
|
{
|
||||||
|
if (phase == ArchitecturePhase.Ready)
|
||||||
|
{
|
||||||
|
// 此时所有组件都已初始化完成
|
||||||
|
var config = this.GetModel<ConfigModel>();
|
||||||
|
ApplyConfiguration(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题:什么时候使用同步 vs 异步生命周期?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- **同步**:简单的初始化逻辑,如创建对象、设置默认值
|
||||||
|
- **异步**:需要 I/O 操作的场景,如加载文件、网络请求、数据库连接
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 同步:简单初始化
|
||||||
|
public class ScoreModel : AbstractModel
|
||||||
|
{
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
Score = 0; // 简单赋值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步:需要 I/O
|
||||||
|
public class SaveSystem : ISystem, IAsyncInitializable
|
||||||
|
{
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
await LoadSaveDataAsync(); // 文件 I/O
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:组件的初始化和销毁顺序是什么?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- **初始化顺序**:按注册顺序(先注册先初始化)
|
||||||
|
- **销毁顺序**:按注册的逆序(后注册先销毁)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
RegisterModel(new A()); // 1. 初始化,3. 销毁
|
||||||
|
RegisterModel(new B()); // 2. 初始化,2. 销毁
|
||||||
|
RegisterSystem(new C()); // 3. 初始化,1. 销毁
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何在初始化时访问其他组件?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
不要在 `OnInit()` 中访问其他组件,使用 `OnArchitecturePhase()` 在 Ready 阶段访问:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class DependentSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
// ✗ 不要在这里访问其他组件
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnArchitecturePhase(ArchitecturePhase phase)
|
||||||
|
{
|
||||||
|
if (phase == ArchitecturePhase.Ready)
|
||||||
|
{
|
||||||
|
// ✓ 在这里安全访问其他组件
|
||||||
|
var config = this.GetModel<ConfigModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:Destroy() 方法一定会被调用吗?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
只有在正常销毁架构时才会调用。如果应用程序崩溃或被强制终止,`Destroy()` 可能不会被调用。因此:
|
||||||
|
|
||||||
|
- 不要依赖 `Destroy()` 保存关键数据
|
||||||
|
- 使用自动保存机制保护重要数据
|
||||||
|
- 非托管资源应该实现 `IDisposable` 模式
|
||||||
|
|
||||||
|
### 问题:可以在 Destroy() 中访问其他组件吗?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
不推荐。销毁时其他组件可能已经被销毁。如果必须访问,确保检查组件是否仍然可用:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public void Destroy()
|
||||||
|
{
|
||||||
|
// ✗ 不安全
|
||||||
|
var system = this.GetSystem<OtherSystem>();
|
||||||
|
system.DoSomething();
|
||||||
|
|
||||||
|
// ✓ 安全
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var system = this.GetSystem<OtherSystem>();
|
||||||
|
system?.DoSomething();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 组件可能已销毁
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [架构组件](/zh-CN/core/architecture) - 架构基础和组件注册
|
||||||
|
- [Model 层](/zh-CN/core/model) - 数据模型的生命周期
|
||||||
|
- [System 层](/zh-CN/core/system) - 业务系统的生命周期
|
||||||
|
- [异步初始化](/zh-CN/core/async-initialization) - 异步架构初始化详解
|
||||||
507
docs/zh-CN/core/resource.md
Normal file
507
docs/zh-CN/core/resource.md
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
---
|
||||||
|
title: 资源管理系统
|
||||||
|
description: 资源管理系统提供了统一的资源加载、缓存和卸载机制,支持引用计数和多种释放策略。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 资源管理系统
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
资源管理系统是 GFramework 中用于管理游戏资源(如纹理、音频、模型等)的核心组件。它提供了统一的资源加载接口,自动缓存机制,以及灵活的资源释放策略,帮助你高效管理游戏资源的生命周期。
|
||||||
|
|
||||||
|
通过资源管理器,你可以避免重复加载相同资源,使用引用计数自动管理资源生命周期,并根据需求选择合适的释放策略。
|
||||||
|
|
||||||
|
**主要特性**:
|
||||||
|
|
||||||
|
- 统一的资源加载接口(同步/异步)
|
||||||
|
- 自动资源缓存和去重
|
||||||
|
- 引用计数管理
|
||||||
|
- 可插拔的资源加载器
|
||||||
|
- 灵活的释放策略(手动/自动)
|
||||||
|
- 线程安全操作
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### 资源管理器
|
||||||
|
|
||||||
|
`ResourceManager` 是资源管理的核心类,负责加载、缓存和卸载资源:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.Abstractions.resource;
|
||||||
|
|
||||||
|
// 获取资源管理器(通常通过架构获取)
|
||||||
|
var resourceManager = this.GetUtility<IResourceManager>();
|
||||||
|
|
||||||
|
// 加载资源
|
||||||
|
var texture = resourceManager.Load<Texture>("textures/player.png");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 资源句柄
|
||||||
|
|
||||||
|
`IResourceHandle<T>` 用于管理资源的引用计数,确保资源在使用期间不被释放:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 获取资源句柄(自动增加引用计数)
|
||||||
|
using var handle = resourceManager.GetHandle<Texture>("textures/player.png");
|
||||||
|
|
||||||
|
// 使用资源
|
||||||
|
var texture = handle.Resource;
|
||||||
|
|
||||||
|
// 离开作用域时自动减少引用计数
|
||||||
|
```
|
||||||
|
|
||||||
|
### 资源加载器
|
||||||
|
|
||||||
|
`IResourceLoader<T>` 定义了如何加载特定类型的资源:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IResourceLoader<T> where T : class
|
||||||
|
{
|
||||||
|
T Load(string path);
|
||||||
|
Task<T> LoadAsync(string path);
|
||||||
|
void Unload(T resource);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 释放策略
|
||||||
|
|
||||||
|
`IResourceReleaseStrategy` 决定何时释放资源:
|
||||||
|
|
||||||
|
- **手动释放**(`ManualReleaseStrategy`):引用计数为 0 时不自动释放,需要手动调用 `Unload`
|
||||||
|
- **自动释放**(`AutoReleaseStrategy`):引用计数为 0 时自动释放资源
|
||||||
|
|
||||||
|
## 基本用法
|
||||||
|
|
||||||
|
### 注册资源加载器
|
||||||
|
|
||||||
|
首先需要为每种资源类型注册加载器:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.Abstractions.resource;
|
||||||
|
|
||||||
|
// 实现纹理加载器
|
||||||
|
public class TextureLoader : IResourceLoader<Texture>
|
||||||
|
{
|
||||||
|
public Texture Load(string path)
|
||||||
|
{
|
||||||
|
// 同步加载纹理
|
||||||
|
return LoadTextureFromFile(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Texture> LoadAsync(string path)
|
||||||
|
{
|
||||||
|
// 异步加载纹理
|
||||||
|
return await LoadTextureFromFileAsync(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unload(Texture resource)
|
||||||
|
{
|
||||||
|
// 释放纹理资源
|
||||||
|
resource?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在架构中注册加载器
|
||||||
|
public class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
var resourceManager = new ResourceManager();
|
||||||
|
resourceManager.RegisterLoader(new TextureLoader());
|
||||||
|
RegisterUtility<IResourceManager>(resourceManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 同步加载资源
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 加载资源
|
||||||
|
var texture = resourceManager.Load<Texture>("textures/player.png");
|
||||||
|
|
||||||
|
if (texture != null)
|
||||||
|
{
|
||||||
|
// 使用纹理
|
||||||
|
sprite.Texture = texture;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 异步加载资源
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 异步加载资源
|
||||||
|
var texture = await resourceManager.LoadAsync<Texture>("textures/player.png");
|
||||||
|
|
||||||
|
if (texture != null)
|
||||||
|
{
|
||||||
|
sprite.Texture = texture;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用资源句柄
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class PlayerController
|
||||||
|
{
|
||||||
|
private IResourceHandle<Texture>? _textureHandle;
|
||||||
|
|
||||||
|
public void LoadTexture()
|
||||||
|
{
|
||||||
|
var resourceManager = this.GetUtility<IResourceManager>();
|
||||||
|
|
||||||
|
// 获取句柄(增加引用计数)
|
||||||
|
_textureHandle = resourceManager.GetHandle<Texture>("textures/player.png");
|
||||||
|
|
||||||
|
if (_textureHandle?.Resource != null)
|
||||||
|
{
|
||||||
|
sprite.Texture = _textureHandle.Resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnloadTexture()
|
||||||
|
{
|
||||||
|
// 释放句柄(减少引用计数)
|
||||||
|
_textureHandle?.Dispose();
|
||||||
|
_textureHandle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### 预加载资源
|
||||||
|
|
||||||
|
在游戏启动或场景切换时预加载资源:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public async Task PreloadGameAssets()
|
||||||
|
{
|
||||||
|
var resourceManager = this.GetUtility<IResourceManager>();
|
||||||
|
|
||||||
|
// 预加载多个资源
|
||||||
|
await Task.WhenAll(
|
||||||
|
resourceManager.PreloadAsync<Texture>("textures/player.png"),
|
||||||
|
resourceManager.PreloadAsync<Texture>("textures/enemy.png"),
|
||||||
|
resourceManager.PreloadAsync<AudioClip>("audio/bgm.mp3")
|
||||||
|
);
|
||||||
|
|
||||||
|
Console.WriteLine("资源预加载完成");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用自动释放策略
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.resource;
|
||||||
|
|
||||||
|
// 设置自动释放策略
|
||||||
|
var resourceManager = this.GetUtility<IResourceManager>();
|
||||||
|
resourceManager.SetReleaseStrategy(new AutoReleaseStrategy());
|
||||||
|
|
||||||
|
// 使用资源句柄
|
||||||
|
using (var handle = resourceManager.GetHandle<Texture>("textures/temp.png"))
|
||||||
|
{
|
||||||
|
// 使用资源
|
||||||
|
var texture = handle.Resource;
|
||||||
|
}
|
||||||
|
// 离开作用域后,引用计数为 0,资源自动释放
|
||||||
|
```
|
||||||
|
|
||||||
|
### 批量卸载资源
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 卸载特定资源
|
||||||
|
resourceManager.Unload("textures/old_texture.png");
|
||||||
|
|
||||||
|
// 卸载所有资源
|
||||||
|
resourceManager.UnloadAll();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查询资源状态
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 检查资源是否已加载
|
||||||
|
if (resourceManager.IsLoaded("textures/player.png"))
|
||||||
|
{
|
||||||
|
Console.WriteLine("资源已在缓存中");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取已加载资源数量
|
||||||
|
Console.WriteLine($"已加载 {resourceManager.LoadedResourceCount} 个资源");
|
||||||
|
|
||||||
|
// 获取所有已加载资源的路径
|
||||||
|
foreach (var path in resourceManager.GetLoadedResourcePaths())
|
||||||
|
{
|
||||||
|
Console.WriteLine($"已加载: {path}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义释放策略
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.Abstractions.resource;
|
||||||
|
|
||||||
|
// 实现基于时间的释放策略
|
||||||
|
public class TimeBasedReleaseStrategy : IResourceReleaseStrategy
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, DateTime> _lastAccessTime = new();
|
||||||
|
private readonly TimeSpan _timeout = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
|
public bool ShouldRelease(string path, int refCount)
|
||||||
|
{
|
||||||
|
// 引用计数为 0 且超过 5 分钟未访问
|
||||||
|
if (refCount > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_lastAccessTime.TryGetValue(path, out var lastAccess))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return DateTime.Now - lastAccess > _timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateAccessTime(string path)
|
||||||
|
{
|
||||||
|
_lastAccessTime[path] = DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用自定义策略
|
||||||
|
resourceManager.SetReleaseStrategy(new TimeBasedReleaseStrategy());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 资源池模式
|
||||||
|
|
||||||
|
结合对象池实现资源复用:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class BulletPool
|
||||||
|
{
|
||||||
|
private readonly IResourceManager _resourceManager;
|
||||||
|
private readonly Queue<Bullet> _pool = new();
|
||||||
|
private IResourceHandle<Texture>? _textureHandle;
|
||||||
|
|
||||||
|
public BulletPool(IResourceManager resourceManager)
|
||||||
|
{
|
||||||
|
_resourceManager = resourceManager;
|
||||||
|
// 加载并持有纹理句柄
|
||||||
|
_textureHandle = _resourceManager.GetHandle<Texture>("textures/bullet.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bullet Get()
|
||||||
|
{
|
||||||
|
if (_pool.Count > 0)
|
||||||
|
{
|
||||||
|
return _pool.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新子弹,使用缓存的纹理
|
||||||
|
var bullet = new Bullet();
|
||||||
|
bullet.Texture = _textureHandle?.Resource;
|
||||||
|
return bullet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Return(Bullet bullet)
|
||||||
|
{
|
||||||
|
bullet.Reset();
|
||||||
|
_pool.Enqueue(bullet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// 释放纹理句柄
|
||||||
|
_textureHandle?.Dispose();
|
||||||
|
_textureHandle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 资源依赖管理
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MaterialLoader : IResourceLoader<Material>
|
||||||
|
{
|
||||||
|
private readonly IResourceManager _resourceManager;
|
||||||
|
|
||||||
|
public MaterialLoader(IResourceManager resourceManager)
|
||||||
|
{
|
||||||
|
_resourceManager = resourceManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Material Load(string path)
|
||||||
|
{
|
||||||
|
var material = new Material();
|
||||||
|
|
||||||
|
// 加载材质依赖的纹理
|
||||||
|
material.DiffuseTexture = _resourceManager.Load<Texture>($"{path}/diffuse.png");
|
||||||
|
material.NormalTexture = _resourceManager.Load<Texture>($"{path}/normal.png");
|
||||||
|
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Material> LoadAsync(string path)
|
||||||
|
{
|
||||||
|
var material = new Material();
|
||||||
|
|
||||||
|
// 并行加载依赖资源
|
||||||
|
var tasks = new[]
|
||||||
|
{
|
||||||
|
_resourceManager.LoadAsync<Texture>($"{path}/diffuse.png"),
|
||||||
|
_resourceManager.LoadAsync<Texture>($"{path}/normal.png")
|
||||||
|
};
|
||||||
|
|
||||||
|
var results = await Task.WhenAll(tasks);
|
||||||
|
material.DiffuseTexture = results[0];
|
||||||
|
material.NormalTexture = results[1];
|
||||||
|
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unload(Material resource)
|
||||||
|
{
|
||||||
|
// 材质卸载时,纹理由资源管理器自动管理
|
||||||
|
resource?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **使用资源句柄管理生命周期**:优先使用句柄而不是直接加载
|
||||||
|
```csharp
|
||||||
|
✓ using var handle = resourceManager.GetHandle<Texture>(path);
|
||||||
|
✗ var texture = resourceManager.Load<Texture>(path); // 需要手动管理
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **选择合适的释放策略**:根据游戏需求选择策略
|
||||||
|
- 手动释放:适合长期使用的资源(如 UI 纹理)
|
||||||
|
- 自动释放:适合临时资源(如特效纹理)
|
||||||
|
|
||||||
|
3. **预加载关键资源**:避免游戏中途加载导致卡顿
|
||||||
|
```csharp
|
||||||
|
// 在场景加载时预加载
|
||||||
|
await PreloadSceneAssets();
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **避免重复加载**:使用 `IsLoaded` 检查缓存
|
||||||
|
```csharp
|
||||||
|
if (!resourceManager.IsLoaded(path))
|
||||||
|
{
|
||||||
|
await resourceManager.LoadAsync<Texture>(path);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **及时释放不用的资源**:避免内存泄漏
|
||||||
|
```csharp
|
||||||
|
// 场景切换时卸载旧场景资源
|
||||||
|
foreach (var path in oldSceneResources)
|
||||||
|
{
|
||||||
|
resourceManager.Unload(path);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **使用 using 语句管理句柄**:确保引用计数正确
|
||||||
|
```csharp
|
||||||
|
✓ using (var handle = resourceManager.GetHandle<Texture>(path))
|
||||||
|
{
|
||||||
|
// 使用资源
|
||||||
|
} // 自动释放
|
||||||
|
|
||||||
|
✗ var handle = resourceManager.GetHandle<Texture>(path);
|
||||||
|
// 忘记调用 Dispose()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题:资源加载失败怎么办?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
`Load` 和 `LoadAsync` 方法在失败时返回 `null`,应该检查返回值:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var texture = resourceManager.Load<Texture>(path);
|
||||||
|
if (texture == null)
|
||||||
|
{
|
||||||
|
Logger.Error($"Failed to load texture: {path}");
|
||||||
|
// 使用默认纹理
|
||||||
|
texture = defaultTexture;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何避免重复加载相同资源?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
资源管理器自动缓存已加载的资源,多次加载相同路径只会返回缓存的实例:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var texture1 = resourceManager.Load<Texture>("player.png");
|
||||||
|
var texture2 = resourceManager.Load<Texture>("player.png");
|
||||||
|
// texture1 和 texture2 是同一个实例
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:什么时候使用手动释放 vs 自动释放?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- **手动释放**:适合长期使用的资源,如 UI、角色模型
|
||||||
|
- **自动释放**:适合临时资源,如特效、临时纹理
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 手动释放:UI 资源长期使用
|
||||||
|
resourceManager.SetReleaseStrategy(new ManualReleaseStrategy());
|
||||||
|
|
||||||
|
// 自动释放:特效资源用完即释放
|
||||||
|
resourceManager.SetReleaseStrategy(new AutoReleaseStrategy());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:资源句柄的引用计数如何工作?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- `GetHandle` 增加引用计数
|
||||||
|
- `Dispose` 减少引用计数
|
||||||
|
- 引用计数为 0 时,根据释放策略决定是否卸载
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 引用计数: 0
|
||||||
|
var handle1 = resourceManager.GetHandle<Texture>(path); // 引用计数: 1
|
||||||
|
var handle2 = resourceManager.GetHandle<Texture>(path); // 引用计数: 2
|
||||||
|
|
||||||
|
handle1.Dispose(); // 引用计数: 1
|
||||||
|
handle2.Dispose(); // 引用计数: 0(可能被释放)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何实现资源热重载?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
卸载旧资源后重新加载:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public void ReloadResource(string path)
|
||||||
|
{
|
||||||
|
// 卸载旧资源
|
||||||
|
resourceManager.Unload(path);
|
||||||
|
|
||||||
|
// 重新加载
|
||||||
|
var newResource = resourceManager.Load<Texture>(path);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:资源管理器是线程安全的吗?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
是的,所有公共方法都是线程安全的,可以在多线程环境中使用:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 在多个线程中并行加载
|
||||||
|
Parallel.For(0, 10, i =>
|
||||||
|
{
|
||||||
|
var texture = resourceManager.Load<Texture>($"texture_{i}.png");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [对象池系统](/zh-CN/core/pool) - 结合对象池复用资源
|
||||||
|
- [协程系统](/zh-CN/core/coroutine) - 异步加载资源
|
||||||
|
- [Godot 资源仓储](/zh-CN/godot/resource) - Godot 引擎的资源管理
|
||||||
|
- [资源管理最佳实践](/zh-CN/tutorials/resource-management) - 详细教程
|
||||||
576
docs/zh-CN/core/state-machine.md
Normal file
576
docs/zh-CN/core/state-machine.md
Normal file
@ -0,0 +1,576 @@
|
|||||||
|
---
|
||||||
|
title: 状态机系统
|
||||||
|
description: 状态机系统提供了灵活的状态管理机制,支持状态转换、历史记录和异步操作。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 状态机系统
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
状态机系统是 GFramework 中用于管理游戏状态的核心组件。通过状态机,你可以清晰地定义游戏的各种状态(如菜单、游戏中、暂停、游戏结束等),以及状态之间的转换规则,使游戏逻辑更加结构化和易于维护。
|
||||||
|
|
||||||
|
状态机系统支持同步和异步状态操作,提供状态历史记录,并与架构系统深度集成,让你可以在状态中访问所有架构组件。
|
||||||
|
|
||||||
|
**主要特性**:
|
||||||
|
|
||||||
|
- 类型安全的状态管理
|
||||||
|
- 支持同步和异步状态
|
||||||
|
- 状态转换验证
|
||||||
|
- 状态历史记录和回退
|
||||||
|
- 与架构系统集成
|
||||||
|
- 线程安全操作
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### 状态接口
|
||||||
|
|
||||||
|
`IState` 定义了状态的基本行为:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IState
|
||||||
|
{
|
||||||
|
void OnEnter(IState? from); // 进入状态
|
||||||
|
void OnExit(IState? to); // 退出状态
|
||||||
|
bool CanTransitionTo(IState target); // 转换验证
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态机
|
||||||
|
|
||||||
|
`IStateMachine` 管理状态的注册和切换:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IStateMachine
|
||||||
|
{
|
||||||
|
IState? Current { get; } // 当前状态
|
||||||
|
IStateMachine Register(IState state); // 注册状态
|
||||||
|
Task<bool> ChangeToAsync<T>() where T : IState; // 切换状态
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态机系统
|
||||||
|
|
||||||
|
`IStateMachineSystem` 结合了状态机和系统的能力:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IStateMachineSystem : ISystem, IStateMachine
|
||||||
|
{
|
||||||
|
// 继承 ISystem 和 IStateMachine 的所有功能
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基本用法
|
||||||
|
|
||||||
|
### 定义状态
|
||||||
|
|
||||||
|
继承 `ContextAwareStateBase` 创建状态:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.state;
|
||||||
|
|
||||||
|
// 菜单状态
|
||||||
|
public class MenuState : ContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override void OnEnter(IState? from)
|
||||||
|
{
|
||||||
|
Console.WriteLine("进入菜单");
|
||||||
|
// 显示菜单 UI
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnExit(IState? to)
|
||||||
|
{
|
||||||
|
Console.WriteLine("退出菜单");
|
||||||
|
// 隐藏菜单 UI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 游戏状态
|
||||||
|
public class GameplayState : ContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override void OnEnter(IState? from)
|
||||||
|
{
|
||||||
|
Console.WriteLine("开始游戏");
|
||||||
|
// 初始化游戏场景
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnExit(IState? to)
|
||||||
|
{
|
||||||
|
Console.WriteLine("结束游戏");
|
||||||
|
// 清理游戏场景
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注册和使用状态机
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.state;
|
||||||
|
|
||||||
|
public class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
// 创建状态机系统
|
||||||
|
var stateMachine = new StateMachineSystem();
|
||||||
|
|
||||||
|
// 注册状态
|
||||||
|
stateMachine
|
||||||
|
.Register(new MenuState())
|
||||||
|
.Register(new GameplayState())
|
||||||
|
.Register(new PauseState());
|
||||||
|
|
||||||
|
// 注册到架构
|
||||||
|
RegisterSystem<IStateMachineSystem>(stateMachine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 切换状态
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameController : IController
|
||||||
|
{
|
||||||
|
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||||
|
|
||||||
|
public async Task StartGame()
|
||||||
|
{
|
||||||
|
var stateMachine = this.GetSystem<IStateMachineSystem>();
|
||||||
|
|
||||||
|
// 切换到游戏状态
|
||||||
|
var success = await stateMachine.ChangeToAsync<GameplayState>();
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
Console.WriteLine("成功进入游戏状态");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### 状态转换验证
|
||||||
|
|
||||||
|
控制状态之间的转换规则:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameplayState : ContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override bool CanTransitionTo(IState target)
|
||||||
|
{
|
||||||
|
// 只能从游戏状态转换到暂停或游戏结束状态
|
||||||
|
return target is PauseState or GameOverState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEnter(IState? from)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"从 {from?.GetType().Name ?? "初始"} 进入游戏");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PauseState : ContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override bool CanTransitionTo(IState target)
|
||||||
|
{
|
||||||
|
// 暂停状态只能返回游戏状态
|
||||||
|
return target is GameplayState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 异步状态
|
||||||
|
|
||||||
|
处理需要异步操作的状态:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.Abstractions.state;
|
||||||
|
|
||||||
|
public class LoadingState : AsyncContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override async Task OnEnterAsync(IState? from)
|
||||||
|
{
|
||||||
|
Console.WriteLine("开始加载...");
|
||||||
|
|
||||||
|
// 异步加载资源
|
||||||
|
await LoadResourcesAsync();
|
||||||
|
|
||||||
|
Console.WriteLine("加载完成");
|
||||||
|
|
||||||
|
// 自动切换到下一个状态
|
||||||
|
var stateMachine = this.GetSystem<IStateMachineSystem>();
|
||||||
|
await stateMachine.ChangeToAsync<GameplayState>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadResourcesAsync()
|
||||||
|
{
|
||||||
|
// 模拟异步加载
|
||||||
|
await Task.Delay(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnExitAsync(IState? to)
|
||||||
|
{
|
||||||
|
Console.WriteLine("退出加载状态");
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态历史和回退
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameController : IController
|
||||||
|
{
|
||||||
|
public async Task NavigateBack()
|
||||||
|
{
|
||||||
|
var stateMachine = this.GetSystem<IStateMachineSystem>();
|
||||||
|
|
||||||
|
// 回退到上一个状态
|
||||||
|
var success = await stateMachine.GoBackAsync();
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
Console.WriteLine("已返回上一个状态");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowHistory()
|
||||||
|
{
|
||||||
|
var stateMachine = this.GetSystem<IStateMachineSystem>();
|
||||||
|
|
||||||
|
// 获取状态历史
|
||||||
|
var history = stateMachine.GetStateHistory();
|
||||||
|
|
||||||
|
Console.WriteLine("状态历史:");
|
||||||
|
foreach (var state in history)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"- {state.GetType().Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在状态中访问架构组件
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameplayState : ContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override void OnEnter(IState? from)
|
||||||
|
{
|
||||||
|
// 访问 Model
|
||||||
|
var playerModel = this.GetModel<PlayerModel>();
|
||||||
|
playerModel.Reset();
|
||||||
|
|
||||||
|
// 访问 System
|
||||||
|
var audioSystem = this.GetSystem<AudioSystem>();
|
||||||
|
audioSystem.PlayBGM("gameplay");
|
||||||
|
|
||||||
|
// 发送事件
|
||||||
|
this.SendEvent(new GameStartedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnExit(IState? to)
|
||||||
|
{
|
||||||
|
// 停止音乐
|
||||||
|
var audioSystem = this.GetSystem<AudioSystem>();
|
||||||
|
audioSystem.StopBGM();
|
||||||
|
|
||||||
|
// 发送事件
|
||||||
|
this.SendEvent(new GameEndedEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态数据传递
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 定义带数据的状态
|
||||||
|
public class GameplayState : ContextAwareStateBase
|
||||||
|
{
|
||||||
|
public int Level { get; set; }
|
||||||
|
public string Difficulty { get; set; } = "Normal";
|
||||||
|
|
||||||
|
public override void OnEnter(IState? from)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"开始关卡 {Level},难度: {Difficulty}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换状态并设置数据
|
||||||
|
public async Task StartLevel(int level, string difficulty)
|
||||||
|
{
|
||||||
|
var stateMachine = this.GetSystem<IStateMachineSystem>();
|
||||||
|
|
||||||
|
// 获取状态实例并设置数据
|
||||||
|
var gameplayState = stateMachine.GetState<GameplayState>();
|
||||||
|
if (gameplayState != null)
|
||||||
|
{
|
||||||
|
gameplayState.Level = level;
|
||||||
|
gameplayState.Difficulty = difficulty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换状态
|
||||||
|
await stateMachine.ChangeToAsync<GameplayState>();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态事件通知
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 定义状态变更事件
|
||||||
|
public class StateChangedEvent
|
||||||
|
{
|
||||||
|
public IState? From { get; set; }
|
||||||
|
public IState To { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义状态机系统
|
||||||
|
public class CustomStateMachineSystem : StateMachineSystem
|
||||||
|
{
|
||||||
|
protected override async Task OnStateChangedAsync(IState? from, IState to)
|
||||||
|
{
|
||||||
|
// 发送状态变更事件
|
||||||
|
this.SendEvent(new StateChangedEvent
|
||||||
|
{
|
||||||
|
From = from,
|
||||||
|
To = to
|
||||||
|
});
|
||||||
|
|
||||||
|
await base.OnStateChangedAsync(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 条件状态转换
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class BattleState : ContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override bool CanTransitionTo(IState target)
|
||||||
|
{
|
||||||
|
// 战斗中不能直接退出,必须先结束战斗
|
||||||
|
if (target is MenuState)
|
||||||
|
{
|
||||||
|
var battleModel = this.GetModel<BattleModel>();
|
||||||
|
return battleModel.IsBattleEnded;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试切换状态
|
||||||
|
public async Task TryExitBattle()
|
||||||
|
{
|
||||||
|
var stateMachine = this.GetSystem<IStateMachineSystem>();
|
||||||
|
|
||||||
|
// 检查是否可以切换
|
||||||
|
var canChange = await stateMachine.CanChangeToAsync<MenuState>();
|
||||||
|
|
||||||
|
if (canChange)
|
||||||
|
{
|
||||||
|
await stateMachine.ChangeToAsync<MenuState>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("战斗尚未结束,无法退出");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **使用基类创建状态**:继承 `ContextAwareStateBase` 或 `AsyncContextAwareStateBase`
|
||||||
|
```csharp
|
||||||
|
✓ public class MyState : ContextAwareStateBase { }
|
||||||
|
✗ public class MyState : IState { } // 需要手动实现所有接口
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **在 OnEnter 中初始化,在 OnExit 中清理**:保持状态的独立性
|
||||||
|
```csharp
|
||||||
|
public override void OnEnter(IState? from)
|
||||||
|
{
|
||||||
|
// 初始化状态相关资源
|
||||||
|
LoadUI();
|
||||||
|
StartBackgroundMusic();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnExit(IState? to)
|
||||||
|
{
|
||||||
|
// 清理状态相关资源
|
||||||
|
UnloadUI();
|
||||||
|
StopBackgroundMusic();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **使用转换验证控制状态流**:避免非法状态转换
|
||||||
|
```csharp
|
||||||
|
public override bool CanTransitionTo(IState target)
|
||||||
|
{
|
||||||
|
// 定义明确的转换规则
|
||||||
|
return target is AllowedState1 or AllowedState2;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **异步操作使用异步状态**:避免阻塞主线程
|
||||||
|
```csharp
|
||||||
|
✓ public class LoadingState : AsyncContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override async Task OnEnterAsync(IState? from)
|
||||||
|
{
|
||||||
|
await LoadDataAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
✗ public class LoadingState : ContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override void OnEnter(IState? from)
|
||||||
|
{
|
||||||
|
LoadDataAsync().Wait(); // 阻塞主线程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **合理使用状态历史**:避免历史记录过大
|
||||||
|
```csharp
|
||||||
|
// 创建状态机时设置历史大小
|
||||||
|
var stateMachine = new StateMachineSystem(maxHistorySize: 10);
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **状态保持单一职责**:每个状态只负责一个场景或功能
|
||||||
|
```csharp
|
||||||
|
✓ MenuState, GameplayState, PauseState // 职责清晰
|
||||||
|
✗ GameState // 职责不明确,包含太多逻辑
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题:状态切换失败怎么办?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
`ChangeToAsync` 返回 `false` 表示切换失败,通常是因为 `CanTransitionTo` 返回 `false`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var success = await stateMachine.ChangeToAsync<TargetState>();
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
Console.WriteLine("状态切换被拒绝");
|
||||||
|
// 检查转换规则
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何在状态之间传递数据?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
有几种方式:
|
||||||
|
|
||||||
|
1. **通过状态属性**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var state = stateMachine.GetState<GameplayState>();
|
||||||
|
state.Level = 5;
|
||||||
|
await stateMachine.ChangeToAsync<GameplayState>();
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **通过 Model**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 在切换前设置 Model
|
||||||
|
var gameModel = this.GetModel<GameModel>();
|
||||||
|
gameModel.CurrentLevel = 5;
|
||||||
|
|
||||||
|
// 在状态中读取
|
||||||
|
public override void OnEnter(IState? from)
|
||||||
|
{
|
||||||
|
var gameModel = this.GetModel<GameModel>();
|
||||||
|
var level = gameModel.CurrentLevel;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **通过事件**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
this.SendEvent(new LevelSelectedEvent { Level = 5 });
|
||||||
|
await stateMachine.ChangeToAsync<GameplayState>();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:状态机系统和普通状态机有什么区别?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- **StateMachine**:纯状态机,不依赖架构
|
||||||
|
- **StateMachineSystem**:集成到架构中,状态可以访问所有架构组件
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用 StateMachineSystem(推荐)
|
||||||
|
RegisterSystem<IStateMachineSystem>(new StateMachineSystem());
|
||||||
|
|
||||||
|
// 使用 StateMachine(独立使用)
|
||||||
|
var stateMachine = new StateMachine();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何处理状态切换动画?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
在 `OnExit` 和 `OnEnter` 中使用协程:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MenuState : AsyncContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override async Task OnExitAsync(IState? to)
|
||||||
|
{
|
||||||
|
// 播放淡出动画
|
||||||
|
await PlayFadeOutAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GameplayState : AsyncContextAwareStateBase
|
||||||
|
{
|
||||||
|
public override async Task OnEnterAsync(IState? from)
|
||||||
|
{
|
||||||
|
// 播放淡入动画
|
||||||
|
await PlayFadeInAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:可以在状态中切换到其他状态吗?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
可以,但要注意避免递归切换:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public override async void OnEnter(IState? from)
|
||||||
|
{
|
||||||
|
// 检查条件后自动切换
|
||||||
|
if (ShouldSkip())
|
||||||
|
{
|
||||||
|
var stateMachine = this.GetSystem<IStateMachineSystem>();
|
||||||
|
await stateMachine.ChangeToAsync<NextState>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:状态机是线程安全的吗?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
是的,状态机的所有操作都是线程安全的,使用了内部锁机制。
|
||||||
|
|
||||||
|
### 问题:如何实现状态栈(多层状态)?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
使用状态历史功能:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 进入子状态
|
||||||
|
await stateMachine.ChangeToAsync<SubMenuState>();
|
||||||
|
|
||||||
|
// 返回上一层
|
||||||
|
await stateMachine.GoBackAsync();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [生命周期管理](/zh-CN/core/lifecycle) - 状态的初始化和销毁
|
||||||
|
- [事件系统](/zh-CN/core/events) - 状态变更通知
|
||||||
|
- [协程系统](/zh-CN/core/coroutine) - 异步状态操作
|
||||||
|
- [状态机实现教程](/zh-CN/tutorials/state-machine-tutorial) - 完整示例
|
||||||
652
docs/zh-CN/game/scene.md
Normal file
652
docs/zh-CN/game/scene.md
Normal file
@ -0,0 +1,652 @@
|
|||||||
|
---
|
||||||
|
title: 场景系统
|
||||||
|
description: 场景系统提供了完整的场景生命周期管理、路由导航和转换控制功能。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 场景系统
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
场景系统是 GFramework.Game 中用于管理游戏场景的核心组件。它提供了场景的加载、卸载、切换、暂停和恢复等完整生命周期管理,以及基于栈的场景导航机制。
|
||||||
|
|
||||||
|
通过场景系统,你可以轻松实现场景之间的平滑切换,管理场景栈(如主菜单 -> 游戏 -> 暂停菜单),并在场景转换时执行自定义逻辑。
|
||||||
|
|
||||||
|
**主要特性**:
|
||||||
|
|
||||||
|
- 完整的场景生命周期管理
|
||||||
|
- 基于栈的场景导航
|
||||||
|
- 场景转换管道和钩子
|
||||||
|
- 路由守卫(Route Guard)
|
||||||
|
- 场景工厂和行为模式
|
||||||
|
- 异步加载和卸载
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### 场景接口
|
||||||
|
|
||||||
|
`IScene` 定义了场景的完整生命周期:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IScene
|
||||||
|
{
|
||||||
|
ValueTask OnLoadAsync(ISceneEnterParam? param); // 加载资源
|
||||||
|
ValueTask OnEnterAsync(); // 进入场景
|
||||||
|
ValueTask OnPauseAsync(); // 暂停场景
|
||||||
|
ValueTask OnResumeAsync(); // 恢复场景
|
||||||
|
ValueTask OnExitAsync(); // 退出场景
|
||||||
|
ValueTask OnUnloadAsync(); // 卸载资源
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景路由
|
||||||
|
|
||||||
|
`ISceneRouter` 管理场景的导航和切换:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface ISceneRouter : ISystem
|
||||||
|
{
|
||||||
|
ISceneBehavior? Current { get; } // 当前场景
|
||||||
|
string? CurrentKey { get; } // 当前场景键
|
||||||
|
IEnumerable<ISceneBehavior> Stack { get; } // 场景栈
|
||||||
|
bool IsTransitioning { get; } // 是否正在切换
|
||||||
|
|
||||||
|
ValueTask ReplaceAsync(string sceneKey, ISceneEnterParam? param = null);
|
||||||
|
ValueTask PushAsync(string sceneKey, ISceneEnterParam? param = null);
|
||||||
|
ValueTask PopAsync();
|
||||||
|
ValueTask ClearAsync();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景行为
|
||||||
|
|
||||||
|
`ISceneBehavior` 封装了场景的具体实现和引擎集成:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface ISceneBehavior
|
||||||
|
{
|
||||||
|
string Key { get; } // 场景唯一标识
|
||||||
|
IScene Scene { get; } // 场景实例
|
||||||
|
ValueTask LoadAsync(ISceneEnterParam? param);
|
||||||
|
ValueTask UnloadAsync();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基本用法
|
||||||
|
|
||||||
|
### 定义场景
|
||||||
|
|
||||||
|
实现 `IScene` 接口创建场景:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Game.Abstractions.scene;
|
||||||
|
|
||||||
|
public class MainMenuScene : IScene
|
||||||
|
{
|
||||||
|
public async ValueTask OnLoadAsync(ISceneEnterParam? param)
|
||||||
|
{
|
||||||
|
// 加载场景资源
|
||||||
|
Console.WriteLine("加载主菜单资源");
|
||||||
|
await Task.Delay(100); // 模拟加载
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnEnterAsync()
|
||||||
|
{
|
||||||
|
// 进入场景
|
||||||
|
Console.WriteLine("进入主菜单");
|
||||||
|
// 显示 UI、播放音乐等
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnPauseAsync()
|
||||||
|
{
|
||||||
|
// 暂停场景
|
||||||
|
Console.WriteLine("暂停主菜单");
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnResumeAsync()
|
||||||
|
{
|
||||||
|
// 恢复场景
|
||||||
|
Console.WriteLine("恢复主菜单");
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnExitAsync()
|
||||||
|
{
|
||||||
|
// 退出场景
|
||||||
|
Console.WriteLine("退出主菜单");
|
||||||
|
// 隐藏 UI、停止音乐等
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnUnloadAsync()
|
||||||
|
{
|
||||||
|
// 卸载场景资源
|
||||||
|
Console.WriteLine("卸载主菜单资源");
|
||||||
|
await Task.Delay(50); // 模拟卸载
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注册场景
|
||||||
|
|
||||||
|
在场景注册表中注册场景:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Game.Abstractions.scene;
|
||||||
|
|
||||||
|
public class GameSceneRegistry : IGameSceneRegistry
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, Type> _scenes = new();
|
||||||
|
|
||||||
|
public GameSceneRegistry()
|
||||||
|
{
|
||||||
|
// 注册场景
|
||||||
|
Register("MainMenu", typeof(MainMenuScene));
|
||||||
|
Register("Gameplay", typeof(GameplayScene));
|
||||||
|
Register("Pause", typeof(PauseScene));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(string key, Type sceneType)
|
||||||
|
{
|
||||||
|
_scenes[key] = sceneType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type? GetSceneType(string key)
|
||||||
|
{
|
||||||
|
return _scenes.TryGetValue(key, out var type) ? type : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 切换场景
|
||||||
|
|
||||||
|
使用场景路由进行导航:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameController : IController
|
||||||
|
{
|
||||||
|
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||||
|
|
||||||
|
public async Task StartGame()
|
||||||
|
{
|
||||||
|
var sceneRouter = this.GetSystem<ISceneRouter>();
|
||||||
|
|
||||||
|
// 替换当前场景(清空场景栈)
|
||||||
|
await sceneRouter.ReplaceAsync("Gameplay");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ShowPauseMenu()
|
||||||
|
{
|
||||||
|
var sceneRouter = this.GetSystem<ISceneRouter>();
|
||||||
|
|
||||||
|
// 压入新场景(保留当前场景)
|
||||||
|
await sceneRouter.PushAsync("Pause");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClosePauseMenu()
|
||||||
|
{
|
||||||
|
var sceneRouter = this.GetSystem<ISceneRouter>();
|
||||||
|
|
||||||
|
// 弹出当前场景(恢复上一个场景)
|
||||||
|
await sceneRouter.PopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### 场景参数传递
|
||||||
|
|
||||||
|
通过 `ISceneEnterParam` 传递数据:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 定义场景参数
|
||||||
|
public class GameplayEnterParam : ISceneEnterParam
|
||||||
|
{
|
||||||
|
public int Level { get; set; }
|
||||||
|
public string Difficulty { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在场景中接收参数
|
||||||
|
public class GameplayScene : IScene
|
||||||
|
{
|
||||||
|
private int _level;
|
||||||
|
private string _difficulty;
|
||||||
|
|
||||||
|
public async ValueTask OnLoadAsync(ISceneEnterParam? param)
|
||||||
|
{
|
||||||
|
if (param is GameplayEnterParam gameplayParam)
|
||||||
|
{
|
||||||
|
_level = gameplayParam.Level;
|
||||||
|
_difficulty = gameplayParam.Difficulty;
|
||||||
|
Console.WriteLine($"加载关卡 {_level},难度: {_difficulty}");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... 其他生命周期方法
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换场景时传递参数
|
||||||
|
await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam
|
||||||
|
{
|
||||||
|
Level = 1,
|
||||||
|
Difficulty = "Normal"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 路由守卫
|
||||||
|
|
||||||
|
使用路由守卫控制场景切换:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Game.Abstractions.scene;
|
||||||
|
|
||||||
|
public class SaveGameGuard : ISceneRouteGuard
|
||||||
|
{
|
||||||
|
public async ValueTask<bool> CanLeaveAsync(
|
||||||
|
ISceneBehavior from,
|
||||||
|
string toKey,
|
||||||
|
ISceneEnterParam? param)
|
||||||
|
{
|
||||||
|
// 离开游戏场景前检查是否需要保存
|
||||||
|
if (from.Key == "Gameplay")
|
||||||
|
{
|
||||||
|
var needsSave = CheckIfNeedsSave();
|
||||||
|
if (needsSave)
|
||||||
|
{
|
||||||
|
await SaveGameAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // 允许离开
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<bool> CanEnterAsync(
|
||||||
|
string toKey,
|
||||||
|
ISceneEnterParam? param)
|
||||||
|
{
|
||||||
|
// 进入场景前的验证
|
||||||
|
if (toKey == "Gameplay")
|
||||||
|
{
|
||||||
|
// 检查是否满足进入条件
|
||||||
|
var canEnter = CheckGameplayRequirements();
|
||||||
|
return canEnter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckIfNeedsSave() => true;
|
||||||
|
private async Task SaveGameAsync() => await Task.Delay(100);
|
||||||
|
private bool CheckGameplayRequirements() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册守卫
|
||||||
|
sceneRouter.AddGuard(new SaveGameGuard());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景转换处理器
|
||||||
|
|
||||||
|
自定义场景转换逻辑:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Game.Abstractions.scene;
|
||||||
|
|
||||||
|
public class FadeTransitionHandler : ISceneTransitionHandler
|
||||||
|
{
|
||||||
|
public async ValueTask OnBeforeLoadAsync(SceneTransitionEvent @event)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"准备加载场景: {@event.ToKey}");
|
||||||
|
// 显示加载画面
|
||||||
|
await ShowLoadingScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnAfterLoadAsync(SceneTransitionEvent @event)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"场景加载完成: {@event.ToKey}");
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnBeforeEnterAsync(SceneTransitionEvent @event)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"准备进入场景: {@event.ToKey}");
|
||||||
|
// 播放淡入动画
|
||||||
|
await PlayFadeIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"已进入场景: {@event.ToKey}");
|
||||||
|
// 隐藏加载画面
|
||||||
|
await HideLoadingScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnBeforeExitAsync(SceneTransitionEvent @event)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"准备退出场景: {@event.FromKey}");
|
||||||
|
// 播放淡出动画
|
||||||
|
await PlayFadeOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnAfterExitAsync(SceneTransitionEvent @event)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"已退出场景: {@event.FromKey}");
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowLoadingScreen() => await Task.Delay(100);
|
||||||
|
private async Task HideLoadingScreen() => await Task.Delay(100);
|
||||||
|
private async Task PlayFadeIn() => await Task.Delay(200);
|
||||||
|
private async Task PlayFadeOut() => await Task.Delay(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册转换处理器
|
||||||
|
sceneRouter.AddTransitionHandler(new FadeTransitionHandler());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景栈管理
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class SceneNavigationController : IController
|
||||||
|
{
|
||||||
|
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||||
|
|
||||||
|
public async Task NavigateToSettings()
|
||||||
|
{
|
||||||
|
var sceneRouter = this.GetSystem<ISceneRouter>();
|
||||||
|
|
||||||
|
// 检查场景是否已在栈中
|
||||||
|
if (sceneRouter.Contains("Settings"))
|
||||||
|
{
|
||||||
|
Console.WriteLine("设置场景已打开");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 压入设置场景
|
||||||
|
await sceneRouter.PushAsync("Settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowSceneStack()
|
||||||
|
{
|
||||||
|
var sceneRouter = this.GetSystem<ISceneRouter>();
|
||||||
|
|
||||||
|
Console.WriteLine("当前场景栈:");
|
||||||
|
foreach (var scene in sceneRouter.Stack)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"- {scene.Key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReturnToMainMenu()
|
||||||
|
{
|
||||||
|
var sceneRouter = this.GetSystem<ISceneRouter>();
|
||||||
|
|
||||||
|
// 清空所有场景并加载主菜单
|
||||||
|
await sceneRouter.ClearAsync();
|
||||||
|
await sceneRouter.ReplaceAsync("MainMenu");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景加载进度
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameplayScene : IScene
|
||||||
|
{
|
||||||
|
public async ValueTask OnLoadAsync(ISceneEnterParam? param)
|
||||||
|
{
|
||||||
|
var resourceManager = GetResourceManager();
|
||||||
|
|
||||||
|
// 加载多个资源并报告进度
|
||||||
|
var resources = new[]
|
||||||
|
{
|
||||||
|
"textures/player.png",
|
||||||
|
"textures/enemy.png",
|
||||||
|
"audio/bgm.mp3",
|
||||||
|
"models/level.obj"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < resources.Length; i++)
|
||||||
|
{
|
||||||
|
await resourceManager.LoadAsync<object>(resources[i]);
|
||||||
|
|
||||||
|
// 报告进度
|
||||||
|
var progress = (i + 1) / (float)resources.Length;
|
||||||
|
ReportProgress(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReportProgress(float progress)
|
||||||
|
{
|
||||||
|
// 发送进度事件
|
||||||
|
Console.WriteLine($"加载进度: {progress * 100:F0}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... 其他生命周期方法
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景预加载
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class PreloadController : IController
|
||||||
|
{
|
||||||
|
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||||
|
|
||||||
|
public async Task PreloadNextLevel()
|
||||||
|
{
|
||||||
|
var sceneFactory = this.GetUtility<ISceneFactory>();
|
||||||
|
|
||||||
|
// 预加载下一关场景
|
||||||
|
var scene = sceneFactory.Create("Level2");
|
||||||
|
await scene.OnLoadAsync(null);
|
||||||
|
|
||||||
|
Console.WriteLine("下一关预加载完成");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **在 OnLoad 中加载资源,在 OnUnload 中释放**:保持资源管理清晰
|
||||||
|
```csharp
|
||||||
|
public async ValueTask OnLoadAsync(ISceneEnterParam? param)
|
||||||
|
{
|
||||||
|
_texture = await LoadTextureAsync("player.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnUnloadAsync()
|
||||||
|
{
|
||||||
|
_texture?.Dispose();
|
||||||
|
_texture = null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **使用 Push/Pop 管理临时场景**:如暂停菜单、设置界面
|
||||||
|
```csharp
|
||||||
|
// 打开暂停菜单(保留游戏场景)
|
||||||
|
await sceneRouter.PushAsync("Pause");
|
||||||
|
|
||||||
|
// 关闭暂停菜单(恢复游戏场景)
|
||||||
|
await sceneRouter.PopAsync();
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **使用 Replace 切换主要场景**:如从菜单到游戏
|
||||||
|
```csharp
|
||||||
|
// 开始游戏(清空场景栈)
|
||||||
|
await sceneRouter.ReplaceAsync("Gameplay");
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **在 OnPause/OnResume 中管理状态**:暂停和恢复游戏逻辑
|
||||||
|
```csharp
|
||||||
|
public async ValueTask OnPauseAsync()
|
||||||
|
{
|
||||||
|
// 暂停游戏逻辑
|
||||||
|
_gameTimer.Pause();
|
||||||
|
_audioSystem.PauseBGM();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnResumeAsync()
|
||||||
|
{
|
||||||
|
// 恢复游戏逻辑
|
||||||
|
_gameTimer.Resume();
|
||||||
|
_audioSystem.ResumeBGM();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **使用路由守卫处理业务逻辑**:如保存检查、权限验证
|
||||||
|
```csharp
|
||||||
|
public async ValueTask<bool> CanLeaveAsync(...)
|
||||||
|
{
|
||||||
|
if (HasUnsavedChanges())
|
||||||
|
{
|
||||||
|
var confirmed = await ShowSaveDialog();
|
||||||
|
if (confirmed)
|
||||||
|
{
|
||||||
|
await SaveAsync();
|
||||||
|
}
|
||||||
|
return confirmed;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **避免在场景切换时阻塞**:使用异步操作
|
||||||
|
```csharp
|
||||||
|
✓ await sceneRouter.ReplaceAsync("Gameplay");
|
||||||
|
✗ sceneRouter.ReplaceAsync("Gameplay").Wait(); // 可能死锁
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题:Replace、Push、Pop 有什么区别?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- **Replace**:清空场景栈,加载新场景(用于主要场景切换)
|
||||||
|
- **Push**:压入新场景,暂停当前场景(用于临时场景)
|
||||||
|
- **Pop**:弹出当前场景,恢复上一个场景(用于关闭临时场景)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 场景栈示例
|
||||||
|
await sceneRouter.ReplaceAsync("MainMenu"); // [MainMenu]
|
||||||
|
await sceneRouter.PushAsync("Settings"); // [MainMenu, Settings]
|
||||||
|
await sceneRouter.PushAsync("About"); // [MainMenu, Settings, About]
|
||||||
|
await sceneRouter.PopAsync(); // [MainMenu, Settings]
|
||||||
|
await sceneRouter.PopAsync(); // [MainMenu]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何在场景之间传递数据?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
有几种方式:
|
||||||
|
|
||||||
|
1. **通过场景参数**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam
|
||||||
|
{
|
||||||
|
Level = 5
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **通过 Model**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var gameModel = this.GetModel<GameModel>();
|
||||||
|
gameModel.CurrentLevel = 5;
|
||||||
|
await sceneRouter.ReplaceAsync("Gameplay");
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **通过事件**:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
this.SendEvent(new LevelSelectedEvent { Level = 5 });
|
||||||
|
await sceneRouter.ReplaceAsync("Gameplay");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:场景切换时如何显示加载画面?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
使用场景转换处理器:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class LoadingScreenHandler : ISceneTransitionHandler
|
||||||
|
{
|
||||||
|
public async ValueTask OnBeforeLoadAsync(SceneTransitionEvent @event)
|
||||||
|
{
|
||||||
|
await ShowLoadingScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event)
|
||||||
|
{
|
||||||
|
await HideLoadingScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... 其他方法
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何防止用户在场景切换时操作?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
检查 `IsTransitioning` 状态:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public async Task ChangeScene(string sceneKey)
|
||||||
|
{
|
||||||
|
var sceneRouter = this.GetSystem<ISceneRouter>();
|
||||||
|
|
||||||
|
if (sceneRouter.IsTransitioning)
|
||||||
|
{
|
||||||
|
Console.WriteLine("场景正在切换中,请稍候");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sceneRouter.ReplaceAsync(sceneKey);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:场景切换失败怎么办?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
使用 try-catch 捕获异常:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await sceneRouter.ReplaceAsync("Gameplay");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"场景切换失败: {ex.Message}");
|
||||||
|
// 回退到安全场景
|
||||||
|
await sceneRouter.ReplaceAsync("MainMenu");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题:如何实现场景预加载?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
在后台预先加载场景资源:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 在当前场景中预加载下一个场景
|
||||||
|
var factory = this.GetUtility<ISceneFactory>();
|
||||||
|
var nextScene = factory.Create("NextLevel");
|
||||||
|
await nextScene.OnLoadAsync(null);
|
||||||
|
|
||||||
|
// 稍后快速切换
|
||||||
|
await sceneRouter.ReplaceAsync("NextLevel");
|
||||||
|
```
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [UI 系统](/zh-CN/game/ui) - UI 页面管理
|
||||||
|
- [资源管理系统](/zh-CN/core/resource) - 场景资源加载
|
||||||
|
- [状态机系统](/zh-CN/core/state-machine) - 场景状态管理
|
||||||
|
- [Godot 场景系统](/zh-CN/godot/scene) - Godot 引擎集成
|
||||||
|
- [存档系统实现教程](/zh-CN/tutorials/save-system) - 场景切换时保存数据
|
||||||
496
docs/zh-CN/game/ui.md
Normal file
496
docs/zh-CN/game/ui.md
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
---
|
||||||
|
title: UI 系统
|
||||||
|
description: UI 系统提供了完整的 UI 页面管理、路由导航和多层级显示功能。
|
||||||
|
---
|
||||||
|
|
||||||
|
# UI 系统
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
UI 系统是 GFramework.Game 中用于管理游戏 UI 界面的核心组件。它提供了 UI 页面的生命周期管理、基于栈的导航机制,以及多层级的
|
||||||
|
UI 显示系统(Page、Overlay、Modal、Toast、Topmost)。
|
||||||
|
|
||||||
|
通过 UI 系统,你可以轻松实现 UI 页面之间的切换,管理 UI 栈(如主菜单 -> 设置 -> 关于),以及在不同层级显示各种类型的
|
||||||
|
UI(对话框、提示、加载界面等)。
|
||||||
|
|
||||||
|
**主要特性**:
|
||||||
|
|
||||||
|
- 完整的 UI 生命周期管理
|
||||||
|
- 基于栈的 UI 导航
|
||||||
|
- 多层级 UI 显示(5 个层级)
|
||||||
|
- UI 转换管道和钩子
|
||||||
|
- 路由守卫(Route Guard)
|
||||||
|
- UI 工厂和行为模式
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### UI 页面接口
|
||||||
|
|
||||||
|
`IUiPage` 定义了 UI 页面的生命周期:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IUiPage
|
||||||
|
{
|
||||||
|
void OnEnter(IUiPageEnterParam? param); // 进入页面
|
||||||
|
void OnExit(); // 退出页面
|
||||||
|
void OnPause(); // 暂停页面
|
||||||
|
void OnResume(); // 恢复页面
|
||||||
|
void OnShow(); // 显示页面
|
||||||
|
void OnHide(); // 隐藏页面
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI 路由
|
||||||
|
|
||||||
|
`IUiRouter` 管理 UI 的导航和切换:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IUiRouter : ISystem
|
||||||
|
{
|
||||||
|
int Count { get; } // UI 栈深度
|
||||||
|
IUiPageBehavior? Peek(); // 栈顶 UI
|
||||||
|
|
||||||
|
ValueTask PushAsync(string uiKey, IUiPageEnterParam? param = null);
|
||||||
|
ValueTask PopAsync(UiPopPolicy policy = UiPopPolicy.Destroy);
|
||||||
|
ValueTask ReplaceAsync(string uiKey, IUiPageEnterParam? param = null);
|
||||||
|
ValueTask ClearAsync();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI 层级
|
||||||
|
|
||||||
|
UI 系统支持 5 个显示层级:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public enum UiLayer
|
||||||
|
{
|
||||||
|
Page, // 页面层(栈管理,不可重入)
|
||||||
|
Overlay, // 浮层(可重入,对话框等)
|
||||||
|
Modal, // 模态层(可重入,带遮罩)
|
||||||
|
Toast, // 提示层(可重入,轻量提示)
|
||||||
|
Topmost // 顶层(不可重入,系统级)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基本用法
|
||||||
|
|
||||||
|
### 定义 UI 页面
|
||||||
|
|
||||||
|
实现 `IUiPage` 接口创建 UI 页面:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Game.Abstractions.ui;
|
||||||
|
|
||||||
|
public class MainMenuPage : IUiPage
|
||||||
|
{
|
||||||
|
public void OnEnter(IUiPageEnterParam? param)
|
||||||
|
{
|
||||||
|
Console.WriteLine("进入主菜单");
|
||||||
|
// 初始化 UI、绑定事件
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnExit()
|
||||||
|
{
|
||||||
|
Console.WriteLine("退出主菜单");
|
||||||
|
// 清理资源、解绑事件
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPause()
|
||||||
|
{
|
||||||
|
Console.WriteLine("暂停主菜单");
|
||||||
|
// 暂停动画、停止交互
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnResume()
|
||||||
|
{
|
||||||
|
Console.WriteLine("恢复主菜单");
|
||||||
|
// 恢复动画、启用交互
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnShow()
|
||||||
|
{
|
||||||
|
Console.WriteLine("显示主菜单");
|
||||||
|
// 显示 UI 元素
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnHide()
|
||||||
|
{
|
||||||
|
Console.WriteLine("隐藏主菜单");
|
||||||
|
// 隐藏 UI 元素
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 切换 UI 页面
|
||||||
|
|
||||||
|
使用 UI 路由进行导航:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class UiController : IController
|
||||||
|
{
|
||||||
|
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||||
|
|
||||||
|
public async Task ShowSettings()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// 压入设置页面(保留当前页面)
|
||||||
|
await uiRouter.PushAsync("Settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CloseSettings()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// 弹出当前页面(返回上一页)
|
||||||
|
await uiRouter.PopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ShowMainMenu()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// 替换所有页面(清空 UI 栈)
|
||||||
|
await uiRouter.ReplaceAsync("MainMenu");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 显示不同层级的 UI
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class UiController : IController
|
||||||
|
{
|
||||||
|
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
|
||||||
|
|
||||||
|
public void ShowDialog()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// 在 Modal 层显示对话框
|
||||||
|
var handle = uiRouter.Show("ConfirmDialog", UiLayer.Modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowToast(string message)
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// 在 Toast 层显示提示
|
||||||
|
var handle = uiRouter.Show("ToastMessage", UiLayer.Toast,
|
||||||
|
new ToastParam { Message = message });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowLoading()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// 在 Topmost 层显示加载界面
|
||||||
|
var handle = uiRouter.Show("LoadingScreen", UiLayer.Topmost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### UI 参数传递
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 定义 UI 参数
|
||||||
|
public class SettingsEnterParam : IUiPageEnterParam
|
||||||
|
{
|
||||||
|
public string Category { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在 UI 中接收参数
|
||||||
|
public class SettingsPage : IUiPage
|
||||||
|
{
|
||||||
|
private string _category;
|
||||||
|
|
||||||
|
public void OnEnter(IUiPageEnterParam? param)
|
||||||
|
{
|
||||||
|
if (param is SettingsEnterParam settingsParam)
|
||||||
|
{
|
||||||
|
_category = settingsParam.Category;
|
||||||
|
Console.WriteLine($"打开设置分类: {_category}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... 其他生命周期方法
|
||||||
|
}
|
||||||
|
|
||||||
|
// 传递参数
|
||||||
|
await uiRouter.PushAsync("Settings", new SettingsEnterParam
|
||||||
|
{
|
||||||
|
Category = "Audio"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 路由守卫
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Game.Abstractions.ui;
|
||||||
|
|
||||||
|
public class UnsavedChangesGuard : IUiRouteGuard
|
||||||
|
{
|
||||||
|
public async ValueTask<bool> CanLeaveAsync(
|
||||||
|
IUiPageBehavior from,
|
||||||
|
string toKey,
|
||||||
|
IUiPageEnterParam? param)
|
||||||
|
{
|
||||||
|
// 检查是否有未保存的更改
|
||||||
|
if (from.Key == "Settings" && HasUnsavedChanges())
|
||||||
|
{
|
||||||
|
var confirmed = await ShowConfirmDialog();
|
||||||
|
return confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<bool> CanEnterAsync(
|
||||||
|
string toKey,
|
||||||
|
IUiPageEnterParam? param)
|
||||||
|
{
|
||||||
|
// 进入前的验证
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasUnsavedChanges() => true;
|
||||||
|
private async Task<bool> ShowConfirmDialog() => await Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册守卫
|
||||||
|
uiRouter.AddGuard(new UnsavedChangesGuard());
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI 转换处理器
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Game.Abstractions.ui;
|
||||||
|
|
||||||
|
public class FadeTransitionHandler : IUiTransitionHandler
|
||||||
|
{
|
||||||
|
public async ValueTask OnBeforeEnterAsync(UiTransitionEvent @event)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"准备进入 UI: {@event.ToKey}");
|
||||||
|
await PlayFadeIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnAfterEnterAsync(UiTransitionEvent @event)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"已进入 UI: {@event.ToKey}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnBeforeExitAsync(UiTransitionEvent @event)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"准备退出 UI: {@event.FromKey}");
|
||||||
|
await PlayFadeOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask OnAfterExitAsync(UiTransitionEvent @event)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"已退出 UI: {@event.FromKey}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PlayFadeIn() => await Task.Delay(200);
|
||||||
|
private async Task PlayFadeOut() => await Task.Delay(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册转换处理器
|
||||||
|
uiRouter.RegisterHandler(new FadeTransitionHandler());
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI 句柄管理
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class DialogController : IController
|
||||||
|
{
|
||||||
|
private UiHandle? _dialogHandle;
|
||||||
|
|
||||||
|
public void ShowDialog()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// 显示对话框并保存句柄
|
||||||
|
_dialogHandle = uiRouter.Show("ConfirmDialog", UiLayer.Modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseDialog()
|
||||||
|
{
|
||||||
|
if (_dialogHandle.HasValue)
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// 使用句柄关闭对话框
|
||||||
|
uiRouter.Hide(_dialogHandle.Value, UiLayer.Modal, destroy: true);
|
||||||
|
_dialogHandle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI 栈管理
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class NavigationController : IController
|
||||||
|
{
|
||||||
|
public void ShowUiStack()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
Console.WriteLine($"UI 栈深度: {uiRouter.Count}");
|
||||||
|
|
||||||
|
var current = uiRouter.Peek();
|
||||||
|
if (current != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"当前 UI: {current.Key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSettingsOpen()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
return uiRouter.Contains("Settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsTopPage(string uiKey)
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
return uiRouter.IsTop(uiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 多层级 UI 管理
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class LayerController : IController
|
||||||
|
{
|
||||||
|
public void ShowMultipleToasts()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// Toast 层支持重入,可以同时显示多个
|
||||||
|
uiRouter.Show("Toast1", UiLayer.Toast);
|
||||||
|
uiRouter.Show("Toast2", UiLayer.Toast);
|
||||||
|
uiRouter.Show("Toast3", UiLayer.Toast);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearAllToasts()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// 清空 Toast 层的所有 UI
|
||||||
|
uiRouter.ClearLayer(UiLayer.Toast, destroy: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideAllDialogs()
|
||||||
|
{
|
||||||
|
var uiRouter = this.GetSystem<IUiRouter>();
|
||||||
|
|
||||||
|
// 隐藏 Modal 层的所有对话框
|
||||||
|
uiRouter.HideByKey("ConfirmDialog", UiLayer.Modal, hideAll: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **使用合适的层级**:根据 UI 类型选择正确的层级
|
||||||
|
```csharp
|
||||||
|
✓ Page: 主要页面(主菜单、设置、游戏界面)
|
||||||
|
✓ Overlay: 浮层(信息面板、小窗口)
|
||||||
|
✓ Modal: 模态对话框(确认框、输入框)
|
||||||
|
✓ Toast: 轻量提示(消息、通知)
|
||||||
|
✓ Topmost: 系统级(加载界面、全屏遮罩)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **使用 Push/Pop 管理临时 UI**:如设置、帮助页面
|
||||||
|
```csharp
|
||||||
|
// 打开设置(保留当前页面)
|
||||||
|
await uiRouter.PushAsync("Settings");
|
||||||
|
|
||||||
|
// 关闭设置(返回上一页)
|
||||||
|
await uiRouter.PopAsync();
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **使用 Replace 切换主要页面**:如从菜单到游戏
|
||||||
|
```csharp
|
||||||
|
// 开始游戏(清空 UI 栈)
|
||||||
|
await uiRouter.ReplaceAsync("Gameplay");
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **在 OnEnter/OnExit 中管理资源**:保持资源管理清晰
|
||||||
|
```csharp
|
||||||
|
public void OnEnter(IUiPageEnterParam? param)
|
||||||
|
{
|
||||||
|
// 加载资源、绑定事件
|
||||||
|
BindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnExit()
|
||||||
|
{
|
||||||
|
// 清理资源、解绑事件
|
||||||
|
UnbindEvents();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **使用句柄管理非栈 UI**:对于 Overlay、Modal、Toast 层
|
||||||
|
```csharp
|
||||||
|
// 保存句柄
|
||||||
|
var handle = uiRouter.Show("Dialog", UiLayer.Modal);
|
||||||
|
|
||||||
|
// 使用句柄关闭
|
||||||
|
uiRouter.Hide(handle, UiLayer.Modal, destroy: true);
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **避免在 UI 切换时阻塞**:使用异步操作
|
||||||
|
```csharp
|
||||||
|
✓ await uiRouter.PushAsync("Settings");
|
||||||
|
✗ uiRouter.PushAsync("Settings").Wait(); // 可能死锁
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题:Push、Pop、Replace 有什么区别?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- **Push**:压入新 UI,暂停当前 UI(用于临时页面)
|
||||||
|
- **Pop**:弹出当前 UI,恢复上一个 UI(用于关闭临时页面)
|
||||||
|
- **Replace**:清空 UI 栈,加载新 UI(用于主要页面切换)
|
||||||
|
|
||||||
|
### 问题:什么时候使用不同的 UI 层级?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
- **Page**:主要页面,使用栈管理
|
||||||
|
- **Overlay**:浮层,可叠加显示
|
||||||
|
- **Modal**:模态对话框,阻挡下层交互
|
||||||
|
- **Toast**:轻量提示,不阻挡交互
|
||||||
|
- **Topmost**:系统级,最高优先级
|
||||||
|
|
||||||
|
### 问题:如何在 UI 之间传递数据?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
|
||||||
|
1. 通过 UI 参数
|
||||||
|
2. 通过 Model
|
||||||
|
3. 通过事件
|
||||||
|
|
||||||
|
### 问题:UI 切换时如何显示过渡动画?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
使用 UI 转换处理器在 `OnBeforeEnter`/`OnAfterExit` 中播放动画。
|
||||||
|
|
||||||
|
### 问题:如何防止用户在 UI 切换时操作?
|
||||||
|
|
||||||
|
**解答**:
|
||||||
|
在转换处理器中显示遮罩或禁用输入。
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [场景系统](/zh-CN/game/scene) - 场景管理
|
||||||
|
- [Godot UI 系统](/zh-CN/godot/ui) - Godot 引擎集成
|
||||||
|
- [事件系统](/zh-CN/core/events) - UI 事件通信
|
||||||
|
- [状态机系统](/zh-CN/core/state-machine) - UI 状态管理
|
||||||
Loading…
x
Reference in New Issue
Block a user