docs: 添加协程系统、CQRS与Mediator、生命周期管理、资源管理系统文档

- 新增协程系统文档,介绍协程调度器、等待指令、基本用法和最佳实践
- 新增CQRS与Mediator文档,涵盖命令查询职责分离、处理器实现和管道行为
- 新增生命周期管理文档,说明同步异步初始化和销毁机制
- 新增资源管理系统文档,介绍资源加载、缓存和引用计数管理功能
This commit is contained in:
GeWuYou 2026-03-05 21:53:55 +08:00
parent ae8c3e4fc4
commit bbb91d597a
7 changed files with 3811 additions and 0 deletions

View 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
View File

@ -0,0 +1,613 @@
---
title: CQRS 与 Mediator
description: CQRS 模式通过 Mediator 实现命令查询职责分离,提供清晰的业务逻辑组织方式。
---
# CQRS 与 Mediator
## 概述
CQRSCommand 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

View 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
View 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) - 详细教程

View 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
View 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
View 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 状态管理