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