Merge pull request #84 from GeWuYou/docs/technical-documentation-update

Docs/technical documentation update
This commit is contained in:
gewuyou 2026-03-07 17:45:19 +08:00 committed by GitHub
commit 519e3a480b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 32899 additions and 25877 deletions

5
.claude/settings.json Normal file
View File

@ -0,0 +1,5 @@
{
"enabledPlugins": {
"oh-my-claudecode@omc": true
}
}

View File

@ -0,0 +1,205 @@
# GFramework 文档编写规范
## Markdown 语法规范
### 1. 泛型标记转义
在 Markdown 文档中,所有泛型标记必须转义,否则会被 VitePress 误认为 HTML 标签。
**错误示例**:
```markdown
`Option<T>` 是一个泛型类型
`Result<TValue, TError>` 表示结果
public class Repository<TData> { }
```
**正确示例**:
```markdown
`Option&lt;T&gt;` 是一个泛型类型
`Result&lt;TValue, TError&gt;` 表示结果
public class Repository&lt;TData&gt; { }
```
**常见泛型标记**:
- `<T>``&lt;T&gt;`
- `<TResult>``&lt;TResult&gt;`
- `<TValue>``&lt;TValue&gt;`
- `<TError>``&lt;TError&gt;`
- `<TSaveData>``&lt;TSaveData&gt;`
- `<TData>``&lt;TData&gt;`
- `<TNode>``&lt;TNode&gt;`
### 2. HTML 标签转义
如果需要在文档中显示 HTML 标签,必须转义:
- `<summary>``&lt;summary&gt;`
- `<param>``&lt;param&gt;`
- `<returns>``&lt;returns&gt;`
### 3. 链接验证
**内部链接规则**:
- 使用相对路径: `/zh-CN/core/events`
- 确保目标文件存在
- 不要链接到尚未创建的页面
**已存在的文档路径**:
**Core 模块**:
- `/zh-CN/core/architecture` - 架构系统
- `/zh-CN/core/ioc` - IoC 容器
- `/zh-CN/core/events` - 事件系统
- `/zh-CN/core/command` - 命令系统
- `/zh-CN/core/query` - 查询系统
- `/zh-CN/core/model` - Model 系统
- `/zh-CN/core/system` - System 系统
- `/zh-CN/core/utility` - Utility 系统
- `/zh-CN/core/controller` - Controller 系统
- `/zh-CN/core/logging` - 日志系统
- `/zh-CN/core/pool` - 对象池
- `/zh-CN/core/property` - 可绑定属性
- `/zh-CN/core/lifecycle` - 生命周期管理
- `/zh-CN/core/coroutine` - 协程系统
- `/zh-CN/core/resource` - 资源管理
- `/zh-CN/core/state-machine` - 状态机
- `/zh-CN/core/cqrs` - CQRS 与 Mediator
- `/zh-CN/core/functional` - 函数式编程
- `/zh-CN/core/pause` - 暂停管理
- `/zh-CN/core/configuration` - 配置管理
- `/zh-CN/core/ecs` - ECS 系统集成
- `/zh-CN/core/extensions` - 扩展方法
- `/zh-CN/core/rule` - 规则系统
- `/zh-CN/core/environment` - 环境系统
- `/zh-CN/core/context` - 上下文系统
- `/zh-CN/core/async-initialization` - 异步初始化
**Game 模块**:
- `/zh-CN/game/scene` - 场景系统
- `/zh-CN/game/ui` - UI 系统
- `/zh-CN/game/data` - 数据与存档
- `/zh-CN/game/storage` - 存储系统
- `/zh-CN/game/serialization` - 序列化系统
- `/zh-CN/game/setting` - 设置系统
**Godot 模块**:
- `/zh-CN/godot/architecture` - Godot 架构集成
- `/zh-CN/godot/scene` - Godot 场景系统
- `/zh-CN/godot/ui` - Godot UI 系统
- `/zh-CN/godot/pool` - Godot 节点池
- `/zh-CN/godot/resource` - Godot 资源仓储
- `/zh-CN/godot/logging` - Godot 日志系统
- `/zh-CN/godot/pause` - Godot 暂停处理
- `/zh-CN/godot/extensions` - Godot 扩展
- `/zh-CN/godot/coroutine` - Godot 协程
- `/zh-CN/godot/signal` - Godot 信号
- `/zh-CN/godot/storage` - Godot 存储
**教程**:
- `/zh-CN/tutorials/coroutine-tutorial` - 协程系统教程
- `/zh-CN/tutorials/state-machine-tutorial` - 状态机教程
- `/zh-CN/tutorials/resource-management` - 资源管理教程
- `/zh-CN/tutorials/save-system` - 存档系统教程
- `/zh-CN/tutorials/godot-complete-project` - Godot 完整项目
- `/zh-CN/tutorials/functional-programming` - 函数式编程实践
- `/zh-CN/tutorials/pause-system` - 暂停系统实现
- `/zh-CN/tutorials/data-migration` - 数据迁移实践
- `/zh-CN/tutorials/godot-integration` - Godot 集成
- `/zh-CN/tutorials/advanced-patterns` - 高级模式
**其他**:
- `/zh-CN/getting-started/quick-start` - 快速开始
- `/zh-CN/getting-started/installation` - 安装指南
- `/zh-CN/best-practices/architecture-patterns` - 架构模式
**不存在的路径** (不要链接):
- `/zh-CN/best-practices/performance` - 尚未创建
- `/zh-CN/core/serializer` - 错误路径,应使用 `/zh-CN/game/serialization`
## 代码块规范
### 1. 代码块语言标识
始终指定代码块的语言:
```markdown
\`\`\`csharp
public class Example { }
\`\`\`
\`\`\`bash
npm install
\`\`\`
```
### 2. 代码注释
代码示例应包含中文注释:
```csharp
// 创建玩家实体
var player = new Player
{
Name = "玩家1", // 玩家名称
Level = 1 // 初始等级
};
```
## Frontmatter 规范
每个文档必须包含正确的 frontmatter:
```yaml
---
title: 文档标题
description: 简短描述1-2 句话)
---
```
## 文档结构规范
### 指南文档结构
1. 概述
2. 核心概念
3. 基本用法
4. 高级用法
5. 最佳实践
6. 常见问题
7. 相关文档
### 教程文档结构
1. 学习目标
2. 前置条件
3. 步骤 1-N (3-7 步)
4. 完整代码
5. 运行结果
6. 下一步
7. 相关文档
## 验证清单
生成文档后,必须检查:
- [ ] 所有泛型标记已转义 (`<T>``&lt;T&gt;`)
- [ ] 所有内部链接指向存在的页面
- [ ] Frontmatter 格式正确
- [ ] 代码块指定了语言
- [ ] 代码包含中文注释
- [ ] 文档结构完整
- [ ] 没有 HTML 标签错误
## 自动修复脚本
如果文档已生成,可以使用以下脚本修复常见问题:
```bash
# 修复泛型标记
sed -i 's/<T>/\&lt;T\&gt;/g' file.md
sed -i 's/<TResult>/\&lt;TResult\&gt;/g' file.md
sed -i 's/<TValue>/\&lt;TValue\&gt;/g' file.md
sed -i 's/<TError>/\&lt;TError\&gt;/g' file.md
# 验证构建
cd docs && bun run build
```

6
.gitignore vendored
View File

@ -5,5 +5,9 @@ riderModule.iml
/_ReSharper.Caches/
GFramework.sln.DotSettings.user
.idea/
# ai
opencode.json
.claude/settings.local.json
.claude/settings.local.json
.omc/
docs/.omc/
docs/.vitepress/cache/

View File

@ -1,31 +0,0 @@
{
"hash": "2500e5b1",
"configHash": "3af8c6da",
"lockfileHash": "42b6a898",
"browserHash": "36f3405c",
"optimized": {
"vue": {
"src": "../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "d870a771",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "d4c69f44",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../node_modules/@vueuse/core/dist/index.js",
"file": "vitepress___@vueuse_core.js",
"fileHash": "05ae35d2",
"needsInterop": false
}
},
"chunks": {
"chunk-FOJXB67H": {
"file": "chunk-FOJXB67H.js"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
{
"type": "module"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,346 +0,0 @@
import {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBaseVNode,
createBlock,
createCommentVNode,
createElementBlock,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
} from "./chunk-FOJXB67H.js";
export {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBlock,
createCommentVNode,
createElementBlock,
createBaseVNode as createElementVNode,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
};

View File

@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

View File

@ -78,8 +78,10 @@ export default defineConfig({
{ text: '事件系统', link: '/zh-CN/core/events' },
{ text: '属性系统', link: '/zh-CN/core/property' },
{ text: 'IoC容器', link: '/zh-CN/core/ioc' },
{ text: 'ECS 系统集成', link: '/zh-CN/core/ecs' },
{ text: '对象池', link: '/zh-CN/core/pool' },
{ text: '日志系统', link: '/zh-CN/core/logging' },
{ text: '函数式编程', link: '/zh-CN/core/functional' },
{ text: '扩展方法', link: '/zh-CN/core/extensions' },
{ text: '工具类', link: '/zh-CN/core/utility' },
{ text: '模型层', link: '/zh-CN/core/model' },
@ -105,6 +107,10 @@ export default defineConfig({
text: 'Godot 集成',
items: [
{ text: '概览', link: '/zh-CN/godot/' },
{ text: '架构集成', link: '/zh-CN/godot/architecture' },
{ text: '场景系统', link: '/zh-CN/godot/scene' },
{ text: 'UI 系统', link: '/zh-CN/godot/ui' },
{ text: '资源仓储', link: '/zh-CN/godot/resource' },
{ text: '协程系统', link: '/zh-CN/godot/coroutine' },
{ text: '节点扩展', link: '/zh-CN/godot/extensions' },
{ text: '信号系统', link: '/zh-CN/godot/signal' },
@ -156,7 +162,13 @@ export default defineConfig({
{ text: '7. 总结与最佳实践', link: '/zh-CN/tutorials/basic/07-summary' }
]
},
{ text: 'Godot集成', link: '/zh-CN/tutorials/godot-integration' },
{ text: '使用协程系统', link: '/zh-CN/tutorials/coroutine-tutorial' },
{ text: '实现状态机', link: '/zh-CN/tutorials/state-machine-tutorial' },
{ text: '函数式编程实践', link: '/zh-CN/tutorials/functional-programming' },
{ text: '资源管理最佳实践', link: '/zh-CN/tutorials/resource-management' },
{ text: '实现存档系统', link: '/zh-CN/tutorials/save-system' },
{ text: 'Godot 集成', link: '/zh-CN/tutorials/godot-integration' },
{ text: 'Godot 完整项目', link: '/zh-CN/tutorials/godot-complete-project' },
{ text: '高级模式', link: '/zh-CN/tutorials/advanced-patterns' }
]
}

View File

@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"devDependencies": {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,384 @@
---
title: 移动平台优化指南
description: 针对移动平台的性能优化、内存管理和电池优化最佳实践
---
# 移动平台优化指南
## 概述
移动平台游戏开发面临着独特的挑战:有限的内存、较弱的处理器、电池续航限制、触摸输入、多样的屏幕尺寸等。本指南将帮助你使用
GFramework 开发高性能的移动游戏,提供针对性的优化策略和最佳实践。
**移动平台的主要限制**
- **内存限制**:移动设备内存通常在 2-8GB远低于 PC
- **CPU 性能**:移动 CPU 性能较弱,且受热量限制
- **GPU 性能**:移动 GPU 功能有限,填充率和带宽受限
- **电池续航**:高性能运行会快速消耗电池
- **存储空间**:应用包大小受限,用户存储空间有限
- **网络环境**:移动网络不稳定,延迟较高
**优化目标**
- 减少内存占用(目标:&lt;200MB
- 降低 CPU 使用率(目标:&lt;30%
- 优化 GPU 渲染目标60 FPS
- 延长电池续航目标3+ 小时)
- 减小包体大小(目标:&lt;100MB
## 核心概念
### 1. 内存管理
移动设备内存有限,需要精细管理:
```csharp
// 监控内存使用
public class MemoryMonitor : AbstractSystem
{
private const long MemoryWarningThreshold = 150 * 1024 * 1024; // 150MB
private const long MemoryCriticalThreshold = 200 * 1024 * 1024; // 200MB
protected override void OnInit()
{
this.RegisterEvent&lt;GameUpdateEvent&gt;(OnUpdate);
}
private void OnUpdate(GameUpdateEvent e)
{
// 每 5 秒检查一次内存
if (e.TotalTime % 5.0 &lt; e.DeltaTime)
{
CheckMemoryUsage();
}
}
private void CheckMemoryUsage()
{
var memoryUsage = GC.GetTotalMemory(false);
if (memoryUsage &gt; MemoryCriticalThreshold)
{
// 内存严重不足,强制清理
SendEvent(new MemoryCriticalEvent());
ForceMemoryCleanup();
}
else if (memoryUsage &gt; MemoryWarningThreshold)
{
// 内存警告,温和清理
SendEvent(new MemoryWarningEvent());
SoftMemoryCleanup();
}
}
private void ForceMemoryCleanup()
{
// 卸载不必要的资源
var resourceManager = this.GetUtility&lt;IResourceManager&gt;();
resourceManager.UnloadUnusedResources();
// 清理对象池
var poolSystem = this.GetSystem&lt;ObjectPoolSystem&gt;();
poolSystem.TrimPools();
// 强制 GC
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
private void SoftMemoryCleanup()
{
// 温和清理:只清理明确不需要的资源
var resourceManager = this.GetUtility&lt;IResourceManager&gt;();
resourceManager.UnloadUnusedResources();
}
}
```
### 2. 性能分析
使用性能分析工具识别瓶颈:
```csharp
public class PerformanceProfiler : AbstractSystem
{
private readonly Dictionary&lt;string, PerformanceMetrics&gt; _metrics = new();
public IDisposable Profile(string name)
{
return new ProfileScope(name, this);
}
private void RecordMetric(string name, double duration)
{
if (!_metrics.TryGetValue(name, out var metrics))
{
metrics = new PerformanceMetrics();
_metrics[name] = metrics;
}
metrics.AddSample(duration);
}
public void PrintReport()
{
Console.WriteLine("\n=== 性能报告 ===");
foreach (var (name, metrics) in _metrics.OrderByDescending(x =&gt; x.Value.AverageMs))
{
Console.WriteLine($"{name}:");
Console.WriteLine($" 平均: {metrics.AverageMs:F2}ms");
Console.WriteLine($" 最大: {metrics.MaxMs:F2}ms");
Console.WriteLine($" 最小: {metrics.MinMs:F2}ms");
Console.WriteLine($" 调用次数: {metrics.SampleCount}");
}
}
private class ProfileScope : IDisposable
{
private readonly string _name;
private readonly PerformanceProfiler _profiler;
private readonly Stopwatch _stopwatch;
public ProfileScope(string name, PerformanceProfiler profiler)
{
_name = name;
_profiler = profiler;
_stopwatch = Stopwatch.StartNew();
}
public void Dispose()
{
_stopwatch.Stop();
_profiler.RecordMetric(_name, _stopwatch.Elapsed.TotalMilliseconds);
}
}
}
// 使用示例
public class GameSystem : AbstractSystem
{
private PerformanceProfiler _profiler;
protected override void OnInit()
{
_profiler = this.GetSystem&lt;PerformanceProfiler&gt;();
}
private void UpdateGame()
{
using (_profiler.Profile("GameUpdate"))
{
// 游戏更新逻辑
}
}
}
```
### 3. 电池优化
减少不必要的计算和渲染:
```csharp
public class PowerSavingSystem : AbstractSystem
{
private bool _isPowerSavingMode;
private int _targetFrameRate = 60;
protected override void OnInit()
{
this.RegisterEvent&lt;BatteryLowEvent&gt;(OnBatteryLow);
this.RegisterEvent&lt;BatteryNormalEvent&gt;(OnBatteryNormal);
}
private void OnBatteryLow(BatteryLowEvent e)
{
EnablePowerSavingMode();
}
private void OnBatteryNormal(BatteryNormalEvent e)
{
DisablePowerSavingMode();
}
private void EnablePowerSavingMode()
{
_isPowerSavingMode = true;
// 降低帧率
_targetFrameRate = 30;
Application.targetFrameRate = _targetFrameRate;
// 降低渲染质量
QualitySettings.SetQualityLevel(0);
// 减少粒子效果
SendEvent(new ReduceEffectsEvent());
// 暂停非关键系统
PauseNonCriticalSystems();
Console.WriteLine("省电模式已启用");
}
private void DisablePowerSavingMode()
{
_isPowerSavingMode = false;
// 恢复帧率
_targetFrameRate = 60;
Application.targetFrameRate = _targetFrameRate;
// 恢复渲染质量
QualitySettings.SetQualityLevel(2);
// 恢复粒子效果
SendEvent(new RestoreEffectsEvent());
// 恢复非关键系统
ResumeNonCriticalSystems();
Console.WriteLine("省电模式已禁用");
}
private void PauseNonCriticalSystems()
{
// 暂停动画系统
var animationSystem = this.GetSystem&lt;AnimationSystem&gt;();
animationSystem?.Pause();
// 暂停音效系统(保留音乐)
var audioSystem = this.GetSystem&lt;AudioSystem&gt;();
audioSystem?.PauseSoundEffects();
}
private void ResumeNonCriticalSystems()
{
var animationSystem = this.GetSystem&lt;AnimationSystem&gt;();
animationSystem?.Resume();
var audioSystem = this.GetSystem&lt;AudioSystem&gt;();
audioSystem?.ResumeSoundEffects();
}
}
```
## 内存优化
### 1. 资源管理策略
实现智能资源加载和卸载:
```csharp
public class MobileResourceManager : AbstractSystem
{
private readonly IResourceManager _resourceManager;
private readonly Dictionary&lt;string, ResourcePriority&gt; _resourcePriorities = new();
private readonly HashSet&lt;string&gt; _loadedResources = new();
public MobileResourceManager(IResourceManager resourceManager)
{
_resourceManager = resourceManager;
}
protected override void OnInit()
{
// 配置资源优先级
ConfigureResourcePriorities();
// 监听场景切换事件
this.RegisterEvent&lt;SceneChangedEvent&gt;(OnSceneChanged);
// 监听内存警告
this.RegisterEvent&lt;MemoryWarningEvent&gt;(OnMemoryWarning);
}
private void ConfigureResourcePriorities()
{
// 高优先级UI、玩家资源
_resourcePriorities["ui/"] = ResourcePriority.High;
_resourcePriorities["player/"] = ResourcePriority.High;
// 中优先级:敌人、道具
_resourcePriorities["enemy/"] = ResourcePriority.Medium;
_resourcePriorities["item/"] = ResourcePriority.Medium;
// 低优先级:特效、装饰
_resourcePriorities["effect/"] = ResourcePriority.Low;
_resourcePriorities["decoration/"] = ResourcePriority.Low;
}
public async Task&lt;T&gt; LoadResourceAsync&lt;T&gt;(string path) where T : class
{
// 检查内存
if (IsMemoryLow())
{
// 内存不足,先清理低优先级资源
UnloadLowPriorityResources();
}
var resource = await _resourceManager.LoadAsync&lt;T&gt;(path);
_loadedResources.Add(path);
return resource;
}
private void OnSceneChanged(SceneChangedEvent e)
{
// 场景切换时,卸载旧场景资源
UnloadSceneResources(e.PreviousScene);
// 预加载新场景资源
PreloadSceneResources(e.NewScene);
}
private void OnMemoryWarning(MemoryWarningEvent e)
{
// 内存警告,卸载低优先级资源
UnloadLowPriorityResources();
}
private void UnloadLowPriorityResources()
{
var resourcesToUnload = _loadedResources
.Where(path =&gt; GetResourcePriority(path) == ResourcePriority.Low)
.ToList();
foreach (var path in resourcesToUnload)
{
_resourceManager.Unload(path);
_loadedResources.Remove(path);
}
Console.WriteLine($"卸载了 {resourcesToUnload.Count} 个低优先级资源");
}
private ResourcePriority GetResourcePriority(string path)
{
foreach (var (prefix, priority) in _resourcePriorities)
{
if (path.StartsWith(prefix))
return priority;
}
return ResourcePriority.Medium;
}
private bool IsMemoryLow()
{
var memoryUsage = GC.GetTotalMemory(false);
return memoryUsage &gt; 150 * 1024 * 1024; // 150MB
}
}
public enum ResourcePriority
{
Low,
Medium,
High
}
```
### 2. 纹理压缩和优化

View File

@ -0,0 +1,546 @@
# 多人游戏架构指南
> 基于 GFramework 架构设计高性能、可扩展的多人游戏系统。
## 📋 目录
- [概述](#概述)
- [核心概念](#核心概念)
- [架构设计](#架构设计)
- [状态管理](#状态管理)
- [命令模式与输入处理](#命令模式与输入处理)
- [事件同步](#事件同步)
- [网络优化](#网络优化)
- [安全考虑](#安全考虑)
- [最佳实践](#最佳实践)
- [常见问题](#常见问题)
## 概述
多人游戏开发面临着单机游戏所没有的独特挑战:
### 主要挑战
1. **网络延迟** - 玩家操作和服务器响应之间存在不可避免的延迟
2. **状态同步** - 确保所有客户端看到一致的游戏状态
3. **带宽限制** - 需要高效传输游戏数据,避免网络拥塞
4. **作弊防护** - 防止客户端篡改游戏逻辑和数据
5. **并发处理** - 同时处理多个玩家的输入和状态更新
6. **断线重连** - 优雅处理网络中断和玩家重新连接
### GFramework 的优势
GFramework 的架构设计天然适合多人游戏开发:
- **分层架构** - 清晰分离客户端逻辑、网络层和服务器逻辑
- **事件系统** - 松耦合的事件驱动架构便于状态同步
- **命令模式** - 统一的输入处理和验证机制
- **Model-System 分离** - 数据和逻辑分离便于状态管理
- **模块化设计** - 网络功能可以作为独立模块集成
## 核心概念
### 1. 客户端-服务器架构
```csharp
// 服务器架构
public class ServerArchitecture : Architecture
{
protected override void Init()
{
// 注册服务器专用的 Model
RegisterModel(new ServerGameStateModel());
RegisterModel(new PlayerConnectionModel());
// 注册服务器专用的 System
RegisterSystem(new ServerNetworkSystem());
RegisterSystem(new AuthorityGameLogicSystem());
RegisterSystem(new StateReplicationSystem());
RegisterSystem(new AntiCheatSystem());
// 注册工具
RegisterUtility(new NetworkUtility());
RegisterUtility(new ValidationUtility());
}
}
// 客户端架构
public class ClientArchitecture : Architecture
{
protected override void Init()
{
// 注册客户端专用的 Model
RegisterModel(new ClientGameStateModel());
RegisterModel(new PredictionModel());
// 注册客户端专用的 System
RegisterSystem(new ClientNetworkSystem());
RegisterSystem(new PredictionSystem());
RegisterSystem(new InterpolationSystem());
RegisterSystem(new ClientInputSystem());
// 注册工具
RegisterUtility(new NetworkUtility());
}
}
```
### 2. 状态同步策略
#### 状态同步 (State Synchronization)
服务器定期向客户端发送完整的游戏状态。
```csharp
// 游戏状态快照
public struct GameStateSnapshot
{
public uint Tick { get; set; }
public long Timestamp { get; set; }
public PlayerState[] Players { get; set; }
public EntityState[] Entities { get; set; }
}
public struct PlayerState
{
public string PlayerId { get; set; }
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; }
public int Health { get; set; }
public PlayerAnimationState AnimationState { get; set; }
}
// 状态复制系统
public class StateReplicationSystem : AbstractSystem
{
private ServerGameStateModel _gameState;
private PlayerConnectionModel _connections;
private uint _currentTick;
protected override void OnInit()
{
_gameState = this.GetModel&lt;ServerGameStateModel&gt;();
_connections = this.GetModel&lt;PlayerConnectionModel&gt;();
// 每个 tick 复制状态
this.RegisterEvent&lt;ServerTickEvent&gt;(OnServerTick);
}
private void OnServerTick(ServerTickEvent e)
{
_currentTick++;
// 创建状态快照
var snapshot = CreateSnapshot();
// 发送给所有连接的客户端
foreach (var connection in _connections.ActiveConnections)
{
SendSnapshotToClient(connection, snapshot);
}
}
private GameStateSnapshot CreateSnapshot()
{
return new GameStateSnapshot
{
Tick = _currentTick,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
Players = _gameState.Players.Select(p => new PlayerState
{
PlayerId = p.Id,
Position = p.Position,
Rotation = p.Rotation,
Health = p.Health,
AnimationState = p.AnimationState
}).ToArray(),
Entities = _gameState.Entities.Select(e => CreateEntityState(e)).ToArray()
};
}
}
```
#### 增量同步 (Delta Synchronization)
只发送状态变化,减少带宽消耗。
```csharp
// 增量状态
public struct DeltaState
{
public uint Tick { get; set; }
public uint BaseTick { get; set; }
public PlayerDelta[] PlayerDeltas { get; set; }
public EntityDelta[] EntityDeltas { get; set; }
}
public struct PlayerDelta
{
public string PlayerId { get; set; }
public DeltaFlags Flags { get; set; }
public Vector3? Position { get; set; }
public Quaternion? Rotation { get; set; }
public int? Health { get; set; }
}
[Flags]
public enum DeltaFlags
{
None = 0,
Position = 1 &lt;&lt; 0,
Rotation = 1 &lt;&lt; 1,
Health = 1 &lt;&lt; 2,
Animation = 1 &lt;&lt; 3
}
// 增量复制系统
public class DeltaReplicationSystem : AbstractSystem
{
private readonly Dictionary&lt;uint, GameStateSnapshot&gt; _snapshotHistory = new();
private const int MaxHistorySize = 60; // 保留 60 个快照
protected override void OnInit()
{
this.RegisterEvent&lt;ServerTickEvent&gt;(OnServerTick);
}
private void OnServerTick(ServerTickEvent e)
{
var currentSnapshot = CreateSnapshot();
_snapshotHistory[e.Tick] = currentSnapshot;
// 清理旧快照
CleanupOldSnapshots(e.Tick);
// 为每个客户端生成增量
foreach (var connection in GetConnections())
{
var lastAckedTick = connection.LastAcknowledgedTick;
if (_snapshotHistory.TryGetValue(lastAckedTick, out var baseSnapshot))
{
var delta = CreateDelta(baseSnapshot, currentSnapshot);
SendDeltaToClient(connection, delta);
}
else
{
// 客户端太落后,发送完整快照
SendSnapshotToClient(connection, currentSnapshot);
}
}
}
private DeltaState CreateDelta(GameStateSnapshot baseSnapshot, GameStateSnapshot currentSnapshot)
{
var delta = new DeltaState
{
Tick = currentSnapshot.Tick,
BaseTick = baseSnapshot.Tick,
PlayerDeltas = new List&lt;PlayerDelta&gt;()
};
// 比较玩家状态
foreach (var currentPlayer in currentSnapshot.Players)
{
var basePlayer = baseSnapshot.Players.FirstOrDefault(p => p.PlayerId == currentPlayer.PlayerId);
if (basePlayer.PlayerId == null)
{
// 新玩家,发送完整状态
delta.PlayerDeltas.Add(CreateFullPlayerDelta(currentPlayer));
}
else
{
// 计算差异
var playerDelta = CreatePlayerDelta(basePlayer, currentPlayer);
if (playerDelta.Flags != DeltaFlags.None)
{
delta.PlayerDeltas.Add(playerDelta);
}
}
}
return delta;
}
private PlayerDelta CreatePlayerDelta(PlayerState baseState, PlayerState currentState)
{
var delta = new PlayerDelta { PlayerId = currentState.PlayerId };
if (Vector3.Distance(baseState.Position, currentState.Position) &gt; 0.01f)
{
delta.Flags |= DeltaFlags.Position;
delta.Position = currentState.Position;
}
if (Quaternion.Angle(baseState.Rotation, currentState.Rotation) &gt; 0.1f)
{
delta.Flags |= DeltaFlags.Rotation;
delta.Rotation = currentState.Rotation;
}
if (baseState.Health != currentState.Health)
{
delta.Flags |= DeltaFlags.Health;
delta.Health = currentState.Health;
}
return delta;
}
}
```
### 3. 客户端预测与回滚
客户端立即响应玩家输入,然后在收到服务器确认后进行校正。
```csharp
// 输入命令
public struct PlayerInputCommand
{
public uint Tick { get; set; }
public long Timestamp { get; set; }
public Vector2 MoveDirection { get; set; }
public Vector2 LookDirection { get; set; }
public InputFlags Flags { get; set; }
}
[Flags]
public enum InputFlags
{
None = 0,
Jump = 1 &lt;&lt; 0,
Attack = 1 &lt;&lt; 1,
Interact = 1 &lt;&lt; 2,
Reload = 1 &lt;&lt; 3
}
// 客户端预测系统
public class ClientPredictionSystem : AbstractSystem
{
private PredictionModel _prediction;
private ClientGameStateModel _gameState;
private readonly Queue&lt;PlayerInputCommand&gt; _pendingInputs = new();
private uint _lastProcessedServerTick;
protected override void OnInit()
{
_prediction = this.GetModel&lt;PredictionModel&gt;();
_gameState = this.GetModel&lt;ClientGameStateModel&gt;();
this.RegisterEvent&lt;PlayerInputEvent&gt;(OnPlayerInput);
this.RegisterEvent&lt;ServerStateReceivedEvent&gt;(OnServerStateReceived);
}
private void OnPlayerInput(PlayerInputEvent e)
{
var input = new PlayerInputCommand
{
Tick = _prediction.CurrentTick,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
MoveDirection = e.MoveDirection,
LookDirection = e.LookDirection,
Flags = e.Flags
};
// 保存输入用于重放
_pendingInputs.Enqueue(input);
// 立即应用预测
ApplyInput(input);
// 发送到服务器
SendInputToServer(input);
}
private void OnServerStateReceived(ServerStateReceivedEvent e)
{
_lastProcessedServerTick = e.Snapshot.Tick;
// 应用服务器状态
ApplyServerState(e.Snapshot);
// 移除已确认的输入
while (_pendingInputs.Count &gt; 0 && _pendingInputs.Peek().Tick &lt;= e.Snapshot.Tick)
{
_pendingInputs.Dequeue();
}
// 重放未确认的输入
ReplayPendingInputs();
}
private void ApplyInput(PlayerInputCommand input)
{
var player = _gameState.LocalPlayer;
// 应用移动
var movement = input.MoveDirection * player.Speed * Time.DeltaTime;
player.Position += new Vector3(movement.X, 0, movement.Y);
// 应用旋转
if (input.LookDirection != Vector2.Zero)
{
player.Rotation = Quaternion.LookRotation(
new Vector3(input.LookDirection.X, 0, input.LookDirection.Y)
);
}
// 应用动作
if ((input.Flags &amp; InputFlags.Jump) != 0 &amp;&amp; player.IsGrounded)
{
player.Velocity = new Vector3(player.Velocity.X, player.JumpForce, player.Velocity.Z);
}
}
private void ReplayPendingInputs()
{
// 从服务器状态开始重放所有未确认的输入
var savedState = SavePlayerState();
foreach (var input in _pendingInputs)
{
ApplyInput(input);
}
}
}
```
## 架构设计
### 1. 分离逻辑层
```csharp
// 共享游戏逻辑 (客户端和服务器都使用)
public class SharedGameLogic
{
public static void ProcessMovement(PlayerState player, Vector2 moveDirection, float deltaTime)
{
var movement = moveDirection.Normalized() * player.Speed * deltaTime;
player.Position += new Vector3(movement.X, 0, movement.Y);
}
public static bool CanJump(PlayerState player)
{
return player.IsGrounded &amp;&amp; !player.IsStunned;
}
public static int CalculateDamage(int attackPower, int defense, float criticalChance)
{
var baseDamage = Math.Max(1, attackPower - defense);
var isCritical = Random.Shared.NextDouble() &lt; criticalChance;
return isCritical ? baseDamage * 2 : baseDamage;
}
}
// 服务器权威逻辑
public class ServerGameLogicSystem : AbstractSystem
{
private ServerGameStateModel _gameState;
protected override void OnInit()
{
_gameState = this.GetModel&lt;ServerGameStateModel&gt;();
this.RegisterEvent&lt;PlayerInputReceivedEvent&gt;(OnPlayerInputReceived);
this.RegisterEvent&lt;AttackRequestEvent&gt;(OnAttackRequest);
}
private void OnPlayerInputReceived(PlayerInputReceivedEvent e)
{
var player = _gameState.GetPlayer(e.PlayerId);
// 验证输入
if (!ValidateInput(e.Input))
{
SendInputRejection(e.PlayerId, "Invalid input");
return;
}
// 应用共享逻辑
SharedGameLogic.ProcessMovement(player, e.Input.MoveDirection, Time.DeltaTime);
// 服务器端验证
if ((e.Input.Flags &amp; InputFlags.Jump) != 0)
{
if (SharedGameLogic.CanJump(player))
{
player.Velocity = new Vector3(player.Velocity.X, player.JumpForce, player.Velocity.Z);
}
}
}
private void OnAttackRequest(AttackRequestEvent e)
{
var attacker = _gameState.GetPlayer(e.AttackerId);
var target = _gameState.GetPlayer(e.TargetId);
// 服务器端验证
if (!CanAttack(attacker, target))
{
return;
}
// 计算伤害
var damage = SharedGameLogic.CalculateDamage(
attacker.AttackPower,
target.Defense,
attacker.CriticalChance
);
// 应用伤害
target.Health = Math.Max(0, target.Health - damage);
// 广播事件
this.SendEvent(new PlayerDamagedEvent
{
AttackerId = e.AttackerId,
TargetId = e.TargetId,
Damage = damage,
RemainingHealth = target.Health
});
if (target.Health == 0)
{
this.SendEvent(new PlayerDiedEvent
{
PlayerId = e.TargetId,
KillerId = e.AttackerId
});
}
}
}
// 客户端表现逻辑
public class ClientPresentationSystem : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent&lt;PlayerDamagedEvent&gt;(OnPlayerDamaged);
this.RegisterEvent&lt;PlayerDiedEvent&gt;(OnPlayerDied);
}
private void OnPlayerDamaged(PlayerDamagedEvent e)
{
// 播放受击特效
PlayDamageEffect(e.TargetId, e.Damage);
// 播放受击音效
PlayDamageSound(e.TargetId);
// 更新 UI
UpdateHealthBar(e.TargetId, e.RemainingHealth);
}
private void OnPlayerDied(PlayerDiedEvent e)
{
// 播放死亡动画
PlayDeathAnimation(e.PlayerId);
// 播放死亡音效
PlayDeathSound(e.PlayerId);
// 显示击杀提示
ShowKillFeed(e.KillerId, e.PlayerId);
}
}

File diff suppressed because it is too large Load Diff

839
docs/zh-CN/contributing.md Normal file
View File

@ -0,0 +1,839 @@
# 贡献指南
欢迎为 GFramework 贡献代码!本指南将帮助你了解如何参与项目开发。
## 概述
GFramework 是一个开源的游戏开发框架,我们欢迎所有形式的贡献:
- 报告 Bug 和提出功能建议
- 提交代码修复和新功能
- 改进文档和示例
- 参与讨论和代码审查
## 行为准则
### 社区规范
我们致力于为所有贡献者提供友好、安全和包容的环境。参与本项目时,请遵守以下准则:
- **尊重他人**:尊重不同的观点和经验
- **建设性沟通**:提供有建设性的反馈,避免人身攻击
- **协作精神**:帮助新贡献者融入社区
- **专业态度**:保持专业和礼貌的交流方式
### 不可接受的行为
- 使用性别化语言或图像
- 人身攻击或侮辱性评论
- 骚扰行为(公开或私下)
- 未经许可发布他人的私人信息
- 其他不道德或不专业的行为
## 如何贡献
### 报告问题
发现 Bug 或有功能建议时,请通过 GitHub Issues 提交:
1. **搜索现有 Issue**:避免重复提交
2. **使用清晰的标题**:简洁描述问题
3. **提供详细信息**
- Bug 报告:复现步骤、预期行为、实际行为、环境信息
- 功能建议:使用场景、预期效果、可能的实现方案
**Bug 报告模板**
```markdown
**描述**
简要描述 Bug
**复现步骤**
1. 执行操作 A
2. 执行操作 B
3. 观察到错误
**预期行为**
应该发生什么
**实际行为**
实际发生了什么
**环境信息**
- GFramework 版本:
- .NET 版本:
- 操作系统:
- Godot 版本(如适用):
**附加信息**
日志、截图等
```
### 提交 Pull Request
#### 基本流程
1. **Fork 仓库**:在 GitHub 上 Fork 本项目
2. **克隆到本地**
```bash
git clone https://github.com/your-username/GFramework.git
cd GFramework
```
3. **创建特性分支**
```bash
git checkout -b feature/your-feature-name
# 或
git checkout -b fix/your-bug-fix
```
4. **进行开发**:编写代码、添加测试、更新文档
5. **提交更改**:遵循提交规范(见下文)
6. **推送分支**
```bash
git push origin feature/your-feature-name
```
7. **创建 PR**:在 GitHub 上创建 Pull Request
#### PR 要求
- **清晰的标题**:简洁描述变更内容
- **详细的描述**
- 变更的背景和动机
- 实现方案说明
- 测试验证结果
- 相关 Issue 链接(如有)
- **代码质量**:通过所有 CI 检查
- **测试覆盖**:为新功能添加测试
- **文档更新**:更新相关文档
### 改进文档
文档改进同样重要:
- **修正错误**:拼写、语法、技术错误
- **补充示例**:添加代码示例和使用场景
- **完善说明**:改进不清晰的描述
- **翻译工作**:帮助翻译文档(如需要)
文档位于 `docs/` 目录,使用 Markdown 格式编写。
## 开发环境设置
### 前置要求
- **.NET SDK**8.0、9.0 或 10.0
- **Git**:版本控制工具
- **IDE**(推荐):
- Visual Studio 2022+
- JetBrains Rider
- Visual Studio Code + C# Dev Kit
### 克隆仓库
```bash
# 克隆你 Fork 的仓库
git clone https://github.com/your-username/GFramework.git
cd GFramework
# 添加上游仓库
git remote add upstream https://github.com/GeWuYou/GFramework.git
```
### 安装依赖
```bash
# 恢复 NuGet 包
dotnet restore
# 恢复 .NET 本地工具
dotnet tool restore
```
### 构建项目
```bash
# 构建所有项目
dotnet build
# 构建特定配置
dotnet build -c Release
```
### 运行测试
```bash
# 运行所有测试
dotnet test
# 运行特定测试项目
dotnet test GFramework.Core.Tests
dotnet test GFramework.SourceGenerators.Tests
# 生成测试覆盖率报告
dotnet test --collect:"XPlat Code Coverage"
```
### 验证代码质量
项目使用 MegaLinter 进行代码质量检查:
```bash
# 本地运行 MegaLinter需要 Docker
docker run --rm -v $(pwd):/tmp/lint oxsecurity/megalinter:v9
# 或使用 CI 流程验证
git push origin your-branch
```
## 代码规范
### 命名规范
遵循 C# 标准命名约定:
- **类、接口、方法**PascalCase
```csharp
public class PlayerController { }
public interface IEventBus { }
public void ProcessInput() { }
```
- **私有字段**_camelCase下划线前缀
```csharp
private int _health;
private readonly ILogger _logger;
```
- **参数、局部变量**camelCase
```csharp
public void SetHealth(int newHealth)
{
var oldHealth = _health;
_health = newHealth;
}
```
- **常量**PascalCase
```csharp
public const int MaxPlayers = 4;
private const string DefaultName = "Player";
```
- **接口**I 前缀
```csharp
public interface IArchitecture { }
public interface ICommand&lt;TInput&gt; { }
```
### 代码风格
- **缩进**4 个空格(不使用 Tab
- **大括号**Allman 风格(独占一行)
```csharp
if (condition)
{
DoSomething();
}
```
- **using 指令**:文件顶部,按字母顺序排列
```csharp
using System;
using System.Collections.Generic;
using GFramework.Core.Abstractions;
```
- **空行**
- 命名空间后空一行
- 类成员之间空一行
- 逻辑块之间适当空行
- **行长度**:建议不超过 120 字符
### 注释规范
#### XML 文档注释
所有公共 API 必须包含 XML 文档注释:
```csharp
/// &lt;summary&gt;
/// 架构基类,提供系统、模型、工具等组件的注册与管理功能。
/// &lt;/summary&gt;
/// &lt;typeparam name="TModel"&gt;模型类型&lt;/typeparam&gt;
/// &lt;param name="configuration"&gt;架构配置&lt;/param&gt;
/// &lt;returns&gt;注册的模型实例&lt;/returns&gt;
/// &lt;exception cref="ArgumentNullException"&gt;当 model 为 null 时抛出&lt;/exception&gt;
public TModel RegisterModel&lt;TModel&gt;(TModel model) where TModel : IModel
{
// 实现代码
}
```
#### 代码注释
- **何时添加注释**
- 复杂的算法逻辑
- 非显而易见的设计决策
- 临时解决方案(使用 TODO 或 HACK 标记)
- 性能关键代码的优化说明
- **注释风格**
```csharp
// 单行注释使用双斜杠
// 多行注释可以使用多个单行注释
// 每行都以双斜杠开始
/* 或使用块注释
* 适用于较长的说明
*/
```
- **避免无用注释**
```csharp
// 不好:注释重复代码内容
// 设置健康值为 100
health = 100;
// 好:解释为什么这样做
// 初始化时设置满血,避免首次战斗时的边界情况
health = MaxHealth;
```
### 设计原则
- **SOLID 原则**:遵循面向对象设计原则
- **依赖注入**:优先使用构造函数注入
- **接口隔离**:定义小而专注的接口
- **不可变性**:优先使用 `readonly` 和不可变类型
- **异步编程**I/O 操作使用 `async`/`await`
## 提交规范
### Commit 消息格式
使用 Conventional Commits 规范:
```
<type>(<scope>): <subject>
<body>
<footer>
```
#### Type类型
- **feat**:新功能
- **fix**Bug 修复
- **docs**:文档更新
- **style**:代码格式调整(不影响功能)
- **refactor**:重构(不是新功能也不是修复)
- **perf**:性能优化
- **test**:添加或修改测试
- **chore**:构建过程或辅助工具的变动
- **ci**CI 配置文件和脚本的变动
#### Scope范围
指明变更影响的模块:
- `core`GFramework.Core
- `game`GFramework.Game
- `godot`GFramework.Godot
- `generators`:源码生成器
- `docs`:文档
- `tests`:测试
#### Subject主题
- 使用祈使句,现在时态:"add" 而不是 "added" 或 "adds"
- 首字母小写
- 结尾不加句号
- 限制在 50 字符以内
#### Body正文
- 详细描述变更的动机和实现细节
- 与主题空一行
- 每行不超过 72 字符
#### Footer页脚
- 关联 Issue`Closes #123`
- 破坏性变更:`BREAKING CHANGE: 描述`
#### 示例
```bash
# 简单提交
git commit -m "feat(core): add event priority support"
# 详细提交
git commit -m "fix(godot): resolve scene loading race condition
修复了在快速切换场景时可能出现的资源加载竞态条件。
通过引入场景加载锁机制,确保同一时间只有一个场景在加载。
Closes #456"
# 破坏性变更
git commit -m "refactor(core): change IArchitecture interface
BREAKING CHANGE: IArchitecture.Init() 现在返回 Task 而不是 void。
所有继承 Architecture 的类需要更新为异步初始化。
Migration guide: 将 Init() 改为 async Task Init()
"
```
### 分支策略
- **main**:主分支,保持稳定
- **feature/***:新功能分支
- `feature/event-priority`
- `feature/godot-ui-system`
- **fix/***Bug 修复分支
- `fix/memory-leak`
- `fix/null-reference`
- **docs/***:文档更新分支
- `docs/api-reference`
- `docs/tutorial-update`
- **refactor/***:重构分支
- `refactor/logging-system`
#### 分支命名规范
- 使用小写字母和连字符
- 简洁描述分支目的
- 避免使用个人名称
## 测试要求
### 单元测试
所有新功能和 Bug 修复都应包含单元测试:
```csharp
using Xunit;
namespace GFramework.Core.Tests.events;
public class EventBusTests
{
[Fact]
public void Subscribe_ShouldReceiveEvent()
{
// Arrange
var eventBus = new EventBus();
var received = false;
// Act
eventBus.Subscribe&lt;TestEvent&gt;(e =&gt; received = true);
eventBus.Publish(new TestEvent());
// Assert
Assert.True(received);
}
[Theory]
[InlineData(1)]
[InlineData(10)]
[InlineData(100)]
public void Subscribe_MultipleEvents_ShouldReceiveAll(int count)
{
// 测试实现
}
}
```
### 测试组织
- **测试项目**`*.Tests` 后缀
- **测试类**`*Tests` 后缀,与被测试类对应
- **测试方法**`MethodName_Scenario_ExpectedResult` 格式
- **测试数据**:使用 `[Theory]``[InlineData]` 进行参数化测试
### 测试覆盖率
- **目标**:新代码覆盖率 &gt; 80%
- **关键路径**:核心功能覆盖率 &gt; 90%
- **边界情况**:测试异常情况和边界值
### 集成测试
对于涉及多个组件交互的功能,添加集成测试:
```csharp
public class ArchitectureIntegrationTests
{
[Fact]
public async Task Architecture_FullLifecycle_ShouldWork()
{
// Arrange
var architecture = new TestArchitecture();
// Act
await architecture.InitAsync();
var result = architecture.GetModel&lt;TestModel&gt;();
await architecture.DestroyAsync();
// Assert
Assert.NotNull(result);
}
}
```
### 性能测试
对性能敏感的代码,添加基准测试:
```csharp
using BenchmarkDotNet.Attributes;
[MemoryDiagnoser]
public class EventBusBenchmarks
{
private EventBus _eventBus;
[GlobalSetup]
public void Setup()
{
_eventBus = new EventBus();
}
[Benchmark]
public void Publish_1000Events()
{
for (int i = 0; i &lt; 1000; i++)
{
_eventBus.Publish(new TestEvent());
}
}
}
```
## 文档要求
### XML 注释
所有公共 API 必须包含完整的 XML 文档注释:
```csharp
/// &lt;summary&gt;
/// 事件总线接口,提供事件的发布和订阅功能。
/// &lt;/summary&gt;
/// &lt;remarks&gt;
/// 事件总线使用观察者模式实现,支持类型安全的事件分发。
/// 所有订阅都是弱引用,避免内存泄漏。
/// &lt;/remarks&gt;
public interface IEventBus
{
/// &lt;summary&gt;
/// 订阅指定类型的事件。
/// &lt;/summary&gt;
/// &lt;typeparam name="TEvent"&gt;事件类型&lt;/typeparam&gt;
/// &lt;param name="handler"&gt;事件处理器&lt;/param&gt;
/// &lt;returns&gt;取消订阅的句柄&lt;/returns&gt;
/// &lt;exception cref="ArgumentNullException"&gt;当 handler 为 null 时抛出&lt;/exception&gt;
/// &lt;example&gt;
/// &lt;code&gt;
/// var unregister = eventBus.Subscribe&amp;lt;PlayerDiedEvent&amp;gt;(e =&amp;gt;
/// {
/// Console.WriteLine($"Player {e.PlayerId} died");
/// });
///
/// // 取消订阅
/// unregister.Dispose();
/// &lt;/code&gt;
/// &lt;/example&gt;
IUnRegister Subscribe&lt;TEvent&gt;(Action&lt;TEvent&gt; handler) where TEvent : IEvent;
}
```
### Markdown 文档
#### 文档结构
```markdown
# 标题
简要介绍模块功能和用途。
## 核心概念
解释关键概念和术语。
## 快速开始
提供最简单的使用示例。
## 详细用法
### 功能 A
详细说明和代码示例。
### 功能 B
详细说明和代码示例。
## 最佳实践
推荐的使用模式和注意事项。
## 常见问题
FAQ 列表。
## 相关资源
链接到相关文档和示例。
```
#### 代码示例
- **完整性**:示例代码应该可以直接运行
- **注释**:关键步骤添加注释说明
- **格式化**:使用正确的语法高亮
```csharp
// 创建架构实例
var architecture = new GameArchitecture();
// 初始化架构
await architecture.InitAsync();
// 注册模型
var playerModel = architecture.GetModel&lt;PlayerModel&gt;();
// 发送命令
await architecture.SendCommandAsync(new AttackCommand
{
TargetId = enemyId
});
```
#### 图表
使用 Mermaid 或 ASCII 图表说明复杂概念:
```markdown
```mermaid
graph TD
A[Controller] --&gt; B[Command]
B --&gt; C[System]
C --&gt; D[Model]
```
```
## PR 流程
### 创建 PR
1. **确保分支最新**
```bash
git fetch upstream
git rebase upstream/main
```
2. **推送到 Fork**
```bash
git push origin feature/your-feature
```
3. **创建 PR**
- 在 GitHub 上点击 "New Pull Request"
- 选择 base: `main` ← compare: `your-branch`
- 填写 PR 模板
### PR 模板
```markdown
## 变更说明
简要描述本 PR 的变更内容。
## 变更类型
- [ ] Bug 修复
- [ ] 新功能
- [ ] 破坏性变更
- [ ] 文档更新
- [ ] 性能优化
- [ ] 代码重构
## 相关 Issue
Closes #123
## 测试
描述如何测试这些变更:
- [ ] 添加了单元测试
- [ ] 添加了集成测试
- [ ] 手动测试通过
## 检查清单
- [ ] 代码遵循项目规范
- [ ] 添加了必要的注释
- [ ] 更新了相关文档
- [ ] 所有测试通过
- [ ] 没有引入新的警告
## 截图(如适用)
添加截图或 GIF 展示变更效果。
## 附加说明
其他需要说明的内容。
```
### 代码审查
PR 提交后,维护者会进行代码审查:
- **响应反馈**:及时回复审查意见
- **修改代码**:根据建议进行调整
- **讨论方案**:对有争议的地方进行讨论
- **保持耐心**:审查可能需要时间
#### 审查关注点
- **功能正确性**:代码是否实现了预期功能
- **代码质量**:是否遵循项目规范
- **测试覆盖**:是否有足够的测试
- **性能影响**:是否有性能问题
- **向后兼容**:是否破坏现有 API
### 合并流程
1. **通过 CI 检查**:所有自动化测试通过
2. **代码审查通过**:至少一位维护者批准
3. **解决冲突**:如有冲突需先解决
4. **合并方式**
- 功能分支Squash and merge
- 修复分支Merge commit
- 文档更新Squash and merge
## 常见问题
### 如何同步上游更新?
```bash
# 获取上游更新
git fetch upstream
# 合并到本地 main
git checkout main
git merge upstream/main
# 更新你的 Fork
git push origin main
# 更新特性分支
git checkout feature/your-feature
git rebase main
```
### 如何解决合并冲突?
```bash
# 拉取最新代码
git fetch upstream
git rebase upstream/main
# 如果有冲突,手动解决后
git add .
git rebase --continue
# 强制推送(因为 rebase 改变了历史)
git push origin feature/your-feature --force-with-lease
```
### 提交了错误的代码怎么办?
```bash
# 修改最后一次提交
git add .
git commit --amend
# 或者撤销最后一次提交
git reset --soft HEAD~1
# 修改后重新提交
git add .
git commit -m "fix: correct implementation"
```
### 如何运行特定的测试?
```bash
# 运行单个测试类
dotnet test --filter "FullyQualifiedName~EventBusTests"
# 运行单个测试方法
dotnet test --filter "FullyQualifiedName~EventBusTests.Subscribe_ShouldReceiveEvent"
# 运行特定类别的测试
dotnet test --filter "Category=Integration"
```
### 如何生成文档?
```bash
# 安装 VitePress如果还没安装
cd docs
npm install
# 本地预览文档
npm run docs:dev
# 构建文档
npm run docs:build
```
### 代码审查需要多长时间?
- **简单修复**:通常 1-3 天
- **新功能**:可能需要 1-2 周
- **大型重构**:可能需要更长时间
请耐心等待,维护者会尽快审查。
### 我的 PR 被拒绝了怎么办?
不要气馁!被拒绝的原因可能是:
- 不符合项目方向
- 需要更多讨论
- 实现方式需要调整
你可以:
- 在 Issue 中讨论方案
- 根据反馈调整实现
- 寻求维护者的建议
### 如何成为维护者?
持续贡献高质量的代码和文档,积极参与社区讨论,帮助其他贡献者。维护者会邀请活跃且负责任的贡献者加入维护团队。
## 获取帮助
如果你在贡献过程中遇到问题:
- **GitHub Issues**:提问或报告问题
- **GitHub Discussions**:参与讨论
- **代码注释**:查看现有代码的注释和文档
## 致谢
感谢所有为 GFramework 做出贡献的开发者!你们的努力让这个项目变得更好。
## 许可证
通过向本项目提交代码,你同意你的贡献将在 Apache License 2.0 下发布。

View File

@ -0,0 +1,938 @@
# Configuration 包使用说明
## 概述
Configuration 包提供了线程安全的配置管理系统,支持类型安全的配置存储、访问、监听和持久化。配置管理器可以用于管理游戏设置、运行时参数、开发配置等各种键值对数据。
配置系统是 GFramework 架构中的实用工具Utility可以在架构的任何层级中使用提供统一的配置管理能力。
## 核心接口
### IConfigurationManager
配置管理器接口,提供类型安全的配置存储和访问。所有方法都是线程安全的。
**核心方法:**
```csharp
// 配置访问
T? GetConfig&lt;T&gt;(string key); // 获取配置值
T GetConfig&lt;T&gt;(string key, T defaultValue); // 获取配置值(带默认值)
void SetConfig&lt;T&gt;(string key, T value); // 设置配置值
bool HasConfig(string key); // 检查配置是否存在
bool RemoveConfig(string key); // 移除配置
void Clear(); // 清空所有配置
// 配置监听
IUnRegister WatchConfig&lt;T&gt;(string key, Action&lt;T&gt; onChange); // 监听配置变化
// 持久化
void LoadFromJson(string json); // 从 JSON 加载
string SaveToJson(); // 保存为 JSON
void LoadFromFile(string path); // 从文件加载
void SaveToFile(string path); // 保存到文件
// 工具方法
int Count { get; } // 获取配置数量
IEnumerable<string> GetAllKeys(); // 获取所有配置键
```
## 核心类
### ConfigurationManager
配置管理器实现,提供线程安全的配置存储和访问。
**特性:**
- 线程安全:所有公共方法都是线程安全的
- 类型安全:支持泛型类型的配置值
- 自动类型转换:支持基本类型的自动转换
- 配置监听:支持监听配置变化并触发回调
- JSON 持久化:支持 JSON 格式的配置加载和保存
**使用示例:**
```csharp
// 创建配置管理器
var configManager = new ConfigurationManager();
// 设置配置
configManager.SetConfig("game.difficulty", "Normal");
configManager.SetConfig("audio.volume", 0.8f);
configManager.SetConfig("graphics.quality", 2);
// 获取配置
var difficulty = configManager.GetConfig<string>("game.difficulty");
var volume = configManager.GetConfig<float>("audio.volume");
var quality = configManager.GetConfig<int>("graphics.quality");
// 使用默认值
var fov = configManager.GetConfig("graphics.fov", 90);
```
## 基本用法
### 1. 设置和获取配置
```csharp
public class GameSettings : IUtility
{
private readonly IConfigurationManager _config = new ConfigurationManager();
public void Initialize()
{
// 设置游戏配置
_config.SetConfig("player.name", "Player1");
_config.SetConfig("player.level", 1);
_config.SetConfig("player.experience", 0);
// 设置游戏选项
_config.SetConfig("options.showTutorial", true);
_config.SetConfig("options.language", "zh-CN");
}
public string GetPlayerName()
{
return _config.GetConfig<string>("player.name") ?? "Unknown";
}
public int GetPlayerLevel()
{
return _config.GetConfig("player.level", 1);
}
}
```
### 2. 检查和移除配置
```csharp
public class ConfigurationService
{
private readonly IConfigurationManager _config;
public ConfigurationService(IConfigurationManager config)
{
_config = config;
}
public void ResetPlayerData()
{
// 检查配置是否存在
if (_config.HasConfig("player.name"))
{
_config.RemoveConfig("player.name");
}
if (_config.HasConfig("player.level"))
{
_config.RemoveConfig("player.level");
}
// 或者清空所有配置
// _config.Clear();
}
public void PrintAllConfigs()
{
Console.WriteLine($"Total configs: {_config.Count}");
foreach (var key in _config.GetAllKeys())
{
Console.WriteLine($"Key: {key}");
}
}
}
```
### 3. 支持的数据类型
```csharp
public class TypeExamples
{
private readonly IConfigurationManager _config = new ConfigurationManager();
public void SetupConfigs()
{
// 基本类型
_config.SetConfig("int.value", 42);
_config.SetConfig("float.value", 3.14f);
_config.SetConfig("double.value", 2.718);
_config.SetConfig("bool.value", true);
_config.SetConfig("string.value", "Hello");
// 复杂类型
_config.SetConfig("vector.position", new Vector3(1, 2, 3));
_config.SetConfig("list.items", new List<string> { "A", "B", "C" });
_config.SetConfig("dict.data", new Dictionary<string, int>
{
["key1"] = 1,
["key2"] = 2
});
}
public void GetConfigs()
{
var intValue = _config.GetConfig<int>("int.value");
var floatValue = _config.GetConfig<float>("float.value");
var boolValue = _config.GetConfig<bool>("bool.value");
var stringValue = _config.GetConfig<string>("string.value");
var position = _config.GetConfig<Vector3>("vector.position");
var items = _config.GetConfig<List<string>>("list.items");
}
}
```
## 高级用法
### 1. 配置监听(热更新)
配置监听允许在配置值变化时自动触发回调,实现配置的热更新。
```csharp
public class AudioManager : AbstractSystem
{
private IUnRegister _volumeWatcher;
protected override void OnInit()
{
var config = this.GetUtility<IConfigurationManager>();
// 监听音量配置变化
_volumeWatcher = config.WatchConfig<float>("audio.masterVolume", newVolume =>
{
UpdateMasterVolume(newVolume);
this.GetUtility<ILogger>()?.Info($"Master volume changed to: {newVolume}");
});
// 监听音效开关
config.WatchConfig<bool>("audio.sfxEnabled", enabled =>
{
if (enabled)
EnableSoundEffects();
else
DisableSoundEffects();
});
}
private void UpdateMasterVolume(float volume)
{
// 更新音频引擎的主音量
AudioEngine.SetMasterVolume(volume);
}
protected override void OnDestroy()
{
// 取消监听
_volumeWatcher?.UnRegister();
}
}
```
### 2. 多个监听器
```csharp
public class GraphicsManager : AbstractSystem
{
protected override void OnInit()
{
var config = this.GetUtility<IConfigurationManager>();
// 多个组件监听同一个配置
config.WatchConfig<int>("graphics.quality", quality =>
{
UpdateTextureQuality(quality);
});
config.WatchConfig<int>("graphics.quality", quality =>
{
UpdateShadowQuality(quality);
});
config.WatchConfig<int>("graphics.quality", quality =>
{
UpdatePostProcessing(quality);
});
}
private void UpdateTextureQuality(int quality) { }
private void UpdateShadowQuality(int quality) { }
private void UpdatePostProcessing(int quality) { }
}
```
### 3. 配置持久化
#### 保存和加载 JSON
```csharp
public class ConfigurationPersistence
{
private readonly IConfigurationManager _config;
private readonly string _configPath = "config/game_settings.json";
public ConfigurationPersistence(IConfigurationManager config)
{
_config = config;
}
public void SaveConfiguration()
{
try
{
// 保存到文件
_config.SaveToFile(_configPath);
Console.WriteLine($"Configuration saved to {_configPath}");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to save configuration: {ex.Message}");
}
}
public void LoadConfiguration()
{
try
{
// 从文件加载
if (File.Exists(_configPath))
{
_config.LoadFromFile(_configPath);
Console.WriteLine($"Configuration loaded from {_configPath}");
}
else
{
Console.WriteLine("Configuration file not found, using defaults");
SetDefaultConfiguration();
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load configuration: {ex.Message}");
SetDefaultConfiguration();
}
}
private void SetDefaultConfiguration()
{
_config.SetConfig("audio.masterVolume", 1.0f);
_config.SetConfig("audio.musicVolume", 0.8f);
_config.SetConfig("audio.sfxVolume", 1.0f);
_config.SetConfig("graphics.quality", 2);
_config.SetConfig("graphics.fullscreen", true);
}
}
```
#### JSON 字符串操作
```csharp
public class ConfigurationExport
{
private readonly IConfigurationManager _config;
public ConfigurationExport(IConfigurationManager config)
{
_config = config;
}
public string ExportToJson()
{
// 导出为 JSON 字符串
return _config.SaveToJson();
}
public void ImportFromJson(string json)
{
// 从 JSON 字符串导入
_config.LoadFromJson(json);
}
public void ShareConfiguration()
{
// 导出配置用于分享
var json = _config.SaveToJson();
// 可以通过网络发送、保存到剪贴板等
Clipboard.SetText(json);
Console.WriteLine("Configuration copied to clipboard");
}
}
```
### 4. 多环境配置
```csharp
public class EnvironmentConfiguration
{
private readonly IConfigurationManager _config;
private readonly string _environment;
public EnvironmentConfiguration(IConfigurationManager config, string environment)
{
_config = config;
_environment = environment;
}
public void LoadEnvironmentConfig()
{
// 根据环境加载不同的配置文件
var configPath = _environment switch
{
"development" => "config/dev.json",
"staging" => "config/staging.json",
"production" => "config/prod.json",
_ => "config/default.json"
};
if (File.Exists(configPath))
{
_config.LoadFromFile(configPath);
Console.WriteLine($"Loaded {_environment} configuration");
}
// 设置环境特定的配置
ApplyEnvironmentOverrides();
}
private void ApplyEnvironmentOverrides()
{
switch (_environment)
{
case "development":
_config.SetConfig("debug.enabled", true);
_config.SetConfig("logging.level", "Debug");
_config.SetConfig("api.endpoint", "http://localhost:3000");
break;
case "production":
_config.SetConfig("debug.enabled", false);
_config.SetConfig("logging.level", "Warning");
_config.SetConfig("api.endpoint", "https://api.production.com");
break;
}
}
}
```
### 5. 配置验证
```csharp
public class ConfigurationValidator
{
private readonly IConfigurationManager _config;
public ConfigurationValidator(IConfigurationManager config)
{
_config = config;
}
public bool ValidateConfiguration()
{
var isValid = true;
// 验证必需的配置项
if (!_config.HasConfig("game.version"))
{
Console.WriteLine("Error: game.version is required");
isValid = false;
}
// 验证配置值范围
var volume = _config.GetConfig("audio.masterVolume", -1f);
if (volume < 0f || volume > 1f)
{
Console.WriteLine("Error: audio.masterVolume must be between 0 and 1");
isValid = false;
}
// 验证配置类型
try
{
var quality = _config.GetConfig<int>("graphics.quality");
if (quality < 0 || quality > 3)
{
Console.WriteLine("Error: graphics.quality must be between 0 and 3");
isValid = false;
}
}
catch
{
Console.WriteLine("Error: graphics.quality must be an integer");
isValid = false;
}
return isValid;
}
public void ApplyConstraints()
{
// 自动修正超出范围的值
var volume = _config.GetConfig("audio.masterVolume", 1f);
if (volume < 0f) _config.SetConfig("audio.masterVolume", 0f);
if (volume > 1f) _config.SetConfig("audio.masterVolume", 1f);
var quality = _config.GetConfig("graphics.quality", 2);
if (quality < 0) _config.SetConfig("graphics.quality", 0);
if (quality > 3) _config.SetConfig("graphics.quality", 3);
}
}
```
### 6. 配置分组管理
```csharp
public class ConfigurationGroups
{
private readonly IConfigurationManager _config;
public ConfigurationGroups(IConfigurationManager config)
{
_config = config;
}
// 音频配置组
public class AudioConfig
{
public float MasterVolume { get; set; } = 1.0f;
public float MusicVolume { get; set; } = 0.8f;
public float SfxVolume { get; set; } = 1.0f;
public bool Muted { get; set; } = false;
}
// 图形配置组
public class GraphicsConfig
{
public int Quality { get; set; } = 2;
public bool Fullscreen { get; set; } = true;
public int ResolutionWidth { get; set; } = 1920;
public int ResolutionHeight { get; set; } = 1080;
public bool VSync { get; set; } = true;
}
public void SaveAudioConfig(AudioConfig audio)
{
_config.SetConfig("audio.masterVolume", audio.MasterVolume);
_config.SetConfig("audio.musicVolume", audio.MusicVolume);
_config.SetConfig("audio.sfxVolume", audio.SfxVolume);
_config.SetConfig("audio.muted", audio.Muted);
}
public AudioConfig LoadAudioConfig()
{
return new AudioConfig
{
MasterVolume = _config.GetConfig("audio.masterVolume", 1.0f),
MusicVolume = _config.GetConfig("audio.musicVolume", 0.8f),
SfxVolume = _config.GetConfig("audio.sfxVolume", 1.0f),
Muted = _config.GetConfig("audio.muted", false)
};
}
public void SaveGraphicsConfig(GraphicsConfig graphics)
{
_config.SetConfig("graphics.quality", graphics.Quality);
_config.SetConfig("graphics.fullscreen", graphics.Fullscreen);
_config.SetConfig("graphics.resolutionWidth", graphics.ResolutionWidth);
_config.SetConfig("graphics.resolutionHeight", graphics.ResolutionHeight);
_config.SetConfig("graphics.vsync", graphics.VSync);
}
public GraphicsConfig LoadGraphicsConfig()
{
return new GraphicsConfig
{
Quality = _config.GetConfig("graphics.quality", 2),
Fullscreen = _config.GetConfig("graphics.fullscreen", true),
ResolutionWidth = _config.GetConfig("graphics.resolutionWidth", 1920),
ResolutionHeight = _config.GetConfig("graphics.resolutionHeight", 1080),
VSync = _config.GetConfig("graphics.vsync", true)
};
}
}
```
## 在架构中使用
### 注册为 Utility
```csharp
public class GameArchitecture : Architecture<GameArchitecture>
{
protected override void Init()
{
// 注册配置管理器
this.RegisterUtility<IConfigurationManager>(new ConfigurationManager());
}
}
```
### 在 System 中使用
```csharp
public class SettingsSystem : AbstractSystem
{
private IConfigurationManager _config;
protected override void OnInit()
{
_config = this.GetUtility<IConfigurationManager>();
// 加载配置
LoadSettings();
// 监听配置变化
_config.WatchConfig<string>("game.language", OnLanguageChanged);
}
private void LoadSettings()
{
try
{
_config.LoadFromFile("settings.json");
}
catch
{
// 使用默认设置
SetDefaultSettings();
}
}
private void SetDefaultSettings()
{
_config.SetConfig("game.language", "en-US");
_config.SetConfig("game.difficulty", "Normal");
_config.SetConfig("audio.masterVolume", 1.0f);
}
private void OnLanguageChanged(string newLanguage)
{
// 切换游戏语言
LocalizationManager.SetLanguage(newLanguage);
}
public void SaveSettings()
{
_config.SaveToFile("settings.json");
}
}
```
### 在 Controller 中使用
```csharp
public class SettingsController : IController
{
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void ApplyGraphicsSettings(int quality, bool fullscreen)
{
var config = this.GetUtility<IConfigurationManager>();
// 更新配置(会自动触发监听器)
config.SetConfig("graphics.quality", quality);
config.SetConfig("graphics.fullscreen", fullscreen);
// 保存配置
SaveSettings();
}
public void ResetToDefaults()
{
var config = this.GetUtility<IConfigurationManager>();
// 清空所有配置
config.Clear();
// 重新设置默认值
config.SetConfig("audio.masterVolume", 1.0f);
config.SetConfig("graphics.quality", 2);
config.SetConfig("game.language", "en-US");
SaveSettings();
}
private void SaveSettings()
{
var config = this.GetUtility<IConfigurationManager>();
config.SaveToFile("settings.json");
}
}
```
## 最佳实践
### 1. 配置键命名规范
使用分层的点号命名法,便于组织和管理:
```csharp
// 推荐的命名方式
_config.SetConfig("audio.master.volume", 1.0f);
_config.SetConfig("audio.music.volume", 0.8f);
_config.SetConfig("graphics.quality.level", 2);
_config.SetConfig("graphics.resolution.width", 1920);
_config.SetConfig("player.stats.health", 100);
_config.SetConfig("player.stats.mana", 50);
// 避免的命名方式
_config.SetConfig("AudioMasterVolume", 1.0f); // 不使用驼峰命名
_config.SetConfig("vol", 1.0f); // 不使用缩写
_config.SetConfig("config_1", 1.0f); // 不使用无意义的名称
```
### 2. 使用默认值
始终为 `GetConfig` 提供合理的默认值,避免空引用:
```csharp
// 推荐
var volume = _config.GetConfig("audio.volume", 1.0f);
var quality = _config.GetConfig("graphics.quality", 2);
// 不推荐
var volume = _config.GetConfig<float>("audio.volume"); // 可能返回 0
if (volume == 0) volume = 1.0f; // 需要额外的检查
```
### 3. 配置文件组织
将配置文件按环境和用途分类:
```
config/
├── default.json # 默认配置
├── dev.json # 开发环境配置
├── staging.json # 测试环境配置
├── prod.json # 生产环境配置
└── user/
├── settings.json # 用户设置
└── keybindings.json # 键位绑定
```
### 4. 配置安全
不要在配置中存储敏感信息:
```csharp
// 不要这样做
_config.SetConfig("api.key", "secret_key_12345");
_config.SetConfig("user.password", "password123");
// 应该使用专门的安全存储
SecureStorage.SetSecret("api.key", "secret_key_12345");
```
### 5. 监听器管理
及时注销不再需要的监听器,避免内存泄漏:
```csharp
public class MySystem : AbstractSystem
{
private readonly List<IUnRegister> _watchers = new();
protected override void OnInit()
{
var config = this.GetUtility<IConfigurationManager>();
// 保存监听器引用
_watchers.Add(config.WatchConfig<float>("audio.volume", OnVolumeChanged));
_watchers.Add(config.WatchConfig<int>("graphics.quality", OnQualityChanged));
}
protected override void OnDestroy()
{
// 注销所有监听器
foreach (var watcher in _watchers)
{
watcher.UnRegister();
}
_watchers.Clear();
}
}
```
### 6. 线程安全使用
虽然 `ConfigurationManager` 是线程安全的,但在多线程环境中仍需注意:
```csharp
public class ThreadSafeConfigAccess
{
private readonly IConfigurationManager _config;
public void UpdateFromMultipleThreads()
{
// 可以安全地从多个线程访问
Parallel.For(0, 10, i =>
{
_config.SetConfig($"thread.{i}.value", i);
var value = _config.GetConfig($"thread.{i}.value", 0);
});
}
public void WatchFromMultipleThreads()
{
// 监听器回调可能在不同线程执行
_config.WatchConfig<int>("shared.value", newValue =>
{
// 确保线程安全的操作
lock (_lockObject)
{
UpdateSharedResource(newValue);
}
});
}
private readonly object _lockObject = new();
private void UpdateSharedResource(int value) { }
}
```
### 7. 配置变更通知
避免在配置监听器中触发大量的配置变更,可能导致循环调用:
```csharp
// 不推荐:可能导致无限循环
_config.WatchConfig<int>("value.a", a =>
{
_config.SetConfig("value.b", a + 1); // 触发 b 的监听器
});
_config.WatchConfig<int>("value.b", b =>
{
_config.SetConfig("value.a", b + 1); // 触发 a 的监听器
});
// 推荐:使用标志位避免循环
private bool _isUpdating = false;
_config.WatchConfig<int>("value.a", a =>
{
if (_isUpdating) return;
_isUpdating = true;
_config.SetConfig("value.b", a + 1);
_isUpdating = false;
});
```
## 常见问题
### Q1: 配置值类型转换失败怎么办?
A: `ConfigurationManager` 会尝试自动转换类型,如果失败会返回默认值。建议使用带默认值的 `GetConfig` 方法:
```csharp
// 如果转换失败,返回默认值 1.0f
var volume = _config.GetConfig("audio.volume", 1.0f);
```
### Q2: 如何处理配置文件不存在的情况?
A: 使用 try-catch 捕获异常,并提供默认配置:
```csharp
try
{
_config.LoadFromFile("settings.json");
}
catch (FileNotFoundException)
{
// 使用默认配置
SetDefaultConfiguration();
// 保存默认配置
_config.SaveToFile("settings.json");
}
```
### Q3: 配置监听器何时被触发?
A: 只有当配置值真正发生变化时才会触发监听器。如果设置相同的值,监听器不会被触发:
```csharp
_config.SetConfig("key", 42);
_config.WatchConfig<int>("key", value =>
{
Console.WriteLine($"Changed to: {value}");
});
_config.SetConfig("key", 42); // 不会触发(值未变化)
_config.SetConfig("key", 100); // 会触发
```
### Q4: 如何实现配置的版本控制?
A: 可以在配置中添加版本号,并在加载时进行迁移:
```csharp
public class ConfigurationMigration
{
private readonly IConfigurationManager _config;
public void LoadAndMigrate(string path)
{
_config.LoadFromFile(path);
var version = _config.GetConfig("config.version", 1);
if (version < 2)
{
MigrateToV2();
}
if (version < 3)
{
MigrateToV3();
}
_config.SetConfig("config.version", 3);
_config.SaveToFile(path);
}
private void MigrateToV2()
{
// 迁移逻辑
if (_config.HasConfig("old.key"))
{
var value = _config.GetConfig<string>("old.key");
_config.SetConfig("new.key", value);
_config.RemoveConfig("old.key");
}
}
private void MigrateToV3() { }
}
```
### Q5: 配置管理器的性能如何?
A: `ConfigurationManager` 使用 `ConcurrentDictionary` 实现,具有良好的并发性能。但要注意:
- 避免频繁的文件 I/O 操作
- 监听器回调应保持轻量
- 大量配置项时考虑分组管理
```csharp
// 推荐:批量更新后一次性保存
_config.SetConfig("key1", value1);
_config.SetConfig("key2", value2);
_config.SetConfig("key3", value3);
_config.SaveToFile("settings.json"); // 一次性保存
// 不推荐:每次更新都保存
_config.SetConfig("key1", value1);
_config.SaveToFile("settings.json");
_config.SetConfig("key2", value2);
_config.SaveToFile("settings.json");
```
## 相关包
- [`architecture`](./architecture.md) - 配置管理器作为 Utility 注册到架构
- [`utility`](./utility.md) - 配置管理器实现 IUtility 接口
- [`events`](./events.md) - 配置变化可以触发事件
- [`logging`](./logging.md) - 配置管理器内部使用日志记录

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

1005
docs/zh-CN/core/ecs.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,677 @@
# 函数式编程指南
## 概述
GFramework.Core 提供了一套完整的函数式编程工具,帮助开发者编写更安全、更简洁、更易维护的代码。函数式编程强调不可变性、纯函数和声明式编程风格,能够有效减少副作用,提高代码的可测试性和可组合性。
本模块提供以下核心功能:
- **Option 类型**:安全处理可能不存在的值,替代 null 引用
- **Result 类型**:优雅处理操作结果和错误,避免异常传播
- **管道操作**:构建流式的函数调用链
- **函数组合**:组合多个函数形成新函数
- **控制流扩展**:函数式风格的条件执行和重试机制
- **异步函数式编程**:支持异步操作的函数式封装
## 核心概念
### Option 类型
`Option&lt;T&gt;` 表示可能存在或不存在的值,用于替代 null 引用。它有两种状态:
- **Some**:包含一个值
- **None**:不包含值
使用 Option 可以在编译时强制处理"无值"的情况,避免空引用异常。
### Result 类型
`Result&lt;T&gt;` 表示操作的结果,可能是成功值或失败异常。它有三种状态:
- **Success**:操作成功,包含返回值
- **Faulted**:操作失败,包含异常信息
- **Bottom**:未初始化状态
Result 类型将错误处理显式化,避免使用异常进行流程控制。
### 管道操作
管道操作允许将值通过一系列函数进行转换,形成流式的调用链。这种风格使代码更易读,逻辑更清晰。
### 函数组合
函数组合是将多个简单函数组合成复杂函数的技术。通过组合,可以构建可复用的函数库,提高代码的模块化程度。
## 基本用法
### Option 基础
#### 创建 Option
```csharp
using GFramework.Core.functional;
// 创建包含值的 Option
var someValue = Option<int>.Some(42);
// 创建空 Option
var noneValue = Option<int>.None;
// 隐式转换
Option<string> name = "Alice"; // Some("Alice")
Option<string> empty = null; // None
```
#### 获取值
```csharp
// 使用默认值
var value1 = someValue.GetOrElse(0); // 42
var value2 = noneValue.GetOrElse(0); // 0
// 使用工厂函数(延迟计算)
var value3 = noneValue.GetOrElse(() => ExpensiveDefault());
```
#### 转换值
```csharp
// Map映射值到新类型
var option = Option<int>.Some(42);
var mapped = option.Map(x => x.ToString()); // Option<string>.Some("42")
// Bind链式转换单子绑定
var result = Option<string>.Some("42")
.Bind(s => int.TryParse(s, out var i)
? Option<int>.Some(i)
: Option<int>.None);
```
#### 过滤值
```csharp
var option = Option<int>.Some(42);
var filtered = option.Filter(x => x > 0); // Some(42)
var filtered2 = option.Filter(x => x < 0); // None
```
#### 模式匹配
```csharp
// 返回值的模式匹配
var message = option.Match(
some: value => $"Value: {value}",
none: () => "No value"
);
// 副作用的模式匹配
option.Match(
some: value => Console.WriteLine($"Value: {value}"),
none: () => Console.WriteLine("No value")
);
```
### Result 基础
#### 创建 Result
```csharp
using GFramework.Core.functional;
// 创建成功结果
var success = Result<int>.Succeed(42);
var success2 = Result<int>.Success(42); // 别名
// 创建失败结果
var failure = Result<int>.Fail(new Exception("Error"));
var failure2 = Result<int>.Failure("Error message");
// 隐式转换
Result<int> result = 42; // Success(42)
```
#### 安全执行
```csharp
// 自动捕获异常
var result = Result<int>.Try(() => int.Parse("42"));
// 异步安全执行
var asyncResult = await ResultExtensions.TryAsync(async () =>
await GetDataAsync());
```
#### 获取值
```csharp
// 失败时返回默认值
var value1 = result.IfFail(0);
// 失败时通过函数处理
var value2 = result.IfFail(ex =>
{
Console.WriteLine($"Error: {ex.Message}");
return -1;
});
```
#### 转换值
```csharp
// Map映射成功值
var mapped = result.Map(x => x * 2);
// Bind链式转换
var bound = result.Bind(x => x > 0
? Result<string>.Succeed(x.ToString())
: Result<string>.Fail(new ArgumentException("Must be positive")));
// 异步映射
var asyncMapped = await result.MapAsync(async x =>
await ProcessAsync(x));
```
#### 模式匹配
```csharp
// 返回值的模式匹配
var message = result.Match(
succ: value => $"Success: {value}",
fail: ex => $"Error: {ex.Message}"
);
// 副作用的模式匹配
result.Match(
succ: value => Console.WriteLine($"Success: {value}"),
fail: ex => Console.WriteLine($"Error: {ex.Message}")
);
```
### 管道操作
#### Pipe管道转换
```csharp
using GFramework.Core.functional.pipe;
var result = 42
.Pipe(x => x * 2) // 84
.Pipe(x => x.ToString()) // "84"
.Pipe(s => $"Result: {s}"); // "Result: 84"
```
#### Tap副作用操作
```csharp
var result = GetUser()
.Tap(user => Console.WriteLine($"User: {user.Name}"))
.Tap(user => _logger.LogInfo($"Processing user {user.Id}"))
.Pipe(user => new UserDto { Id = user.Id, Name = user.Name });
```
#### Let作用域转换
```csharp
var dto = GetUser().Let(user => new UserDto
{
Id = user.Id,
Name = user.Name,
Email = user.Email
});
```
#### PipeIf条件管道
```csharp
var result = 42.PipeIf(
predicate: x => x > 0,
ifTrue: x => $"Positive: {x}",
ifFalse: x => $"Non-positive: {x}"
);
```
### 函数组合
#### Compose函数组合
```csharp
using GFramework.Core.functional.functions;
Func<int, int> addOne = x => x + 1;
Func<int, int> multiplyTwo = x => x * 2;
// f(g(x)) = (x + 1) * 2
var composed = multiplyTwo.Compose(addOne);
var result = composed(5); // (5 + 1) * 2 = 12
```
#### AndThen链式组合
```csharp
// g(f(x)) = (x + 1) * 2
var chained = addOne.AndThen(multiplyTwo);
var result = chained(5); // (5 + 1) * 2 = 12
```
#### Curry柯里化
```csharp
Func<int, int, int> add = (x, y) => x + y;
var curriedAdd = add.Curry();
var add5 = curriedAdd(5);
var result = add5(3); // 8
```
## 高级用法
### Result 扩展操作
#### 链式副作用
```csharp
using GFramework.Core.functional.result;
Result<int>.Succeed(42)
.OnSuccess(x => Console.WriteLine($"Value: {x}"))
.OnFailure(ex => Console.WriteLine($"Error: {ex.Message}"))
.Map(x => x * 2);
```
#### 验证约束
```csharp
var result = Result<int>.Succeed(42)
.Ensure(x => x > 0, "Value must be positive")
.Ensure(x => x < 100, "Value must be less than 100");
```
#### 聚合多个结果
```csharp
var results = new[]
{
Result<int>.Succeed(1),
Result<int>.Succeed(2),
Result<int>.Succeed(3)
};
var combined = results.Combine(); // Result<List<int>>
```
### 控制流扩展
#### TakeIf条件返回
```csharp
using GFramework.Core.functional.control;
var user = GetUser().TakeIf(u => u.IsActive); // 活跃用户或 null
// 值类型版本
var number = 42.TakeIfValue(x => x > 0); // 42 或 null
```
#### When条件执行
```csharp
var result = 42
.When(x => x > 0, x => Console.WriteLine($"Positive: {x}"))
.When(x => x % 2 == 0, x => Console.WriteLine("Even"));
```
#### RepeatUntil重复执行
```csharp
var result = 1.RepeatUntil(
func: x => x * 2,
predicate: x => x >= 100,
maxIterations: 10
); // 128
```
#### Retry同步重试
```csharp
var result = ControlExtensions.Retry(
func: () => UnstableOperation(),
maxRetries: 3,
delayMilliseconds: 100
);
```
### 异步函数式编程
#### 异步重试
```csharp
using GFramework.Core.functional.async;
var result = await (() => UnreliableOperationAsync())
.WithRetryAsync(
maxRetries: 3,
delay: TimeSpan.FromSeconds(1),
shouldRetry: ex => ex is TimeoutException
);
```
#### 异步安全执行
```csharp
var result = await (() => RiskyOperationAsync()).TryAsync();
result.Match(
value => Console.WriteLine($"Success: {value}"),
error => Console.WriteLine($"Failed: {error.Message}")
);
```
#### 异步绑定
```csharp
var result = Result<int>.Succeed(42);
var bound = await result.BindAsync(async x =>
await GetUserAsync(x) is User user
? Result<User>.Succeed(user)
: Result<User>.Fail(new Exception("User not found"))
);
```
### 高级函数操作
#### Partial偏函数应用
```csharp
Func<int, int, int> add = (x, y) => x + y;
var add5 = add.Partial(5);
var result = add5(3); // 8
```
#### Repeat重复应用
```csharp
var result = 2.Repeat(3, x => x * 2); // 2 * 2 * 2 * 2 = 16
```
#### Once单次执行
```csharp
var counter = 0;
var once = (() => ++counter).Once();
var result1 = once(); // 1
var result2 = once(); // 1不会再次执行
```
#### Defer延迟执行
```csharp
var lazy = (() => ExpensiveComputation()).Defer();
// 此时尚未执行
var result = lazy.Value; // 首次访问时才执行
```
#### Memoize结果缓存
```csharp
Func<int, int> expensive = x =>
{
Thread.Sleep(1000);
return x * x;
};
var memoized = expensive.MemoizeUnbounded();
var result1 = memoized(5); // 耗时 1 秒
var result2 = memoized(5); // 立即返回(从缓存)
```
## 最佳实践
### 何时使用 Option
1. **替代 null 引用**:当函数可能返回空值时
2. **配置参数**:表示可选的配置项
3. **查找操作**:字典查找、数据库查询等可能失败的操作
4. **链式操作**:需要安全地链式调用多个可能返回空值的方法
```csharp
// 不推荐:使用 null
public User? FindUser(int id)
{
return _users.ContainsKey(id) ? _users[id] : null;
}
// 推荐:使用 Option
public Option<User> FindUser(int id)
{
return _users.TryGetValue(id, out var user)
? Option<User>.Some(user)
: Option<User>.None;
}
```
### 何时使用 Result
1. **错误处理**:需要显式处理错误的操作
2. **验证逻辑**:输入验证、业务规则检查
3. **外部调用**:网络请求、文件操作等可能失败的 I/O 操作
4. **避免异常**:不希望使用异常进行流程控制的场景
```csharp
// 不推荐:使用异常
public int ParseNumber(string input)
{
if (!int.TryParse(input, out var number))
throw new ArgumentException("Invalid number");
return number;
}
// 推荐:使用 Result
public Result<int> ParseNumber(string input)
{
return int.TryParse(input, out var number)
? Result<int>.Succeed(number)
: Result<int>.Failure("Invalid number");
}
```
### 何时使用管道操作
1. **数据转换**:需要对数据进行多步转换
2. **流式处理**:构建数据处理管道
3. **副作用隔离**:使用 Tap 隔离副作用操作
4. **提高可读性**:使复杂的嵌套调用变得线性
```csharp
// 不推荐:嵌套调用
var result = FormatResult(
ValidateInput(
ParseInput(
GetInput()
)
)
);
// 推荐:管道操作
var result = GetInput()
.Pipe(ParseInput)
.Pipe(ValidateInput)
.Tap(x => _logger.LogInfo($"Validated: {x}"))
.Pipe(FormatResult);
```
### 错误处理模式
#### 模式 1早期返回
```csharp
public Result<User> CreateUser(string name, string email)
{
return ValidateName(name)
.Bind(_ => ValidateEmail(email))
.Bind(_ => CheckDuplicate(email))
.Bind(_ => SaveUser(name, email));
}
```
#### 模式 2聚合验证
```csharp
public Result<User> CreateUser(UserDto dto)
{
var validations = new[]
{
ValidateName(dto.Name),
ValidateEmail(dto.Email),
ValidateAge(dto.Age)
};
return validations.Combine()
.Bind(_ => SaveUser(dto));
}
```
#### 模式 3错误恢复
```csharp
public Result<Data> GetData(int id)
{
return GetFromCache(id)
.Match(
succ: data => Result<Data>.Succeed(data),
fail: _ => GetFromDatabase(id)
);
}
```
### 组合模式
#### 模式 1Option + Result
```csharp
public Result<User> GetActiveUser(int id)
{
return FindUser(id) // Option<User>
.ToResult("User not found") // Result<User>
.Ensure(u => u.IsActive, "User is not active");
}
```
#### 模式 2Result + 管道
```csharp
public Result<UserDto> ProcessUser(int id)
{
return Result<int>.Succeed(id)
.Bind(GetUser)
.Map(user => user.Tap(u => _logger.LogInfo($"Processing {u.Name}")))
.Map(user => new UserDto { Id = user.Id, Name = user.Name });
}
```
#### 模式 3异步组合
```csharp
public async Task<Result<Response>> ProcessRequestAsync(Request request)
{
return await Result<Request>.Succeed(request)
.Ensure(r => r.IsValid, "Invalid request")
.BindAsync(async r => await ValidateAsync(r))
.BindAsync(async r => await ProcessAsync(r))
.MapAsync(async r => await FormatResponseAsync(r));
}
```
## 常见问题
### Option vs Nullable
**Q: Option 和 Nullable&lt;T&gt; 有什么区别?**
A:
- `Nullable&lt;T&gt;` 只能用于值类型,`Option&lt;T&gt;` 可用于任何类型
- `Option&lt;T&gt;` 提供丰富的函数式操作Map、Bind、Filter 等)
- `Option&lt;T&gt;` 强制显式处理"无值"情况,更安全
- `Option&lt;T&gt;` 可以与 Result 等其他函数式类型组合
### Result vs Exception
**Q: 什么时候应该使用 Result 而不是异常?**
A:
- **使用 Result**:预期的错误情况(验证失败、资源不存在等)
- **使用 Exception**:意外的错误情况(系统错误、编程错误等)
- Result 使错误处理显式化,提高代码可读性
- Result 避免异常的性能开销
### 性能考虑
**Q: 函数式编程会影响性能吗?**
A:
- Option 和 Result 是值类型struct性能开销很小
- 管道操作本质是方法调用JIT 会进行内联优化
- Memoize 等缓存机制可以提高性能
- 对于性能敏感的代码,可以选择性使用函数式特性
### 与 LINQ 的关系
**Q: 函数式扩展与 LINQ 有什么区别?**
A:
- LINQ 主要用于集合操作,函数式扩展用于单值操作
- 两者可以很好地组合使用
- Option 和 Result 可以转换为 IEnumerable 与 LINQ 集成
```csharp
// Option 转 LINQ
var options = new[]
{
Option<int>.Some(1),
Option<int>.None,
Option<int>.Some(3)
};
var values = options
.SelectMany(o => o.ToEnumerable())
.ToList(); // [1, 3]
```
### 学习曲线
**Q: 函数式编程难学吗?**
A:
- 从简单的 Option 和 Result 开始
- 逐步引入管道操作和函数组合
- 不需要一次性掌握所有特性
- 在实际项目中逐步应用,积累经验
## 参考资源
### 相关文档
- [Architecture 包使用说明](./architecture.md)
- [Extensions 扩展方法](./extensions.md)
- [CQRS 模式](./cqrs.md)
### 外部资源
- [函数式编程原理](https://en.wikipedia.org/wiki/Functional_programming)
- [Railway Oriented Programming](https://fsharpforfunandprofit.com/rop/)
- [Option 类型模式](https://en.wikipedia.org/wiki/Option_type)
### 示例代码
完整的示例代码可以在测试项目中找到:
- `GFramework.Core.Tests/functional/OptionTests.cs`
- `GFramework.Core.Tests/functional/ResultTests.cs`
- `GFramework.Core.Tests/functional/pipe/PipeExtensionsTests.cs`
- `GFramework.Core.Tests/functional/functions/FunctionExtensionsTests.cs`
- `GFramework.Core.Tests/functional/control/ControlExtensionsTests.cs`

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) - 异步架构初始化详解

918
docs/zh-CN/core/pause.md Normal file
View File

@ -0,0 +1,918 @@
# 暂停管理系统使用说明
## 概述
暂停管理系统Pause System提供了一套完整的游戏暂停控制机制支持多层嵌套暂停、分组暂停、以及灵活的暂停处理器扩展。该系统基于栈结构实现能够优雅地处理复杂的暂停场景如菜单叠加、对话框弹出等。
暂停系统是 GFramework 架构中的核心工具Utility与其他系统协同工作为游戏提供统一的暂停管理能力。
**主要特性:**
- **嵌套暂停**:支持多层暂停请求,只有所有请求都解除后才恢复
- **分组管理**:不同系统可以独立暂停(游戏逻辑、动画、音频等)
- **线程安全**:使用读写锁保证并发安全
- **作用域管理**:支持 `using` 语法自动管理暂停生命周期
- **事件通知**:状态变化时通知所有注册的处理器
- **优先级控制**:处理器按优先级顺序执行
## 核心概念
### 暂停栈Pause Stack
暂停系统使用栈结构管理暂停请求。每次调用 `Push` 会将暂停请求压入栈中,调用 `Pop` 会从栈中移除对应的请求。只有当栈为空时,游戏才会恢复运行。
```
栈深度 3: [暂停原因: "库存界面"]
栈深度 2: [暂停原因: "对话框"]
栈深度 1: [暂停原因: "暂停菜单"]
```
### 暂停组Pause Group
暂停组允许不同系统独立控制暂停状态。例如,打开菜单时可以暂停游戏逻辑但保持 UI 动画运行。
**预定义组:**
- `Global` - 全局暂停(影响所有系统)
- `Gameplay` - 游戏逻辑暂停(不影响 UI
- `Animation` - 动画暂停
- `Audio` - 音频暂停
- `Custom1/2/3` - 自定义组
### 暂停令牌Pause Token
每次暂停请求都会返回一个唯一的令牌,用于后续恢复操作。令牌基于 GUID 实现,确保唯一性。
```csharp
public readonly struct PauseToken
{
public Guid Id { get; }
public bool IsValid => Id != Guid.Empty;
}
```
### 暂停处理器Pause Handler
处理器实现具体的暂停/恢复逻辑,如控制物理引擎、音频系统等。处理器按优先级顺序执行。
```csharp
public interface IPauseHandler
{
int Priority { get; } // 优先级(数值越小越高)
void OnPauseStateChanged(PauseGroup group, bool isPaused);
}
```
## 核心接口
### IPauseStackManager
暂停栈管理器接口,提供暂停控制的所有功能。
**核心方法:**
```csharp
// 推入暂停请求
PauseToken Push(string reason, PauseGroup group = PauseGroup.Global);
// 弹出暂停请求
bool Pop(PauseToken token);
// 查询暂停状态
bool IsPaused(PauseGroup group = PauseGroup.Global);
// 获取暂停深度
int GetPauseDepth(PauseGroup group = PauseGroup.Global);
// 获取暂停原因列表
IReadOnlyList<string> GetPauseReasons(PauseGroup group = PauseGroup.Global);
// 创建暂停作用域
IDisposable PauseScope(string reason, PauseGroup group = PauseGroup.Global);
// 清空指定组
void ClearGroup(PauseGroup group);
// 清空所有组
void ClearAll();
// 注册/注销处理器
void RegisterHandler(IPauseHandler handler);
void UnregisterHandler(IPauseHandler handler);
// 状态变化事件
event Action<PauseGroup, bool>? OnPauseStateChanged;
```
## 基本用法
### 1. 获取暂停管理器
```csharp
public class GameController : IController
{
private IPauseStackManager _pauseManager;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void Initialize()
{
// 从架构中获取暂停管理器
_pauseManager = this.GetUtility<IPauseStackManager>();
}
}
```
### 2. 简单的暂停/恢复
```csharp
public class PauseMenuController : IController
{
private IPauseStackManager _pauseManager;
private PauseToken _pauseToken;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void Initialize()
{
_pauseManager = this.GetUtility<IPauseStackManager>();
}
public void OpenPauseMenu()
{
// 暂停游戏
_pauseToken = _pauseManager.Push("暂停菜单");
Console.WriteLine($"游戏已暂停,深度: {_pauseManager.GetPauseDepth()}");
}
public void ClosePauseMenu()
{
// 恢复游戏
if (_pauseToken.IsValid)
{
_pauseManager.Pop(_pauseToken);
Console.WriteLine("游戏已恢复");
}
}
}
```
### 3. 使用作用域自动管理
```csharp
public class DialogController : IController
{
private IPauseStackManager _pauseManager;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void ShowDialog(string message)
{
// 使用 using 语法,自动管理暂停生命周期
using (_pauseManager.PauseScope("对话框"))
{
Console.WriteLine($"显示对话框: {message}");
// 对话框显示期间游戏暂停
WaitForUserInput();
}
// 离开作用域后自动恢复
}
}
```
### 4. 查询暂停状态
```csharp
public class GameplaySystem : AbstractSystem
{
private IPauseStackManager _pauseManager;
protected override void OnInit()
{
_pauseManager = this.GetUtility<IPauseStackManager>();
}
public void Update(float deltaTime)
{
// 检查是否暂停
if (_pauseManager.IsPaused(PauseGroup.Gameplay))
{
return; // 暂停时跳过更新
}
// 正常游戏逻辑
UpdateGameLogic(deltaTime);
}
}
```
## 高级用法
### 1. 嵌套暂停
```csharp
public class UIManager : IController
{
private IPauseStackManager _pauseManager;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void ShowNestedMenus()
{
// 第一层:主菜单
var token1 = _pauseManager.Push("主菜单");
Console.WriteLine($"深度: {_pauseManager.GetPauseDepth()}"); // 输出: 1
// 第二层:设置菜单
var token2 = _pauseManager.Push("设置菜单");
Console.WriteLine($"深度: {_pauseManager.GetPauseDepth()}"); // 输出: 2
// 第三层:确认对话框
var token3 = _pauseManager.Push("确认对话框");
Console.WriteLine($"深度: {_pauseManager.GetPauseDepth()}"); // 输出: 3
// 关闭对话框
_pauseManager.Pop(token3);
Console.WriteLine($"仍然暂停: {_pauseManager.IsPaused()}"); // 输出: True
// 关闭设置菜单
_pauseManager.Pop(token2);
Console.WriteLine($"仍然暂停: {_pauseManager.IsPaused()}"); // 输出: True
// 关闭主菜单
_pauseManager.Pop(token1);
Console.WriteLine($"已恢复: {!_pauseManager.IsPaused()}"); // 输出: True
}
}
```
### 2. 分组暂停
```csharp
public class GameManager : IController
{
private IPauseStackManager _pauseManager;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void OpenInventory()
{
// 只暂停游戏逻辑UI 和音频继续运行
var token = _pauseManager.Push("库存界面", PauseGroup.Gameplay);
Console.WriteLine($"游戏逻辑暂停: {_pauseManager.IsPaused(PauseGroup.Gameplay)}");
Console.WriteLine($"音频暂停: {_pauseManager.IsPaused(PauseGroup.Audio)}");
Console.WriteLine($"全局暂停: {_pauseManager.IsPaused(PauseGroup.Global)}");
}
public void OpenPauseMenu()
{
// 全局暂停,影响所有系统
var token = _pauseManager.Push("暂停菜单", PauseGroup.Global);
Console.WriteLine($"所有系统已暂停");
}
public void MuteAudio()
{
// 只暂停音频
var token = _pauseManager.Push("静音", PauseGroup.Audio);
}
}
```
### 3. 自定义暂停处理器
```csharp
// 物理引擎暂停处理器
public class PhysicsPauseHandler : IPauseHandler
{
private readonly PhysicsWorld _physicsWorld;
public PhysicsPauseHandler(PhysicsWorld physicsWorld)
{
_physicsWorld = physicsWorld;
}
// 高优先级,确保物理引擎最先暂停
public int Priority => 10;
public void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
// 只响应游戏逻辑和全局暂停
if (group == PauseGroup.Gameplay || group == PauseGroup.Global)
{
_physicsWorld.Enabled = !isPaused;
Console.WriteLine($"物理引擎 {(isPaused ? "已暂停" : "已恢复")}");
}
}
}
// 音频系统暂停处理器
public class AudioPauseHandler : IPauseHandler
{
private readonly AudioSystem _audioSystem;
public AudioPauseHandler(AudioSystem audioSystem)
{
_audioSystem = audioSystem;
}
public int Priority => 20;
public void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
// 响应音频和全局暂停
if (group == PauseGroup.Audio || group == PauseGroup.Global)
{
if (isPaused)
{
_audioSystem.PauseAll();
}
else
{
_audioSystem.ResumeAll();
}
}
}
}
// 注册处理器
public class GameInitializer
{
public void Initialize()
{
var pauseManager = architecture.GetUtility<IPauseStackManager>();
var physicsWorld = GetPhysicsWorld();
var audioSystem = GetAudioSystem();
// 注册处理器
pauseManager.RegisterHandler(new PhysicsPauseHandler(physicsWorld));
pauseManager.RegisterHandler(new AudioPauseHandler(audioSystem));
}
}
```
### 4. 监听暂停状态变化
```csharp
public class PauseIndicator : IController
{
private IPauseStackManager _pauseManager;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void Initialize()
{
_pauseManager = this.GetUtility<IPauseStackManager>();
// 订阅状态变化事件
_pauseManager.OnPauseStateChanged += OnPauseStateChanged;
}
private void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
Console.WriteLine($"暂停状态变化: 组={group}, 暂停={isPaused}");
if (group == PauseGroup.Global)
{
if (isPaused)
{
ShowPauseIndicator();
}
else
{
HidePauseIndicator();
}
}
}
public void Cleanup()
{
_pauseManager.OnPauseStateChanged -= OnPauseStateChanged;
}
}
```
### 5. 调试暂停状态
```csharp
public class PauseDebugger : IController
{
private IPauseStackManager _pauseManager;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void PrintPauseStatus()
{
Console.WriteLine("=== 暂停状态 ===");
foreach (PauseGroup group in Enum.GetValues(typeof(PauseGroup)))
{
var isPaused = _pauseManager.IsPaused(group);
var depth = _pauseManager.GetPauseDepth(group);
var reasons = _pauseManager.GetPauseReasons(group);
Console.WriteLine($"\n组: {group}");
Console.WriteLine($" 状态: {(isPaused ? "暂停" : "运行")}");
Console.WriteLine($" 深度: {depth}");
if (reasons.Count > 0)
{
Console.WriteLine(" 原因:");
foreach (var reason in reasons)
{
Console.WriteLine($" - {reason}");
}
}
}
}
}
```
### 6. 紧急恢复
```csharp
public class EmergencyController : IController
{
private IPauseStackManager _pauseManager;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void ForceResumeAll()
{
// 清空所有暂停请求(谨慎使用)
_pauseManager.ClearAll();
Console.WriteLine("已强制恢复所有系统");
}
public void ForceResumeGameplay()
{
// 只清空游戏逻辑组
_pauseManager.ClearGroup(PauseGroup.Gameplay);
Console.WriteLine("已强制恢复游戏逻辑");
}
}
```
## Godot 集成
### GodotPauseHandler
GFramework.Godot 提供了 Godot 引擎的暂停处理器实现:
```csharp
public class GodotPauseHandler : IPauseHandler
{
private readonly SceneTree _tree;
public GodotPauseHandler(SceneTree tree)
{
_tree = tree;
}
public int Priority => 0;
public void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
// 只有 Global 组影响 Godot 的全局暂停
if (group == PauseGroup.Global)
{
_tree.Paused = isPaused;
}
}
}
```
### 在 Godot 中使用
```csharp
public partial class GameRoot : Node
{
private IPauseStackManager _pauseManager;
public override void _Ready()
{
// 获取暂停管理器
_pauseManager = architecture.GetUtility<IPauseStackManager>();
// 注册 Godot 处理器
var godotHandler = new GodotPauseHandler(GetTree());
_pauseManager.RegisterHandler(godotHandler);
}
public void OnPauseButtonPressed()
{
// 暂停游戏
_pauseManager.Push("玩家暂停", PauseGroup.Global);
}
}
```
### 配合 ProcessMode
```csharp
public partial class PauseMenu : Control
{
public override void _Ready()
{
// 设置为 Always 模式,暂停时仍然处理输入
ProcessMode = ProcessModeEnum.Always;
}
public override void _Input(InputEvent @event)
{
if (@event.IsActionPressed("ui_cancel"))
{
var pauseManager = this.GetUtility<IPauseStackManager>();
if (pauseManager.IsPaused())
{
// 恢复游戏
ResumeGame();
}
else
{
// 暂停游戏
PauseGame();
}
}
}
}
```
## 最佳实践
### 1. 使用作用域管理
优先使用 `PauseScope` 而不是手动 `Push/Pop`,避免忘记恢复:
```csharp
// ✅ 推荐
public void ShowDialog()
{
using (_pauseManager.PauseScope("对话框"))
{
// 对话框逻辑
}
// 自动恢复
}
// ❌ 不推荐
public void ShowDialog()
{
var token = _pauseManager.Push("对话框");
// 对话框逻辑
_pauseManager.Pop(token); // 容易忘记
}
```
### 2. 提供清晰的暂停原因
暂停原因用于调试,应该清晰描述暂停来源:
```csharp
// ✅ 推荐
_pauseManager.Push("主菜单 - 设置页面");
_pauseManager.Push("过场动画 - 关卡加载");
_pauseManager.Push("教程对话框 - 第一关");
// ❌ 不推荐
_pauseManager.Push("pause");
_pauseManager.Push("menu");
```
### 3. 合理选择暂停组
根据实际需求选择合适的暂停组:
```csharp
// 打开库存:只暂停游戏逻辑
_pauseManager.Push("库存界面", PauseGroup.Gameplay);
// 打开暂停菜单:全局暂停
_pauseManager.Push("暂停菜单", PauseGroup.Global);
// 播放过场动画:暂停游戏逻辑和输入
_pauseManager.Push("过场动画", PauseGroup.Gameplay);
```
### 4. 处理器优先级设计
合理设置处理器优先级,确保正确的执行顺序:
```csharp
// 物理引擎高优先级10最先暂停
public class PhysicsPauseHandler : IPauseHandler
{
public int Priority => 10;
}
// 音频系统中优先级20
public class AudioPauseHandler : IPauseHandler
{
public int Priority => 20;
}
// UI 动画低优先级30最后暂停
public class UiAnimationPauseHandler : IPauseHandler
{
public int Priority => 30;
}
```
### 5. 避免在处理器中抛出异常
处理器异常会被捕获并记录,但不会中断其他处理器:
```csharp
public class SafePauseHandler : IPauseHandler
{
public int Priority => 0;
public void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
try
{
// 可能失败的操作
RiskyOperation();
}
catch (Exception ex)
{
// 记录错误但不抛出
Console.WriteLine($"暂停处理失败: {ex.Message}");
}
}
}
```
### 6. 线程安全考虑
暂停管理器是线程安全的,但处理器回调在主线程执行:
```csharp
public class ThreadSafeUsage
{
private IPauseStackManager _pauseManager;
public void WorkerThread()
{
// ✅ 可以从任何线程调用
Task.Run(() =>
{
var token = _pauseManager.Push("后台任务");
// 执行任务
_pauseManager.Pop(token);
});
}
}
```
### 7. 清理资源
在组件销毁时注销处理器和事件:
```csharp
public class ProperCleanup : IController
{
private IPauseStackManager _pauseManager;
private IPauseHandler _customHandler;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void Initialize()
{
_pauseManager = this.GetUtility<IPauseStackManager>();
_customHandler = new CustomPauseHandler();
_pauseManager.RegisterHandler(_customHandler);
_pauseManager.OnPauseStateChanged += OnPauseChanged;
}
public void Cleanup()
{
_pauseManager.UnregisterHandler(_customHandler);
_pauseManager.OnPauseStateChanged -= OnPauseChanged;
}
private void OnPauseChanged(PauseGroup group, bool isPaused) { }
}
```
## 常见问题
### Q1: 为什么调用 Pop 后游戏还是暂停?
A: 暂停系统使用栈结构,只有当栈为空时才会恢复。检查是否有其他暂停请求:
```csharp
// 调试暂停状态
var depth = _pauseManager.GetPauseDepth();
var reasons = _pauseManager.GetPauseReasons();
Console.WriteLine($"当前暂停深度: {depth}");
Console.WriteLine("暂停原因:");
foreach (var reason in reasons)
{
Console.WriteLine($" - {reason}");
}
```
### Q2: 如何实现"暂停时显示菜单"
A: 使用 Godot 的 `ProcessMode` 或监听暂停事件:
```csharp
public partial class PauseMenu : Control
{
public override void _Ready()
{
// 方案 1: 设置为 Always 模式
ProcessMode = ProcessModeEnum.Always;
Visible = false;
// 方案 2: 监听暂停事件
var pauseManager = this.GetUtility<IPauseStackManager>();
pauseManager.OnPauseStateChanged += (group, isPaused) =>
{
if (group == PauseGroup.Global)
{
Visible = isPaused;
}
};
}
}
```
### Q3: 可以在暂停期间执行某些逻辑吗?
A: 可以,通过检查暂停状态或使用不同的暂停组:
```csharp
public class SelectiveSystem : AbstractSystem
{
protected override void OnInit() { }
public void Update(float deltaTime)
{
var pauseManager = this.GetUtility<IPauseStackManager>();
// 方案 1: 检查特定组
if (!pauseManager.IsPaused(PauseGroup.Gameplay))
{
UpdateGameplay(deltaTime);
}
// UI 始终更新(不检查暂停)
UpdateUI(deltaTime);
}
}
```
### Q4: 如何实现"慢动作"效果?
A: 暂停系统控制是否执行,时间缩放需要使用时间系统:
```csharp
public class SlowMotionController : IController
{
private ITimeProvider _timeProvider;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void EnableSlowMotion()
{
// 使用时间缩放而不是暂停
_timeProvider.TimeScale = 0.3f;
}
public void DisableSlowMotion()
{
_timeProvider.TimeScale = 1.0f;
}
}
```
### Q5: 暂停管理器的性能如何?
A: 暂停管理器使用读写锁优化并发性能:
- 查询操作(`IsPaused`)使用读锁,支持并发
- 修改操作(`Push/Pop`)使用写锁,互斥执行
- 事件通知在锁外执行,避免死锁
- 适合频繁查询、偶尔修改的场景
### Q6: 可以动态添加/移除暂停组吗?
A: 暂停组是枚举类型,不支持动态添加。可以使用自定义组:
```csharp
// 使用预定义的自定义组
_pauseManager.Push("特殊效果", PauseGroup.Custom1);
_pauseManager.Push("天气系统", PauseGroup.Custom2);
_pauseManager.Push("AI 系统", PauseGroup.Custom3);
```
### Q7: 如何处理异步操作中的暂停?
A: 使用 `PauseScope` 配合 `async/await`
```csharp
public class AsyncPauseExample : IController
{
private IPauseStackManager _pauseManager;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public async Task ShowAsyncDialog()
{
using (_pauseManager.PauseScope("异步对话框"))
{
await Task.Delay(1000);
Console.WriteLine("对话框显示中...");
await WaitForUserInput();
}
// 自动恢复
}
}
```
## 架构集成
### 在架构中注册
```csharp
public class GameArchitecture : Architecture<GameArchitecture>
{
protected override void OnRegisterUtility()
{
// 注册暂停管理器
RegisterUtility<IPauseStackManager>(new PauseStackManager());
}
protected override void OnInit()
{
// 注册默认处理器
var pauseManager = GetUtility<IPauseStackManager>();
// Godot 处理器
if (Engine.IsEditorHint() == false)
{
var tree = (GetTree() as SceneTree)!;
pauseManager.RegisterHandler(new GodotPauseHandler(tree));
}
}
}
```
### 与其他系统协同
```csharp
// 与事件系统配合
public class PauseEventBridge : AbstractSystem
{
protected override void OnInit()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
pauseManager.OnPauseStateChanged += (group, isPaused) =>
{
// 发送暂停事件
this.SendEvent(new GamePausedEvent
{
Group = group,
IsPaused = isPaused
});
};
}
}
// 与命令系统配合
public class PauseCommand : AbstractCommand
{
private readonly string _reason;
private readonly PauseGroup _group;
public PauseCommand(string reason, PauseGroup group = PauseGroup.Global)
{
_reason = reason;
_group = group;
}
protected override void OnExecute()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
pauseManager.Push(_reason, _group);
}
}
```
## 相关包
- [`architecture`](./architecture.md) - 架构核心,提供工具注册
- [`utility`](./utility.md) - 工具基类
- [`events`](./events.md) - 事件系统,用于状态通知
- [`lifecycle`](./lifecycle.md) - 生命周期管理
- [`logging`](./logging.md) - 日志系统,用于调试
- [Godot 集成](../godot/index.md) - Godot 引擎集成

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/extensions) - 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) - 完整示例

588
docs/zh-CN/game/data.md Normal file
View File

@ -0,0 +1,588 @@
---
title: 数据与存档系统
description: 数据与存档系统提供了完整的数据持久化解决方案,支持多槽位存档、版本管理和数据迁移。
---
# 数据与存档系统
## 概述
数据与存档系统是 GFramework.Game 中用于管理游戏数据持久化的核心组件。它提供了统一的数据加载和保存接口,支持多槽位存档管理、数据版本控制和自动迁移,让你可以轻松实现游戏存档、设置保存等功能。
通过数据系统,你可以将游戏数据保存到本地存储,支持多个存档槽位,并在数据结构变化时自动进行版本迁移。
**主要特性**
- 统一的数据持久化接口
- 多槽位存档管理
- 数据版本控制和迁移
- 异步加载和保存
- 批量数据操作
- 与存储系统集成
## 核心概念
### 数据接口
`IData` 标记数据类型:
```csharp
public interface IData
{
// 标记接口,用于标识可持久化的数据
}
```
### 数据仓库
`IDataRepository` 提供通用的数据操作:
```csharp
public interface IDataRepository : IUtility
{
Task<T> LoadAsync<T>(IDataLocation location) where T : class, IData, new();
Task SaveAsync<T>(IDataLocation location, T data) where T : class, IData;
Task<bool> ExistsAsync(IDataLocation location);
Task DeleteAsync(IDataLocation location);
Task SaveAllAsync(IEnumerable<(IDataLocation, IData)> dataList);
}
```
### 存档仓库
`ISaveRepository<T>` 专门用于管理游戏存档:
```csharp
public interface ISaveRepository<TSaveData> : IUtility
where TSaveData : class, IData, new()
{
Task<bool> ExistsAsync(int slot);
Task<TSaveData> LoadAsync(int slot);
Task SaveAsync(int slot, TSaveData data);
Task DeleteAsync(int slot);
Task<IReadOnlyList<int>> ListSlotsAsync();
}
```
### 版本化数据
`IVersionedData` 支持数据版本管理:
```csharp
public interface IVersionedData : IData
{
int Version { get; set; }
}
```
## 基本用法
### 定义数据类型
```csharp
using GFramework.Game.Abstractions.data;
// 简单数据
public class PlayerData : IData
{
public string Name { get; set; }
public int Level { get; set; }
public int Experience { get; set; }
}
// 版本化数据
public class SaveData : IVersionedData
{
public int Version { get; set; } = 1;
public PlayerData Player { get; set; }
public DateTime SaveTime { get; set; }
}
```
### 使用存档仓库
```csharp
public class SaveController : IController
{
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public async Task SaveGame(int slot)
{
var saveRepo = this.GetUtility<ISaveRepository<SaveData>>();
// 创建存档数据
var saveData = new SaveData
{
Player = new PlayerData
{
Name = "Player1",
Level = 10,
Experience = 1000
},
SaveTime = DateTime.Now
};
// 保存到指定槽位
await saveRepo.SaveAsync(slot, saveData);
Console.WriteLine($"游戏已保存到槽位 {slot}");
}
public async Task LoadGame(int slot)
{
var saveRepo = this.GetUtility<ISaveRepository<SaveData>>();
// 检查存档是否存在
if (!await saveRepo.ExistsAsync(slot))
{
Console.WriteLine($"槽位 {slot} 不存在存档");
return;
}
// 加载存档
var saveData = await saveRepo.LoadAsync(slot);
Console.WriteLine($"加载存档: {saveData.Player.Name}, 等级 {saveData.Player.Level}");
}
public async Task DeleteSave(int slot)
{
var saveRepo = this.GetUtility<ISaveRepository<SaveData>>();
// 删除存档
await saveRepo.DeleteAsync(slot);
Console.WriteLine($"已删除槽位 {slot} 的存档");
}
}
```
### 注册存档仓库
```csharp
using GFramework.Game.data;
public class GameArchitecture : Architecture
{
protected override void Init()
{
// 获取存储系统
var storage = this.GetUtility<IStorage>();
// 创建存档配置
var saveConfig = new SaveConfiguration
{
SaveRoot = "saves",
SaveSlotPrefix = "slot_",
SaveFileName = "save.json"
};
// 注册存档仓库
var saveRepo = new SaveRepository<SaveData>(storage, saveConfig);
RegisterUtility<ISaveRepository<SaveData>>(saveRepo);
}
}
```
## 高级用法
### 列出所有存档
```csharp
public async Task ShowSaveList()
{
var saveRepo = this.GetUtility<ISaveRepository<SaveData>>();
// 获取所有存档槽位
var slots = await saveRepo.ListSlotsAsync();
Console.WriteLine($"找到 {slots.Count} 个存档:");
foreach (var slot in slots)
{
var saveData = await saveRepo.LoadAsync(slot);
Console.WriteLine($"槽位 {slot}: {saveData.Player.Name}, " +
$"等级 {saveData.Player.Level}, " +
$"保存时间 {saveData.SaveTime}");
}
}
```
### 自动保存
```csharp
public class AutoSaveController : IController
{
private CancellationTokenSource? _autoSaveCts;
public void StartAutoSave(int slot, TimeSpan interval)
{
_autoSaveCts = new CancellationTokenSource();
Task.Run(async () =>
{
while (!_autoSaveCts.Token.IsCancellationRequested)
{
await Task.Delay(interval, _autoSaveCts.Token);
try
{
await SaveGame(slot);
Console.WriteLine("自动保存完成");
}
catch (Exception ex)
{
Console.WriteLine($"自动保存失败: {ex.Message}");
}
}
}, _autoSaveCts.Token);
}
public void StopAutoSave()
{
_autoSaveCts?.Cancel();
_autoSaveCts?.Dispose();
_autoSaveCts = null;
}
private async Task SaveGame(int slot)
{
var saveRepo = this.GetUtility<ISaveRepository<SaveData>>();
var saveData = CreateSaveData();
await saveRepo.SaveAsync(slot, saveData);
}
private SaveData CreateSaveData()
{
// 从游戏状态创建存档数据
return new SaveData();
}
}
```
### 数据版本迁移
```csharp
// 版本 1 的数据
public class SaveDataV1 : IVersionedData
{
public int Version { get; set; } = 1;
public string PlayerName { get; set; }
public int Level { get; set; }
}
// 版本 2 的数据(添加了新字段)
public class SaveDataV2 : IVersionedData
{
public int Version { get; set; } = 2;
public string PlayerName { get; set; }
public int Level { get; set; }
public int Experience { get; set; } // 新增字段
public DateTime LastPlayTime { get; set; } // 新增字段
}
// 数据迁移器
public class SaveDataMigrator
{
public SaveDataV2 Migrate(SaveDataV1 oldData)
{
return new SaveDataV2
{
Version = 2,
PlayerName = oldData.PlayerName,
Level = oldData.Level,
Experience = oldData.Level * 100, // 根据等级计算经验
LastPlayTime = DateTime.Now
};
}
}
// 加载时自动迁移
public async Task<SaveDataV2> LoadWithMigration(int slot)
{
var saveRepo = this.GetUtility<ISaveRepository<SaveDataV2>>();
var data = await saveRepo.LoadAsync(slot);
if (data.Version < 2)
{
// 需要迁移
var oldData = data as SaveDataV1;
var migrator = new SaveDataMigrator();
var newData = migrator.Migrate(oldData);
// 保存迁移后的数据
await saveRepo.SaveAsync(slot, newData);
return newData;
}
return data;
}
```
### 使用数据仓库
```csharp
public class SettingsController : IController
{
public async Task SaveSettings()
{
var dataRepo = this.GetUtility<IDataRepository>();
var settings = new GameSettings
{
MasterVolume = 0.8f,
MusicVolume = 0.6f,
SfxVolume = 0.7f
};
// 定义数据位置
var location = new DataLocation("settings", "game_settings.json");
// 保存设置
await dataRepo.SaveAsync(location, settings);
}
public async Task<GameSettings> LoadSettings()
{
var dataRepo = this.GetUtility<IDataRepository>();
var location = new DataLocation("settings", "game_settings.json");
// 检查是否存在
if (!await dataRepo.ExistsAsync(location))
{
return new GameSettings(); // 返回默认设置
}
// 加载设置
return await dataRepo.LoadAsync<GameSettings>(location);
}
}
```
### 批量保存数据
```csharp
public async Task SaveAllGameData()
{
var dataRepo = this.GetUtility<IDataRepository>();
var dataList = new List<(IDataLocation, IData)>
{
(new DataLocation("player", "profile.json"), playerData),
(new DataLocation("inventory", "items.json"), inventoryData),
(new DataLocation("quests", "progress.json"), questData)
};
// 批量保存
await dataRepo.SaveAllAsync(dataList);
Console.WriteLine("所有数据已保存");
}
```
### 存档备份
```csharp
public async Task BackupSave(int slot)
{
var saveRepo = this.GetUtility<ISaveRepository<SaveData>>();
if (!await saveRepo.ExistsAsync(slot))
{
Console.WriteLine("存档不存在");
return;
}
// 加载原存档
var saveData = await saveRepo.LoadAsync(slot);
// 保存到备份槽位
int backupSlot = slot + 100;
await saveRepo.SaveAsync(backupSlot, saveData);
Console.WriteLine($"存档已备份到槽位 {backupSlot}");
}
public async Task RestoreBackup(int slot)
{
int backupSlot = slot + 100;
var saveRepo = this.GetUtility<ISaveRepository<SaveData>>();
if (!await saveRepo.ExistsAsync(backupSlot))
{
Console.WriteLine("备份不存在");
return;
}
// 加载备份
var backupData = await saveRepo.LoadAsync(backupSlot);
// 恢复到原槽位
await saveRepo.SaveAsync(slot, backupData);
Console.WriteLine($"已从备份恢复到槽位 {slot}");
}
```
## 最佳实践
1. **使用版本化数据**:为存档数据实现 `IVersionedData`
```csharp
✓ public class SaveData : IVersionedData { public int Version { get; set; } = 1; }
✗ public class SaveData : IData { } // 无法进行版本管理
```
2. **定期自动保存**:避免玩家数据丢失
```csharp
// 每 5 分钟自动保存
StartAutoSave(currentSlot, TimeSpan.FromMinutes(5));
```
3. **保存前验证数据**:确保数据完整性
```csharp
public async Task SaveGame(int slot)
{
var saveData = CreateSaveData();
if (!ValidateSaveData(saveData))
{
throw new InvalidOperationException("存档数据无效");
}
await saveRepo.SaveAsync(slot, saveData);
}
```
4. **处理保存失败**:使用 try-catch 捕获异常
```csharp
try
{
await saveRepo.SaveAsync(slot, saveData);
}
catch (Exception ex)
{
Logger.Error($"保存失败: {ex.Message}");
ShowErrorMessage("保存失败,请重试");
}
```
5. **提供多个存档槽位**:让玩家可以管理多个存档
```csharp
// 支持 10 个存档槽位
for (int i = 1; i <= 10; i++)
{
if (await saveRepo.ExistsAsync(i))
{
ShowSaveSlot(i);
}
}
```
6. **在关键时刻保存**:场景切换、关卡完成等
```csharp
public async Task OnLevelComplete()
{
// 关卡完成时自动保存
await SaveGame(currentSlot);
}
```
## 常见问题
### 问题:如何实现多个存档槽位?
**解答**
使用 `ISaveRepository<T>` 的槽位参数:
```csharp
// 保存到不同槽位
await saveRepo.SaveAsync(1, saveData); // 槽位 1
await saveRepo.SaveAsync(2, saveData); // 槽位 2
await saveRepo.SaveAsync(3, saveData); // 槽位 3
```
### 问题:如何处理数据版本升级?
**解答**
实现 `IVersionedData` 并在加载时检查版本:
```csharp
var data = await saveRepo.LoadAsync(slot);
if (data.Version < CurrentVersion)
{
data = MigrateData(data);
await saveRepo.SaveAsync(slot, data);
}
```
### 问题:存档数据保存在哪里?
**解答**
由存储系统决定,通常在:
- Windows: `%AppData%/GameName/saves/`
- Linux: `~/.local/share/GameName/saves/`
- macOS: `~/Library/Application Support/GameName/saves/`
### 问题:如何实现云存档?
**解答**
实现自定义的 `IStorage`,将数据保存到云端:
```csharp
public class CloudStorage : IStorage
{
public async Task WriteAsync(string path, byte[] data)
{
await UploadToCloud(path, data);
}
public async Task<byte[]> ReadAsync(string path)
{
return await DownloadFromCloud(path);
}
}
```
### 问题:如何加密存档数据?
**解答**
在保存和加载时进行加密/解密:
```csharp
public async Task SaveEncrypted(int slot, SaveData data)
{
var json = JsonSerializer.Serialize(data);
var encrypted = Encrypt(json);
await storage.WriteAsync(path, encrypted);
}
public async Task<SaveData> LoadEncrypted(int slot)
{
var encrypted = await storage.ReadAsync(path);
var json = Decrypt(encrypted);
return JsonSerializer.Deserialize<SaveData>(json);
}
```
### 问题:存档损坏怎么办?
**解答**
实现备份和恢复机制:
```csharp
public async Task SaveWithBackup(int slot, SaveData data)
{
// 先备份旧存档
if (await saveRepo.ExistsAsync(slot))
{
var oldData = await saveRepo.LoadAsync(slot);
await saveRepo.SaveAsync(slot + 100, oldData);
}
// 保存新存档
await saveRepo.SaveAsync(slot, data);
}
```
## 相关文档
- [设置系统](/zh-CN/game/setting) - 游戏设置管理
- [场景系统](/zh-CN/game/scene) - 场景切换时保存
- [存档系统实现教程](/zh-CN/tutorials/save-system) - 完整示例
- [Godot 集成](/zh-CN/godot/index) - Godot 中的数据管理

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) - 场景切换时保存数据

View File

@ -0,0 +1,756 @@
---
title: 序列化系统
description: 序列化系统提供了统一的对象序列化和反序列化接口,支持 JSON 格式和运行时类型处理。
---
# 序列化系统
## 概述
序列化系统是 GFramework.Game 中用于对象序列化和反序列化的核心组件。它提供了统一的序列化接口,支持将对象转换为字符串格式(如
JSON进行存储或传输并能够将字符串数据还原为对象。
序列化系统与数据存储、配置管理、存档系统等模块深度集成,为游戏数据的持久化提供了基础支持。
**主要特性**
- 统一的序列化接口
- JSON 格式支持
- 运行时类型序列化
- 泛型和非泛型 API
- 与存储系统无缝集成
- 类型安全的反序列化
## 核心概念
### 序列化器接口
`ISerializer` 定义了基本的序列化操作:
```csharp
public interface ISerializer : IUtility
{
// 将对象序列化为字符串
string Serialize&lt;T&gt;(T value);
// 将字符串反序列化为对象
T Deserialize&lt;T&gt;(string data);
}
```
### 运行时类型序列化器
`IRuntimeTypeSerializer` 扩展了基本接口,支持运行时类型处理:
```csharp
public interface IRuntimeTypeSerializer : ISerializer
{
// 使用运行时类型序列化对象
string Serialize(object obj, Type type);
// 使用运行时类型反序列化对象
object Deserialize(string data, Type type);
}
```
### JSON 序列化器
`JsonSerializer` 是基于 Newtonsoft.Json 的实现:
```csharp
public sealed class JsonSerializer : IRuntimeTypeSerializer
{
string Serialize&lt;T&gt;(T value);
T Deserialize&lt;T&gt;(string data);
string Serialize(object obj, Type type);
object Deserialize(string data, Type type);
}
```
## 基本用法
### 注册序列化器
在架构中注册序列化器:
```csharp
using GFramework.Core.Abstractions.serializer;
using GFramework.Game.serializer;
public class GameArchitecture : Architecture
{
protected override void Init()
{
// 注册 JSON 序列化器
var jsonSerializer = new JsonSerializer();
RegisterUtility<ISerializer>(jsonSerializer);
RegisterUtility<IRuntimeTypeSerializer>(jsonSerializer);
}
}
```
### 序列化对象
使用泛型 API 序列化对象:
```csharp
public class PlayerData
{
public string Name { get; set; }
public int Level { get; set; }
public int Experience { get; set; }
}
public class SaveController : IController
{
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void SavePlayer()
{
var serializer = this.GetUtility<ISerializer>();
var player = new PlayerData
{
Name = "Player1",
Level = 10,
Experience = 1000
};
// 序列化为 JSON 字符串
string json = serializer.Serialize(player);
Console.WriteLine(json);
// 输出: {"Name":"Player1","Level":10,"Experience":1000}
}
}
```
### 反序列化对象
从字符串还原对象:
```csharp
public void LoadPlayer()
{
var serializer = this.GetUtility<ISerializer>();
string json = "{\"Name\":\"Player1\",\"Level\":10,\"Experience\":1000}";
// 反序列化为对象
var player = serializer.Deserialize<PlayerData>(json);
Console.WriteLine($"玩家: {player.Name}, 等级: {player.Level}");
}
```
### 运行时类型序列化
处理不确定类型的对象:
```csharp
public void SerializeRuntimeType()
{
var serializer = this.GetUtility<IRuntimeTypeSerializer>();
object data = new PlayerData { Name = "Player1", Level = 10 };
Type dataType = data.GetType();
// 使用运行时类型序列化
string json = serializer.Serialize(data, dataType);
// 使用运行时类型反序列化
object restored = serializer.Deserialize(json, dataType);
var player = restored as PlayerData;
Console.WriteLine($"玩家: {player?.Name}");
}
```
## 高级用法
### 与存储系统集成
序列化器与存储系统配合使用:
```csharp
using GFramework.Core.Abstractions.storage;
using GFramework.Game.storage;
public class DataManager : IController
{
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public async Task SaveData()
{
var serializer = this.GetUtility<ISerializer>();
var storage = this.GetUtility<IStorage>();
var gameData = new GameData
{
Score = 1000,
Coins = 500
};
// 序列化数据
string json = serializer.Serialize(gameData);
// 写入存储
await storage.WriteAsync("game_data", json);
}
public async Task<GameData> LoadData()
{
var serializer = this.GetUtility<ISerializer>();
var storage = this.GetUtility<IStorage>();
// 从存储读取
string json = await storage.ReadAsync<string>("game_data");
// 反序列化数据
return serializer.Deserialize<GameData>(json);
}
}
```
### 序列化复杂对象
处理嵌套和集合类型:
```csharp
public class InventoryData
{
public List<ItemData> Items { get; set; }
public Dictionary<string, int> Resources { get; set; }
}
public class ItemData
{
public string Id { get; set; }
public string Name { get; set; }
public int Quantity { get; set; }
}
public void SerializeComplexData()
{
var serializer = this.GetUtility<ISerializer>();
var inventory = new InventoryData
{
Items = new List<ItemData>
{
new ItemData { Id = "sword_01", Name = "铁剑", Quantity = 1 },
new ItemData { Id = "potion_hp", Name = "生命药水", Quantity = 5 }
},
Resources = new Dictionary<string, int>
{
{ "gold", 1000 },
{ "wood", 500 }
}
};
// 序列化复杂对象
string json = serializer.Serialize(inventory);
// 反序列化
var restored = serializer.Deserialize<InventoryData>(json);
Console.WriteLine($"物品数量: {restored.Items.Count}");
Console.WriteLine($"金币: {restored.Resources["gold"]}");
}
```
### 处理多态类型
序列化继承层次结构:
```csharp
public abstract class EntityData
{
public string Id { get; set; }
public string Type { get; set; }
}
public class PlayerEntityData : EntityData
{
public int Level { get; set; }
public int Experience { get; set; }
}
public class EnemyEntityData : EntityData
{
public int Health { get; set; }
public int Damage { get; set; }
}
public void SerializePolymorphic()
{
var serializer = this.GetUtility<IRuntimeTypeSerializer>();
// 创建不同类型的实体
EntityData player = new PlayerEntityData
{
Id = "player_1",
Type = "Player",
Level = 10,
Experience = 1000
};
EntityData enemy = new EnemyEntityData
{
Id = "enemy_1",
Type = "Enemy",
Health = 100,
Damage = 20
};
// 使用运行时类型序列化
string playerJson = serializer.Serialize(player, player.GetType());
string enemyJson = serializer.Serialize(enemy, enemy.GetType());
// 根据类型反序列化
var restoredPlayer = serializer.Deserialize(playerJson, typeof(PlayerEntityData));
var restoredEnemy = serializer.Deserialize(enemyJson, typeof(EnemyEntityData));
}
```
### 自定义序列化逻辑
虽然 GFramework 使用 Newtonsoft.Json但你可以通过特性控制序列化行为
```csharp
using Newtonsoft.Json;
public class CustomData
{
// 忽略此属性
[JsonIgnore]
public string InternalId { get; set; }
// 使用不同的属性名
[JsonProperty("player_name")]
public string Name { get; set; }
// 仅在值不为 null 时序列化
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string? OptionalField { get; set; }
// 格式化日期
[JsonProperty("created_at")]
[JsonConverter(typeof(IsoDateTimeConverter))]
public DateTime CreatedAt { get; set; }
}
```
### 批量序列化
处理多个对象的序列化:
```csharp
public async Task SaveMultipleData()
{
var serializer = this.GetUtility<ISerializer>();
var storage = this.GetUtility<IStorage>();
var dataList = new Dictionary<string, object>
{
{ "player", new PlayerData { Name = "Player1", Level = 10 } },
{ "inventory", new InventoryData { Items = new List<ItemData>() } },
{ "settings", new SettingsData { Volume = 0.8f } }
};
// 批量序列化和保存
foreach (var (key, data) in dataList)
{
string json = serializer.Serialize(data);
await storage.WriteAsync(key, json);
}
Console.WriteLine($"已保存 {dataList.Count} 个数据文件");
}
```
### 错误处理
处理序列化和反序列化错误:
```csharp
public void SafeDeserialize()
{
var serializer = this.GetUtility<ISerializer>();
string json = "{\"Name\":\"Player1\",\"Level\":\"invalid\"}"; // 错误的数据
try
{
var player = serializer.Deserialize<PlayerData>(json);
}
catch (ArgumentException ex)
{
Console.WriteLine($"反序列化失败: {ex.Message}");
// 返回默认值或重新尝试
}
catch (JsonException ex)
{
Console.WriteLine($"JSON 格式错误: {ex.Message}");
}
}
public PlayerData DeserializeWithFallback(string json)
{
var serializer = this.GetUtility<ISerializer>();
try
{
return serializer.Deserialize<PlayerData>(json);
}
catch
{
// 返回默认数据
return new PlayerData
{
Name = "DefaultPlayer",
Level = 1,
Experience = 0
};
}
}
```
### 版本兼容性
处理数据结构变化:
```csharp
// 旧版本数据
public class PlayerDataV1
{
public string Name { get; set; }
public int Level { get; set; }
}
// 新版本数据(添加了新字段)
public class PlayerDataV2
{
public string Name { get; set; }
public int Level { get; set; }
public int Experience { get; set; } = 0; // 新增字段,提供默认值
public DateTime LastLogin { get; set; } = DateTime.Now; // 新增字段
}
public PlayerDataV2 LoadWithMigration(string json)
{
var serializer = this.GetUtility<ISerializer>();
try
{
// 尝试加载新版本
return serializer.Deserialize<PlayerDataV2>(json);
}
catch
{
// 如果失败,尝试加载旧版本并迁移
var oldData = serializer.Deserialize<PlayerDataV1>(json);
return new PlayerDataV2
{
Name = oldData.Name,
Level = oldData.Level,
Experience = oldData.Level * 100, // 根据等级计算经验
LastLogin = DateTime.Now
};
}
}
```
## 最佳实践
1. **使用接口而非具体类型**:依赖 `ISerializer` 接口
```csharp
✓ var serializer = this.GetUtility<ISerializer>();
✗ var serializer = new JsonSerializer(); // 避免直接实例化
```
2. **为数据类提供默认值**:确保反序列化的健壮性
```csharp
public class GameData
{
public string Name { get; set; } = "Default";
public int Score { get; set; } = 0;
public List<string> Items { get; set; } = new();
}
```
3. **处理反序列化异常**:避免程序崩溃
```csharp
try
{
var data = serializer.Deserialize<GameData>(json);
}
catch (Exception ex)
{
Logger.Error($"反序列化失败: {ex.Message}");
return GetDefaultData();
}
```
4. **避免序列化敏感数据**:使用 `[JsonIgnore]` 标记
```csharp
public class UserData
{
public string Username { get; set; }
[JsonIgnore]
public string Password { get; set; } // 不序列化密码
}
```
5. **使用运行时类型处理多态**:保持类型信息
```csharp
var serializer = this.GetUtility<IRuntimeTypeSerializer>();
string json = serializer.Serialize(obj, obj.GetType());
```
6. **验证反序列化的数据**:确保数据完整性
```csharp
var data = serializer.Deserialize<GameData>(json);
if (string.IsNullOrEmpty(data.Name) || data.Score < 0)
{
throw new InvalidDataException("数据验证失败");
}
```
## 性能优化
### 减少序列化开销
```csharp
// 避免频繁序列化大对象
public class CachedSerializer
{
private string? _cachedJson;
private GameData? _cachedData;
public string GetJson(GameData data)
{
if (_cachedData == data && _cachedJson != null)
{
return _cachedJson;
}
var serializer = GetSerializer();
_cachedJson = serializer.Serialize(data);
_cachedData = data;
return _cachedJson;
}
}
```
### 异步序列化
```csharp
public async Task SaveDataAsync()
{
var serializer = this.GetUtility<ISerializer>();
var storage = this.GetUtility<IStorage>();
var data = GetLargeData();
// 在后台线程序列化
string json = await Task.Run(() => serializer.Serialize(data));
// 异步写入存储
await storage.WriteAsync("large_data", json);
}
```
### 分块序列化
```csharp
public async Task SaveLargeDataset()
{
var serializer = this.GetUtility<ISerializer>();
var storage = this.GetUtility<IStorage>();
var largeDataset = GetLargeDataset();
// 分块保存
const int chunkSize = 100;
for (int i = 0; i < largeDataset.Count; i += chunkSize)
{
var chunk = largeDataset.Skip(i).Take(chunkSize).ToList();
string json = serializer.Serialize(chunk);
await storage.WriteAsync($"data_chunk_{i / chunkSize}", json);
}
}
```
## 常见问题
### 问题:如何序列化循环引用的对象?
**解答**
Newtonsoft.Json 默认不支持循环引用,需要配置:
```csharp
// 注意GFramework 的 JsonSerializer 使用默认设置
// 如需处理循环引用,避免创建循环引用的数据结构
// 或使用 [JsonIgnore] 打破循环
public class Node
{
public string Name { get; set; }
public List<Node> Children { get; set; }
[JsonIgnore] // 忽略父节点引用,避免循环
public Node? Parent { get; set; }
}
```
### 问题:序列化后的 JSON 太大怎么办?
**解答**
使用压缩或分块存储:
```csharp
public async Task SaveCompressed()
{
var serializer = this.GetUtility<ISerializer>();
var storage = this.GetUtility<IStorage>();
var data = GetLargeData();
string json = serializer.Serialize(data);
// 压缩 JSON
byte[] compressed = Compress(json);
// 保存压缩数据
await storage.WriteAsync("data_compressed", compressed);
}
private byte[] Compress(string text)
{
using var output = new MemoryStream();
using (var gzip = new GZipStream(output, CompressionMode.Compress))
using (var writer = new StreamWriter(gzip))
{
writer.Write(text);
}
return output.ToArray();
}
```
### 问题:如何处理不同平台的序列化差异?
**解答**
使用平台无关的数据类型:
```csharp
public class CrossPlatformData
{
// 使用 string 而非 DateTime避免时区问题
public string CreatedAt { get; set; } = DateTime.UtcNow.ToString("O");
// 使用 double 而非 float精度一致
public double Score { get; set; }
// 明确指定编码
public string Text { get; set; }
}
```
### 问题:反序列化失败时如何恢复?
**解答**
实现备份和恢复机制:
```csharp
public async Task<GameData> LoadWithBackup(string key)
{
var serializer = this.GetUtility<ISerializer>();
var storage = this.GetUtility<IStorage>();
try
{
// 尝试加载主数据
string json = await storage.ReadAsync<string>(key);
return serializer.Deserialize<GameData>(json);
}
catch
{
// 尝试加载备份
try
{
string backupJson = await storage.ReadAsync<string>($"{key}_backup");
return serializer.Deserialize<GameData>(backupJson);
}
catch
{
// 返回默认数据
return new GameData();
}
}
}
```
### 问题:如何加密序列化的数据?
**解答**
在序列化后加密:
```csharp
public async Task SaveEncrypted(string key, GameData data)
{
var serializer = this.GetUtility<ISerializer>();
var storage = this.GetUtility<IStorage>();
// 序列化
string json = serializer.Serialize(data);
// 加密
byte[] encrypted = EncryptString(json);
// 保存
await storage.WriteAsync(key, encrypted);
}
public async Task<GameData> LoadEncrypted(string key)
{
var serializer = this.GetUtility<ISerializer>();
var storage = this.GetUtility<IStorage>();
// 读取
byte[] encrypted = await storage.ReadAsync<byte[]>(key);
// 解密
string json = DecryptToString(encrypted);
// 反序列化
return serializer.Deserialize<GameData>(json);
}
```
### 问题:序列化器是线程安全的吗?
**解答**
`JsonSerializer` 本身是线程安全的,但建议通过架构的 Utility 系统访问:
```csharp
// 线程安全的访问方式
public async Task ParallelSave()
{
var tasks = Enumerable.Range(0, 10).Select(async i =>
{
var serializer = this.GetUtility<ISerializer>();
var data = new GameData { Score = i };
string json = serializer.Serialize(data);
await SaveToStorage($"data_{i}", json);
});
await Task.WhenAll(tasks);
}
```
## 相关文档
- [数据与存档系统](/zh-CN/game/data) - 数据持久化
- [存储系统](/zh-CN/game/storage) - 文件存储
- [设置系统](/zh-CN/game/setting) - 设置数据序列化
- [Utility 系统](/zh-CN/core/utility) - 工具类注册

735
docs/zh-CN/game/storage.md Normal file
View File

@ -0,0 +1,735 @@
---
title: 存储系统详解
description: 存储系统提供了灵活的文件存储和作用域隔离功能,支持跨平台数据持久化。
---
# 存储系统详解
## 概述
存储系统是 GFramework.Game 中用于管理文件存储的核心组件。它提供了统一的存储接口,支持键值对存储、作用域隔离、目录操作等功能,让你可以轻松实现游戏数据的持久化。
存储系统采用装饰器模式设计,通过 `IStorage` 接口定义统一的存储操作,`FileStorage` 提供基于文件系统的实现,`ScopedStorage`
提供作用域隔离功能。
**主要特性**
- 统一的键值对存储接口
- 基于文件系统的持久化
- 作用域隔离和命名空间管理
- 线程安全的并发访问
- 支持同步和异步操作
- 目录和文件列举功能
- 路径安全防护
- 跨平台支持(包括 Godot
## 核心概念
### 存储接口
`IStorage` 定义了统一的存储操作:
```csharp
public interface IStorage : IUtility
{
// 检查键是否存在
bool Exists(string key);
Task<bool> ExistsAsync(string key);
// 读取数据
T Read&lt;T&gt;(string key);
T Read&lt;T&gt;(string key, T defaultValue);
Task&lt;T&gt; ReadAsync&lt;T&gt;(string key);
// 写入数据
void Write&lt;T&gt;(string key, T value);
Task WriteAsync&lt;T&gt;(string key, T value);
// 删除数据
void Delete(string key);
Task DeleteAsync(string key);
// 目录操作
Task<IReadOnlyList<string>> ListDirectoriesAsync(string path = "");
Task<IReadOnlyList<string>> ListFilesAsync(string path = "");
Task<bool> DirectoryExistsAsync(string path);
Task CreateDirectoryAsync(string path);
}
```
### 文件存储
`FileStorage` 是基于文件系统的存储实现:
- 将数据序列化后保存为文件
- 支持自定义文件扩展名(默认 `.dat`
- 使用细粒度锁保证线程安全
- 自动创建目录结构
- 防止路径遍历攻击
### 作用域存储
`ScopedStorage` 提供命名空间隔离:
- 为所有键添加前缀
- 支持嵌套作用域
- 透明包装底层存储
- 实现逻辑分组
### 存储类型
`StorageKinds` 枚举定义了不同的存储方式:
```csharp
[Flags]
public enum StorageKinds
{
None = 0,
Local = 1 << 0, // 本地文件系统
Memory = 1 << 1, // 内存存储
Remote = 1 << 2, // 远程存储
Database = 1 << 3 // 数据库存储
}
```
## 基本用法
### 创建文件存储
```csharp
using GFramework.Game.storage;
using GFramework.Game.serializer;
// 创建序列化器
var serializer = new JsonSerializer();
// 创Windows 示例)
var storage = new FileStorage(@"C:\MyGame\Data", serializer);
// 或使用自定义扩展名
var storage = new FileStorage(@"C:\MyGame\Data", serializer, ".json");
```
### 写入和读取数据
```csharp
// 写入简单类型
storage.Write("player_score", 1000);
storage.Write("player_name", "Alice");
// 写入复杂对象
var settings = new GameSettings
{
Volume = 0.8f,
Difficulty = "Hard",
Language = "zh-CN"
};
storage.Write("settings", settings);
// 读取数据
int score = storage.Read<int>("player_score");
string name = storage.Read<string>("player_name");
var loadedSettings = storage.Read<GameSettings>("settings");
// 读取数据(带默认值)
int highScore = storage.Read("high_score", 0);
```
### 异步操作
```csharp
// 异步写入
await storage.WriteAsync("player_level", 10);
// 异步读取
int level = await storage.ReadAsync<int>("player_level");
// 异步检查存在
bool exists = await storage.ExistsAsync("player_level");
// 异步删除
await storage.DeleteAsync("player_level");
```
### 检查和删除
```csharp
// 检查键是否存在
if (storage.Exists("player_score"))
{
Console.WriteLine("存档存在");
}
// 删除数据
storage.Delete("player_score");
// 异步检查
bool exists = await storage.ExistsAsync("player_score");
```
### 使用层级键
```csharp
// 使用 / 分隔符创建层级结构
storage.Write("player/profile/name", "Alice");
storage.Write("player/profile/level", 10);
storage.Write("player/inventory/gold", 1000);
// 文件结构:
// Data/
// player/
// profile/
// name.dat
// level.dat
// inventory/
// gold.dat
// 读取层级数据
string name = storage.Read<string>("player/profile/name");
int gold = storage.Read<int>("player/inventory/gold");
```
## 作用域存储
### 创建作用域存储
```csharp
using GFramework.Game.storage;
// 基于文件存储创建作用域存储
var baseStorage = new FileStorage(@"C:\MyGame\Data", serializer);
var playerStorage = new ScopedStorage(baseStorage, "player");
// 所有操作都会添加 "player/" 前缀
playerStorage.Write("name", "Alice"); // 实际存储为 "player/name.dat"
playerStorage.Write("level", 10); // 实际存储为 "player/level.dat"
// 读取时也使用相同的前缀
string name = playerStorage.Read<string>("name"); // 从 "player/name.dat" 读取
```
### 嵌套作用域
```csharp
// 创建嵌套作用域
var settingsStorage = new ScopedStorage(baseStorage, "settings");
var graphicsStorage = new ScopedStorage(settingsStorage, "graphics");
// 前缀变为 "settings/graphics/"
graphicsStorage.Write("resolution", "1920x1080");
// 实际存储为 "settings/graphics/resolution.dat"
// 或使用 Scope 方法
var audioStorage = settingsStorage.Scope("audio");
audioStorage.Write("volume", 0.8f);
// 实际存储为 "settings/audio/volume.dat"
```
### 多作用域隔离
```csharp
// 创建不同作用域的存储
var playerStorage = new ScopedStorage(baseStorage, "player");
var gameStorage = new ScopedStorage(baseStorage, "game");
var settingsStorage = new ScopedStorage(baseStorage, "settings");
// 在不同作用域中使用相同的键不会冲突
playerStorage.Write("level", 5); // player/level.dat
gameStorage.Write("level", "forest_area_1"); // game/level.dat
settingsStorage.Write("level", "high"); // settings/level.dat
// 读取时各自独立
int playerLevel = playerStorage.Read<int>("level"); // 5
string gameLevel = gameStorage.Read<string>("level"); // "forest_area_1"
string settingsLevel = settingsStorage.Read<string>("level"); // "high"
```
## 高级用法
### 目录操作
```csharp
// 列举子目录
var directories = await storage.ListDirectoriesAsync("player");
foreach (var dir in directories)
{
Console.WriteLine($"目录: {dir}");
}
// 列举文件
var files = await storage.ListFilesAsync("player/inventory");
foreach (var file in files)
{
Console.WriteLine($"文件: {file}");
}
// 检查目录是否存在
bool exists = await storage.DirectoryExistsAsync("player/quests");
// 创建目录
await storage.CreateDirectoryAsync("player/achievements");
```
### 批量操作
```csharp
public async Task SaveAllPlayerData(PlayerData player)
{
var playerStorage = new ScopedStorage(baseStorage, $"player_{player.Id}");
// 批量写入
var tasks = new List<Task>
{
playerStorage.WriteAsync("profile", player.Profile),
playerStorage.WriteAsync("inventory", player.Inventory),
playerStorage.WriteAsync("quests", player.Quests),
playerStorage.WriteAsync("achievements", player.Achievements)
};
await Task.WhenAll(tasks);
Console.WriteLine("所有玩家数据已保存");
}
public async Task<PlayerData> LoadAllPlayerData(int playerId)
{
var playerStorage = new ScopedStorage(baseStorage, $"player_{playerId}");
// 批量读取
var tasks = new[]
{
playerStorage.ReadAsync<Profile>("profile"),
playerStorage.ReadAsync<Inventory>("inventory"),
playerStorage.ReadAsync<QuestData>("quests"),
playerStorage.ReadAsync<Achievements>("achievements")
};
await Task.WhenAll(tasks);
return new PlayerData
{
Id = playerId,
Profile = tasks[0].Result,
Inventory = tasks[1].Result,
Quests = tasks[2].Result,
Achievements = tasks[3].Result
};
}
```
### 存储迁移
```csharp
public async Task MigrateStorage(IStorage oldStorage, IStorage newStorage, string path = "")
{
// 列举所有文件
var files = await oldStorage.ListFilesAsync(path);
foreach (var file in files)
{
var key = string.IsNullOrEmpty(path) ? file : $"{path}/{file}";
// 读取旧数据
var data = await oldStorage.ReadAsync<object>(key);
// 写入新存储
await newStorage.WriteAsync(key, data);
Console.WriteLine($"已迁移: {key}");
}
// 递归处理子目录
var directories = await oldStorage.ListDirectoriesAsync(path);
foreach (var dir in directories)
{
var subPath = string.IsNullOrEmpty(path) ? dir : $"{path}/{dir}";
await MigrateStorage(oldStorage, newStorage, subPath);
}
}
```
### 存储备份
```csharp
public class StorageBackupSystem
{
private readonly IStorage _storage;
private readonly string _backupPrefix = "backup";
public async Task CreateBackup(string sourcePath)
{
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
var backupPath = $"{_backupPrefix}/{timestamp}";
await CopyDirectory(sourcePath, backupPath);
Console.WriteLine($"备份已创建: {backupPath}");
}
public async Task RestoreBackup(string backupName, string targetPath)
{
var backupPath = $"{_backupPrefix}/{backupName}";
if (!await _storage.DirectoryExistsAsync(backupPath))
{
throw new DirectoryNotFoundException($"备份不存在: {backupName}");
}
await CopyDirectory(backupPath, targetPath);
Console.WriteLine($"已从备份恢复: {backupName}");
}
private async Task CopyDirectory(string source, string target)
{
var files = await _storage.ListFilesAsync(source);
foreach (var file in files)
{
var sourceKey = $"{source}/{file}";
var targetKey = $"{target}/{file}";
var data = await _storage.ReadAsync<object>(sourceKey);
await _storage.WriteAsync(targetKey, data);
}
var directories = await _storage.ListDirectoriesAsync(source);
foreach (var dir in directories)
{
await CopyDirectory($"{source}/{dir}", $"{target}/{dir}");
}
}
}
```
### 缓存层
```csharp
public class CachedStorage : IStorage
{
private readonly IStorage _innerStorage;
private readonly ConcurrentDictionary<string, object> _cache = new();
public CachedStorage(IStorage innerStorage)
{
_innerStorage = innerStorage;
}
public T Read&lt;T&gt;(string key)
{
// 先从缓存读取
if (_cache.TryGetValue(key, out var cached))
{
return (T)cached;
}
// 从存储读取并缓存
var value = _innerStorage.Read&lt;T&gt;(key);
_cache[key] = value;
return value;
}
public void Write&lt;T&gt;(string key, T value)
{
// 写入存储
_innerStorage.Write(key, value);
// 更新缓存
_cache[key] = value;
}
public void Delete(string key)
{
_innerStorage.Delete(key);
_cache.TryRemove(key, out _);
}
public void ClearCache()
{
_cache.Clear();
}
}
```
## Godot 集成
### 使用 Godot 文件存储
```csharp
using GFramework.Godot.storage;
// 创建 Godot 文件存储
var storage = new GodotFileStorage(serializer);
// 使用 user:// 路径(用户数据目录)
storage.Write("user://saves/slot1.dat", saveData);
var data = storage.Read<SaveData>("user://saves/slot1.dat");
// 使用 res:// 路径(资源目录,只读)
var config = storage.Read<Config>("res://config/default.json");
// 普通文件路径也支持
storage.Write("/tmp/temp_data.dat", tempData);
```
### Godot 路径说明
```csharp
// user:// - 用户数据目录
// Windows: %APPDATA%/Godot/app_userdata/[project_name]
// Linux: ~/.local/share/godot/app_userdata/[project_name]
// macOS: ~/Library/Application Support/Godot/app_userdata/[project_name]
storage.Write("user://save.dat", data);
// res:// - 项目资源目录(只读)
var config = storage.Read<Config>("res://data/config.json");
// 绝对路径
storage.Write("/home/user/game/data.dat", data);
```
## 最佳实践
1. **使用作用域隔离不同类型的数据**
```csharp
✓ var playerStorage = new ScopedStorage(baseStorage, "player");
✓ var settingsStorage = new ScopedStorage(baseStorage, "settings");
✗ storage.Write("player_name", name); // 不使用作用域
```
2. **使用异步操作避免阻塞**
```csharp
✓ await storage.WriteAsync("data", value);
✗ storage.Write("data", value); // 在 UI 线程中同步操作
```
3. **读取时提供默认值**
```csharp
✓ int score = storage.Read("score", 0);
✗ int score = storage.Read<int>("score"); // 键不存在时抛异常
```
4. **使用层级键组织数据**
```csharp
✓ storage.Write("player/inventory/gold", 1000);
✗ storage.Write("player_inventory_gold", 1000);
```
5. **处理存储异常**
```csharp
try
{
await storage.WriteAsync("data", value);
}
catch (IOException ex)
{
Logger.Error($"存储失败: {ex.Message}");
ShowErrorMessage("保存失败,请检查磁盘空间");
}
```
6. **定期清理过期数据**
```csharp
public async Task CleanupOldData(TimeSpan maxAge)
{
var files = await storage.ListFilesAsync("temp");
foreach (var file in files)
{
var data = await storage.ReadAsync<TimestampedData>($"temp/{file}");
if (DateTime.Now - data.Timestamp > maxAge)
{
await storage.DeleteAsync($"temp/{file}");
}
}
}
```
7. **使用合适的序列化器**
```csharp
// JSON - 可读性好,适合配置文件
var jsonStorage = new FileStorage(path, new JsonSerializer(), ".json");
// 二进制 - 性能好,适合大量数据
var binaryStorage = new FileStorage(path, new BinarySerializer(), ".dat");
```
## 常见问题
### 问题:如何实现跨平台存储路径?
**解答**
使用 `Environment.GetFolderPath` 获取平台特定路径:
```csharp
public static string GetStoragePath()
{
var appData = Environment.GetFolderPath(
Environment.SpecialFolder.ApplicationData);
return Path.Combine(appData, "MyGame", "Data");
}
var storage = new FileStorage(GetStoragePath(), serializer);
```
### 问题:存储系统是否线程安全?
**解答**
是的,`FileStorage` 使用细粒度锁机制保证线程安全:
```csharp
// 不同键的操作可以并发执行
Task.Run(() => storage.Write("key1", value1));
Task.Run(() => storage.Write("key2", value2));
// 相同键的操作会串行化
Task.Run(() => storage.Write("key", value1));
Task.Run(() => storage.Write("key", value2)); // 等待第一个完成
```
### 问题:如何实现存储加密?
**解答**
创建加密存储包装器:
```csharp
public class EncryptedStorage : IStorage
{
private readonly IStorage _innerStorage;
private readonly IEncryption _encryption;
public void Write&lt;T&gt;(string key, T value)
{
var json = JsonSerializer.Serialize(value);
var encrypted = _encryption.Encrypt(json);
_innerStorage.Write(key, encrypted);
}
public T Read&lt;T&gt;(string key)
{
var encrypted = _innerStorage.Read<byte[]>(key);
var json = _encryption.Decrypt(encrypted);
return JsonSerializer.Deserialize&lt;T&gt;(json);
}
}
```
### 问题:如何限制存储大小?
**解答**
实现配额管理:
```csharp
public class QuotaStorage : IStorage
{
private readonly IStorage _innerStorage;
private readonly long _maxSize;
private long _currentSize;
public void Write&lt;T&gt;(string key, T value)
{
var data = Serialize(value);
var size = data.Length;
if (_currentSize + size > _maxSize)
{
throw new InvalidOperationException("存储配额已满");
}
_innerStorage.Write(key, value);
_currentSize += size;
}
}
```
### 问题:如何实现存储压缩?
**解答**
使用压缩序列化器:
```csharp
public class CompressedSerializer : ISerializer
{
private readonly ISerializer _innerSerializer;
public string Serialize&lt;T&gt;(T value)
{
var json = _innerSerializer.Serialize(value);
var bytes = Encoding.UTF8.GetBytes(json);
var compressed = Compress(bytes);
return Convert.ToBase64String(compressed);
}
public T Deserialize&lt;T&gt;(string data)
{
var compressed = Convert.FromBase64String(data);
var bytes = Decompress(compressed);
var json = Encoding.UTF8.GetString(bytes);
return _innerSerializer.Deserialize&lt;T&gt;(json);
}
private byte[] Compress(byte[] data)
{
using var output = new MemoryStream();
using (var gzip = new GZipStream(output, CompressionMode.Compress))
{
gzip.Write(data, 0, data.Length);
}
return output.ToArray();
}
private byte[] Decompress(byte[] data)
{
using var input = new MemoryStream(data);
using var gzip = new GZipStream(input, CompressionMode.Decompress);
using var output = new MemoryStream();
gzip.CopyTo(output);
return output.ToArray();
}
}
```
### 问题:如何监控存储操作?
**解答**
实现日志存储包装器:
```csharp
public class LoggingStorage : IStorage
{
private readonly IStorage _innerStorage;
private readonly ILogger _logger;
public void Write&lt;T&gt;(string key, T value)
{
var stopwatch = Stopwatch.StartNew();
try
{
_innerStorage.Write(key, value);
_logger.Info($"写入成功: {key}, 耗时: {stopwatch.ElapsedMilliseconds}ms");
}
catch (Exception ex)
{
_logger.Error($"写入失败: {key}, 错误: {ex.Message}");
throw;
}
}
public T Read&lt;T&gt;(string key)
{
var stopwatch = Stopwatch.StartNew();
try
{
var value = _innerStorage.Read&lt;T&gt;(key);
_logger.Info($"读取成功: {key}, 耗时: {stopwatch.ElapsedMilliseconds}ms");
return value;
}
catch (Exception ex)
{
_logger.Error($"读取失败: {key}, 错误: {ex.Message}");
throw;
}
}
}
```
## 相关文档
- [数据与存档系统](/zh-CN/game/data) - 数据持久化
- [序列化系统](/zh-CN/game/serialization) - 数据序列化
- [Godot 集成](/zh-CN/godot/index) - 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 状态管理

View File

@ -0,0 +1,590 @@
---
title: Godot 架构集成
description: Godot 架构集成提供了 GFramework 与 Godot 引擎的无缝连接,实现生命周期同步和模块化开发。
---
# Godot 架构集成
## 概述
Godot 架构集成是 GFramework.Godot 中连接框架与 Godot 引擎的核心组件。它提供了架构与 Godot 场景树的生命周期绑定、模块化扩展系统,以及与
Godot 节点系统的深度集成。
通过 Godot 架构集成,你可以在 Godot 项目中使用 GFramework 的所有功能,同时保持与 Godot 引擎的完美兼容。
**主要特性**
- 架构与 Godot 生命周期自动同步
- 模块化的 Godot 扩展系统
- 架构锚点节点管理
- 自动资源清理
- 热重载支持
- 与 Godot 场景树深度集成
## 核心概念
### 抽象架构
`AbstractArchitecture` 是 Godot 项目中架构的基类:
```csharp
public abstract class AbstractArchitecture : Architecture
{
protected Node ArchitectureRoot { get; }
protected abstract void InstallModules();
protected Task InstallGodotModule<TModule>(TModule module);
}
```
### 架构锚点
`ArchitectureAnchor` 是连接架构与 Godot 场景树的桥梁:
```csharp
public partial class ArchitectureAnchor : Node
{
public void Bind(Action onExit);
public override void _ExitTree();
}
```
### Godot 模块
`IGodotModule` 定义了 Godot 特定的模块接口:
```csharp
public interface IGodotModule : IArchitectureModule
{
Node Node { get; }
void OnPhase(ArchitecturePhase phase, IArchitecture architecture);
void OnAttach(Architecture architecture);
void OnDetach();
}
```
## 基本用法
### 创建 Godot 架构
```csharp
using GFramework.Godot.architecture;
using GFramework.Core.Abstractions.architecture;
public class GameArchitecture : AbstractArchitecture
{
// 单例实例
public static GameArchitecture Interface { get; private set; }
public GameArchitecture()
{
Interface = this;
}
protected override void InstallModules()
{
// 注册 Model
RegisterModel(new PlayerModel());
RegisterModel(new GameModel());
// 注册 System
RegisterSystem(new GameplaySystem());
RegisterSystem(new AudioSystem());
// 注册 Utility
RegisterUtility(new StorageUtility());
}
}
```
### 在 Godot 场景中初始化架构
```csharp
using Godot;
using GFramework.Godot.architecture;
public partial class GameRoot : Node
{
private GameArchitecture _architecture;
public override void _Ready()
{
// 创建并初始化架构
_architecture = new GameArchitecture();
_architecture.InitializeAsync().AsTask().Wait();
GD.Print("架构已初始化");
}
}
```
### 使用架构锚点
架构锚点会自动创建并绑定到场景树:
```csharp
// 架构会自动创建锚点节点
// 节点名称格式: __GFramework__GameArchitecture__[HashCode]__ArchitectureAnchor__
// 当场景树销毁时,锚点会自动触发架构清理
```
## 高级用法
### 创建 Godot 模块
```csharp
using GFramework.Godot.architecture;
using Godot;
public class CoroutineModule : AbstractGodotModule
{
private Node _coroutineNode;
public override Node Node => _coroutineNode;
public CoroutineModule()
{
_coroutineNode = new Node { Name = "CoroutineScheduler" };
}
public override void Install(IArchitecture architecture)
{
// 注册协程调度器
var scheduler = new CoroutineScheduler(new GodotTimeSource());
architecture.RegisterSystem<ICoroutineScheduler>(scheduler);
GD.Print("协程模块已安装");
}
public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
{
if (phase == ArchitecturePhase.Ready)
{
GD.Print("协程模块已就绪");
}
}
public override void OnDetach()
{
GD.Print("协程模块已分离");
_coroutineNode?.QueueFree();
}
}
```
### 安装 Godot 模块
```csharp
public class GameArchitecture : AbstractArchitecture
{
protected override void InstallModules()
{
// 安装核心模块
RegisterModel(new PlayerModel());
RegisterSystem(new GameplaySystem());
// 安装 Godot 模块
InstallGodotModule(new CoroutineModule()).Wait();
InstallGodotModule(new SceneModule()).Wait();
InstallGodotModule(new UiModule()).Wait();
}
}
```
### 访问架构根节点
```csharp
public class SceneModule : AbstractGodotModule
{
private Node _sceneRoot;
public override Node Node => _sceneRoot;
public SceneModule()
{
_sceneRoot = new Node { Name = "SceneRoot" };
}
public override void Install(IArchitecture architecture)
{
// 访问架构根节点
if (architecture is AbstractArchitecture godotArch)
{
var root = godotArch.ArchitectureRoot;
root.AddChild(_sceneRoot);
}
}
}
```
### 监听架构阶段
```csharp
public class AnalyticsModule : AbstractGodotModule
{
private Node _analyticsNode;
public override Node Node => _analyticsNode;
public AnalyticsModule()
{
_analyticsNode = new Node { Name = "Analytics" };
}
public override void Install(IArchitecture architecture)
{
// 安装分析系统
}
public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
{
switch (phase)
{
case ArchitecturePhase.Initializing:
GD.Print("架构正在初始化");
break;
case ArchitecturePhase.Ready:
GD.Print("架构已就绪,开始追踪");
StartTracking();
break;
case ArchitecturePhase.Destroying:
GD.Prin构正在销毁停止追踪");
StopTracking();
break;
}
}
private void StartTracking() { }
private void StopTracking() { }
}
```
### 自定义架构配置
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.Abstractions.environment;
public class GameArchitecture : AbstractArchitecture
{
public GameArchitecture() : base(
configuration: CreateConfiguration(),
environment: CreateEnvironment()
)
{
}
private static IArchitectureConfiguration CreateConfiguration()
{
return new ArchitectureConfiguration
{
EnableLogging
LogLevel = LogLevel.Debug
};
}
private static IEnvironment CreateEnvironment()
{
return new DefaultEnvironment
{
IsDevelopment = OS.IsDebugBuild()
};
}
protected override void InstallModules()
{
// 根据环境配置安装模块
if (Environment.IsDevelopment)
{
InstallGodotModule(new DebugModule()).Wait();
}
// 安装核心模块
RegisterModel(new PlayerModel());
RegisterSystem(new GameplaySystem());
}
}
```
### 热重载支持
```csharp
public class GameArchitecture : AbstractArchitecture
{
private static bool _initialized;
protected override void OnInitialize()
{
// 防止热重载时重复初始化
if (_initialized)
{
GD.Print("架构已初始化,跳过重复初始化");
return;
}
base.OnInitialize();
_initialized = true;
}
protected override async ValueTask OnDestroyAsync()
{
await base.OnDestroyAsync();
_initialized = false;
}
}
```
### 在节点中使用架构
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class Player : CharacterBody2D
{
public override void _Ready()
{
// 通过扩展方法访问架构组件
var playerModel = this.GetModel<PlayerModel>();
var gameplaySystem = this.GetSystem<GameplaySystem>();
// 发送事件
this.SendEvent(new PlayerSpawnedEvent());
// 执行命令
this.SendCommand(new InitPlayerCommand());
}
public override void _Process(double delta)
{
// 在 Process 中使用架构组件
var inputSystem = this.GetSystem<InputSystem>();
var movement = inputSystem.GetMovementInput();
Velocity = movement * 200;
MoveAndSlide();
}
}
```
### 多架构支持
```csharp
// 游戏架构
public class GameArchitecture : AbstractArchitecture
{
public static GameArchitecture Interface { get; private set; }
public GameArchitecture()
{
Interface = this;
}
protected override void InstallModules()
{
RegisterModel(new PlayerModel());
RegisterSystem(new GameplaySystem());
}
}
// UI 架构
public class UiArchitecture : AbstractArchitecture
{
public static UiArchitecture Interface { get; private set; }
public UiArchitecture()
{
Interface = this;
}
protected override void InstallModules()
{
RegisterModel(new UiModel());
RegisterSystem(new UiSystem());
}
}
// 在不同节点中使用不同架构
public partial class GameNode : Node, IController
{
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
}
public partial class UiNode : Control, IController
{
public IArchitecture GetArchitecture() => UiArchitecture.Interface;
}
```
## 最佳实践
1. **使用单例模式**:为架构提供全局访问点
```csharp
public class GameArchitecture : AbstractArchitecture
{
public static GameArchitecture Interface { get; private set; }
public GameArchitecture()
{
Interface = this;
}
}
```
2. **在根节点初始化架构**:确保架构在所有节点之前就绪
```csharp
public partial class GameRoot : Node
{
public override void _Ready()
{
new GameArchitecture().InitializeAsync().AsTask().Wait();
}
}
```
3. **使用 Godot 模块组织功能**:将相关功能封装为模块
```csharp
InstallGodotModule(new CoroutineModule()).Wait();
InstallGodotModule(new SceneModule()).Wait();
InstallGodotModule(new UiModule()).Wait();
```
4. **利用架构阶段钩子**:在适当的时机执行逻辑
```csharp
public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
{
if (phase == ArchitecturePhase.Ready)
{
// 架构就绪后的初始化
}
}
```
5. **正确清理资源**:在 OnDetach 中释放 Godot 节点
```csharp
public override void OnDetach()
{
_node?.QueueFree();
_node = null;
}
```
6. **避免在构造函数中访问架构**:使用 _Ready 或 OnPhase
```csharp
✗ public Player()
{
var model = this.GetModel<PlayerModel>(); // 架构可能未就绪
}
✓ public override void _Ready()
{
var model = this.GetModel<PlayerModel>(); // 安全
}
```
## 常见问题
### 问题:架构什么时候初始化?
**解答**
在根节点的 `_Ready` 方法中初始化:
```csharp
public partial class GameRoot : Node
{
public override void _Ready()
{
new GameArchitecture().InitializeAsync().AsTask().Wait();
}
}
```
### 问题:如何在节点中访问架构?
**解答**
实现 `IController` 接口或使用扩展方法:
```csharp
// 方式 1: 实现 IController
public partial class Player : Node, IController
{
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public override void _Ready()
{
var model = this.GetModel<PlayerModel>();
}
}
// 方式 2: 直接使用单例
public partial class Enemy : Node
{
public override void _Ready()
{
var model = GameArchitecture.Interface.GetModel<EnemyModel>();
}
}
```
### 问题:架构锚点节点是什么?
**解答**
架构锚点是一个隐藏的节点,用于将架构绑定到 Godot 场景树。当场景树销毁时,锚点会自动触发架构清理。
### 问题:如何支持热重载?
**解答**
使用静态标志防止重复初始化:
```csharp
private static bool _initialized;
protected override void OnInitialize()
{
if (_initialized) return;
base.OnInitialize();
_initialized = true;
}
```
### 问题:可以有多个架构吗?
**解答**
可以,但通常一个游戏只需要一个主架构。如果需要多个架构,为每个架构提供独立的单例:
```csharp
public class GameArchitecture : AbstractArchitecture
{
public static GameArchitecture Interface { get; private set; }
}
public class UiArchitecture : AbstractArchitecture
{
public static UiArchitecture Interface { get; private set; }
}
```
### 问题Godot 模块和普通模块有什么区别?
**解答**
- **普通模块**:纯 C# 逻辑,不依赖 Godot
- **Godot 模块**:包含 Godot 节点,与场景树集成
```csharp
// 普通模块
InstallModule(new CoreModule());
// Godot 模块
InstallGodotModule(new SceneModule()).Wait();
```
## 相关文档
- [架构组件](/zh-CN/core/architecture) - 核心架构系统
- [生命周期管理](/zh-CN/core/lifecycle) - 组件生命周期
- [Godot 场景系统](/zh-CN/godot/scene) - Godot 场景集成
- [Godot UI 系统](/zh-CN/godot/ui) - Godot UI 集成
- [Godot 扩展](/zh-CN/godot/extensions) - Godot 扩展方法

628
docs/zh-CN/godot/logging.md Normal file
View File

@ -0,0 +1,628 @@
---
title: Godot 日志系统
description: Godot 日志系统提供了 GFramework 日志功能与 Godot 引擎控制台的完整集成。
---
# Godot 日志系统
## 概述
Godot 日志系统是 GFramework.Godot 中连接框架日志功能与 Godot 引擎控制台的核心组件。它提供了与 Godot
控制台的深度集成,支持彩色输出、多级别日志记录,以及与 GFramework 日志系统的无缝对接。
通过 Godot 日志系统,你可以在 Godot 项目中使用统一的日志接口,日志会自动输出到 Godot 编辑器控制台,并根据日志级别使用不同的颜色和输出方式。
**主要特性**
- 与 Godot 控制台深度集成
- 支持彩色日志输出
- 多级别日志记录Trace、Debug、Info、Warning、Error、Fatal
- 日志缓存机制
- 时间戳和格式化支持
- 异常信息记录
## 核心概念
### GodotLogger
`GodotLogger` 是 Godot 平台的日志记录器实现,继承自 `AbstractLogger`
```csharp
public sealed class GodotLogger : AbstractLogger
{
public GodotLogger(string? name = null, LogLevel minLevel = LogLevel.Info);
protected override void Write(LogLevel level, string message, Exception? exception);
}
```
### GodotLoggerFactory
`GodotLoggerFactory` 用于创建 Godot 日志记录器实例:
```csharp
public class GodotLoggerFactory : ILoggerFactory
{
public ILogger GetLogger(string name, LogLevel minLevel = LogLevel.Info);
}
```
### GodotLoggerFactoryProvider
`GodotLoggerFactoryProvider` 提供日志工厂实例,并支持日志缓存:
```csharp
public sealed class GodotLoggerFactoryProvider : ILoggerFactoryProvider
{
public LogLevel MinLevel { get; set; }
public ILogger CreateLogger(string name);
}
```
## 基本用法
### 配置 Godot 日志系统
在架构初始化时配置日志提供程序:
```csharp
using GFramework.Godot.architecture;
using GFramework.Godot.logging;
using GFramework.Core.logging;
using GFramework.Core.Abstractions.logging;
public class GameArchitecture : AbstractArchitecture
{
public static GameArchitecture Interface { get; private set; }
public GameArchitecture()
{
Interface = this;
// 配置 Godot 日志系统
LoggerFactoryResolver.Provider = new GodotLoggerFactoryProvider
{
MinLevel = LogLevel.Debug // 设置最小日志级别
};
}
protected override void InstallModules()
{
var logger = LoggerFactoryResolver.Provider.CreateLogger("GameArchitecture");
logger.Info("游戏架构初始化开始");
RegisterModel(new PlayerModel());
RegisterSystem(new GameplaySystem());
logger.Info("游戏架构初始化完成");
}
}
```
### 创建和使用日志记录器
```csharp
using Godot;
using GFramework.Core.logging;
using GFramework.Core.Abstractions.logging;
public partial class Player : CharacterBody2D
{
private ILogger _logger;
public override void _Ready()
{
// 创建日志记录器
_logger = LoggerFactoryResolver.Provider.CreateLogger("Player");
_logger.Info("玩家初始化");
_logger.Debug("玩家位置: {0}", Position);
}
public override void _Process(double delta)
{
if (_logger.IsDebugEnabled())
{
_logger.Debug("玩家速度: {0}", Velocity);
}
}
private void TakeDamage(float damage)
{
_logger.Warn("玩家受到伤害: {0}", damage);
}
private void OnError()
{
_logger.Error("玩家状态异常");
}
}
```
### 记录不同级别的日志
```csharp
var logger = LoggerFactoryResolver.Provider.CreateLogger("GameSystem");
// Trace - 最详细的跟踪信息(灰色)
logger.Trace("执行函数: UpdatePlayerPosition");
// Debug - 调试信息(青色)
logger.Debug("当前帧率: {0}", Engine.GetFramesPerSecond());
// Info - 一般信息(白色)
logger.Info("游戏开始");
// Warning - 警告信息(黄色)
logger.Warn("资源加载缓慢: {0}ms", loadTime);
// Error - 错误信息(红色)
logger.Error("无法加载配置文件");
// Fatal - 致命错误(红色,使用 PushError
logger.Fatal("游戏崩溃");
```
### 记录异常信息
```csharp
var logger = LoggerFactoryResolver.Provider.CreateLogger("SaveSystem");
try
{
SaveGame();
}
catch (Exception ex)
{
// 记录异常信息
logger.Error("保存游戏失败", ex);
}
```
## 高级用法
### 在 System 中使用日志
```csharp
using GFramework.Core.system;
using GFramework.Core.logging;
using GFramework.Core.Abstractions.logging;
public class CombatSystem : AbstractSystem
{
private ILogger _logger;
protected override void OnInit()
{
_logger = LoggerFactoryResolver.Provider.CreateLogger("CombatSystem");
_logger.Info("战斗系统初始化完成");
}
public void ProcessCombat(Entity attacker, Entity target, float damage)
{
_logger.Debug("战斗处理: {0} 攻击 {1}, 伤害: {2}",
attacker.Name, target.Name, damage);
if (damage > 100)
{
_logger.Warn("高伤害攻击: {0}", damage);
}
}
protected override void OnDestroy()
{
_logger.Info("战斗系统已销毁");
}
}
```
### 在 Model 中使用日志
```csharp
using GFramework.Core.model;
using GFramework.Core.logging;
using GFramework.Core.Abstractions.logging;
public class PlayerModel : AbstractModel
{
private ILogger _logger;
private int _health;
protected override void OnInit()
{
_logger = LoggerFactoryResolver.Provider.CreateLogger("PlayerModel");
_logger.Info("玩家模型初始化");
_health = 100;
}
public void SetHealth(int value)
{
var oldHealth = _health;
_health = value;
_logger.Debug("玩家生命值变化: {0} -> {1}", oldHealth, _health);
if (_health <= 0)
{
_logger.Warn("玩家生命值归零");
}
}
}
```
### 条件日志记录
```csharp
var logger = LoggerFactoryResolver.Provider.CreateLogger("PerformanceMonitor");
// 检查日志级别是否启用,避免不必要的字符串格式化
if (logger.IsDebugEnabled())
{
var stats = CalculateComplexStats(); // 耗时操作
logger.Debug("性能统计: {0}", stats);
}
// 简化写法
if (logger.IsTraceEnabled())
{
logger.Trace("详细的执行流程信息");
}
```
### 分类日志记录
```csharp
// 为不同模块创建独立的日志记录器
var networkLogger = LoggerFactoryResolver.Provider.CreateLogger("Network");
var databaseLogger = LoggerFactoryResolver.Provider.CreateLogger("Database");
var aiLogger = LoggerFactoryResolver.Provider.CreateLogger("AI");
networkLogger.Info("连接到服务器");
databaseLogger.Debug("查询用户数据");
aiLogger.Trace("AI 决策树遍历");
```
### 自定义日志级别
```csharp
// 在开发环境使用 Debug 级别
#if DEBUG
LoggerFactoryResolver.Provider = new GodotLoggerFactoryProvider
{
MinLevel = LogLevel.Debug
};
#else
// 在生产环境使用 Info 级别
LoggerFactoryResolver.Provider = new GodotLoggerFactoryProvider
{
MinLevel = LogLevel.Info
};
#endif
```
### 在 Godot 模块中使用日志
```csharp
using GFramework.Godot.architecture;
using GFramework.Core.logging;
using GFramework.Core.Abstractions.logging;
using Godot;
public class SceneModule : AbstractGodotModule
{
private ILogger _logger;
private Node _sceneRoot;
public override Node Node => _sceneRoot;
public SceneModule()
{
_sceneRoot = new Node { Name = "SceneRoot" };
_logger = LoggerFactoryResolver.Provider.CreateLogger("SceneModule");
}
public override void Install(IArchitecture architecture)
{
_logger.Info("场景模块安装开始");
// 安装场景系统
var sceneSystem = new SceneSystem();
architecture.RegisterSystem<ISceneSystem>(sceneSystem);
_logger.Info("场景模块安装完成");
}
public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
{
_logger.Debug("场景模块阶段: {0}", phase);
if (phase == ArchitecturePhase.Ready)
{
_logger.Info("场景模块已就绪");
}
}
public override void OnDetach()
{
_logger.Info("场景模块已分离");
_sceneRoot?.QueueFree();
}
}
```
## 日志输出格式
### 输出格式说明
Godot 日志系统使用以下格式输出日志:
```
[时间戳] 日志级别 [日志器名称] 日志消息
```
**示例输出**
```
[2025-01-09 10:30:45.123] INFO [GameArchitecture] 游戏架构初始化开始
[2025-01-09 10:30:45.456] DEBUG [Player] 玩家位置: (100, 200)
[2025-01-09 10:30:46.789] WARNING [CombatSystem] 高伤害攻击: 150
[2025-01-09 10:30:47.012] ERROR [SaveSystem] 保存游戏失败
```
### 日志级别与 Godot 输出方法
| 日志级别 | Godot 方法 | 颜色 | 说明 |
|-------------|------------------|----|----------|
| **Trace** | `GD.PrintRich` | 灰色 | 最详细的跟踪信息 |
| **Debug** | `GD.PrintRich` | 青色 | 调试信息 |
| **Info** | `GD.Print` | 白色 | 一般信息 |
| **Warning** | `GD.PushWarning` | 黄色 | 警告信息 |
| **Error** | `GD.PrintErr` | 红色 | 错误信息 |
| **Fatal** | `GD.PushError` | 红色 | 致命错误 |
### 异常信息格式
当记录异常时,异常信息会附加到日志消息后:
```
[2025-01-09 10:30:47.012] ERROR [SaveSystem] 保存游戏失败
System.IO.IOException: 文件访问被拒绝
at SaveSystem.SaveGame() in SaveSystem.cs:line 42
```
## 最佳实践
1. **在架构初始化时配置日志系统**
```csharp
public GameArchitecture()
{
LoggerFactoryResolver.Provider = new GodotLoggerFactoryProvider
{
MinLevel = LogLevel.Debug
};
}
```
2. **为每个类创建独立的日志记录器**
```csharp
private ILogger _logger;
public override void _Ready()
{
_logger = LoggerFactoryResolver.Provider.CreateLogger(GetType().Name);
}
```
3. **使用合适的日志级别**
- `Trace`:详细的执行流程,仅在深度调试时使用
- `Debug`:调试信息,开发阶段使用
- `Info`:重要的业务流程和状态变化
- `Warning`:潜在问题但不影响功能
- `Error`:错误但程序可以继续运行
- `Fatal`:严重错误,程序无法继续
4. **检查日志级别避免性能损失**
```csharp
if (_logger.IsDebugEnabled())
{
var expensiveData = CalculateExpensiveData();
_logger.Debug("数据: {0}", expensiveData);
}
```
5. **提供有意义的上下文信息**
```csharp
// ✗ 不好
logger.Error("错误");
// ✓ 好
logger.Error("加载场景失败: SceneKey={0}, Path={1}", sceneKey, scenePath);
```
6. **记录异常时提供上下文**
```csharp
try
{
LoadScene(sceneKey);
}
catch (Exception ex)
{
logger.Error($"加载场景失败: {sceneKey}", ex);
}
```
7. **使用分类日志记录器**
```csharp
var networkLogger = LoggerFactoryResolver.Provider.CreateLogger("Network");
var aiLogger = LoggerFactoryResolver.Provider.CreateLogger("AI");
```
8. **在生命周期方法中记录关键事件**
```csharp
protected override void OnInit()
{
_logger.Info("系统初始化完成");
}
protected override void OnDestroy()
{
_logger.Info("系统已销毁");
}
```
## 性能考虑
1. **日志缓存**
- `GodotLoggerFactoryProvider` 使用 `CachedLoggerFactory` 缓存日志记录器实例
- 相同名称和级别的日志记录器会被复用
2. **级别检查**
- 日志方法会自动检查日志级别
- 低于最小级别的日志不会被处理
3. **字符串格式化**
- 使用参数化日志避免不必要的字符串拼接
```csharp
// ✗ 不好 - 总是执行字符串拼接
logger.Debug("位置: " + position.ToString());
// ✓ 好 - 只在 Debug 启用时格式化
logger.Debug("位置: {0}", position);
```
4. **条件日志**
- 对于耗时的数据计算,先检查日志级别
```csharp
if (logger.IsDebugEnabled())
{
var stats = CalculateComplexStats();
logger.Debug("统计: {0}", stats);
}
```
## 常见问题
### 问题:如何配置 Godot 日志系统?
**解答**
在架构构造函数中配置日志提供程序:
```csharp
public GameArchitecture()
{
LoggerFactoryResolver.Provider = new GodotLoggerFactoryProvider
{
MinLevel = LogLevel.Debug
};
}
```
### 问题:日志没有输出到 Godot 控制台?
**解答**
检查以下几点:
1. 确认已配置 `GodotLoggerFactoryProvider`
2. 检查日志级别是否低于最小级别
3. 确认使用了正确的日志记录器
```csharp
// 确认配置
LoggerFactoryResolver.Provider = new GodotLoggerFactoryProvider
{
MinLevel = LogLevel.Trace // 设置为最低级别测试
};
// 创建日志记录器
var logger = LoggerFactoryResolver.Provider.CreateLogger("Test");
logger.Info("测试日志"); // 应该能看到输出
```
### 问题:如何在不同环境使用不同的日志级别?
**解答**
使用条件编译或环境检测:
```csharp
public GameArchitecture()
{
var minLevel = OS.IsDebugBuild() ? LogLevel.Debug : LogLevel.Info;
LoggerFactoryResolver.Provider = new GodotLoggerFactoryProvider
{
MinLevel = minLevel
};
}
```
### 问题:如何禁用某个模块的日志?
**解答**
为该模块创建一个高级别的日志记录器:
```csharp
// 只记录 Error 及以上级别
var logger = new GodotLogger("VerboseModule", LogLevel.Error);
```
### 问题:日志输出影响性能怎么办?
**解答**
1. 提高最小日志级别
2. 使用条件日志
3. 避免在高频调用的方法中记录日志
```csharp
// 提高日志级别
LoggerFactoryResolver.Provider = new GodotLoggerFactoryProvider
{
MinLevel = LogLevel.Warning // 只记录警告及以上
};
// 使用条件日志
if (_logger.IsDebugEnabled())
{
_logger.Debug("高频数据: {0}", data);
}
// 避免在 _Process 中频繁记录
public override void _Process(double delta)
{
// ✗ 不好 - 每帧都记录
// _logger.Debug("帧更新");
// ✓ 好 - 只在特定条件下记录
if (someErrorCondition)
{
_logger.Error("检测到错误");
}
}
```
### 问题:如何记录结构化日志?
**解答**
使用参数化日志或 `IStructuredLogger` 接口:
```csharp
// 参数化日志
logger.Info("玩家登录: UserId={0}, UserName={1}, Level={2}",
userId, userName, level);
// 使用结构化日志(如果实现了 IStructuredLogger
if (logger is IStructuredLogger structuredLogger)
{
structuredLogger.Log(LogLevel.Info, "玩家登录",
("UserId", userId),
("UserName", userName),
("Level", level));
}
```
## 相关文档
- [核心日志系统](/zh-CN/core/logging) - GFramework 核心日志功能
- [Godot 架构集成](/zh-CN/godot/architecture) - Godot 架构系统
- [Godot 扩展](/zh-CN/godot/extensions) - Godot 扩展方法
- [最佳实践](/zh-CN/best-practices/architecture-patterns) - 架构最佳实践

736
docs/zh-CN/godot/pause.md Normal file
View File

@ -0,0 +1,736 @@
---
title: Godot 暂停处理
description: Godot 暂停处理系统提供了 GFramework 暂停管理与 Godot SceneTree 暂停的完整集成。
---
# Godot 暂停处理
## 概述
Godot 暂停处理系统是 GFramework.Godot 中连接框架暂停管理与 Godot 引擎暂停机制的核心组件。它提供了暂停栈管理、分组暂停、嵌套暂停等功能,让你可以在
Godot 项目中使用 GFramework 的暂停系统。
通过 Godot 暂停处理系统你可以实现精细的暂停控制支持游戏逻辑暂停、UI 暂停、动画暂停等多种场景,同时保持与 Godot SceneTree
暂停机制的完美兼容。
**主要特性**
- 暂停栈管理(支持嵌套暂停)
- 分组暂停Global、Gameplay、Animation、Audio 等)
- 与 Godot SceneTree.Paused 集成
- 暂停处理器机制
- 暂停作用域(支持 using 语法)
- 线程安全的暂停管理
## 核心概念
### 暂停栈管理器
`IPauseStackManager` 管理游戏中的暂停状态:
```csharp
public interface IPauseStackManager : IContextUtility
{
// 推入暂停请求
PauseToken Push(string reason, PauseGroup group = PauseGroup.Global);
// 弹出暂停请求
bool Pop(PauseToken token);
// 查询是否暂停
bool IsPaused(PauseGroup group = PauseGroup.Global);
// 获取暂停深度
int GetPauseDepth(PauseGroup group = PauseGroup.Global);
// 暂停状态变化事件
event Action<PauseGroup, bool>? OnPauseStateChanged;
}
```
### 暂停组
`PauseGroup` 定义不同的暂停作用域:
```csharp
public enum PauseGroup
{
Global = 0, // 全局暂停(影响所有系统)
Gameplay = 1, // 游戏逻辑暂停(不影响 UI
Animation = 2, // 动画暂停
Audio = 3, // 音频暂停
Custom1 = 10, // 自定义组 1
Custom2 = 11, // 自定义组 2
Custom3 = 12 // 自定义组 3
}
```
### 暂停令牌
`PauseToken` 唯一标识一个暂停请求:
```csharp
public readonly struct PauseToken
{
public Guid Id { get; }
public bool IsValid { get; }
}
```
### Godot 暂停处理器
`GodotPauseHandler` 响应暂停栈状态变化,控制 SceneTree.Paused
```csharp
public class GodotPauseHandler : IPauseHandler
{
public int Priority => 0;
public void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
// 只有 Global 组影响 Godot 的全局暂停
if (group == PauseGroup.Global)
{
_tree.Paused = isPaused;
}
}
}
```
## 基本用法
### 设置暂停系统
```csharp
using GFramework.Godot.architecture;
using GFramework.Godot.pause;
using GFramework.Core.pause;
public class GameArchitecture : AbstractArchitecture
{
protected override void InstallModules()
{
// 注册暂停栈管理器
var pauseManager = new PauseStackManager();
RegisterUtility<IPauseStackManager>(pauseManager);
// 注册 Godot 暂停处理器
var pauseHandler = new GodotPauseHandler(GetTree());
pauseManager.RegisterHandler(pauseHandler);
}
}
```
### 基本暂停和恢复
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class PauseMenu : Control
{
private PauseToken _pauseToken;
public void ShowPauseMenu()
{
// 暂停游戏
var pauseManager = this.GetUtility<IPauseStackManager>();
_pauseToken = pauseManager.Push("Pause menu opened");
Show();
}
public void HidePauseMenu()
{
// 恢复游戏
var pauseManager = this.GetUtility<IPauseStackManager>();
pauseManager.Pop(_pauseToken);
Hide();
}
}
```
### 使用暂停作用域
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class DialogBox : Control
{
public async void ShowDialog()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
// 使用 using 语法自动管理暂停
using (pauseManager.PauseScope("Dialog shown"))
{
Show();
await ToSignal(GetTree().CreateTimer(3.0f), "timeout");
Hide();
} // 自动恢复
}
}
```
### 查询暂停状态
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class GameController : Node
{
public override void _Process(double delta)
{
var pauseManager = this.GetUtility<IPauseStackManager>();
// 检查是否暂停
if (pauseManager.IsPaused())
{
GD.Print("游戏已暂停");
return;
}
// 游戏逻辑
UpdateGame(delta);
}
private void UpdateGame(double delta)
{
// 游戏更新逻辑
}
}
```
## 高级用法
### 分组暂停
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class GameManager : Node
{
private PauseToken _gameplayPauseToken;
private PauseToken _animationPauseToken;
// 只暂停游戏逻辑UI 仍然可以交互
public void PauseGameplay()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
_gameplayPauseToken = pauseManager.Push("Gameplay paused", PauseGroup.Gameplay);
}
public void ResumeGameplay()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
pauseManager.Pop(_gameplayPauseToken);
}
// 只暂停动画
public void PauseAnimations()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
_animationPauseToken = pauseManager.Push("Animations paused", PauseGroup.Animation);
}
public void ResumeAnimations()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
pauseManager.Pop(_animationPauseToken);
}
// 检查特定组的暂停状态
public bool IsGameplayPaused()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
return pauseManager.IsPaused(PauseGroup.Gameplay);
}
}
```
### 嵌套暂停
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class GameScene : Node
{
public async void ShowNestedDialogs()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
// 第一层暂停
using (pauseManager.PauseScope("First dialog"))
{
GD.Print($"暂停深度: {pauseManager.GetPauseDepth()}"); // 输出: 1
ShowDialog("第一个对话框");
await ToSignal(GetTree().CreateTimer(2.0f), "timeout");
// 第二层暂停
using (pauseManager.PauseScope("Second dialog"))
{
GD.Print($"暂停深度: {pauseManager.GetPauseDepth()}"); // 输出: 2
ShowDialog("第二个对话框");
await ToSignal(GetTree().CreateTimer(2.0f), "timeout");
}
GD.Print($"暂停深度: {pauseManager.GetPauseDepth()}"); // 输出: 1
}
GD.Print($"暂停深度: {pauseManager.GetPauseDepth()}"); // 输出: 0
}
private void ShowDialog(string message)
{
GD.Print(message);
}
}
```
### 自定义暂停处理器
```csharp
using GFramework.Core.Abstractions.pause;
using Godot;
// 自定义动画暂停处理器
public class AnimationPauseHandler : IPauseHandler
{
private readonly AnimationPlayer _animationPlayer;
public AnimationPauseHandler(AnimationPlayer animationPlayer)
{
_animationPlayer = animationPlayer;
}
public int Priority => 10;
public void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
// 只响应 Animation 组
if (group == PauseGroup.Animation)
{
if (isPaused)
{
_animationPlayer.Pause();
GD.Print("动画已暂停");
}
else
{
_animationPlayer.Play();
GD.Print("动画已恢复");
}
}
}
}
// 注册自定义处理器
public partial class GameController : Node
{
public override void _Ready()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
var animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
var animationHandler = new AnimationPauseHandler(animationPlayer);
pauseManager.RegisterHandler(animationHandler);
}
}
```
### 音频暂停处理器
```csharp
using GFramework.Core.Abstractions.pause;
using Godot;
public class AudioPauseHandler : IPauseHandler
{
private readonly AudioStreamPlayer _musicPlayer;
private readonly AudioStreamPlayer _sfxPlayer;
public AudioPauseHandler(AudioStreamPlayer musicPlayer, AudioStreamPlayer sfxPlayer)
{
_musicPlayer = musicPlayer;
_sfxPlayer = sfxPlayer;
}
public int Priority => 20;
public void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
if (group == PauseGroup.Audio || group == PauseGroup.Global)
{
if (isPaused)
{
_musicPlayer.StreamPaused = true;
_sfxPlayer.StreamPaused = true;
}
else
{
_musicPlayer.StreamPaused = false;
_sfxPlayer.StreamPaused = false;
}
}
}
}
```
### 节点暂停模式控制
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class GameNode : Node
{
public override void _Ready()
{
// 设置节点在暂停时的行为
// 暂停时停止处理
ProcessMode = ProcessModeEnum.Pausable;
// 暂停时继续处理(用于 UI
// ProcessMode = ProcessModeEnum.Always;
// 暂停时停止,且子节点也停止
// ProcessMode = ProcessModeEnum.Inherit;
}
public override void _Process(double delta)
{
// 当 SceneTree.Paused = true 且 ProcessMode = Pausable 时
// 此方法不会被调用
UpdateGameLogic(delta);
}
private void UpdateGameLogic(double delta)
{
// 游戏逻辑
}
}
```
### UI 在暂停时继续工作
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class PauseMenuUI : Control
{
public override void _Ready()
{
// UI 在游戏暂停时仍然可以交互
ProcessMode = ProcessModeEnum.Always;
GetNode<Button>("ResumeButton").Pressed += OnResumePressed;
GetNode<Button>("QuitButton").Pressed += OnQuitPressed;
}
private void OnResumePressed()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
// 获取所有暂停原因
var reasons = pauseManager.GetPauseReasons();
GD.Print($"当前暂停原因: {string.Join(", ", reasons)}");
// 清空所有暂停
pauseManager.ClearAll();
Hide();
}
private void OnQuitPressed()
{
GetTree().Quit();
}
}
```
### 监听暂停状态变化
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class PauseIndicator : Label
{
public override void _Ready()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
// 订阅暂停状态变化事件
pauseManager.OnPauseStateChanged += OnPauseStateChanged;
}
public override void _ExitTree()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
pauseManager.OnPauseStateChanged -= OnPauseStateChanged;
}
private void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
if (group == PauseGroup.Global)
{
Text = isPaused ? "游戏已暂停" : "游戏运行中";
Visible = isPaused;
}
}
}
```
### 调试暂停状态
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class PauseDebugger : Node
{
public override void _Ready()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
pauseManager.OnPauseStateChanged += OnPauseStateChanged;
}
private void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
var pauseManager = this.GetUtility<IPauseStackManager>();
GD.Print($"=== 暂停状态变化 ===");
GD.Print($"组: {group}");
GD.Print($"状态: {(isPaused ? "暂停" : "恢复")}");
GD.Print($"深度: {pauseManager.GetPauseDepth(group)}");
var reasons = pauseManager.GetPauseReasons(group);
if (reasons.Count > 0)
{
GD.Print($"原因:");
foreach (var reason in reasons)
{
GD.Print($" - {reason}");
}
}
}
public override void _Input(InputEvent @event)
{
// 按 F12 显示所有暂停状态
if (@event is InputEventKey keyEvent && keyEvent.Pressed && keyEvent.Keycode == Key.F12)
{
PrintAllPauseStates();
}
}
private void PrintAllPauseStates()
{
var pauseManager = this.GetUtility<IPauseStackManager>();
GD.Print("=== 所有暂停状态 ===");
foreach (PauseGroup group in Enum.GetValues(typeof(PauseGroup)))
{
var isPaused = pauseManager.IsPaused(group);
var depth = pauseManager.GetPauseDepth(group);
if (depth > 0)
{
GD.Print($"{group}: 暂停 (深度: {depth})");
var reasons = pauseManager.GetPauseReasons(group);
foreach (var reason in reasons)
{
GD.Print($" - {reason}");
}
}
}
}
}
```
## 最佳实践
1. **使用暂停作用域管理生命周期**:避免忘记恢复
```csharp
✓ using (pauseManager.PauseScope("Dialog")) { ... }
✗ var token = pauseManager.Push("Dialog"); // 可能忘记 Pop
```
2. **为暂停提供清晰的原因**:便于调试
```csharp
✓ pauseManager.Push("Inventory opened");
✗ pauseManager.Push("pause"); // 原因不明确
```
3. **使用正确的暂停组**:避免影响不该暂停的系统
```csharp
✓ pauseManager.Push("Menu", PauseGroup.Gameplay); // 只暂停游戏逻辑
✗ pauseManager.Push("Menu", PauseGroup.Global); // 暂停所有系统包括 UI
```
4. **UI 节点设置 ProcessMode.Always**:确保 UI 在暂停时可用
```csharp
public override void _Ready()
{
ProcessMode = ProcessModeEnum.Always;
}
```
5. **游戏逻辑节点设置 ProcessMode.Pausable**:确保暂停时停止
```csharp
public override void _Ready()
{
ProcessMode = ProcessModeEnum.Pausable;
}
```
6. **保存暂停令牌以便恢复**:确保能正确恢复暂停
```csharp
private PauseToken _pauseToken;
public void Pause()
{
_pauseToken = pauseManager.Push("Paused");
}
public void Resume()
{
pauseManager.Pop(_pauseToken);
}
```
7. **使用事件监听暂停状态**:实现响应式 UI
```csharp
pauseManager.OnPauseStateChanged += (group, isPaused) =>
{
UpdateUI(isPaused);
};
```
8. **清理时注销事件监听**:避免内存泄漏
```csharp
public override void _ExitTree()
{
pauseManager.OnPauseStateChanged -= OnPauseStateChanged;
}
```
## 常见问题
### 问题:如何暂停游戏但保持 UI 可交互?
**解答**
使用 `PauseGroup.Gameplay` 而不是 `PauseGroup.Global`
```csharp
// 只暂停游戏逻辑
pauseManager.Push("Menu opened", PauseGroup.Gameplay);
// UI 节点设置为 Always
public override void _Ready()
{
ProcessMode = ProcessModeEnum.Always;
}
```
### 问题:嵌套暂停如何工作?
**解答**
暂停栈支持嵌套,需要所有 Pop 才能完全恢复:
```csharp
var token1 = pauseManager.Push("First"); // 深度: 1, 暂停
var token2 = pauseManager.Push("Second"); // 深度: 2, 仍然暂停
pauseManager.Pop(token1); // 深度: 1, 仍然暂停
pauseManager.Pop(token2); // 深度: 0, 恢复
```
### 问题:如何实现自定义暂停行为?
**解答**
实现 `IPauseHandler` 接口并注册:
```csharp
public class CustomPauseHandler : IPauseHandler
{
public int Priority => 0;
public void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
// 自定义暂停逻辑
}
}
pauseManager.RegisterHandler(new CustomPauseHandler());
```
### 问题:暂停处理器的优先级如何工作?
**解答**
数值越小优先级越高,按优先级顺序调用:
```csharp
handler1.Priority = 0; // 最先调用
handler2.Priority = 10; // 其次调用
handler3.Priority = 20; // 最后调用
```
### 问题:如何清空所有暂停?
**解答**
使用 `ClearAll()``ClearGroup()`
```csharp
// 清空所有组的暂停
pauseManager.ClearAll();
// 只清空特定组
pauseManager.ClearGroup(PauseGroup.Gameplay);
```
### 问题:暂停系统是线程安全的吗?
**解答**
是的,`PauseStackManager` 使用 `ReaderWriterLockSlim` 确保线程安全:
```csharp
// 可以在多个线程中安全调用
Task.Run(() => pauseManager.Push("Thread 1"));
Task.Run(() => pauseManager.Push("Thread 2"));
```
### 问题:如何调试暂停问题?
**解答**
使用暂停状态查询方法:
```csharp
// 检查是否暂停
bool isPaused = pauseManager.IsPaused(PauseGroup.Global);
// 获取暂停深度
int depth = pauseManager.GetPauseDepth(PauseGroup.Global);
// 获取所有暂停原因
var reasons = pauseManager.GetPauseReasons(PauseGroup.Global);
foreach (var reason in reasons)
{
GD.Print($"暂停原因: {reason}");
}
```
## 相关文档
- [Godot 架构集成](/zh-CN/godot/architecture) - Godot 架构基础
- [Godot 场景系统](/zh-CN/godot/scene) - Godot 场景集成
- [Godot UI 系统](/zh-CN/godot/ui) - Godot UI 集成
- [Godot 扩展](/zh-CN/godot/extensions) - Godot 扩展方法

684
docs/zh-CN/godot/pool.md Normal file
View File

@ -0,0 +1,684 @@
---
title: Godot 节点池系统
description: Godot 节点池系统提供了高性能的节点复用机制,减少频繁创建和销毁节点带来的性能开销。
---
# Godot 节点池系统
## 概述
Godot 节点池系统是 GFramework.Godot 中用于管理和复用 Godot
节点的高性能组件。通过对象池模式,它可以显著减少频繁创建和销毁节点带来的性能开销,特别适用于需要大量动态生成节点的场景,如子弹、特效、敌人等。
节点池系统基于 GFramework 核心的对象池系统,专门针对 Godot 节点进行了优化,提供了完整的生命周期管理和统计功能。
**主要特性**
- 节点复用机制,减少 GC 压力
- 自动生命周期管理
- 池容量限制和预热功能
- 详细的统计信息
- 类型安全的泛型设计
- 与 Godot PackedScene 无缝集成
**性能优势**
- 减少内存分配和垃圾回收
- 降低节点实例化开销
- 提高游戏运行时性能
- 优化大量对象场景的帧率
## 核心概念
### 节点池
节点池是一个存储可复用节点的容器。当需要节点时从池中获取,使用完毕后归还到池中,而不是销毁。这种复用机制可以显著提升性能。
### 可池化节点
实现 `IPoolableNode` 接口的节点可以被对象池管理。接口定义了节点在池中的生命周期回调:
```csharp
public interface IPoolableNode : IPoolableObject
{
// 从池中获取时调用
void OnAcquire();
// 归还到池中时调用
void OnRelease();
// 池被销毁时调用
void OnPoolDestroy();
// 转换为 Node 类型
Node AsNode();
}
```
### 节点复用
节点复用是指重复使用已创建的节点实例,而不是每次都创建新实例。这可以:
- 减少内存分配
- 降低 GC 压力
- 提高实例化速度
- 优化运行时性能
## 基本用法
### 创建可池化节点
```csharp
using Godot;
using GFramework.Godot.pool;
public partial class Bullet : Node2D, IPoolableNode
{
private Vector2 _velocity;
private float _lifetime;
public void OnAcquire()
{
// 从池中获取时重置状态
_lifetime = 5.0f;
Show();
SetProcess(true);
}
public void OnRelease()
{
// 归还到池中时清理状态
Hide();
SetProcess(false);
_velocity = Vector2.Zero;
}
public void OnPoolDestroy()
{
// 池被销毁时的清理工作
QueueFree();
}
public Node AsNode()
{
return this;
}
public void Initialize(Vector2 position, Vector2 velocity)
{
Position = position;
_velocity = velocity;
}
public override void _Process(double delta)
{
Position += _velocity * (float)delta;
_lifetime -= (float)delta;
if (_lifetime <= 0)
{
// 归还到池中
ReturnToPool();
}
}
private void ReturnToPool()
{
// 通过池系统归还
var poolSystem = this.GetSystem<BulletPoolSystem>();
poolSystem.Release("Bullet", this);
}
}
```
### 创建节点池系统
```csharp
using Godot;
using GFramework.Godot.pool;
public class BulletPoolSystem : AbstractNodePoolSystem<string, Bullet>
{
protected override PackedScene LoadScene(string key)
{
// 根据键加载对应的场景
return key switch
{
"Bullet" => GD.Load<PackedScene>("res://prefabs/Bullet.tscn"),
"EnemyBullet" => GD.Load<PackedScene>("res://prefabs/EnemyBullet.tscn"),
_ => throw new ArgumentException($"Unknown bullet type: {key}")
};
}
protected override void OnInit()
{
// 预热池,提前创建一些对象
Prewarm("Bullet", 50);
Prewarm("EnemyBullet", 30);
// 设置最大容量
SetMaxCapacity("Bullet", 100);
SetMaxCapacity("EnemyBullet", 50);
}
}
```
### 注册节点池系统
```csharp
using GFramework.Godot.architecture;
public class GameArchitecture : AbstractArchitecture
{
protected override void InstallModules()
{
// 注册节点池系统
RegisterSystem<BulletPoolSystem>(new BulletPoolSystem());
RegisterSystem<EffectPoolSystem>(new EffectPoolSystem());
}
}
```
### 使用节点池
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class Player : Node2D
{
private BulletPoolSystem _bulletPool;
public override void _Ready()
{
_bulletPool = this.GetSystem<BulletPoolSystem>();
}
public void Shoot()
{
// 从池中获取子弹
var bullet = _bulletPool.Acquire("Bullet");
// 初始化子弹
bullet.Initialize(GlobalPosition, Vector2.Right.Rotated(Rotation) * 500);
// 添加到场景树
GetParent().AddChild(bullet.AsNode());
}
}
```
## 高级用法
### 多类型节点池
```csharp
public class EffectPoolSystem : AbstractNodePoolSystem<string, PoolableEffect>
{
protected override PackedScene LoadScene(string key)
{
return key switch
{
"Explosion" => GD.Load<PackedScene>("res://effects/Explosion.tscn"),
"Hit" => GD.Load<PackedScene>("res://effects/Hit.tscn"),
"Smoke" => GD.Load<PackedScene>("res://effects/Smoke.tscn"),
"Spark" => GD.Load<PackedScene>("res://effects/Spark.tscn"),
_ => throw new ArgumentException($"Unknown effect type: {key}")
};
}
protected override void OnInit()
{
// 为不同类型的特效设置不同的池配置
Prewarm("Explosion", 10);
SetMaxCapacity("Explosion", 20);
Prewarm("Hit", 20);
SetMaxCapacity("Hit", 50);
Prewarm("Smoke", 15);
SetMaxCapacity("Smoke", 30);
}
}
// 使用特效池
public partial class Enemy : Node2D
{
public void Die()
{
var effectPool = this.GetSystem<EffectPoolSystem>();
var explosion = effectPool.Acquire("Explosion");
explosion.AsNode().GlobalPosition = GlobalPosition;
GetParent().AddChild(explosion.AsNode());
QueueFree();
}
}
```
### 自动归还的节点
```csharp
public partial class PoolableEffect : Node2D, IPoolableNode
{
private AnimationPlayer _animationPlayer;
private EffectPoolSystem _poolSystem;
private string _effectKey;
public override void _Ready()
{
_animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
_poolSystem = this.GetSystem<EffectPoolSystem>();
// 动画播放完毕后自动归还
_animationPlayer.AnimationFinished += OnAnimationFinished;
}
public void OnAcquire()
{
Show();
_animationPlayer.Play("default");
}
public void OnRelease()
{
Hide();
_animationPlayer.Stop();
}
public void OnPoolDestroy()
{
_animationPlayer.AnimationFinished -= OnAnimationFinished;
QueueFree();
}
public Node AsNode() => this;
public void SetEffectKey(string key)
{
_effectKey = key;
}
private void OnAnimationFinished(StringName animName)
{
// 动画播放完毕,自动归还到池中
_poolSystem.Release(_effectKey, this);
}
}
```
### 池容量管理
```csharp
public class DynamicPoolSystem : AbstractNodePoolSystem<string, PoolableEnemy>
{
protected override PackedScene LoadScene(string key)
{
return GD.Load<PackedScene>($"res://enemies/{key}.tscn");
}
protected override void OnInit()
{
// 初始配置
SetMaxCapacity("Slime", 50);
SetMaxCapacity("Goblin", 30);
SetMaxCapacity("Boss", 5);
}
// 动态调整池容量
public void AdjustPoolCapacity(string key, int newCapacity)
{
var currentSize = GetPoolSize(key);
var activeCount = GetActiveCount(key);
GD.Print($"池 '{key}' 当前状态:");
GD.Print($" 可用: {currentSize}");
GD.Print($" 活跃: {activeCount}");
GD.Print($" 新容量: {newCapacity}");
SetMaxCapacity(key, newCapacity);
}
// 根据游戏阶段预热
public void PrewarmForStage(int stage)
{
switch (stage)
{
case 1:
Prewarm("Slime", 20);
break;
case 2:
Prewarm("Slime", 30);
Prewarm("Goblin", 15);
break;
case 3:
Prewarm("Goblin", 25);
Prewarm("Boss", 2);
break;
}
}
}
```
### 池统计和监控
```csharp
public partial class PoolMonitor : Control
{
private BulletPoolSystem _bulletPool;
private Label _statsLabel;
public override void _Ready()
{
_bulletPool = this.GetSystem<BulletPoolSystem>();
_statsLabel = GetNode<Label>("StatsLabel");
}
public override void _Process(double delta)
{
// 获取统计信息
var stats = _bulletPool.GetStatistics("Bullet");
// 显示统计信息
_statsLabel.Text = $@"
子弹池统计:
可用对象: {stats.AvailableCount}
活跃对象: {stats.ActiveCount}
最大容量: {stats.MaxCapacity}
总创建数: {stats.TotalCreated}
总获取数: {stats.TotalAcquired}
总释放数: {stats.TotalReleased}
总销毁数: {stats.TotalDestroyed}
复用率: {CalculateReuseRate(stats):P2}
";
}
private float CalculateReuseRate(PoolStatistics stats)
{
if (stats.TotalAcquired == 0) return 0;
return 1.0f - (float)stats.TotalCreated / stats.TotalAcquired;
}
}
```
### 条件释放和清理
```csharp
public class SmartPoolSystem : AbstractNodePoolSystem<string, PoolableNode>
{
protected override PackedScene LoadScene(string key)
{
return GD.Load<PackedScene>($"res://poolable/{key}.tscn");
}
// 清理超出屏幕的对象
public void CleanupOffscreenObjects(Rect2 screenRect)
{
foreach (var pool in Pools)
{
var stats = GetStatistics(pool.Key);
GD.Print($"清理前 '{pool.Key}': 活跃={stats.ActiveCount}");
}
}
// 根据内存压力调整池大小
public void AdjustForMemoryPressure(float memoryUsage)
{
if (memoryUsage > 0.8f)
{
// 内存压力大,减小池容量
foreach (var pool in Pools)
{
var currentCapacity = GetStatistics(pool.Key).MaxCapacity;
SetMaxCapacity(pool.Key, Math.Max(10, currentCapacity / 2));
}
GD.Print("内存压力大,减小池容量");
}
else if (memoryUsage < 0.5f)
{
// 内存充足,增加池容量
foreach (var pool in Pools)
{
var currentCapacity = GetStatistics(pool.Key).MaxCapacity;
SetMaxCapacity(pool.Key, currentCapacity * 2);
}
GD.Print("内存充足,增加池容量");
}
}
}
```
## 最佳实践
1. **在 OnAcquire 中重置状态**:确保对象从池中获取时处于干净状态
```csharp
public void OnAcquire()
{
// 重置所有状态
Position = Vector2.Zero;
Rotation = 0;
Scale = Vector2.One;
Modulate = Colors.White;
Show();
}
```
2. **在 OnRelease 中清理资源**:避免内存泄漏
```csharp
public void OnRelease()
{
// 清理引用
_target = null;
_callbacks.Clear();
// 停止所有动画和计时器
_animationPlayer.Stop();
_timer.Stop();
Hide();
}
```
3. **合理设置池容量**:根据实际需求设置最大容量
```csharp
// 根据游戏设计设置合理的容量
SetMaxCapacity("Bullet", 100); // 屏幕上最多100个子弹
SetMaxCapacity("Enemy", 50); // 同时最多50个敌人
```
4. **使用预热优化启动性能**:在游戏开始前预创建对象
```csharp
protected override void OnInit()
{
// 在加载界面预热池
Prewarm("Bullet", 50);
Prewarm("Effect", 30);
}
```
5. **及时归还对象**:使用完毕后立即归还到池中
```csharp
✓ poolSystem.Release("Bullet", bullet); // 使用完立即归还
✗ // 忘记归还,导致池耗尽
```
6. **监控池统计信息**:定期检查池的使用情况
```csharp
var stats = poolSystem.GetStatistics("Bullet");
if (stats.ActiveCount > stats.MaxCapacity * 0.9f)
{
GD.PrintErr("警告:子弹池接近容量上限");
}
```
7. **避免在池中存储过大的对象**:大对象应该按需创建
```csharp
✓ 小对象子弹、特效、UI元素
✗ 大对象:完整的关卡、大型模型
```
## 常见问题
### 问题:什么时候应该使用节点池?
**解答**
以下场景适合使用节点池:
- 频繁创建和销毁的对象(子弹、特效)
- 数量较多的对象(敌人、道具)
- 生命周期短的对象粒子、UI提示
- 性能敏感的场景(移动平台、大量对象)
不适合使用节点池的场景:
- 只创建一次的对象玩家、UI界面
- 数量很少的对象Boss、关键NPC
- 状态复杂难以重置的对象
### 问题:如何确定合适的池容量?
**解答**
根据游戏实际情况设置:
```csharp
// 1. 测量峰值使用量
var stats = poolSystem.GetStatistics("Bullet");
GD.Print($"峰值活跃数: {stats.ActiveCount}");
// 2. 设置容量为峰值的 1.2-1.5 倍
SetMaxCapacity("Bullet", (int)(peakCount * 1.3f));
// 3. 监控并调整
if (stats.TotalDestroyed > stats.TotalCreated * 0.1f)
{
// 销毁过多,容量可能太小
SetMaxCapacity("Bullet", stats.MaxCapacity * 2);
}
```
### 问题:对象没有正确归还到池中怎么办?
**解答**
检查以下几点:
```csharp
// 1. 确保调用了 Release
poolSystem.Release("Bullet", bullet);
// 2. 检查是否使用了正确的键
✓ poolSystem.Release("Bullet", bullet);
✗ poolSystem.Release("Enemy", bullet); // 错误的键
// 3. 避免重复释放
if (!_isReleased)
{
poolSystem.Release("Bullet", this);
_isReleased = true;
}
// 4. 使用统计信息诊断
var stats = poolSystem.GetStatistics("Bullet");
if (stats.ActiveCount != stats.TotalAcquired - stats.TotalReleased)
{
GD.PrintErr("检测到对象泄漏");
}
```
### 问题:池中的对象状态没有正确重置?
**解答**
在 OnAcquire 中完整重置所有状态:
```csharp
public void OnAcquire()
{
// 重置变换
Position = Vector2.Zero;
Rotation = 0;
Scale = Vector2.One;
// 重置视觉
Modulate = Colors.White;
Visible = true;
// 重置物理
if (this is RigidBody2D rb)
{
rb.LinearVelocity = Vector2.Zero;
rb.AngularVelocity = 0;
}
// 重置逻辑状态
_health = _maxHealth;
_isActive = true;
// 重启动画
_animationPlayer.Play("idle");
}
```
### 问题:如何处理节点的父子关系?
**解答**
在归还前移除父节点:
```csharp
public void ReturnToPool()
{
// 从场景树中移除
if (GetParent() != null)
{
GetParent().RemoveChild(this);
}
// 归还到池中
var poolSystem = this.GetSystem<BulletPoolSystem>();
poolSystem.Release("Bullet", this);
}
// 获取时重新添加到场景树
var bullet = poolSystem.Acquire("Bullet");
GetParent().AddChild(bullet.AsNode());
```
### 问题:池系统对性能的提升有多大?
**解答**
性能提升取决于具体场景:
```csharp
// 测试代码
var stopwatch = new Stopwatch();
// 不使用池
stopwatch.Start();
for (int i = 0; i < 1000; i++)
{
var bullet = scene.Instantiate<Bullet>();
bullet.QueueFree();
}
stopwatch.Stop();
GD.Print($"不使用池: {stopwatch.ElapsedMilliseconds}ms");
// 使用池
stopwatch.Restart();
for (int i = 0; i < 1000; i++)
{
var bullet = poolSystem.Acquire("Bullet");
poolSystem.Release("Bullet", bullet);
}
stopwatch.Stop();
GD.Print($"使用池: {stopwatch.ElapsedMilliseconds}ms");
// 典型结果:使用池可以提升 3-10 倍性能
```
## 相关文档
- [对象池系统](/zh-CN/core/pool) - 核心对象池实现
- [Godot 架构集成](/zh-CN/godot/architecture) - Godot 架构基础
- [Godot 场景系统](/zh-CN/godot/scene) - Godot 场景管理
- [性能优化](/zh-CN/core/pool) - 性能优化最佳实践

View File

@ -0,0 +1,637 @@
---
title: Godot 资源仓储系统
description: Godot 资源仓储系统提供了 Godot Resource 的集中管理和高效加载功能。
---
# Godot 资源仓储系统
## 概述
Godot 资源仓储系统是 GFramework.Godot 中用于管理 Godot Resource
资源的核心组件。它提供了基于键值对的资源存储、批量加载、路径扫描等功能,让你可以高效地组织和访问游戏中的各类资源。
通过资源仓储系统,你可以将 Godot 的 `.tres``.res` 资源文件集中管理,支持按键快速查找、批量预加载、递归扫描目录等功能,简化资源管理流程。
**主要特性**
- 基于键值对的资源管理
- 支持 Godot Resource 类型
- 路径扫描和批量加载
- 递归目录遍历
- 类型安全的资源访问
- 与 GFramework 架构集成
## 核心概念
### 资源仓储接口
`IResourceRepository<TKey, TResource>` 定义了资源仓储的基本操作:
```csharp
public interface IResourceRepository<in TKey, TResource> : IRepository<TKey, TResource>
where TResource : Resource
{
void LoadFromPath(IEnumerable<string> paths);
void LoadFromPath(params string[] paths);
void LoadFromPathRecursive(IEnumerable<string> paths);
void LoadFromPathRecursive(params string[] paths);
}
```
### 资源仓储实现
`GodotResourceRepository<TKey, TResource>` 提供了完整的实现:
```csharp
public class GodotResourceRepository<TKey, TResource>
: IResourceRepository<TKey, TResource>
where TResource : Resource, IHasKey<TKey>
where TKey : notnull
{
public void Add(TKey key, TResource value);
public TResource Get(TKey key);
public bool TryGet(TKey key, out TResource value);
public IReadOnlyCollection<TResource> GetAll();
public bool Contains(TKey key);
public void Remove(TKey key);
public void Clear();
}
```
### 资源键接口
资源必须实现 `IHasKey<TKey>` 接口:
```csharp
public interface IHasKey<out TKey>
{
TKey Key { get; }
}
```
## 基本用法
### 定义资源类型
```csharp
using Godot;
using GFramework.Core.Abstractions.bases;
// 定义资源数据类
[GlobalClass]
public partial class ItemData : Resource, IHasKey<string>
{
[Export]
public string Id { get; set; }
[Export]
public string Name { get; set; }
[Export]
public string Description { get; set; }
[Export]
public Texture2D Icon { get; set; }
[Export]
public int MaxStack { get; set; } = 99;
// 实现 IHasKey 接口
public string Key => Id;
}
```
### 创建资源仓储
```csharp
using GFramework.Godot.data;
public class ItemRepository : GodotResourceRepository<string, ItemData>
{
public ItemRepository()
{
// 从指定路径加载所有物品资源
LoadFromPath("res://data/items");
}
}
```
### 注册到架构
```csharp
using GFramework.Godot.architecture;
public class GameArchitecture : AbstractArchitecture
{
protected override void InstallModules()
{
// 注册物品仓储
var itemRepo = new ItemRepository();
RegisterUtility<IResourceRepository<string, ItemData>>(itemRepo);
}
}
```
### 使用资源仓储
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class InventoryController : Node
{
private IResourceRepository<string, ItemData> _itemRepo;
public override void _Ready()
{
// 获取资源仓储
_itemRepo = this.GetUtility<IResourceRepository<string, ItemData>>();
// 使用资源
ShowItemInfo("sword_001");
}
private void ShowItemInfo(string itemId)
{
// 获取物品数据
if (_itemRepo.TryGet(itemId, out var itemData))
{
GD.Print($"物品: {itemData.Name}");
GD.Print($"描述: {itemData.Description}");
GD.Print($"最大堆叠: {itemData.MaxStack}");
}
else
{
GD.Print($"物品 {itemId} 不存在");
}
}
}
```
## 高级用法
### 递归加载资源
```csharp
public class AssetRepository : GodotResourceRepository<string, AssetData>
{
public AssetRepository()
{
// 递归加载所有子目录中的资源
LoadFromPathRecursive("res://assets");
}
}
```
### 多路径加载
```csharp
public class ConfigRepository : GodotResourceRepository<string, ConfigData>
{
public ConfigRepository()
{
// 从多个路径加载资源
LoadFromPath(
"res://config/gameplay",
"res://config/ui",
"res://config/audio"
);
}
}
```
### 动态添加资源
```csharp
public partial class ResourceManager : Node
{
private IResourceRepository<string, ItemData> _itemRepo;
public override void _Ready()
{
_itemRepo = this.GetUtility<IResourceRepository<string, ItemData>>();
}
public void AddCustomItem(ItemData item)
{
// 动态添加资源
_itemRepo.Add(item.Id, item);
GD.Print($"添加物品: {item.Name}");
}
public void RemoveItem(string itemId)
{
// 移除资源
if (_itemRepo.Contains(itemId))
{
_itemRepo.Remove(itemId);
GD.Print($"移除物品: {itemId}");
}
}
}
```
### 获取所有资源
```csharp
public partial class ItemListUI : Control
{
private IResourceRepository<string, ItemData> _itemRepo;
public override void _Ready()
{
_itemRepo = this.GetUtility<IResourceRepository<string, ItemData>>();
DisplayAllItems();
}
private void DisplayAllItems()
{
// 获取所有物品
var allItems = _itemRepo.GetAll();
GD.Print($"共有 {allItems.Count} 个物品:");
foreach (var item in allItems)
{
GD.Print($"- {item.Name} ({item.Id})");
}
}
}
```
### 资源预加载
```csharp
public partial class GameInitializer : Node
{
public override async void _Ready()
{
await PreloadAllResources();
GD.Print("所有资源预加载完成");
}
private async Task PreloadAllResources()
{
// 预加载物品资源
var itemRepo = new ItemRepository();
this.RegisterUtility<IResourceRepository<string, ItemData>>(itemRepo);
// 预加载技能资源
var skillRepo = new SkillRepository();
this.RegisterUtility<IResourceRepository<string, SkillData>>(skillRepo);
// 预加载敌人资源
var enemyRepo = new EnemyRepository();
this.RegisterUtility<IResourceRepository<string, EnemyData>>(enemyRepo);
await Task.CompletedTask;
}
}
```
### 资源缓存管理
```csharp
public class CachedResourceRepository<TKey, TResource>
where TResource : Resource, IHasKey<TKey>
where TKey : notnull
{
private readonly GodotResourceRepository<TKey, TResource> _repository;
private readonly Dictionary<TKey, TResource> _cache = new();
public CachedResourceRepository(params string[] paths)
{
_repository = new GodotResourceRepository<TKey, TResource>();
_repository.LoadFromPath(paths);
}
public TResource Get(TKey key)
{
// 先从缓存获取
if (_cache.TryGetValue(key, out var cached))
{
return cached;
}
// 从仓储获取并缓存
var resource = _repository.Get(key);
_cache[key] = resource;
return resource;
}
public void ClearCache()
{
_cache.Clear();
GD.Print("资源缓存已清空");
}
}
```
### 资源版本管理
```csharp
[GlobalClass]
public partial class VersionedItemData : Resource, IHasKey<string>
{
[Export]
public string Id { get; set; }
[Export]
public string Name { get; set; }
[Export]
public int Version { get; set; } = 1;
public string Key => Id;
}
public class VersionedItemRepository : GodotResourceRepository<string, VersionedItemData>
{
public VersionedItemRepository()
{
LoadFromPath("res://data/items");
ValidateVersions();
}
private void ValidateVersions()
{
var allItems = GetAll();
foreach (var item in allItems)
{
if (item.Version < 2)
{
GD.PrintErr($"物品 {item.Id} 版本过旧: v{item.Version}");
}
}
}
}
```
### 多类型资源管理
```csharp
// 武器资源
[GlobalClass]
public partial class WeaponData : Resource, IHasKey<string>
{
[Export] public string Id { get; set; }
[Export] public string Name { get; set; }
[Export] public int Damage { get; set; }
public string Key => Id;
}
// 护甲资源
[GlobalClass]
public partial class ArmorData : Resource, IHasKey<string>
{
[Export] public string Id { get; set; }
[Export] public string Name { get; set; }
[Export] public int Defense { get; set; }
public string Key => Id;
}
// 统一管理
public class EquipmentManager
{
private readonly IResourceRepository<string, WeaponData> _weaponRepo;
private readonly IResourceRepository<string, ArmorData> _armorRepo;
public EquipmentManager(
IResourceRepository<string, WeaponData> weaponRepo,
IResourceRepository<string, ArmorData> armorRepo)
{
_weaponRepo = weaponRepo;
_armorRepo = armorRepo;
}
public void ShowAllEquipment()
{
GD.Print("=== 武器 ===");
foreach (var weapon in _weaponRepo.GetAll())
{
GD.Print($"{weapon.Name}: 伤害 {weapon.Damage}");
}
GD.Print("=== 护甲 ===");
foreach (var armor in _armorRepo.GetAll())
{
GD.Print($"{armor.Name}: 防御 {armor.Defense}");
}
}
}
```
### 资源热重载
```csharp
public partial class HotReloadManager : Node
{
private IResourceRepository<string, ItemData> _itemRepo;
public override void _Ready()
{
_itemRepo = this.GetUtility<IResourceRepository<string, ItemData>>();
}
public void ReloadResources()
{
// 清空现有资源
_itemRepo.Clear();
// 重新加载
var repo = _itemRepo as GodotResourceRepository<string, ItemData>;
repo?.LoadFromPath("res://data/items");
GD.Print("资源已重新加载");
}
public override void _Input(InputEvent @event)
{
// 按 F5 热重载
if (@event is InputEventKey keyEvent &&
keyEvent.Pressed &&
keyEvent.Keycode == Key.F5)
{
ReloadResources();
}
}
}
```
## 最佳实践
1. **资源实现 IHasKey 接口**:确保资源可以被仓储管理
```csharp
✓ public partial class ItemData : Resource, IHasKey<string> { }
✗ public partial class ItemData : Resource { } // 无法使用仓储
```
2. **使用有意义的键类型**:根据业务需求选择合适的键类型
```csharp
✓ IResourceRepository<string, ItemData> // 字符串 ID
✓ IResourceRepository<int, LevelData> // 整数关卡号
✓ IResourceRepository<Guid, SaveData> // GUID 唯一标识
```
3. **在架构初始化时加载资源**:避免运行时加载卡顿
```csharp
protected override void InstallModules()
{
var itemRepo = new ItemRepository();
RegisterUtility<IResourceRepository<string, ItemData>>(itemRepo);
}
```
4. **使用递归加载组织资源**:保持目录结构清晰
```csharp
// 推荐的目录结构
res://data/
├── items/
│ ├── weapons/
│ ├── armors/
│ └── consumables/
└── enemies/
├── bosses/
└── minions/
```
5. **处理资源不存在的情况**:使用 TryGet 避免异常
```csharp
✓ if (_itemRepo.TryGet(itemId, out var item)) { }
✗ var item = _itemRepo.Get(itemId); // 可能抛出异常
```
6. **合理使用资源缓存**:平衡内存和性能
```csharp
// 频繁访问的资源可以缓存
private ItemData _cachedPlayerWeapon;
public ItemData GetPlayerWeapon()
{
return _cachedPlayerWeapon ??= _itemRepo.Get("player_weapon");
}
```
## 常见问题
### 问题:如何让资源支持仓储管理?
**解答**
资源类必须实现 `IHasKey<TKey>` 接口:
```csharp
[GlobalClass]
public partial class MyResource : Resource, IHasKey<string>
{
[Export]
public string Id { get; set; }
public string Key => Id;
}
```
### 问题:资源文件必须是什么格式?
**解答**
资源仓储支持 Godot 的 `.tres``.res` 文件格式:
- `.tres`:文本格式,可读性好,适合版本控制
- `.res`:二进制格式,加载更快,适合发布版本
### 问题:如何组织资源目录结构?
**解答**
推荐按类型和功能组织:
```
res://data/
├── items/ # 物品资源
│ ├── weapons/
│ ├── armors/
│ └── consumables/
├── enemies/ # 敌人资源
├── skills/ # 技能资源
└── levels/ # 关卡资源
```
### 问题:资源加载会阻塞主线程吗?
**解答**
`LoadFromPath` 是同步操作,建议在游戏初始化时加载:
```csharp
public override void _Ready()
{
// 在游戏启动时加载
var itemRepo = new ItemRepository();
RegisterUtility(itemRepo);
}
```
### 问题:如何处理重复的资源键?
**解答**
仓储会记录警告但不会抛出异常:
```csharp
// 日志会显示: "Duplicate key detected: item_001"
// 后加载的资源会被忽略
```
### 问题:可以动态添加和移除资源吗?
**解答**
可以,使用 `Add``Remove` 方法:
```csharp
// 添加资源
_itemRepo.Add("new_item", newItemData);
// 移除资源
_itemRepo.Remove("old_item");
// 清空所有资源
_itemRepo.Clear();
```
### 问题:如何实现资源的延迟加载?
**解答**
可以创建包装类实现延迟加载:
```csharp
public class LazyResourceRepository<TKey, TResource>
where TResource : Resource, IHasKey<TKey>
where TKey : notnull
{
private GodotResourceRepository<TKey, TResource> _repository;
private readonly string[] _paths;
private bool _loaded;
public LazyResourceRepository(params string[] paths)
{
_paths = paths;
}
private void EnsureLoaded()
{
if (_loaded) return;
_repository = new GodotResourceRepository<TKey, TResource>();
_repository.LoadFromPath(_paths);
_loaded = true;
}
public TResource Get(TKey key)
{
EnsureLoaded();
return _repository.Get(key);
}
}
```
## 相关文档
- [数据与存档系统](/zh-CN/game/data) - 数据持久化
- [Godot 架构集成](/zh-CN/godot/architecture) - Godot 架构基础
- [Godot 场景系统](/zh-CN/godot/scene) - 场景资源管理
- [资源管理系统](/zh-CN/core/resource) - 核心资源管理

583
docs/zh-CN/godot/scene.md Normal file
View File

@ -0,0 +1,583 @@
---
title: Godot 场景系统
description: Godot 场景系统提供了 GFramework 场景管理与 Godot 场景树的完整集成。
---
# Godot 场景系统
## 概述
Godot 场景系统是 GFramework.Godot 中连接框架场景管理与 Godot 场景树的核心组件。它提供了场景行为封装、场景工厂、场景注册表等功能,让你可以在
Godot 项目中使用 GFramework 的场景管理系统。
通过 Godot 场景系统,你可以使用 GFramework 的场景路由、生命周期管理等功能,同时保持与 Godot 场景系统的完美兼容。
**主要特性**
- 场景行为封装SceneBehavior
- 场景工厂和注册表
- 与 Godot PackedScene 集成
- 多种场景行为类型Node2D、Node3D、Control
- 场景生命周期管理
- 场景根节点管理
## 核心概念
### 场景行为
`SceneBehaviorBase<T>` 封装了 Godot 节点的场景行为:
```csharp
public abstract class SceneBehaviorBase<T> : ISceneBehavior
where T : Node
{
protected readonly T Owner;
public string Key { get; }
public IScene Scene { get; }
}
```
### 场景工厂
`GodotSceneFactory` 负责创建场景实例:
```csharp
public class GodotSceneFactory : ISceneFactory
{
public ISceneBehavior Create(string sceneKey);
}
```
### 场景注册表
`IGodotSceneRegistry` 管理场景资源:
```csharp
public interface IGodotSceneRegistry
{
void Register(string key, PackedScene scene);
PackedScene Get(string key);
}
```
## 基本用法
### 创建场景脚本
```csharp
using Godot;
using GFramework.Game.Abstractions.scene;
public partial class MainMenuScene : Control, IScene
{
public async ValueTask OnLoadAsync(ISceneEnterParam? param)
{
GD.Print("加载主菜单资源");
await Task.CompletedTask;
}
public async ValueTask OnEnterAsync()
{
GD.Print("进入主菜单");
Show();
await Task.CompletedTask;
}
public async ValueTask OnPauseAsync()
{
GD.Print("暂停主菜单");
await Task.CompletedTask;
}
public async ValueTask OnResumeAsync()
{
GD.Print("恢复主菜单");
await Task.CompletedTask;
}
public async ValueTask OnExitAsync()
{
GD.Print("退出主菜单");
Hide();
await Task.CompletedTask;
}
public async ValueTask OnUnloadAsync()
{
GD.Print("卸载主菜单资源");
await Task.CompletedTask;
}
}
```
### 注册场景
```csharp
using GFramework.Godot.scene;
using Godot;
public class GameSceneRegistry : GodotSceneRegistry
{
publieneRegistry()
{
// 注册场景资源
Register("MainMenu", GD.Load<PackedScene>("res://scenes/MainMenu.tscn"));
Register("Gameplay", GD.Load<PackedScene>("res://scenes/Gameplay.tscn"));
Register("Pause", GD.Load<PackedScene>("res://scenes/Pause.tscn"));
}
}
```
### 设置场景系统
```csharp
using GFramework.Godot.architecture;
using GFramework.Godot.scene;
public class GameArchitecture : AbstractArchitecture
{
protected override void InstallModules()
{
// 注册场景注册表
var sceneRegistry = new GameSceneRegistry();
RegisterUtility<IGodotSceneRegistry>(sceneRegistry);
// 注册场景工厂
var sceneFactory = new GodotSceneFactory();
RegisterUtility<ISceneFactory>(sceneFactory);
// 注册场景路由
var sceneRouter = new GodotSceneRouter();
RegisterSystem<ISceneRouter>(sceneRouter);
}
}
```
### 使用场景路由
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class GameController : Node
{
public override void _Ready()
{
// 切换到主菜单
SwitchToMainMenu();
}
private async void SwitchToMainMenu()
{
var sceneRouter = this.GetSystem<ISceneRouter>();
await sceneRouter.ReplaceAsync("MainMenu");
}
private async void StartGame()
{
var sceneRouter = this.GetSystem<ISceneRouter>();
await sceneRouter.ReplaceAsync("Gameplay");
}
private async void ShowPause()
{
var sceneRouter = this.GetSystem<ISceneRouter>();
await sceneRouter.PushAsync("Pause");
}
}
```
## 高级用法
### 使用场景行为提供者
```csharp
using Godot;
using GFramework.Game.Abstractions.scene;
using GFramework.Godot.scene;
public partial class GameplayScene : Node2D, ISceneBehaviorProvider
{
private GameplaySceneBehavior _behavior;
public override void _Ready()
{
_behavior = new GameplaySceneBehavior(this, "Gameplay");
}
public ISceneBehavior GetScene()
{
return _behavior;
}
}
// 自定义场景行为
public class GameplaySceneBehavior : Node2DSceneBehavior
{
public GameplaySceneBehavior(Node2D owner, string key) : base(owner, key)
{
}
protected override async ValueTask OnLoadInternalAsync(ISceneEnterParam? param)
{
GD.Print("加载游戏场景");
// 加载游戏资源
await Task.CompletedTask;
}
protected override async ValueTask OnEnterInternalAsync()
{
GD.Print("进入游戏场景");
Owner.Show();
await Task.CompletedTask;
}
}
```
### 不同类型的场景行为
```csharp
// Node2D 场景
public class Node2DSceneBehavior : SceneBehaviorBase<Node2D>
{
public Node2DSceneBehavior(Node2D owner, string key) : base(owner, key)
{
}
}
// Node3D 场景
public class Node3DSceneBehavior : SceneBehaviorBase<Node3D>
{
public Node3DSceneBehavior(Node3D owner, string key) : base(owner, key)
{
}
}
// Control 场景UI
public class ControlSceneBehavior : SceneBehaviorBase<Control>
{
public ControlSceneBehavior(Control owner, string key) : base(owner, key)
{
}
}
```
### 场景根节点管理
```csharp
using Godot;
using GFramework.Godot.scene;
public partial class SceneRoot : Node, ISceneRoot
{
private Node _currentSceneNode;
public void AttachScene(Node sceneNode)
{
// 移除旧场景
if (_currentSceneNode != null)
{
RemoveChild(_currentSceneNode);
_currentSceneNode.QueueFree();
}
// 添加新场景
_currentSceneNode = sceneNode;
AddChild(_currentSceneNode);
}
public void DetachScene(Node sceneNode)
{
if (_currentSceneNode == sceneNode)
{
RemoveChild(_currentSceneNode);
_currentSceneNode = null;
}
}
}
```
### 场景参数传递
```csharp
// 定义场景参数
public class GameplayEnterParam : ISceneEnterParam
{
public int Level { get; set; }
public string Difficulty { get; set; }
}
// 在场景中接收参数
public partial class GameplayScene : Node2D, IScene
{
private int _level;
private string _difficulty;
public async ValueTask OnLoadAsync(ISceneEnterParam? param)
{
if (param is GameplayEnterParam gameplayParam)
{
_level = gameplayParam.Level;
_difficulty = gameplayParam.Difficulty;
GD.Print($"加载关卡 {_level},难度: {_difficulty}");
}
await Task.CompletedTask;
}
// ... 其他生命周期方法
}
// 切换场景时传递参数
var sceneRouter = this.GetSystem<ISceneRouter>();
await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam
{
Level = 1,
Difficulty = "Normal"
});
```
### 场景预加载
```csharp
public partial class LoadingScene : Control
{
public override async void _Ready()
{
// 预加载下一个场景
await PreloadNextScene();
// 切换到预加载的场景
var sceneRouter = this.GetSystem<ISceneRouter>();
await sceneRouter.ReplaceAsync("Gameplay");
}
private async Task PreloadNextScene()
{
var sceneFactory = this.GetUtility<ISceneFactory>();
var sceneBehavior = sceneFactory.Create("Gameplay");
// 预加载场景资源
await sceneBehavior.LoadAsync(null);
GD.Print("场景预加载完成");
}
}
```
### 场景转换动画
```csharp
using Godot;
using GFramework.Game.Abstractions.scene;
public class FadeTransitionHandler : ISceneTransitionHandler
{
private ColorRect _fadeRect;
public FadeTransitionHandler(ColorRect fadeRect)
{
_fadeRect = fadeRect;
}
public async ValueTask OnBeforeExitAsync(SceneTransitionEvent @event)
{
// 淡出动画
var tween = _fadeRect.CreateTween();
tween.TweenProperty(_fadeRect, "modulate:a", 1.0f, 0.3f);
await tween.ToSignal(tween, Tween.SignalName.Finished);
}
public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event)
{
// 淡入动画
var tween = _fadeRect.CreateTween();
tween.TweenProperty(_fadeRect, "modulate:a", 0.0f, 0.3f);
await tween.ToSignal(tween, Tween.SignalName.Finished);
}
// ... 其他方法
}
```
### 场景间通信
```csharp
// 通过事件通信
public partial class GameplayScene : Node2D, IScene
{
public async ValueTask OnEnterAsync()
{
// 发送场景进入事件
this.SendEvent(new GameplaySceneEnteredEvent());
await Task.CompletedTask;
}
}
// 在其他地方监听
public partial class HUD : Control
{
public override void _Ready()
{
this.RegisterEvent<GameplaySceneEnteredEvent>(OnGameplayEntered);
}
private void OnGameplayEntered(GameplaySceneEnteredEvent evt)
{
GD.Print("游戏场景已进入,显示 HUD");
Show();
}
}
```
## 最佳实践
1. **场景脚本实现 IScene 接口**:获得完整的生命周期管理
```csharp
✓ public partial class MyScene : Node2D, IScene { }
✗ public partial class MyScene : Node2D { } // 无生命周期管理
```
2. **使用场景注册表管理场景资源**:集中管理所有场景
```csharp
public class GameSceneRegistry : GodotSceneRegistry
{
public GameSceneRegistry()
{
Register("MainMenu", GD.Load<PackedScene>("res://scenes/MainMenu.tscn"));
Register("Gameplay", GD.Load<PackedScene>("res://scenes/Gameplay.tscn"));
}
}
```
3. **在 OnLoadAsync 中加载资源**:避免场景切换卡顿
```csharp
public async ValueTask OnLoadAsync(ISceneEnterParam? param)
{
// 异步加载资源
await LoadTexturesAsync();
await LoadAudioAsync();
}
```
4. **使用场景根节点管理场景树**:保持场景树结构清晰
```csharp
// 创建场景根节点
var sceneRoot = new Node { Name = "SceneRoot" };
AddChild(sceneRoot);
// 绑定到场景路由
sceneRouter.BindRoot(sceneRoot);
```
5. **正确清理场景资源**:在 OnUnloadAsync 中释放资源
```csharp
public async ValueTask OnUnloadAsync()
{
// 释放资源
_texture?.Dispose();
_audioStream?.Dispose();
await Task.CompletedTask;
}
```
6. **使用场景参数传递数据**:避免使用全局变量
```csharp
✓ await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam { Level = 1 });
✗ GlobalData.CurrentLevel = 1; // 避免全局状态
```
## 常见问题
### 问题:如何在 Godot 场景中使用 GFramework
**解答**
场景脚本实现 `IScene` 接口:
```csharp
public partial class MyScene : Node2D, IScene
{
public async ValueTask OnLoadAsync(ISceneEnterParam? param) { }
public async ValueTask OnEnterAsync() { }
// ... 实现其他方法
}
```
### 问题:场景切换时节点如何管理?
**解答**
使用场景根节点管理:
```csharp
// 场景路由会自动管理节点的添加和移除
await sceneRouter.ReplaceAsync("NewScene");
// 旧场景节点会被移除,新场景节点会被添加
```
### 问题:如何实现场景预加载?
**解答**
使用场景工厂提前创建场景:
```csharp
var sceneFactory = this.GetUtility<ISceneFactory>();
var sceneBehavior = sceneFactory.Create("NextScene");
await sceneBehavior.LoadAsync(null);
```
### 问题:场景生命周期方法的调用顺序是什么?
**解答**
- 进入场景:`OnLoadAsync` -> `OnEnterAsync` -> `OnShow`
- 暂停场景:`OnPause` -> `OnHide`
- 恢复场景:`OnShow` -> `OnResume`
- 退出场景:`OnHide` -> `OnExitAsync` -> `OnUnloadAsync`
### 问题:如何在场景中访问架构组件?
**解答**
使用扩展方法:
```csharp
public partial class MyScene : Node2D, IScene
{
public async ValueTask OnEnterAsync()
{
var playerModel = this.GetModel<PlayerModel>();
var gameSystem = this.GetSystem<GameSystem>();
await Task.CompletedTask;
}
}
```
### 问题:场景切换时如何显示加载界面?
**解答**
使用场景转换处理器:
```csharp
public class LoadingScreenHandler : ISceneTransitionHandler
{
public async ValueTask OnBeforeLoadAsync(SceneTransitionEvent @event)
{
// 显示加载界面
ShowLoadingScreen();
await Task.CompletedTask;
}
public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event)
{
// 隐藏加载界面
HideLoadingScreen();
await Task.CompletedTask;
}
}
```
## 相关文档
- [场景系统](/zh-CN/game/scene) - 核心场景管理
- [Godot 架构集成](/zh-CN/godot/architecture) - Godot 架构基础
- [Godot UI 系统](/zh-CN/godot/ui) - Godot UI 集成
- [Godot 扩展](/zh-CN/godot/extensions) - Godot 扩展方法

643
docs/zh-CN/godot/ui.md Normal file
View File

@ -0,0 +1,643 @@
---
title: Godot UI 系统
description: Godot UI 系统提供了 GFramework UI 管理与 Godot Control 节点的完整集成。
---
# Godot UI 系统
## 概述
Godot UI 系统是 GFramework.Godot 中连接框架 UI 管理与 Godot Control 节点的核心组件。它提供了 UI 页面行为封装、UI 工厂、UI
注册表等功能,支持多层级 UI 显示,让你可以在 Godot 项目中使用 GFramework 的 UI 管理系统。
通过 Godot UI 系统,你可以使用 GFramework 的 UI 路由、生命周期管理、多层级显示等功能,同时保持与 Godot UI 系统的完美兼容。
**主要特性**
- UI 页面行为封装
- UI 工厂和注册表
- 与 Godot PackedScene 集成
- 多层级 UI 支持Page、Overlay、Modal、Toast、Topmost
- UI 生命周期管理
- UI 根节点管理
## 核心概念
### UI 页面行为
`CanvasItemUiPageBehaviorBase<T>` 封装了 Godot Control 节点的 UI 行为:
```csharp
public abstract class CanvasItemUiPageBehaviorBase<T> : IUiPageBehavior
where T : CanvasItem
{
protected readonly T Owner;
public string Key { get; }
public UiLayer Layer { get; }
public bool IsReentrant { get; }
}
```
### UI 工厂
`GodotUiFactory` 负责创建 UI 实例:
```csharp
public class GodotUiFactory : IUiFactory
{
public IUiPageBehavior Create(string uiKey);
}
```
### UI 层级行为
不同层级的 UI 有不同的行为类:
```csharp
// Page 层(栈管理)
public class PageLayerUiPageBehavior : CanvasItemUiPageBehaviorBase<Control>
{
public override UiLayer Layer => UiLayer.Page;
public override bool IsReentrant => false;
}
// Modal 层(模态对话框)
public class ModalLayerUiPageBehavior : CanvasItemUiPageBehaviorBase<Control>
{
public override UiLayer Layer => UiLayer.Modal;
public override bool IsReentrant => true;
}
```
## 基本用法
### 创建 UI 脚本
```csharp
using Godot;
using GFramework.Game.Abstractions.ui;
public partial class MainMenuPage : Control, IUiPage
{
public void OnEnter(IUiPageEnterParam? param)
{
GD.Print("进入主菜单");
Show();
}
public void OnExit()
{
GD.Print("退出主菜单");
Hide();
}
public void OnPause()
{
GD.Print("暂停主菜单");
}
public void OnResume()
{
GD.Print("恢复主菜单");
}
public void OnShow()
{
Show();
}
public void OnHide()
{
Hide();
}
}
```
### 实现 UI 页面行为提供者
```csharp
using Godot;
using GFramework.Game.Abstractions.ui;
using GFramework.Godot.ui;
public partial class MainMenuPage : Control, IUiPageBehaviorProvider
{
private PageLayerUiPageBehavior _behavior;
public override void _Ready()
{
_behavior = new PageLayerUiPageBehavior(this, "MainMenu");
}
public IUiPageBehavior GetPage()
{
return _behavior;
}
}
```
### 注册 UI
```csharp
using GFramework.Godot.ui;
using Godot;
public class GameUiRegistry : GodotUiRegistry
{
public GameUiRegistry()
{
// 注册 UI 资源
Register("MainMenu", GD.Load<PackedScene>("res://ui/MainMenu.tscn"));
Register("Settings", GD.Load<PackedScene>("res://ui/Settings.tscn"));
Register("ConfirmDialog", GD.Load<PackedScene>("res://ui/ConfirmDialog.tscn"));
Register("Toast", GD.Load<PackedScene>("res://ui/Toast.tscn"));
}
}
```
### 设置 UI 系统
```csharp
using GFramework.Godot.architecture;
using GFramework.Godot.ui;
public class GameArchitecture : AbstractArchitecture
{
protected override void InstallModules()
{
// 注册 UI 注册表
var uiRegistry = new GameUiRegistry();
RegisterUtility<IGodotUiRegistry>(uiRegistry);
// 注册 UI 工厂
var uiFactory = new GodotUiFactory();
RegisterUtility<IUiFactory>(uiFactory);
// 注册 UI 路由
var uiRouter = new GodotUiRouter();
RegisterSystem<IUiRouter>(uiRouter);
}
}
```
### 使用 UI 路由
```csharp
using Godot;
using GFramework.Godot.extensions;
public partial class GameController : Node
{
public override void _Ready()
{
ShowMainMenu();
}
private async void ShowMainMenu()
{
var uiRouter = this.GetSystem<IUiRouter>();
await uiRouter.PushAsync("MainMenu");
}
private async void ShowSettings()
{
var uiRouter = this.GetSystem<IUiRouter>();
await uiRouter.PushAsync("Settings");
}
private void ShowDialog()
{
var uiRouter = this.GetSystem<IUiRouter>();
uiRouter.Show("ConfirmDialog", UiLayer.Modal);
}
private void ShowToast(string message)
{
var uiRouter = this.GetSystem<IUiRouter>();
uiRouter.Show("Toast", UiLayer.Toast, new ToastParam { Message = message });
}
}
```
## 高级用法
### 不同层级的 UI 行为
```csharp
// Page 层 UI栈管理不可重入
public partial class MainMenuPage : Control, IUiPageBehaviorProvider
{
public IUiPageBehavior GetPage()
{
return new PageLayerUiPageBehavior(this, "MainMenu");
}
}
// Overlay 层 UI浮层可重入
public partial class InfoPanel : Control, IUiPageBehaviorProvider
{
public IUiPageBehavior GetPage()
{
return new OverlayLayerUiPageBehavior(this, "InfoPanel");
}
}
// Modal 层 UI模态对话框可重入
public partial class ConfirmDialog : Control, IUiPageBehaviorProvider
{
public IUiPageBehavior GetPage()
{
return new ModalLayerUiPageBehavior(this, "ConfirmDialog");
}
}
// Toast 层 UI提示可重入
public partial class ToastMessage : Control, IUiPageBehaviorProvider
{
public IUiPageBehavior GetPage()
{
return new ToastLayerUiPageBehavior(this, "Toast");
}
}
// Topmost 层 UI顶层不可重入
public partial class LoadingScreen : Control, IUiPageBehaviorProvider
{
public IUiPageBehavior GetPage()
{
return new TopmostLayerUiPageBehavior(this, "Loading");
}
}
```
### UI 参数传递
```csharp
// 定义 UI 参数
public class ConfirmDialogParam : IUiPageEnterParam
{
public string Title { get; set; }
public string Message { get; set; }
public Action OnConfirm { get; set; }
public Action OnCancel { get; set; }
}
// 在 UI 中接收参数
public partial class ConfirmDialog : Control, IUiPage
{
private Label _titleLabel;
private Label _messageLabel;
private Action _onConfirm;
private Action _onCancel;
public override void _Ready()
{
_titleLabel = GetNode<Label>("Title");
_messageLabel = GetNode<Label>("Message");
GetNode<Button>("ConfirmButton").Pressed += OnConfirmPressed;
GetNode<Button>("CancelButton").Pressed += OnCancelPressed;
}
public void OnEnter(IUiPageEnterParam? param)
{
if (param is ConfirmDialogParam dialogParam)
{
_titleLabel.Text = dialogParam.Title;
_messageLabel.Text = dialogParam.Message;
_onConfirm = dialogParam.OnConfirm;
_onCancel = dialogParam.OnCancel;
}
Show();
}
private void OnConfirmPressed()
{
_onConfirm?.Invoke();
CloseDialog();
}
private void OnCancelPressed()
{
_onCancel?.Invoke();
CloseDialog();
}
private void CloseDialog()
{
var uiRouter = this.GetSystem<IUiRouter>();
if (Handle.HasValue)
{
uiRouter.Hide(Handle.Value, UiLayer.Modal, destroy: true);
}
}
// ... 其他生命周期方法
}
// 显示对话框
var uiRouter = this.GetSystem<IUiRouter>();
uiRouter.Show("ConfirmDialog", UiLayer.Modal, new ConfirmDialogParam
{
Title = "确认",
Message = "确定要退出吗?",
OnConfirm = () => GD.Print("确认"),
OnCancel = () => GD.Print("取消")
});
```
### UI 根节点管理
```csharp
using Godot;
using GFramework.Godot.ui;
public partial class UiRoot : CanvasLayer, IUiRoot
{
private Control _pageLayer;
private Control _overlayLayer;
private Control _modalLayer;
private Control _toastLayer;
private Control _topmostLayer;
public override void _Ready()
{
// 创建各层级容器
_pageLayer = new Control { Name = "PageLayer" };
_overlayLayer = new Control { Name = "OverlayLayer" };
_modalLayer = new Control { Name = "ModalLayer" };
_toastLayer = new Control { Name = "ToastLayer" };
_topmostLayer = new Control { Name = "TopmostLayer" };
AddChild(_pageLayer);
AddChild(_overlayLayer);
AddChild(_modalLayer);
AddChild(_toastLayer);
AddChild(_topmostLayer);
}
public void AttachPage(Control page, UiLayer layer)
{
var container = GetLayerContainer(layer);
container.AddChild(page);
}
public void DetachPage(Control page, UiLayer layer)
{
var container = GetLayerContainer(layer);
container.RemoveChild(page);
}
private Control GetLayerContainer(UiLayer layer)
{
return layer switch
{
UiLayer.Page => _pageLayer,
UiLayer.Overlay => _overlayLayer,
UiLayer.Modal => _modalLayer,
UiLayer.Toast => _toastLayer,
UiLayer.Topmost => _topmostLayer,
_ => _pageLayer
};
}
}
```
### UI 动画和过渡
```csharp
public partial class AnimatedPage : Control, IUiPage
{
public void OnEnter(IUiPageEnterParam? param)
{
// 淡入动画
Modulate = new Color(1, 1, 1, 0);
Show();
var tween = CreateTween();
tween.TweenProperty(this, "modulate:a", 1.0f, 0.3f);
}
public void OnExit()
{
// 淡出动画
var tween = CreateTween();
tween.TweenProperty(this, "modulate:a", 0.0f, 0.3f);
tween.TweenCallback(Callable.From(Hide));
}
public void OnShow()
{
Show();
}
public void OnHide()
{
Hide();
}
// ... 其他方法
}
```
### UI 句柄管理
```csharp
public partial class DialogManager : Node
{
private UiHandle? _currentDialog;
public void ShowDialog(string dialogKey)
{
// 关闭当前对话框
CloseCurrentDialog();
// 显示新对话框
var uiRouter = this.GetSystem<IUiRouter>();
_currentDialog = uiRouter.Show(dialogKey, UiLayer.Modal);
}
public void CloseCurrentDialog()
{
if (_currentDialog.HasValue)
{
var uiRouter = this.GetSystem<IUiRouter>();
uiRouter.Hide(_currentDialog.Value, UiLayer.Modal, destroy: true);
_currentDialog = null;
}
}
}
```
### 多个 Toast 显示
```csharp
public partial class ToastManager : Node
{
private readonly List<UiHandle> _activeToasts = new();
public void ShowToast(string message, float duration = 3.0f)
{
var uiRouter = this.GetSystem<IUiRouter>();
// Toast 层支持重入,可以同时显示多个
var handle = uiRouter.Show("Toast", UiLayer.Toast, new ToastParam
{
Message = message
});
_activeToasts.Add(handle);
// 自动隐藏
GetTree().CreateTimer(duration).Timeout += () =>
{
uiRouter.Hide(handle, UiLayer.Toast, destroy: true);
_activeToasts.Remove(handle);
};
}
public void ClearAllToasts()
{
var uiRouter = this.GetSystem<IUiRouter>();
foreach (var handle in _activeToasts)
{
uiRouter.Hide(handle, UiLayer.Toast, destroy: true);
}
_activeToasts.Clear();
}
}
```
## 最佳实践
1. **UI 脚本实现 IUiPage 接口**:获得完整的生命周期管理
```csharp
✓ public partial class MyPage : Control, IUiPage { }
✗ public partial class MyPage : Control { } // 无生命周期管理
```
2. **使用正确的 UI 层级**:根据 UI 类型选择合适的层级
```csharp
✓ Page: 主要页面(主菜单、设置)
✓ Overlay: 浮层(信息面板)
✓ Modal: 模态对话框(确认框)
✓ Toast: 提示消息
✓ Topmost: 系统级(加载界面)
```
3. **在 OnEnter 中显示 UI**:确保 UI 正确显示
```csharp
public void OnEnter(IUiPageEnterParam? param)
{
Show(); // 显示 UI
// 初始化 UI 状态
}
```
4. **在 OnExit 中隐藏 UI**:确保 UI 正确隐藏
```csharp
public void OnExit()
{
Hide(); // 隐藏 UI
// 清理 UI 状态
}
```
5. **使用 UI 句柄管理非栈 UI**:对于 Modal、Toast 等层级
```csharp
var handle = uiRouter.Show("Dialog", UiLayer.Modal);
// 保存句柄以便后续关闭
uiRouter.Hide(handle, UiLayer.Modal, destroy: true);
```
6. **使用 UI 参数传递数据**:避免使用全局变量
```csharp
✓ uiRouter.Show("Dialog", UiLayer.Modal, new DialogParam { ... });
✗ GlobalData.DialogMessage = "..."; // 避免全局状态
```
## 常见问题
### 问题:如何在 Godot UI 中使用 GFramework
**解答**
UI 脚本实现 `IUiPage``IUiPageBehaviorProvider` 接口:
```csharp
public partial class MyPage : Control, IUiPage, IUiPageBehaviorProvider
{
public void OnEnter(IUiPageEnterParam? param) { }
public IUiPageBehavior GetPage() { return new PageLayerUiPageBehavior(this, "MyPage"); }
}
```
### 问题UI 层级有什么区别?
**解答**
- **Page**:栈管理,不可重入,用于主要页面
- **Overlay**:可重入,用于浮层
- **Modal**:可重入,带遮罩,用于对话框
- **Toast**:可重入,轻量提示
- **Topmost**:不可重入,最高优先级
### 问题:如何实现 UI 动画?
**解答**
在生命周期方法中使用 Godot Tween
```csharp
public void OnEnter(IUiPageEnterParam? param)
{
var tween = CreateTween();
tween.TweenProperty(this, "modulate:a", 1.0f, 0.3f);
}
```
### 问题:如何在 UI 中访问架构组件?
**解答**
使用扩展方法:
```csharp
public partial class MyPage : Control, IUiPage
{
public void OnEnter(IUiPageEnterParam? param)
{
var playerModel = this.GetModel<PlayerModel>();
var gameSystem = this.GetSystem<GameSystem>();
}
}
```
### 问题:如何关闭 Modal 或 Toast
**解答**
使用 UI 句柄:
```csharp
// 显示时保存句柄
var handle = uiRouter.Show("Dialog", UiLayer.Modal);
// 关闭时使用句柄
uiRouter.Hide(handle, UiLayer.Modal, destroy: true);
```
### 问题UI 生命周期方法的调用顺序是什么?
**解答**
- 进入:`OnEnter` -> `OnShow`
- 暂停:`OnPause` -> `OnHide`
- 恢复:`OnShow` -> `OnResume`
- 退出:`OnHide` -> `OnExit`
## 相关文档
- [UI 系统](/zh-CN/game/ui) - 核心 UI 管理
- [Godot 架构集成](/zh-CN/godot/architecture) - Godot 架构基础
- [Godot 场景系统](/zh-CN/godot/scene) - Godot 场景集成
- [Godot 扩展](/zh-CN/godot/extensions) - Godot 扩展方法

View File

@ -0,0 +1,950 @@
# 版本迁移指南
本文档提供 GFramework 不同版本之间的迁移指导,帮助开发者平滑升级到新版本。
## 概述
### 迁移指南的使用
本迁移指南旨在帮助开发者:
- **了解版本间的重大变更**:明确不同版本之间的 API 变化和行为差异
- **规划迁移路径**:根据项目实际情况选择合适的迁移策略
- **减少迁移风险**:通过详细的步骤说明和代码示例降低升级风险
- **快速定位问题**:提供常见问题的解决方案和回滚策略
### 阅读建议
1. **确认当前版本**:查看项目中使用的 GFramework 版本号
2. **查看目标版本**:确定要升级到的目标版本
3. **阅读相关章节**:重点关注涉及的版本迁移章节
4. **测试验证**:在测试环境中完成迁移并充分测试
5. **逐步升级**:对于跨多个大版本的升级,建议分步进行
## 版本兼容性
### 版本号说明
GFramework 遵循 [语义化版本](https://semver.org/lang/zh-CN/) 规范:
```
主版本号.次版本号.修订号 (MAJOR.MINOR.PATCH)
```
- **主版本号MAJOR**:不兼容的 API 变更
- **次版本号MINOR**:向后兼容的功能新增
- **修订号PATCH**:向后兼容的问题修正
### 兼容性矩阵
| 源版本 | 目标版本 | 兼容性 | 迁移难度 | 说明 |
|-------|-------|---------|------|---------------|
| 0.0.x | 0.0.y | ✅ 完全兼容 | 低 | 修订版本,直接升级 |
| 0.0.x | 1.0.0 | ⚠️ 部分兼容 | 中 | 需要代码调整 |
| 0.x.x | 1.x.x | ❌ 不兼容 | 高 | 重大变更,需要重构 |
| 1.x.x | 1.y.y | ✅ 向后兼容 | 低 | 次版本升级,可能有废弃警告 |
| 1.x.x | 2.0.0 | ❌ 不兼容 | 高 | 重大变更,需要重构 |
### .NET 版本支持
| GFramework 版本 | .NET 8.0 | .NET 9.0 | .NET 10.0 |
|---------------|----------|----------|-----------|
| 0.0.x | ✅ | ✅ | ✅ |
| 1.0.x | ✅ | ✅ | ✅ |
| 2.0.x | ❌ | ✅ | ✅ |
### Godot 版本支持
| GFramework 版本 | Godot 4.3 | Godot 4.4 | Godot 4.5 | Godot 4.6+ |
|---------------|-----------|-----------|-----------|------------|
| 0.0.x | ✅ | ✅ | ✅ | ✅ |
| 1.0.x | ❌ | ✅ | ✅ | ✅ |
| 2.0.x | ❌ | ❌ | ✅ | ✅ |
## 从 0.x 迁移到 1.x
### 重大变更概述
1.0 版本是 GFramework 的第一个稳定版本,引入了多项重大变更以提升框架的稳定性、性能和可维护性。
#### 架构层面变更
- **架构初始化方式变更**:统一使用异步初始化
- **生命周期阶段调整**:简化阶段流程,移除冗余阶段
- **IOC 容器增强**:支持作用域和生命周期管理
- **模块系统重构**:引入新的模块注册机制
#### API 变更
- **命名空间调整**:部分类型移动到新的命名空间
- **接口签名变更**:部分接口方法签名调整
- **废弃 API 移除**:移除 0.x 中标记为废弃的 API
- **泛型约束调整**:部分泛型方法增加或调整约束
#### 行为变更
- **事件传播机制**:优化事件传播逻辑
- **协程调度策略**:改进协程调度算法
- **资源管理策略**:引入新的资源释放策略
### 迁移前准备
#### 1. 备份项目
```bash
# 创建项目备份
git checkout -b backup-before-migration
git push origin backup-before-migration
# 或使用文件系统备份
cp -r YourProject YourProject-backup
```
#### 2. 检查当前版本
```bash
# 查看当前使用的 GFramework 版本
dotnet list package | grep GFramework
```
#### 3. 更新依赖工具
```bash
# 更新 .NET SDK
dotnet --version
# 更新 NuGet 客户端
dotnet nuget --version
```
#### 4. 运行现有测试
```bash
# 确保所有测试通过
dotnet test
# 记录测试结果作为基准
dotnet test --logger "trx;LogFileName=baseline-tests.trx"
```
### 迁移步骤
#### 步骤 1更新 NuGet 包
```bash
# 更新核心包
dotnet add package GeWuYou.GFramework.Core --version 1.0.0
dotnet add package GeWuYou.GFramework.Core.Abstractions --version 1.0.0
# 更新游戏扩展包
dotnet add package GeWuYou.GFramework.Game --version 1.0.0
dotnet add package GeWuYou.GFramework.Game.Abstractions --version 1.0.0
# 更新 Godot 集成包(如果使用)
dotnet add package GeWuYou.GFramework.Godot --version 1.0.0
# 更新源码生成器
dotnet add package GeWuYou.GFramework.SourceGenerators --version 1.0.0
```
#### 步骤 2更新命名空间引用
**0.x 版本:**
```csharp
using GFramework.Core;
using GFramework.Core.Architecture;
using GFramework.Core.Events;
```
**1.x 版本:**
```csharp
using GFramework.Core.Abstractions.Architecture;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Architecture;
using GFramework.Core.Events;
```
#### 步骤 3更新架构初始化代码
**0.x 版本:**
```csharp
public class GameArchitecture : Architecture
{
protected override void Init()
{
RegisterModel(new PlayerModel());
RegisterSystem(new GameplaySystem());
}
}
// 同步初始化
var architecture = new GameArchitecture();
architecture.Initialize();
```
**1.x 版本:**
```csharp
public class GameArchitecture : Architecture
{
protected override void Init()
{
RegisterModel(new PlayerModel());
RegisterSystem(new GameplaySystem());
}
}
// 推荐使用异步初始化
var architecture = new GameArchitecture();
await architecture.InitializeAsync();
// 或者使用同步初始化(不推荐)
// architecture.Initialize();
```
#### 步骤 4更新事件注册代码
**0.x 版本:**
```csharp
// 注册事件
this.RegisterEvent&lt;PlayerDiedEvent&gt;(OnPlayerDied);
// 发送事件
this.SendEvent(new PlayerDiedEvent());
```
**1.x 版本:**
```csharp
// 注册事件API 保持兼容)
this.RegisterEvent&lt;PlayerDiedEvent&gt;(OnPlayerDied);
// 发送事件API 保持兼容)
this.SendEvent(new PlayerDiedEvent());
// 新增:带优先级的事件注册
this.RegisterEvent&lt;PlayerDiedEvent&gt;(OnPlayerDied, priority: 100);
```
#### 步骤 5更新命令和查询代码
**0.x 版本:**
```csharp
public class MovePlayerCommand : AbstractCommand
{
public Vector2 Direction { get; set; }
protected override void OnDo()
{
// 执行逻辑
}
}
// 发送命令
this.SendCommand(new MovePlayerCommand { Direction = direction });
```
**1.x 版本:**
```csharp
// 命令 API 保持兼容
public class MovePlayerCommand : AbstractCommand
{
public Vector2 Direction { get; set; }
protected override void OnDo()
{
// 执行逻辑
}
}
// 发送命令API 保持兼容)
this.SendCommand(new MovePlayerCommand { Direction = direction });
// 新增:异步命令支持
public class LoadDataCommand : AbstractAsyncCommand
{
protected override async Task OnDoAsync()
{
await Task.Delay(100);
}
}
```
#### 步骤 6更新 IOC 容器使用
**0.x 版本:**
```csharp
// 注册服务
RegisterUtility(new StorageUtility());
// 获取服务
var storage = this.GetUtility&lt;StorageUtility&gt;();
```
**1.x 版本:**
```csharp
// 注册服务API 保持兼容)
RegisterUtility(new StorageUtility());
// 获取服务API 保持兼容)
var storage = this.GetUtility&lt;StorageUtility&gt;();
// 新增:按优先级获取服务
var storages = this.GetUtilities&lt;IStorageUtility&gt;();
var primaryStorage = storages.FirstOrDefault();
```
#### 步骤 7更新协程代码
**0.x 版本:**
```csharp
// 启动协程
var handle = CoroutineHelper.Start(MyCoroutine());
// 等待协程
yield return new WaitForCoroutine(handle);
```
**1.x 版本:**
```csharp
// 启动协程API 保持兼容)
var handle = CoroutineHelper.Start(MyCoroutine());
// 等待协程API 保持兼容)
yield return new WaitForCoroutine(handle);
// 新增:协程分组和优先级
var handle = CoroutineHelper.Start(
MyCoroutine(),
group: "gameplay",
priority: CoroutinePriority.High
);
```
### API 变更详解
#### 废弃的 API
以下 API 在 1.0 版本中已被移除:
##### 1. 同步命令查询扩展(已废弃)
**0.x 版本:**
```csharp
// 这些方法在 1.0 中已移除
this.SendCommandSync(command);
this.SendQuerySync(query);
```
**1.x 版本:**
```csharp
// 使用标准方法
this.SendCommand(command);
this.SendQuery(query);
// 或使用异步方法
await this.SendCommandAsync(command);
await this.SendQueryAsync(query);
```
##### 2. 旧版事件 API已废弃
**0.x 版本:**
```csharp
// 旧版事件注册方式
EventBus.Register&lt;MyEvent&gt;(handler);
```
**1.x 版本:**
```csharp
// 使用新的事件注册方式
this.RegisterEvent&lt;MyEvent&gt;(handler);
// 或使用事件总线
this.GetEventBus().Register&lt;MyEvent&gt;(handler);
```
##### 3. 直接访问 IOC 容器(已限制)
**0.x 版本:**
```csharp
// 直接访问容器
var container = architecture.Container;
container.Register&lt;IService, ServiceImpl&gt;();
```
**1.x 版本:**
```csharp
// 使用架构提供的注册方法
architecture.RegisterUtility&lt;IService&gt;(new ServiceImpl());
// 或在 Init 方法中注册
protected override void Init()
{
RegisterUtility&lt;IService&gt;(new ServiceImpl());
}
```
#### 新增的 API
##### 1. 优先级支持
```csharp
// 事件优先级
this.RegisterEvent&lt;MyEvent&gt;(handler, priority: 100);
// 服务优先级
RegisterUtility&lt;IService&gt;(service, priority: 10);
// 协程优先级
CoroutineHelper.Start(routine, priority: CoroutinePriority.High);
```
##### 2. 异步初始化增强
```csharp
// 异步初始化架构
await architecture.InitializeAsync();
// 等待架构就绪
await architecture.WaitUntilReadyAsync();
// 异步初始化组件
public class MyModel : AbstractModel, IAsyncInitializable
{
public async Task InitializeAsync()
{
await LoadDataAsync();
}
}
```
##### 3. 事件过滤和统计
```csharp
// 事件过滤
this.RegisterEvent&lt;MyEvent&gt;(handler, filter: e => e.IsValid);
// 事件统计
var stats = eventBus.GetStatistics();
Console.WriteLine($"Total events: {stats.TotalEventsSent}");
```
##### 4. 协程分组管理
```csharp
// 创建协程组
var handle = CoroutineHelper.Start(
routine,
group: "ui-animations"
);
// 暂停协程组
CoroutineHelper.PauseGroup("ui-animations");
// 恢复协程组
CoroutineHelper.ResumeGroup("ui-animations");
// 停止协程组
CoroutineHelper.StopGroup("ui-animations");
```
### 配置变更
#### 架构配置
**0.x 版本:**
```csharp
var architecture = new GameArchitecture();
```
**1.x 版本:**
```csharp
// 使用配置对象
var config = new ArchitectureConfiguration
{
ArchitectureProperties = new ArchitectureProperties
{
StrictPhaseValidation = true,
AllowLateRegistration = false
},
LoggerProperties = new LoggerProperties
{
MinimumLevel = LogLevel.Information
}
};
var architecture = new GameArchitecture(configuration: config);
```
#### 日志配置
**0.x 版本:**
```csharp
// 使用默认日志
```
**1.x 版本:**
```csharp
// 配置日志系统
var logConfig = new LoggingConfiguration
{
MinimumLevel = LogLevel.Debug,
Appenders = new List&lt;ILogAppender&gt;
{
new ConsoleAppender(),
new FileAppender("logs/game.log")
},
Filters = new List&lt;ILogFilter&gt;
{
new LogLevelFilter(LogLevel.Warning),
new NamespaceFilter("GFramework.*")
}
};
```
### 依赖变更
#### NuGet 包更新
| 包名 | 0.x 版本 | 1.x 版本 | 变更说明 |
|------------------------------------------|--------|--------|----------|
| Microsoft.Extensions.DependencyInjection | 8.0.0 | 10.0.3 | 升级到最新版本 |
| Arch | 1.x | 2.1.0 | ECS 框架升级 |
| Arch.System | 1.0.x | 1.1.0 | 系统组件升级 |
#### 包拆分
1.0 版本对包结构进行了优化:
**0.x 版本:**
```xml
<PackageReference Include="GeWuYou.GFramework" Version="0.0.200" />
```
**1.x 版本:**
```xml
<!-- 推荐按需引用 -->
<PackageReference Include="GeWuYou.GFramework.Core" Version="1.0.0" />
<PackageReference Include="GeWuYou.GFramework.Core.Abstractions" Version="1.0.0" />
<PackageReference Include="GeWuYou.GFramework.Game" Version="1.0.0" />
<PackageReference Include="GeWuYou.GFramework.Godot" Version="1.0.0" />
```
### 代码迁移工具
#### 自动化迁移工具
GFramework 提供了迁移工具来自动化部分迁移工作:
```bash
# 安装迁移工具
dotnet tool install -g GFramework.MigrationTool
# 运行迁移分析
gframework-migrate analyze --project YourProject.csproj
# 执行自动迁移
gframework-migrate apply --project YourProject.csproj --target-version 1.0.0
# 生成迁移报告
gframework-migrate report --output migration-report.html
```
#### 手动迁移检查清单
使用以下清单确保完整迁移:
- [ ] 更新所有 NuGet 包到 1.0.0
- [ ] 更新命名空间引用
- [ ] 替换废弃的 API
- [ ] 更新架构初始化代码
- [ ] 更新配置代码
- [ ] 运行所有单元测试
- [ ] 运行集成测试
- [ ] 执行性能测试
- [ ] 更新文档和注释
- [ ] 代码审查
### 测试迁移
#### 单元测试更新
**0.x 版本:**
```csharp
[Test]
public void TestArchitectureInit()
{
var architecture = new TestArchitecture();
architecture.Initialize();
Assert.That(architecture.CurrentPhase, Is.EqualTo(ArchitecturePhase.Ready));
}
```
**1.x 版本:**
```csharp
[Test]
public async Task TestArchitectureInit()
{
var architecture = new TestArchitecture();
await architecture.InitializeAsync();
Assert.That(architecture.CurrentPhase, Is.EqualTo(ArchitecturePhase.Ready));
}
```
#### 集成测试更新
**0.x 版本:**
```csharp
[Test]
public void TestGameFlow()
{
var game = new GameArchitecture();
game.Initialize();
game.SendCommand(new StartGameCommand());
var score = game.SendQuery(new GetScoreQuery());
Assert.That(score, Is.EqualTo(0));
}
```
**1.x 版本:**
```csharp
[Test]
public async Task TestGameFlow()
{
var game = new GameArchitecture();
await game.InitializeAsync();
await game.SendCommandAsync(new StartGameCommand());
var score = await game.SendQueryAsync(new GetScoreQuery());
Assert.That(score, Is.EqualTo(0));
}
```
## 常见问题
### 编译错误
#### 问题 1命名空间找不到
**错误信息:**
```
error CS0246: The type or namespace name 'IArchitecture' could not be found
```
**解决方案:**
```csharp
// 添加正确的命名空间引用
using GFramework.Core.Abstractions.Architecture;
```
#### 问题 2方法签名不匹配
**错误信息:**
```
error CS1501: No overload for method 'RegisterEvent' takes 1 arguments
```
**解决方案:**
```csharp
// 0.x 版本
this.RegisterEvent&lt;MyEvent&gt;(handler);
// 1.x 版本(兼容)
this.RegisterEvent&lt;MyEvent&gt;(handler);
// 1.x 版本(带优先级)
this.RegisterEvent&lt;MyEvent&gt;(handler, priority: 100);
```
#### 问题 3泛型约束不满足
**错误信息:**
```
error CS0311: The type 'MyType' cannot be used as type parameter 'T'
in the generic type or method. There is no implicit reference conversion
from 'MyType' to 'GFramework.Core.Abstractions.IModel'.
```
**解决方案:**
```csharp
// 确保类型实现了正确的接口
public class MyModel : AbstractModel, IModel
{
// 实现
}
```
### 运行时错误
#### 问题 1架构未初始化
**错误信息:**
```
InvalidOperationException: Architecture is not initialized
```
**解决方案:**
```csharp
// 确保在使用前初始化架构
var architecture = new GameArchitecture();
await architecture.InitializeAsync();
await architecture.WaitUntilReadyAsync();
// 然后再使用
this.SendCommand(new MyCommand());
```
#### 问题 2服务未注册
**错误信息:**
```
InvalidOperationException: Service of type 'IMyService' is not registered
```
**解决方案:**
```csharp
// 在 Init 方法中注册服务
protected override void Init()
{
RegisterUtility&lt;IMyService&gt;(new MyServiceImpl());
}
```
#### 问题 3事件处理器未触发
**问题描述:**
事件发送后,注册的处理器没有被调用。
**解决方案:**
```csharp
// 确保事件处理器正确注册
var unregister = this.RegisterEvent&lt;MyEvent&gt;(OnMyEvent);
// 确保在对象销毁时注销
protected override void OnDestroy()
{
unregister?.UnRegister();
}
// 检查事件类型是否匹配
this.SendEvent(new MyEvent()); // 确保类型完全一致
```
### 性能问题
#### 问题 1初始化时间过长
**问题描述:**
架构初始化耗时明显增加。
**解决方案:**
```csharp
// 使用异步初始化
await architecture.InitializeAsync();
// 对于耗时的初始化操作,使用异步方法
public class MyModel : AbstractModel, IAsyncInitializable
{
public async Task InitializeAsync()
{
// 异步加载数据
await LoadDataAsync();
}
}
```
#### 问题 2事件处理性能下降
**问题描述:**
事件处理速度变慢。
**解决方案:**
```csharp
// 使用事件过滤减少不必要的处理
this.RegisterEvent&lt;MyEvent&gt;(
handler,
filter: e => e.ShouldProcess
);
// 使用优先级控制处理顺序
this.RegisterEvent&lt;MyEvent&gt;(
criticalHandler,
priority: 100
);
```
### 兼容性问题
#### 问题 1Godot 版本不兼容
**问题描述:**
升级后在 Godot 4.3 中无法运行。
**解决方案:**
```bash
# GFramework 1.0 要求 Godot 4.4+
# 升级 Godot 到 4.4 或更高版本
```
#### 问题 2.NET 版本不兼容
**问题描述:**
项目使用 .NET 7.0,无法使用 GFramework 1.0。
**解决方案:**
```xml
<!-- 升级项目到 .NET 8.0 或更高版本 -->
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
```
## 回滚方案
如果迁移过程中遇到无法解决的问题,可以按以下步骤回滚:
### 步骤 1恢复包版本
```bash
# 回滚到 0.x 版本
dotnet add package GeWuYou.GFramework.Core --version 0.0.200
dotnet add package GeWuYou.GFramework.Core.Abstractions --version 0.0.200
dotnet add package GeWuYou.GFramework.Game --version 0.0.200
dotnet add package GeWuYou.GFramework.Godot --version 0.0.200
```
### 步骤 2恢复代码
```bash
# 从备份分支恢复
git checkout backup-before-migration
# 或从文件系统备份恢复
rm -rf YourProject
cp -r YourProject-backup YourProject
```
### 步骤 3验证回滚
```bash
# 清理构建缓存
dotnet clean
rm -rf bin obj
# 重新构建
dotnet build
# 运行测试
dotnet test
```
### 步骤 4记录问题
创建问题报告,包含:
- 遇到的具体错误
- 错误发生的环境信息
- 复现步骤
- 相关代码片段
提交到 [GitHub Issues](https://github.com/GeWuYou/GFramework/issues)。
## 获取帮助
### 官方资源
- **文档中心**[https://gewuyou.github.io/GFramework/](https://gewuyou.github.io/GFramework/)
- **GitHub 仓库**[https://github.com/GeWuYou/GFramework](https://github.com/GeWuYou/GFramework)
- **问题追踪**[https://github.com/GeWuYou/GFramework/issues](https://github.com/GeWuYou/GFramework/issues)
- **讨论区**[https://github.com/GeWuYou/GFramework/discussions](https://github.com/GeWuYou/GFramework/discussions)
### 社区支持
- 在 GitHub Discussions 中提问
- 查看已有的 Issues 和 Pull Requests
- 参考示例项目和教程
### 商业支持
如需专业的迁移支持服务,请联系项目维护团队。
## 附录
### A. 完整的 API 对照表
| 0.x API | 1.x API | 说明 |
|-----------------------------|----------------------------------------|------------|
| `architecture.Initialize()` | `await architecture.InitializeAsync()` | 推荐使用异步初始化 |
| `this.SendCommandSync()` | `this.SendCommand()` | 移除 Sync 后缀 |
| `this.SendQuerySync()` | `this.SendQuery()` | 移除 Sync 后缀 |
| `EventBus.Register()` | `this.RegisterEvent()` | 使用扩展方法 |
| `Container.Register()` | `RegisterUtility()` | 使用架构方法 |
### B. 迁移时间估算
| 项目规模 | 预估时间 | 说明 |
|---------------|-------|--------------|
| 小型(&lt;10k 行) | 1-2 天 | 主要是包更新和测试 |
| 中型10k-50k 行) | 3-5 天 | 需要代码审查和重构 |
| 大型(&gt;50k 行) | 1-2 周 | 需要分模块迁移和充分测试 |
### C. 相关资源
- [架构设计文档](./core/architecture.md)
- [事件系统文档](./core/events.md)
- [命令查询文档](./core/command.md)
- [协程系统文档](./core/coroutine.md)
- [最佳实践](./best-practices/architecture-patterns.md)
---
**文档版本**1.0.0
**最后更新**2026-03-07
**许可证**Apache 2.0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,598 @@
---
title: 使用协程系统
description: 学习如何使用协程系统实现异步操作和时间控制
---
# 使用协程系统
## 学习目标
完成本教程后,你将能够:
- 理解协程的基本概念和执行机制
- 创建和启动协程
- 使用各种等待指令控制协程执行
- 在架构组件中使用协程
- 实现常见的游戏逻辑(延迟执行、循环任务、事件等待)
## 前置条件
- 已安装 GFramework.Core NuGet 包
- 了解 C# 基础语法和迭代器IEnumerator
- 阅读过[快速开始](/zh-CN/getting-started/quick-start)
- 了解[生命周期管理](/zh-CN/core/lifecycle)
## 步骤 1创建第一个协程
首先,让我们创建一个简单的协程来理解基本概念。
```csharp
using GFramework.Core.Abstractions.coroutine;
using GFramework.Core.coroutine;
using GFramework.Core.coroutine.instructions;
namespace MyGame.Systems
{
public class TutorialSystem : AbstractSystem
{
protected override void OnInit()
{
// 启动协程
this.StartCoroutine(MyFirstCoroutine());
}
/// <summary>
/// 第一个协程示例
/// </summary>
private IEnumerator<IYieldInstruction> MyFirstCoroutine()
{
Console.WriteLine("协程开始执行");
// 等待 1 秒
yield return CoroutineHelper.WaitForSeconds(1.0);
Console.WriteLine("1 秒后执行");
// 等待 1 帧
yield return CoroutineHelper.WaitForOneFrame();
Console.WriteLine("下一帧执行");
// 等待 5 帧
yield return CoroutineHelper.WaitForFrames(5);
Console.WriteLine("5 帧后执行");
}
}
}
```
**代码说明**
- 协程方法返回 `IEnumerator<IYieldInstruction>`
- 使用 `yield return` 返回等待指令
- `this.StartCoroutine()` 扩展方法启动协程
- `WaitForSeconds` 等待指定秒数
- `WaitForOneFrame` 等待一帧
- `WaitForFrames` 等待多帧
## 步骤 2实现生命值自动恢复
让我们实现一个实用的功能:玩家生命值自动恢复。
```csharp
using GFramework.Core.Abstractions.model;
using GFramework.Core.Abstractions.property;
using GFramework.Core.model;
namespace MyGame.Models
{
public class PlayerModel : AbstractModel
{
// 当前生命值
public BindableProperty<int> Health { get; } = new(100);
// 最大生命值
public BindableProperty<int> MaxHealth { get; } = new(100);
// 是否启用自动恢复
public BindableProperty<bool> AutoRegenEnabled { get; } = new(true);
private CoroutineHandle? _regenHandle;
protected override void OnInit()
{
// 启动生命值恢复协程
StartHealthRegeneration();
}
/// <summary>
/// 启动生命值恢复
/// </summary>
public void StartHealthRegeneration()
{
// 如果已经在运行,先停止
if (_regenHandle.HasValue)
{
this.StopCoroutine(_regenHandle.Value);
}
// 启动新的恢复协程
_regenHandle = this.StartCoroutine(HealthRegenerationCoroutine());
}
/// <summary>
/// 停止生命值恢复
/// </summary>
public void StopHealthRegeneration()
{
if (_regenHandle.HasValue)
{
this.StopCoroutine(_regenHandle.Value);
_regenHandle = null;
}
}
/// <summary>
/// 生命值恢复协程
/// </summary>
private IEnumerator<IYieldInstruction> HealthRegenerationCoroutine()
{
while (true)
{
// 等待 1 秒
yield return CoroutineHelper.WaitForSeconds(1.0);
// 检查是否启用自动恢复
if (!AutoRegenEnabled.Value)
continue;
// 如果生命值未满,恢复 5 点
if (Health.Value < MaxHealth.Value)
{
Health.Value = Math.Min(Health.Value + 5, MaxHealth.Value);
Console.WriteLine($"生命值恢复: {Health.Value}/{MaxHealth.Value}");
}
}
}
}
}
```
**代码说明**
- 使用 `while (true)` 创建无限循环协程
- 保存协程句柄以便后续控制
- 使用 `StopCoroutine` 停止协程
- 协程中可以访问类成员变量
## 步骤 3实现技能冷却系统
接下来实现一个技能冷却系统,展示如何使用协程管理时间相关的游戏逻辑。
```csharp
using GFramework.Core.system;
using System.Collections.Generic;
namespace MyGame.Systems
{
public class SkillSystem : AbstractSystem
{
// 技能冷却状态
private readonly Dictionary<string, bool> _skillCooldowns = new();
/// <summary>
/// 使用技能
/// </summary>
public bool UseSkill(string skillName, double cooldownTime)
{
// 检查是否在冷却中
if (_skillCooldowns.TryGetValue(skillName, out var isOnCooldown) && isOnCooldown)
{
Console.WriteLine($"技能 {skillName} 冷却中...");
return false;
}
// 执行技能
Console.WriteLine($"使用技能: {skillName}");
// 启动冷却协程
this.StartCoroutine(SkillCooldownCoroutine(skillName, cooldownTime));
return true;
}
/// <summary>
/// 技能冷却协程
/// </summary>
private IEnumerator<IYieldInstruction> SkillCooldownCoroutine(string skillName, double cooldownTime)
{
// 标记为冷却中
_skillCooldowns[skillName] = true;
Console.WriteLine($"技能 {skillName} 开始冷却 {cooldownTime} 秒");
// 等待冷却时间
yield return CoroutineHelper.WaitForSeconds(cooldownTime);
// 冷却结束
_skillCooldowns[skillName] = false;
Console.WriteLine($"技能 {skillName} 冷却完成");
}
/// <summary>
/// 带进度显示的技能冷却
/// </summary>
private IEnumerator<IYieldInstruction> SkillCooldownWithProgressCoroutine(
string skillName,
double cooldownTime)
{
_skillCooldowns[skillName] = true;
// 使用 WaitForProgress 显示冷却进度
yield return CoroutineHelper.WaitForProgress(
duration: cooldownTime,
onProgress: progress =>
{
Console.WriteLine($"技能 {skillName} 冷却进度: {progress * 100:F0}%");
}
);
_skillCooldowns[skillName] = false;
Console.WriteLine($"技能 {skillName} 冷却完成");
}
}
}
```
**代码说明**
- 使用字典管理多个技能的冷却状态
- 每个技能使用独立的协程管理冷却
- `WaitForProgress` 可以在等待期间执行回调
- 协程结束后自动清理冷却状态
## 步骤 4等待事件触发
实现一个等待玩家完成任务的系统,展示如何在协程中等待事件。
```csharp
using GFramework.Core.Abstractions.events;
using GFramework.Core.coroutine.instructions;
namespace MyGame.Systems
{
// 任务完成事件
public record QuestCompletedEvent(int QuestId, string QuestName) : IEvent;
public class QuestSystem : AbstractSystem
{
/// <summary>
/// 开始任务并等待完成
/// </summary>
public void StartQuest(int questId, string questName)
{
this.StartCoroutine(QuestCoroutine(questId, questName));
}
/// <summary>
/// 任务协程
/// </summary>
private IEnumerator<IYieldInstruction> QuestCoroutine(int questId, string questName)
{
Console.WriteLine($"任务开始: {questName}");
// 获取事件总线
var eventBus = this.GetService<IEventBus>();
// 等待任务完成事件
var waitEvent = new WaitForEvent<QuestCompletedEvent>(
eventBus,
evt => evt.QuestId == questId // 过滤条件
);
yield return waitEvent;
// 获取事件数据
var completedEvent = waitEvent.EventData;
Console.WriteLine($"任务完成: {completedEvent.QuestName}");
// 发放奖励
GiveReward(questId);
}
/// <summary>
/// 带超时的任务
/// </summary>
private IEnumerator<IYieldInstruction> TimedQuestCoroutine(
int questId,
string questName,
double timeLimit)
{
Console.WriteLine($"限时任务开始: {questName} (时限: {timeLimit}秒)");
var eventBus = this.GetService<IEventBus>();
// 等待事件,带超时
var waitEvent = new WaitForEventWithTimeout<QuestCompletedEvent>(
eventBus,
timeout: timeLimit,
predicate: evt => evt.QuestId == questId
);
yield return waitEvent;
if (waitEvent.IsTimeout)
{
Console.WriteLine($"任务超时失败: {questName}");
}
else
{
Console.WriteLine($"任务完成: {questName}");
GiveReward(questId);
}
}
private void GiveReward(int questId)
{
Console.WriteLine($"发放任务 {questId} 的奖励");
}
}
}
```
**代码说明**
- `WaitForEvent` 等待特定事件触发
- 可以使用 `predicate` 参数过滤事件
- `WaitForEventWithTimeout` 支持超时机制
- 通过 `EventData` 属性获取事件数据
## 步骤 5协程组合与嵌套
实现一个复杂的游戏流程,展示如何组合多个协程。
```csharp
namespace MyGame.Systems
{
public class GameFlowSystem : AbstractSystem
{
/// <summary>
/// 游戏开始流程
/// </summary>
public void StartGame()
{
this.StartCoroutine(GameStartSequence());
}
/// <summary>
/// 游戏开始序列
/// </summary>
private IEnumerator<IYieldInstruction> GameStartSequence()
{
Console.WriteLine("=== 游戏开始 ===");
// 1. 显示标题
yield return ShowTitle();
// 2. 加载资源
yield return LoadResources();
// 3. 初始化玩家
yield return InitializePlayer();
// 4. 播放开场动画
yield return PlayOpeningAnimation();
Console.WriteLine("=== 游戏准备完成 ===");
}
/// <summary>
/// 显示标题
/// </summary>
private IEnumerator<IYieldInstruction> ShowTitle()
{
Console.WriteLine("显示游戏标题...");
yield return CoroutineHelper.WaitForSeconds(2.0);
Console.WriteLine("标题显示完成");
}
/// <summary>
/// 加载资源
/// </summary>
private IEnumerator<IYieldInstruction> LoadResources()
{
Console.WriteLine("开始加载资源...");
// 并行加载多个资源
var loadTextures = LoadTexturesCoroutine();
var loadAudio = LoadAudioCoroutine();
var loadModels = LoadModelsCoroutine();
// 等待所有资源加载完成
yield return new WaitForAllCoroutines(
this.GetCoroutineScheduler(),
loadTextures,
loadAudio,
loadModels
);
Console.WriteLine("所有资源加载完成");
}
private IEnumerator<IYieldInstruction> LoadTexturesCoroutine()
{
Console.WriteLine(" 加载纹理...");
yield return CoroutineHelper.WaitForSeconds(1.0);
Console.WriteLine(" 纹理加载完成");
}
private IEnumerator<IYieldInstruction> LoadAudioCoroutine()
{
Console.WriteLine(" 加载音频...");
yield return CoroutineHelper.WaitForSeconds(1.5);
Console.WriteLine(" 音频加载完成");
}
private IEnumerator<IYieldInstruction> LoadModelsCoroutine()
{
Console.WriteLine(" 加载模型...");
yield return CoroutineHelper.WaitForSeconds(0.8);
Console.WriteLine(" 模型加载完成");
}
private IEnumerator<IYieldInstruction> InitializePlayer()
{
Console.WriteLine("初始化玩家...");
yield return CoroutineHelper.WaitForSeconds(0.5);
Console.WriteLine("玩家初始化完成");
}
private IEnumerator<IYieldInstruction> PlayOpeningAnimation()
{
Console.WriteLine("播放开场动画...");
yield return CoroutineHelper.WaitForSeconds(3.0);
Console.WriteLine("开场动画播放完成");
}
/// <summary>
/// 获取协程调度器
/// </summary>
private CoroutineScheduler GetCoroutineScheduler()
{
// 从架构服务中获取
return this.GetService<CoroutineScheduler>();
}
}
}
```
**代码说明**
- 使用 `yield return` 调用其他协程实现嵌套
- `WaitForAllCoroutines` 并行执行多个协程
- 协程可以像函数一样组合和复用
- 清晰的流程控制,避免回调嵌套
## 完整代码
### GameArchitecture.cs
```csharp
using GFramework.Core.architecture;
namespace MyGame
{
public class GameArchitecture : Architecture
{
public static IArchitecture Interface { get; private set; }
protected override void Init()
{
Interface = this;
// 注册 Model
RegisterModel(new PlayerModel());
// 注册 System
RegisterSystem(new TutorialSystem());
RegisterSystem(new SkillSystem());
RegisterSystem(new QuestSystem());
RegisterSystem(new GameFlowSystem());
}
}
}
```
### 测试代码
```csharp
using MyGame;
using MyGame.Systems;
// 初始化架构
var architecture = new GameArchitecture();
architecture.Initialize();
await architecture.WaitUntilReadyAsync();
// 测试技能系统
var skillSystem = architecture.GetSystem<SkillSystem>();
skillSystem.UseSkill("火球术", 3.0);
await Task.Delay(1000);
skillSystem.UseSkill("火球术", 3.0); // 冷却中
await Task.Delay(3000);
skillSystem.UseSkill("火球术", 3.0); // 冷却完成
// 测试任务系统
var questSystem = architecture.GetSystem<QuestSystem>();
questSystem.StartQuest(1, "击败史莱姆");
// 模拟任务完成
await Task.Delay(2000);
var eventBus = architecture.GetService<IEventBus>();
eventBus.Publish(new QuestCompletedEvent(1, "击败史莱姆"));
// 测试游戏流程
var gameFlowSystem = architecture.GetSystem<GameFlowSystem>();
gameFlowSystem.StartGame();
```
## 运行结果
运行程序后,你将看到类似以下的输出:
```
协程开始执行
1 秒后执行
下一帧执行
5 帧后执行
使用技能: 火球术
技能 火球术 开始冷却 3.0 秒
技能 火球术 冷却中...
技能 火球术 冷却完成
使用技能: 火球术
任务开始: 击败史莱姆
任务完成: 击败史莱姆
发放任务 1 的奖励
=== 游戏开始 ===
显示游戏标题...
标题显示完成
开始加载资源...
加载纹理...
加载音频...
加载模型...
模型加载完成
纹理加载完成
音频加载完成
所有资源加载完成
初始化玩家...
玩家初始化完成
播放开场动画...
开场动画播放完成
=== 游戏准备完成 ===
```
**验证步骤**
1. 协程按预期顺序执行
2. 技能冷却系统正常工作
3. 事件等待功能正确
4. 并行加载资源成功
## 下一步
恭喜!你已经掌握了协程系统的基本用法。接下来可以学习:
- [实现状态机](/zh-CN/tutorials/state-machine-tutorial) - 使用协程实现状态转换
- [资源管理最佳实践](/zh-CN/tutorials/resource-management) - 在协程中加载资源
- [使用事件系统](/zh-CN/core/events) - 协程与事件系统集成
## 相关文档
- [协程系统](/zh-CN/core/coroutine) - 协程系统详细说明
- [事件系统](/zh-CN/core/events) - 事件系统详解
- [生命周期管理](/zh-CN/core/lifecycle) - 组件生命周期
- [System 层](/zh-CN/core/system) - System 详细说明

View File

@ -0,0 +1,785 @@
---
title: 实现数据版本迁移
description: 学习如何实现数据版本迁移系统,处理不同版本间的数据升级
---
# 实现数据版本迁移
## 学习目标
完成本教程后,你将能够:
- 理解数据版本迁移的重要性和应用场景
- 定义版本化数据结构
- 实现数据迁移接口
- 注册和管理迁移策略
- 处理多版本连续升级
- 测试迁移流程的正确性
## 前置条件
- 已安装 GFramework.Game NuGet 包
- 了解 C# 基础语法和接口实现
- 阅读过[快速开始](/zh-CN/getting-started/quick-start)
- 了解[数据与存档系统](/zh-CN/game/data)
- 建议先完成[实现存档系统](/zh-CN/tutorials/save-system)教程
## 为什么需要数据迁移
在游戏开发过程中,数据结构经常会发生变化:
- **新增功能**:添加新的游戏系统需要新的数据字段
- **重构优化**:改进数据结构以提升性能或可维护性
- **修复问题**:修正早期设计的缺陷
- **平衡调整**:调整游戏数值和配置
数据迁移系统能够:
- 自动将旧版本数据升级到新版本
- 保证玩家存档的兼容性
- 避免数据丢失和游戏崩溃
- 提供平滑的版本过渡体验
## 步骤 1定义版本化数据结构
首先,让我们定义一个支持版本控制的游戏数据结构。
```csharp
using GFramework.Game.Abstractions.data;
using System;
using System.Collections.Generic;
namespace MyGame.Data
{
/// <summary>
/// 玩家数据 - 版本 1初始版本
/// </summary>
public class PlayerSaveData : IVersionedData
{
// IVersionedData 接口要求的属性
public int Version { get; set; } = 1;
public DateTime LastModified { get; set; } = DateTime.Now;
// 基础数据
public string PlayerName { get; set; } = "Player";
public int Level { get; set; } = 1;
public int Gold { get; set; } = 0;
// 版本 1 的简单位置数据
public float PositionX { get; set; }
public float PositionY { get; set; }
}
}
```
**代码说明**
- 实现 `IVersionedData` 接口以支持版本管理
- `Version` 属性标识当前数据版本(从 1 开始)
- `LastModified` 记录最后修改时间
- 初始版本使用简单的 X、Y 坐标表示位置
## 步骤 2定义新版本数据结构
随着游戏开发,我们需要添加新功能,数据结构也需要升级。
```csharp
namespace MyGame.Data
{
/// <summary>
/// 玩家数据 - 版本 2添加 Z 轴和经验值)
/// </summary>
public class PlayerSaveDataV2 : IVersionedData
{
public int Version { get; set; } = 2;
public DateTime LastModified { get; set; } = DateTime.Now;
// 基础数据
public string PlayerName { get; set; } = "Player";
public int Level { get; set; } = 1;
public int Gold { get; set; } = 0;
// 版本 2添加 Z 轴支持 3D 游戏
public float PositionX { get; set; }
public float PositionY { get; set; }
public float PositionZ { get; set; } // 新增
// 版本 2添加经验值系统
public int Experience { get; set; } // 新增
public int ExperienceToNextLevel { get; set; } = 100; // 新增
}
/// <summary>
/// 玩家数据 - 版本 3重构为结构化数据
/// </summary>
public class PlayerSaveDataV3 : IVersionedData
{
public int Version { get; set; } = 3;
public DateTime LastModified { get; set; } = DateTime.Now;
// 基础数据
public string PlayerName { get; set; } = "Player";
public int Level { get; set; } = 1;
public int Gold { get; set; } = 0;
// 版本 3使用结构化的位置数据
public Vector3Data Position { get; set; } = new();
// 版本 3使用结构化的经验值数据
public ExperienceData Experience { get; set; } = new();
// 版本 3新增技能系统
public List<string> UnlockedSkills { get; set; } = new();
}
/// <summary>
/// 3D 位置数据
/// </summary>
public class Vector3Data
{
public float X { get; set; }
public float Y { get; set; }
public float Z { get; set; }
}
/// <summary>
/// 经验值数据
/// </summary>
public class ExperienceData
{
public int Current { get; set; }
public int ToNextLevel { get; set; } = 100;
}
}
```
**代码说明**
- **版本 2**:添加 Z 轴坐标和经验值系统
- **版本 3**:重构为更清晰的结构化数据
- 每个版本的 `Version` 属性递增
- 保持向后兼容,新字段提供默认值
## 步骤 3实现数据迁移器
创建迁移器来处理版本间的数据转换。
```csharp
using GFramework.Game.Abstractions.setting;
using System;
namespace MyGame.Data.Migrations
{
/// <summary>
/// 从版本 1 迁移到版本 2
/// </summary>
public class PlayerDataMigration_V1_to_V2 : ISettingsMigration
{
public Type SettingsType => typeof(PlayerSaveData);
public int FromVersion => 1;
public int ToVersion => 2;
public ISettingsSection Migrate(ISettingsSection oldData)
{
if (oldData is not PlayerSaveData v1)
{
throw new ArgumentException($"Expected PlayerSaveData, got {oldData.GetType().Name}");
}
Console.WriteLine($"[迁移] 版本 1 -> 2: {v1.PlayerName}");
// 创建版本 2 数据
var v2 = new PlayerSaveDataV2
{
Version = 2,
LastModified = DateTime.Now,
// 复制现有数据
PlayerName = v1.PlayerName,
Level = v1.Level,
Gold = v1.Gold,
PositionX = v1.PositionX,
PositionY = v1.PositionY,
// 新字段Z 轴默认为 0
PositionZ = 0f,
// 新字段:根据等级计算经验值
Experience = 0,
ExperienceToNextLevel = 100 * v1.Level
};
Console.WriteLine($" - 添加 Z 轴坐标: {v2.PositionZ}");
Console.WriteLine($" - 初始化经验值系统: {v2.Experience}/{v2.ExperienceToNextLevel}");
return v2;
}
}
/// <summary>
/// 从版本 2 迁移到版本 3
/// </summary>
public class PlayerDataMigration_V2_to_V3 : ISettingsMigration
{
public Type SettingsType => typeof(PlayerSaveDataV2);
public int FromVersion => 2;
public int ToVersion => 3;
public ISettingsSection Migrate(ISettingsSection oldData)
{
if (oldData is not PlayerSaveDataV2 v2)
{
throw new ArgumentException($"Expected PlayerSaveDataV2, got {oldData.GetType().Name}");
}
Console.WriteLine($"[迁移] 版本 2 -> 3: {v2.PlayerName}");
// 创建版本 3 数据
var v3 = new PlayerSaveDataV3
{
Version = 3,
LastModified = DateTime.Now,
// 复制基础数据
PlayerName = v2.PlayerName,
Level = v2.Level,
Gold = v2.Gold,
// 迁移位置数据到结构化格式
Position = new Vector3Data
{
X = v2.PositionX,
Y = v2.PositionY,
Z = v2.PositionZ
},
// 迁移经验值数据到结构化格式
Experience = new ExperienceData
{
Current = v2.Experience,
ToNextLevel = v2.ExperienceToNextLevel
},
// 新字段:根据等级解锁基础技能
UnlockedSkills = GenerateDefaultSkills(v2.Level)
};
Console.WriteLine($" - 重构位置数据: ({v3.Position.X}, {v3.Position.Y}, {v3.Position.Z})");
Console.WriteLine($" - 重构经验值数据: {v3.Experience.Current}/{v3.Experience.ToNextLevel}");
Console.WriteLine($" - 初始化技能系统: {v3.UnlockedSkills.Count} 个技能");
return v3;
}
/// <summary>
/// 根据等级生成默认技能
/// </summary>
private List<string> GenerateDefaultSkills(int level)
{
var skills = new List<string> { "basic_attack" };
if (level >= 5)
skills.Add("power_strike");
if (level >= 10)
skills.Add("shield_block");
if (level >= 15)
skills.Add("ultimate_skill");
return skills;
}
}
}
```
**代码说明**
- 实现 `ISettingsMigration` 接口
- `SettingsType` 指定要迁移的数据类型
- `FromVersion``ToVersion` 定义迁移的版本范围
- `Migrate` 方法执行实际的数据转换
- 为新字段提供合理的默认值或计算值
- 添加日志输出便于调试
## 步骤 4注册迁移策略
创建迁移管理器来注册和执行迁移。
```csharp
using GFramework.Game.Abstractions.setting;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MyGame.Data.Migrations
{
/// <summary>
/// 数据迁移管理器
/// </summary>
public class DataMigrationManager
{
private readonly Dictionary<(Type type, int from), ISettingsMigration> _migrations = new();
/// <summary>
/// 注册迁移器
/// </summary>
public void RegisterMigration(ISettingsMigration migration)
{
var key = (migration.SettingsType, migration.FromVersion);
if (_migrations.ContainsKey(key))
{
Console.WriteLine($"警告: 迁移器已存在 {migration.SettingsType.Name} " +
$"v{migration.FromVersion}->v{migration.ToVersion}");
return;
}
_migrations[key] = migration;
Console.WriteLine($"注册迁移器: {migration.SettingsType.Name} " +
$"v{migration.FromVersion} -> v{migration.ToVersion}");
}
/// <summary>
/// 执行迁移(支持跨多个版本)
/// </summary>
public ISettingsSection MigrateToLatest(ISettingsSection data, int targetVersion)
{
if (data is not IVersionedData versioned)
{
Console.WriteLine("数据不支持版本控制,跳过迁移");
return data;
}
var currentVersion = versioned.Version;
if (currentVersion == targetVersion)
{
Console.WriteLine($"数据已是最新版本 v{targetVersion}");
return data;
}
if (currentVersion > targetVersion)
{
Console.WriteLine($"警告: 数据版本 v{currentVersion} 高于目标版本 v{targetVersion}");
return data;
}
Console.WriteLine($"\n开始迁移: v{currentVersion} -> v{targetVersion}");
var current = data;
var currentVer = currentVersion;
// 逐步迁移到目标版本
while (currentVer < targetVersion)
{
var key = (current.GetType(), currentVer);
if (!_migrations.TryGetValue(key, out var migration))
{
Console.WriteLine($"错误: 找不到迁移器 {current.GetType().Name} v{currentVer}");
break;
}
current = migration.Migrate(current);
currentVer = migration.ToVersion;
Console.WriteLine($"迁移完成: v{currentVersion} -> v{currentVer}\n");
return current;
}
/// <summary>
/// 获取迁移路径
/// </summary>
public List<string> GetMigrationPath(Type dataType, int fromVersion, int toVersion)
{
var path = new List<string>();
var currentVer = fromVersion;
var currentType = dataType;
while (currentVer < toVersion)
{
var key = (currentType, currentVer);
if (!_migrations.TryGetValue(key, out var migration))
{
path.Add($"v{currentVer} -> ? (缺失迁移器)");
break;
}
path.Add($"v{migration.FromVersion} -> v{migration.ToVersion}");
currentVer = migration.ToVersion;
currentType = migration.SettingsType;
}
return path;
}
}
}
```
**代码说明**
- 使用字典存储迁移器,键为 (类型, 源版本)
- `RegisterMigration` 注册单个迁移器
- `MigrateToLatest` 自动执行多步迁移
- `GetMigrationPath` 显示迁移路径,便于调试
- 支持跨多个版本的连续迁移
## 步骤 5测试迁移流程
创建完整的测试程序验证迁移功能。
```csharp
using MyGame.Data;
using MyGame.Data.Migrations;
using System;
namespace MyGame
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== 数据迁移系统测试 ===\n");
// 1. 创建迁移管理器
var migrationManager = new DataMigrationManager();
// 2. 注册所有迁移器
Console.WriteLine("--- 注册迁移器 ---");
migrationManager.RegisterMigration(new PlayerDataMigration_V1_to_V2());
migrationManager.RegisterMigration(new PlayerDataMigration_V2_to_V3());
Console.WriteLine();
// 3. 测试场景 1从版本 1 迁移到版本 3
Console.WriteLine("--- 测试 1: V1 -> V3 迁移 ---");
var v1Data = new PlayerSaveData
{
Version = 1,
PlayerName = "老玩家",
Level = 12,
Gold = 5000,
PositionX = 100.5f,
PositionY = 200.3f
};
Console.WriteLine("原始数据 (V1):");
Console.WriteLine($" 玩家: {v1Data.PlayerName}");
Console.WriteLine($" 等级: {v1Data.Level}");
Console.WriteLine($" 金币: {v1Data.Gold}");
Console.WriteLine($" 位置: ({v1Data.PositionX}, {v1Data.PositionY})");
Console.WriteLine();
// 显示迁移路径
var path = migrationManager.GetMigrationPath(typeof(PlayerSaveData), 1, 3);
Console.WriteLine("迁移路径:");
foreach (var step in path)
{
Console.WriteLine($" {step}");
}
Console.WriteLine();
// 执行迁移
var v3Data = (PlayerSaveDataV3)migrationManager.MigrateToLatest(v1Data, 3);
Console.WriteLine("迁移后数据 (V3):");
Console.WriteLine($" 玩家: {v3Data.PlayerName}");
Console.WriteLine($" 等级: {v3Data.Level}");
Console.WriteLine($" 金币: {v3Data.Gold}");
Console.WriteLine($" 位置: ({v3Data.Position.X}, {v3Data.Position.Y}, {v3Data.Position.Z})");
Console.WriteLine($" 经验值: {v3Data.Experience.Current}/{v3Data.Experience.ToNextLevel}");
Console.WriteLine($" 技能: {string.Join(", ", v3Data.UnlockedSkills)}");
Console.WriteLine();
// 4. 测试场景 2从版本 2 迁移到版本 3
Console.WriteLine("--- 测试 2: V2 -> V3 迁移 ---");
var v2Data = new PlayerSaveDataV2
{
Version = 2,
PlayerName = "中期玩家",
Level = 8,
Gold = 2000,
PositionX = 50.0f,
PositionY = 75.0f,
PositionZ = 10.0f,
Experience = 350,
ExperienceToNextLevel = 800
};
Console.WriteLine("原始数据 (V2):");
Console.WriteLine($" 玩家: {v2Data.PlayerName}");
Console.WriteLine($" 等级: {v2Data.Level}");
Console.WriteLine($" 位置: ({v2Data.PositionX}, {v2Data.PositionY}, {v2Data.PositionZ})");
Console.WriteLine($" 经验值: {v2Data.Experience}/{v2Data.ExperienceToNextLevel}");
Console.WriteLine();
var v3Data2 = (PlayerSaveDataV3)migrationManager.MigrateToLatest(v2Data, 3);
Console.WriteLine("迁移后数据 (V3):");
Console.WriteLine($" 玩家: {v3Data2.PlayerName}");
Console.WriteLine($" 等级: {v3Data2.Level}");
Console.WriteLine($" 位置: ({v3Data2.Position.X}, {v3Data2.Position.Y}, {v3Data2.Position.Z})");
Console.WriteLine($" 经验值: {v3Data2.Experience.Current}/{v3Data2.Experience.ToNextLevel}");
Console.WriteLine($" 技能: {string.Join(", ", v3Data2.UnlockedSkills)}");
Console.WriteLine();
// 5. 测试场景 3已是最新版本
Console.WriteLine("--- 测试 3: 已是最新版本 ---");
var v3DataLatest = new PlayerSaveDataV3
{
Version = 3,
PlayerName = "新玩家",
Level = 1
};
migrationManager.MigrateToLatest(v3DataLatest, 3);
Console.WriteLine();
Console.WriteLine("=== 测试完成 ===");
}
}
}
```
**代码说明**
- 创建不同版本的测试数据
- 测试单步迁移V2 -> V3
- 测试多步迁移V1 -> V3
- 测试已是最新版本的情况
- 显示迁移前后的数据对比
## 完整代码
所有代码文件已在上述步骤中提供。项目结构如下:
```
MyGame/
├── Data/
│ ├── PlayerSaveData.cs # 版本 1 数据结构
│ ├── PlayerSaveDataV2.cs # 版本 2 数据结构
│ ├── PlayerSaveDataV3.cs # 版本 3 数据结构
│ └── Migrations/
│ ├── PlayerDataMigration_V1_to_V2.cs
│ ├── PlayerDataMigration_V2_to_V3.cs
│ └── DataMigrationManager.cs
└── Program.cs
```
## 运行结果
运行程序后,你将看到类似以下的输出:
```
=== 数据迁移系统测试 ===
--- 注册迁移器 ---
注册迁移器: PlayerSaveData v1 -> v2
注册迁移器: PlayerSaveDataV2 v2 -> v3
--- 测试 1: V1 -> V3 迁移 ---
原始数据 (V1):
玩家: 老玩家
等级: 12
金币: 5000
位置: (100.5, 200.3)
迁移路径:
v1 -> v2
v2 -> v3
开始迁移: v1 -> v3
[迁移] 版本 1 -> 2: 老玩家
- 添加 Z 轴坐标: 0
- 初始化经验值系统: 0/1200
[迁移] 版本 2 -> 3: 老玩家
- 重构位置数据: (100.5, 200.3, 0)
- 重构经验值数据: 0/1200
- 初始化技能系统: 3 个技能
迁移完成: v1 -> v3
迁移后数据 (V3):
玩家: 老玩家
等级: 12
金币: 5000
位置: (100.5, 200.3, 0)
经验值: 0/1200
技能: basic_attack, power_strike, shield_block
--- 测试 2: V2 -> V3 迁移 ---
原始数据 (V2):
玩家: 中期玩家
等级: 8
金币: 2000
位置: (50, 75, 10)
经验值: 350/800
开始迁移: v2 -> v3
[迁移] 版本 2 -> 3: 中期玩家
- 重构位置数据: (50, 75, 10)
- 重构经验值数据: 350/800
- 初始化技能系统: 2 个技能
迁移完成: v2 -> v3
迁移后数据 (V3):
玩家: 中期玩家
等级: 8
位置: (50, 75, 10)
经验值: 350/800
技能: basic_attack, power_strike
--- 测试 3: 已是最新版本 ---
数据已是最新版本 v3
=== 测试完成 ===
```
**验证步骤**
1. 迁移器成功注册
2. V1 数据正确迁移到 V3
3. V2 数据正确迁移到 V3
4. 新字段获得合理的默认值
5. 已是最新版本的数据不会重复迁移
6. 迁移路径清晰可追踪
## 下一步
恭喜!你已经实现了一个完整的数据版本迁移系统。接下来可以学习:
- [实现存档系统](/zh-CN/tutorials/save-system) - 结合存档系统使用迁移
- [Godot 完整项目搭建](/zh-CN/tutorials/godot-complete-project) - 在实际项目中应用
- [数据与存档系统](/zh-CN/game/data) - 深入了解数据系统
## 最佳实践
### 1. 版本号管理
```csharp
// 使用常量管理版本号
public static class DataVersions
{
public const int PlayerData_V1 = 1;
public const int PlayerData_V2 = 2;
public const int PlayerData_V3 = 3;
public const int PlayerData_Latest = PlayerData_V3;
}
```
### 2. 迁移测试
```csharp
// 为每个迁移器编写单元测试
[Test]
public void TestMigration_V1_to_V2()
{
var v1 = new PlayerSaveData { Level = 10 };
var migration = new PlayerDataMigration_V1_to_V2();
var v2 = (PlayerSaveDataV2)migration.Migrate(v1);
Assert.AreEqual(10, v2.Level);
Assert.AreEqual(0, v2.PositionZ);
Assert.AreEqual(1000, v2.ExperienceToNextLevel);
}
```
### 3. 数据备份
```csharp
// 迁移前自动备份
public ISettingsSection MigrateWithBackup(ISettingsSection data)
{
// 备份原始数据
var backup = SerializeData(data);
SaveBackup(backup);
try
{
// 执行迁移
var migrated = MigrateToLatest(data, targetVersion);
return migrated;
}
catch (Exception ex)
{
// 迁移失败,恢复备份
RestoreBackup(backup);
throw;
}
}
```
### 4. 迁移日志
```csharp
// 记录详细的迁移日志
public class MigrationLogger
{
public void LogMigration(string playerName, int from, int to)
{
var log = $"[{DateTime.Now}] {playerName}: v{from} -> v{to}";
File.AppendAllText("migration.log", log + "\n");
}
}
```
### 5. 向后兼容
- 新版本保留所有旧字段
- 为新字段提供合理的默认值
- 避免删除或重命名字段
- 使用 `[Obsolete]` 标记废弃字段
### 6. 性能优化
```csharp
// 批量迁移优化
public async Task<List<ISettingsSection>> MigrateBatchAsync(
List<ISettingsSection> dataList,
int targetVersion)
{
var tasks = dataList.Select(data =>
Task.Run(() => MigrateToLatest(data, targetVersion)));
return (await Task.WhenAll(tasks)).ToList();
}
```
## 常见问题
### 1. 如何处理跨多个版本的迁移?
迁移管理器会自动按顺序应用所有必要的迁移。例如从 V1 到 V3会先执行 V1->V2再执行 V2->V3。
### 2. 迁移失败如何处理?
建议在迁移前备份原始数据,迁移失败时可以恢复。同时在迁移过程中添加详细的日志记录。
### 3. 如何处理不兼容的数据变更?
对于破坏性变更,建议:
- 提供数据转换工具
- 在迁移中添加数据验证
- 通知用户可能的数据丢失
- 提供回滚机制
### 4. 是否需要保留所有历史版本的数据结构?
建议保留,这样可以:
- 支持从任意旧版本迁移
- 便于调试和测试
- 作为文档记录数据演变
### 5. 如何测试迁移功能?
- 创建各个版本的测试数据
- 验证迁移后的数据完整性
- 测试迁移链的正确性
- 使用真实的历史数据进行测试
## 相关文档
- [数据与存档系统](/zh-CN/game/data) - 数据系统详细说明
- [实现存档系统](/zh-CN/tutorials/save-system) - 存档系统教程
- [架构系统](/zh-CN/core/architecture) - 架构设计原则

View File

@ -0,0 +1,822 @@
---
title: 函数式编程实践
description: 学习如何在实际项目中使用 Option、Result 和管道操作等函数式编程特性
---
# 函数式编程实践
## 学习目标
完成本教程后,你将能够:
- 理解函数式编程的核心概念和优势
- 使用 Option 类型安全地处理可空值
- 使用 Result 类型进行优雅的错误处理
- 使用管道操作构建流式的数据处理流程
- 组合多个函数式操作实现复杂的业务逻辑
- 在实际游戏开发中应用函数式编程模式
## 前置条件
- 已安装 GFramework.Core NuGet 包
- 了解 C# 基础语法和泛型
- 阅读过[快速开始](/zh-CN/getting-started/quick-start)
- 了解 Lambda 表达式和 LINQ
## 步骤 1使用 Option 处理可空值
首先,让我们学习如何使用 Option 类型替代传统的 null 检查,使代码更加安全和优雅。
```csharp
using GFramework.Core.functional;
using GFramework.Core.functional.pipe;
namespace MyGame.Services
{
/// <summary>
/// 玩家数据服务
/// </summary>
public class PlayerDataService
{
private readonly Dictionary<int, PlayerData> _players = new();
/// <summary>
/// 根据 ID 查找玩家(返回 Option
/// </summary>
public Option<PlayerData> FindPlayerById(int playerId)
{
// 使用 Option 包装可能不存在的值
return _players.TryGetValue(playerId, out var player)
? Option<PlayerData>.Some(player)
: Option<PlayerData>.None;
}
/// <summary>
/// 获取玩家名称(安全处理)
/// </summary>
public string GetPlayerName(int playerId)
{
// 使用 Match 模式匹配处理有值和无值的情况
return FindPlayerById(playerId).Match(
some: player => player.Name,
none: () => "未知玩家"
);
}
/// <summary>
/// 获取玩家等级(使用默认值)
/// </summary>
public int GetPlayerLevel(int playerId)
{
// 使用 GetOrElse 提供默认值
return FindPlayerById(playerId)
.Map(player => player.Level)
.GetOrElse(1);
}
/// <summary>
/// 查找高级玩家
/// </summary>
public Option<PlayerData> FindAdvancedPlayer(int playerId)
{
// 使用 Filter 过滤值
return FindPlayerById(playerId)
.Filter(player => player.Level >= 10);
}
/// <summary>
/// 获取玩家公会名称(链式调用)
/// </summary>
public string GetPlayerGuildName(int playerId)
{
// 使用 Bind 处理嵌套的 Option
return FindPlayerById(playerId)
.Bind(player => player.Guild) // Guild 也是 Option<Guild>
.Map(guild => guild.Name)
.GetOrElse("无公会");
}
}
/// <summary>
/// 玩家数据
/// </summary>
public class PlayerData
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Level { get; set; }
public Option<Guild> Guild { get; set; } = Option<Guild>.None;
}
/// <summary>
/// 公会数据
/// </summary>
public class Guild
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
}
```
**代码说明**
- `Option&lt;T&gt;` 明确表示值可能不存在,避免 NullReferenceException
- `Match` 强制处理两种情况,不会遗漏 null 检查
- `Map``Bind` 实现链式转换,代码更简洁
- `Filter` 可以安全地过滤值
- `GetOrElse` 提供默认值,避免空值传播
## 步骤 2使用 Result 进行错误处理
接下来,学习如何使用 Result 类型替代异常处理,实现更可控的错误管理。
```csharp
using GFramework.Core.functional;
using System.Text.Json;
namespace MyGame.Services
{
/// <summary>
/// 存档服务
/// </summary>
public class SaveService
{
private readonly string _saveDirectory = "./saves";
/// <summary>
/// 保存游戏数据
/// </summary>
public Result<string> SaveGame(GameSaveData data)
{
// 使用 Result.Try 自动捕获异常
return Result<string>.Try(() =>
{
// 验证数据
if (string.IsNullOrEmpty(data.PlayerName))
throw new ArgumentException("玩家名称不能为空");
// 创建保存目录
if (!Directory.Exists(_saveDirectory))
Directory.CreateDirectory(_saveDirectory);
// 序列化数据
var json = JsonSerializer.Serialize(data);
var fileName = $"save_{data.PlayerId}_{DateTime.Now:yyyyMMdd_HHmmss}.json";
var filePath = Path.Combine(_saveDirectory, fileName);
// 写入文件
File.WriteAllText(filePath, json);
return filePath;
});
}
/// <summary>
/// 加载游戏数据
/// </summary>
public Result<GameSaveData> LoadGame(int playerId)
{
try
{
// 查找最新的存档文件
var files = Directory.GetFiles(_saveDirectory, $"save_{playerId}_*.json");
if (files.Length == 0)
return Result<GameSaveData>.Failure("未找到存档文件");
var latestFile = files.OrderByDescending(f => f).First();
var json = File.ReadAllText(latestFile);
var data = JsonSerializer.Deserialize<GameSaveData>(json);
return data != null
? Result<GameSaveData>.Success(data)
: Result<GameSaveData>.Failure("存档数据解析失败");
}
catch (Exception ex)
{
return Result<GameSaveData>.Failure(ex);
}
}
/// <summary>
/// 保存并加载游戏(链式操作)
/// </summary>
public Result<GameSaveData> SaveAndReload(GameSaveData data)
{
// 使用 Bind 链接多个 Result 操作
return SaveGame(data)
.Bind(_ => LoadGame(data.PlayerId));
}
/// <summary>
/// 获取存档信息(使用 Match
/// </summary>
public string GetSaveInfo(int playerId)
{
return LoadGame(playerId).Match(
succ: data => $"存档加载成功: {data.PlayerName}, 等级 {data.Level}",
fail: ex => $"加载失败: {ex.Message}"
);
}
/// <summary>
/// 安全加载游戏(提供默认值)
/// </summary>
public GameSaveData LoadGameOrDefault(int playerId)
{
return LoadGame(playerId).IfFail(new GameSaveData
{
PlayerId = playerId,
PlayerName = "新玩家",
Level = 1
});
}
}
/// <summary>
/// 游戏存档数据
/// </summary>
public class GameSaveData
{
public int PlayerId { get; set; }
public string PlayerName { get; set; } = "";
public int Level { get; set; }
public int Gold { get; set; }
public DateTime SaveTime { get; set; } = DateTime.Now;
}
}
```
**代码说明**
- `Result&lt;T&gt;` 将错误作为值返回,而不是抛出异常
- `Result.Try` 自动捕获异常并转换为 Result
- `Bind` 可以链接多个可能失败的操作
- `Match` 强制处理成功和失败两种情况
- `IfFail` 提供失败时的默认值
## 步骤 3使用管道操作组合函数
学习如何使用管道操作符构建流式的数据处理流程。
```csharp
using GFramework.Core.functional.pipe;
using GFramework.Core.functional.functions;
namespace MyGame.Systems
{
/// <summary>
/// 物品处理系统
/// </summary>
public class ItemProcessingSystem
{
/// <summary>
/// 处理物品掉落
/// </summary>
public ItemDrop ProcessItemDrop(Enemy enemy, Player player)
{
// 使用管道操作构建处理流程
return enemy
.Pipe(e => CalculateDropRate(e, player))
.Tap(rate => Console.WriteLine($"掉落率: {rate:P}"))
.Pipe(rate => GenerateItems(rate))
.Tap(items => Console.WriteLine($"生成 {items.Count} 个物品"))
.Pipe(items => ApplyLuckBonus(items, player))
.Pipe(items => FilterByQuality(items))
.Tap(items => Console.WriteLine($"过滤后剩余 {items.Count} 个物品"))
.Let(items => new ItemDrop
{
Items = items,
TotalValue = items.Sum(i => i.Value)
});
}
/// <summary>
/// 计算掉落率
/// </summary>
private double CalculateDropRate(Enemy enemy, Player player)
{
return (enemy.Level * 0.1 + player.Luck * 0.05)
.Pipe(rate => Math.Min(rate, 1.0));
}
/// <summary>
/// 生成物品
/// </summary>
private List<Item> GenerateItems(double dropRate)
{
var random = new Random();
var itemCount = random.NextDouble() < dropRate ? random.Next(1, 5) : 0;
return Enumerable.Range(0, itemCount)
.Select(_ => new Item
{
Id = random.Next(1000),
Name = $"物品_{random.Next(100)}",
Quality = (ItemQuality)random.Next(0, 4),
Value = random.Next(10, 100)
})
.ToList();
}
/// <summary>
/// 应用幸运加成
/// </summary>
private List<Item> ApplyLuckBonus(List<Item> items, Player player)
{
return items
.Select(item => item.Also(i =>
{
if (player.Luck > 50)
i.Quality = (ItemQuality)Math.Min((int)i.Quality + 1, 3);
}))
.ToList();
}
/// <summary>
/// 按品质过滤
/// </summary>
private List<Item> FilterByQuality(List<Item> items)
{
return items
.Where(item => item.Quality >= ItemQuality.Uncommon)
.ToList();
}
/// <summary>
/// 条件处理物品
/// </summary>
public string ProcessItemConditionally(Item item, bool isVip)
{
return item.PipeIf(
predicate: i => isVip,
ifTrue: i => $"VIP 物品: {i.Name} (价值 {i.Value * 2})",
ifFalse: i => $"普通物品: {i.Name} (价值 {i.Value})"
);
}
}
public class Enemy
{
public int Level { get; set; }
}
public class Player
{
public int Luck { get; set; }
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; } = "";
public ItemQuality Quality { get; set; }
public int Value { get; set; }
}
public enum ItemQuality
{
Common,
Uncommon,
Rare,
Epic
}
public class ItemDrop
{
public List<Item> Items { get; set; } = new();
public int TotalValue { get; set; }
}
}
```
**代码说明**
- `Pipe` 将值传递给函数,构建流式处理链
- `Tap` 执行副作用(如日志)但不改变值
- `Let` 在作用域内转换值
- `Also` 对值执行操作后返回原值
- `PipeIf` 根据条件选择不同的处理路径
## 步骤 4实现完整的数据处理流程
现在让我们结合 Option、Result 和管道操作,实现一个完整的游戏功能。
```csharp
using GFramework.Core.functional;
using GFramework.Core.functional.pipe;
using GFramework.Core.functional.control;
namespace MyGame.Features
{
/// <summary>
/// 任务系统
/// </summary>
public class QuestSystem
{
private readonly Dictionary<int, Quest> _quests = new();
private readonly Dictionary<int, List<int>> _playerQuests = new();
/// <summary>
/// 接受任务(完整流程)
/// </summary>
public Result<QuestAcceptResult> AcceptQuest(int playerId, int questId)
{
return FindQuest(questId)
// 转换 Option 为 Result
.ToResult("任务不存在")
// 验证任务等级要求
.Bind(quest => ValidateQuestLevel(playerId, quest))
// 检查前置任务
.Bind(quest => CheckPrerequisites(playerId, quest))
// 检查任务槽位
.Bind(quest => CheckQuestSlots(playerId))
// 添加到玩家任务列表
.Map(quest => AddQuestToPlayer(playerId, quest))
// 记录日志
.Tap(result => Console.WriteLine($"玩家 {playerId} 接受任务: {result.QuestName}"))
// 发放初始奖励
.Map(result => GiveInitialRewards(result));
}
/// <summary>
/// 查找任务
/// </summary>
private Option<Quest> FindQuest(int questId)
{
return _quests.TryGetValue(questId, out var quest)
? Option<Quest>.Some(quest)
: Option<Quest>.None;
}
/// <summary>
/// 验证任务等级
/// </summary>
private Result<Quest> ValidateQuestLevel(int playerId, Quest quest)
{
var playerLevel = GetPlayerLevel(playerId);
return playerLevel >= quest.RequiredLevel
? Result<Quest>.Success(quest)
: Result<Quest>.Failure($"等级不足,需要 {quest.RequiredLevel} 级");
}
/// <summary>
/// 检查前置任务
/// </summary>
private Result<Quest> CheckPrerequisites(int playerId, Quest quest)
{
if (quest.PrerequisiteQuestIds.Count == 0)
return Result<Quest>.Success(quest);
var completedQuests = GetCompletedQuests(playerId);
var hasAllPrerequisites = quest.PrerequisiteQuestIds
.All(id => completedQuests.Contains(id));
return hasAllPrerequisites
? Result<Quest>.Success(quest)
: Result<Quest>.Failure("未完成前置任务");
}
/// <summary>
/// 检查任务槽位
/// </summary>
private Result<Quest> CheckQuestSlots(int playerId)
{
var activeQuests = GetActiveQuests(playerId);
return activeQuests.Count < 10
? Result<Quest>.Success(default!)
: Result<Quest>.Failure("任务栏已满");
}
/// <summary>
/// 添加任务到玩家
/// </summary>
private QuestAcceptResult AddQuestToPlayer(int playerId, Quest quest)
{
if (!_playerQuests.ContainsKey(playerId))
_playerQuests[playerId] = new List<int>();
_playerQuests[playerId].Add(quest.Id);
return new QuestAcceptResult
{
QuestId = quest.Id,
QuestName = quest.Name,
Description = quest.Description,
Rewards = quest.Rewards
};
}
/// <summary>
/// 发放初始奖励
/// </summary>
private QuestAcceptResult GiveInitialRewards(QuestAcceptResult result)
{
// 某些任务接受时就有奖励
if (result.Rewards.Gold > 0)
{
Console.WriteLine($"获得金币: {result.Rewards.Gold}");
}
return result;
}
/// <summary>
/// 完成任务(使用函数组合)
/// </summary>
public Result<QuestCompleteResult> CompleteQuest(int playerId, int questId)
{
return FindQuest(questId)
.ToResult("任务不存在")
.Bind(quest => ValidateQuestOwnership(playerId, quest))
.Bind(quest => ValidateQuestObjectives(quest))
.Map(quest => RemoveQuestFromPlayer(playerId, quest))
.Map(quest => CalculateRewards(quest))
.Tap(result => Console.WriteLine($"任务完成: {result.QuestName}"))
.Map(result => GiveRewards(playerId, result));
}
/// <summary>
/// 验证任务所有权
/// </summary>
private Result<Quest> ValidateQuestOwnership(int playerId, Quest quest)
{
var activeQuests = GetActiveQuests(playerId);
return activeQuests.Contains(quest.Id)
? Result<Quest>.Success(quest)
: Result<Quest>.Failure("玩家未接受此任务");
}
/// <summary>
/// 验证任务目标
/// </summary>
private Result<Quest> ValidateQuestObjectives(Quest quest)
{
return quest.IsCompleted
? Result<Quest>.Success(quest)
: Result<Quest>.Failure("任务目标未完成");
}
/// <summary>
/// 从玩家移除任务
/// </summary>
private Quest RemoveQuestFromPlayer(int playerId, Quest quest)
{
if (_playerQuests.ContainsKey(playerId))
{
_playerQuests[playerId].Remove(quest.Id);
}
return quest;
}
/// <summary>
/// 计算奖励
/// </summary>
private QuestCompleteResult CalculateRewards(Quest quest)
{
return new QuestCompleteResult
{
QuestId = quest.Id,
QuestName = quest.Name,
Rewards = quest.Rewards,
BonusRewards = CalculateBonusRewards(quest)
};
}
/// <summary>
/// 计算额外奖励
/// </summary>
private QuestRewards CalculateBonusRewards(Quest quest)
{
// 根据任务难度给予额外奖励
return new QuestRewards
{
Gold = quest.Rewards.Gold / 10,
Experience = quest.Rewards.Experience / 10
};
}
/// <summary>
/// 发放奖励
/// </summary>
private QuestCompleteResult GiveRewards(int playerId, QuestCompleteResult result)
{
var totalGold = result.Rewards.Gold + result.BonusRewards.Gold;
var totalExp = result.Rewards.Experience + result.BonusRewards.Experience;
Console.WriteLine($"获得金币: {totalGold}");
Console.WriteLine($"获得经验: {totalExp}");
return result;
}
// 辅助方法
private int GetPlayerLevel(int playerId) => 10;
private List<int> GetCompletedQuests(int playerId) => new();
private List<int> GetActiveQuests(int playerId) =>
_playerQuests.GetValueOrDefault(playerId, new List<int>());
}
public class Quest
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public int RequiredLevel { get; set; }
public List<int> PrerequisiteQuestIds { get; set; } = new();
public bool IsCompleted { get; set; }
public QuestRewards Rewards { get; set; } = new();
}
public class QuestRewards
{
public int Gold { get; set; }
public int Experience { get; set; }
}
public class QuestAcceptResult
{
public int QuestId { get; set; }
public string QuestName { get; set; } = "";
public string Description { get; set; } = "";
public QuestRewards Rewards { get; set; } = new();
}
public class QuestCompleteResult
{
public int QuestId { get; set; }
public string QuestName { get; set; } = "";
public QuestRewards Rewards { get; set; } = new();
public QuestRewards BonusRewards { get; set; } = new();
}
}
```
**代码说明**
- 使用 `Option.ToResult` 将可选值转换为结果
- 使用 `Bind` 链接多个验证步骤
- 使用 `Map` 转换成功的值
- 使用 `Tap` 添加日志而不中断流程
- 每个步骤都是纯函数,易于测试和维护
## 完整代码
### Program.cs
```csharp
using MyGame.Services;
using MyGame.Systems;
using MyGame.Features;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== 函数式编程实践示例 ===\n");
// 测试 Option
TestOptionUsage();
Console.WriteLine();
// 测试 Result
TestResultUsage();
Console.WriteLine();
// 测试管道操作
TestPipelineUsage();
Console.WriteLine();
// 测试完整流程
TestCompleteWorkflow();
Console.WriteLine("\n=== 测试完成 ===");
}
static void TestOptionUsage()
{
Console.WriteLine("--- 测试 Option ---");
var service = new PlayerDataService();
// 测试查找存在的玩家
Console.WriteLine(service.GetPlayerName(1));
// 测试查找不存在的玩家
Console.WriteLine(service.GetPlayerName(999));
// 测试获取等级
Console.WriteLine($"玩家等级: {service.GetPlayerLevel(1)}");
}
static void TestResultUsage()
{
Console.WriteLine("--- 测试 Result ---");
var saveService = new SaveService();
var saveData = new GameSaveData
{
PlayerId = 1,
PlayerName = "测试玩家",
Level = 10,
Gold = 1000
};
// 测试保存
var saveResult = saveService.SaveGame(saveData);
saveResult.Match(
succ: path => Console.WriteLine($"保存成功: {path}"),
fail: ex => Console.WriteLine($"保存失败: {ex.Message}")
);
// 测试加载
Console.WriteLine(saveService.GetSaveInfo(1));
}
static void TestPipelineUsage()
{
Console.WriteLine("--- 测试管道操作 ---");
var itemSystem = new ItemProcessingSystem();
var enemy = new Enemy { Level = 5 };
var player = new Player { Luck = 60 };
var drop = itemSystem.ProcessItemDrop(enemy, player);
Console.WriteLine($"掉落总价值: {drop.TotalValue}");
}
static void TestCompleteWorkflow()
{
Console.WriteLine("--- 测试完整工作流 ---");
var questSystem = new QuestSystem();
// 测试接受任务
var acceptResult = questSystem.AcceptQuest(1, 101);
acceptResult.Match(
succ: result => Console.WriteLine($"接受任务成功: {result.QuestName}"),
fail: ex => Console.WriteLine($"接受任务失败: {ex.Message}")
);
}
}
```
## 运行结果
运行程序后,你将看到类似以下的输出:
```
=== 函数式编程实践示例 ===
--- 测试 Option ---
未知玩家
未知玩家
玩家等级: 1
--- 测试 Result ---
保存成功: ./saves/save_1_20260307_143022.json
存档加载成功: 测试玩家, 等级 10
--- 测试管道操作 ---
掉落率: 35.00%
生成 3 个物品
过滤后剩余 2 个物品
掉落总价值: 150
--- 测试完整工作流 ---
玩家 1 接受任务: 新手任务
接受任务成功: 新手任务
=== 测试完成 ===
```
**验证步骤**
1. Option 正确处理了不存在的值
2. Result 成功捕获和传播错误
3. 管道操作构建了清晰的处理流程
4. 完整工作流展示了多种技术的组合使用
## 下一步
恭喜!你已经掌握了函数式编程的核心技术。接下来可以学习:
- [使用协程系统](/zh-CN/tutorials/coroutine-tutorial) - 结合函数式编程和协程
- [实现状态机](/zh-CN/tutorials/state-machine-tutorial) - 在状态机中应用函数式模式
- [资源管理最佳实践](/zh-CN/tutorials/resource-management) - 使用 Result 处理资源加载
## 相关文档
- [扩展方法](/zh-CN/core/extensions) - 更多函数式扩展方法
- [架构组件](/zh-CN/core/architecture) - 在架构中使用函数式编程
- [最佳实践](/zh-CN/best-practices/architecture-patterns) - 函数式编程最佳实践

View File

@ -0,0 +1,813 @@
---
title: Godot 完整项目搭建
description: 从零开始使用 GFramework 构建一个完整的 Godot 游戏项目
---
# Godot 完整项目搭建
## 学习目标
完成本教程后,你将能够:
- 在 Godot 项目中集成 GFramework
- 创建完整的游戏架构
- 实现场景管理和 UI 系统
- 使用协程和事件系统
- 实现游戏存档功能
- 构建一个可运行的完整游戏
## 前置条件
- 已安装 Godot 4.x
- 已安装 .NET SDK 8.0+
- 了解 C# 和 Godot 基础
- 阅读过前面的教程:
- [使用协程系统](/zh-CN/tutorials/coroutine-tutorial)
- [实现状态机](/zh-CN/tutorials/state-machine-tutorial)
- [实现存档系统](/zh-CN/tutorials/save-system)
## 项目概述
我们将创建一个简单的 2D 射击游戏,包含以下功能:
- 主菜单和游戏场景
- 玩家控制和射击
- 敌人生成和 AI
- 分数和生命值系统
- 游戏存档和加载
- 暂停菜单
## 步骤 1创建 Godot 项目并配置
首先创建 Godot 项目并添加 GFramework 依赖。
### 1.1 创建项目
1. 打开 Godot创建新项目 "MyShooterGame"
2. 选择 C# 作为脚本语言
3. 创建项目后,在项目根目录创建 `.csproj` 文件
### 1.2 添加 NuGet 包
编辑 `MyShooterGame.csproj`
```xml
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<!-- GFramework 包 -->
<PackageReference Include="GFramework.Core" Version="1.0.0" />
<PackageReference Include="GFramework.Game" Version="1.0.0" />
<PackageReference Include="GFramework.Godot" Version="1.0.0" />
</ItemGroup>
</Project>
```
### 1.3 创建项目结构
```
MyShooterGame/
├── Scripts/
│ ├── Architecture/
│ │ └── GameArchitecture.cs
│ ├── Models/
│ │ ├── PlayerModel.cs
│ │ └── GameModel.cs
│ ├── Systems/
│ │ ├── GameplaySystem.cs
│ │ └── SpawnSystem.cs
│ ├── Controllers/
│ │ └── PlayerController.cs
│ └── Data/
│ └── GameSaveData.cs
├── Scenes/
│ ├── Main.tscn
│ ├── Menu.tscn
│ ├── Game.tscn
│ ├── Player.tscn
│ └── Enemy.tscn
└── UI/
├── MainMenu.tscn
├── HUD.tscn
└── PauseMenu.tscn
```
**代码说明**
- 使用 Godot.NET.Sdk 4.3.0
- 添加 GFramework 的三个核心包
- 按功能组织代码结构
## 步骤 2创建游戏架构
实现游戏的核心架构和数据模型。
### 2.1 定义数据模型
```csharp
// Scripts/Models/PlayerModel.cs
using GFramework.Core.model;
using GFramework.Core.Abstractions.property;
namespace MyShooterGame.Models
{
public class PlayerModel : AbstractModel
{
public BindableProperty<int> Health { get; } = new(100);
public BindableProperty<int> MaxHealth { get; } = new(100);
public BindableProperty<int> Score { get; } = new(0);
public BindableProperty<int> Lives { get; } = new(3);
public BindableProperty<bool> IsAlive { get; } = new(true);
protected override void OnInit()
{
// 监听生命值变化
Health.RegisterOnValueChanged(health =>
{
if (health <= 0)
{
IsAlive.Value = false;
}
});
}
public void Reset()
{
Health.Value = MaxHealth.Value;
Score.Value = 0;
Lives.Value = 3;
IsAlive.Value = true;
}
public void TakeDamage(int damage)
{
Health.Value = Math.Max(0, Health.Value - damage);
}
public void AddScore(int points)
{
Score.Value += points;
}
public void LoseLife()
{
Lives.Value = Math.Max(0, Lives.Value - 1);
if (Lives.Value > 0)
{
Health.Value = MaxHealth.Value;
IsAlive.Value = true;
}
}
}
}
```
```csharp
// Scripts/Models/GameModel.cs
using GFramework.Core.model;
using GFramework.Core.Abstractions.property;
namespace MyShooterGame.Models
{
public class GameModel : AbstractModel
{
public BindableProperty<bool> IsPlaying { get; } = new(false);
public BindableProperty<bool> IsPaused { get; } = new(false);
public BindableProperty<int> CurrentWave { get; } = new(1);
public BindableProperty<int> EnemiesAlive { get; } = new(0);
public BindableProperty<float> GameTime { get; } = new(0f);
protected override void OnInit()
{
// 初始化
}
public void StartGame()
{
IsPlaying.Value = true;
IsPaused.Value = false;
CurrentWave.Value = 1;
EnemiesAlive.Value = 0;
GameTime.Value = 0f;
}
public void PauseGame()
{
IsPaused.Value = true;
}
public void ResumeGame()
{
IsPaused.Value = false;
}
public void EndGame()
{
IsPlaying.Value = false;
IsPaused.Value = false;
}
}
}
```
### 2.2 定义存档数据
```csharp
// Scripts/Data/GameSaveData.cs
using GFramework.Game.Abstractions.data;
using System;
namespace MyShooterGame.Data
{
public class GameSaveData : IVersionedData
{
public int Version { get; set; } = 1;
public DateTime SaveTime { get; set; }
// 玩家数据
public int HighScore { get; set; }
public int TotalKills { get; set; }
public float TotalPlayTime { get; set; }
// 设置
public float MusicVolume { get; set; } = 0.8f;
public float SfxVolume { get; set; } = 1.0f;
}
}
```
### 2.3 创建游戏架构
```csharp
// Scripts/Architecture/GameArchitecture.cs
using GFramework.Godot.architecture;
using GFramework.Core.Abstractions.architecture;
using GFramework.Game.Abstractions.data;
using GFramework.Game.Abstractions.storage;
using GFramework.Game.data;
using GFramework.Game.storage;
using MyShooterGame.Models;
using MyShooterGame.Systems;
using MyShooterGame.Data;
using Godot;
namespace MyShooterGame.Architecture
{
public class GameArchitecture : AbstractArchitecture
{
public static GameArchitecture Interface { get; private set; }
public GameArchitecture()
{
Interface = this;
}
protected override void InstallModules()
{
GD.Print("=== 初始化游戏架构 ===");
// 注册存储系统
var storage = new FileStorage("user://saves");
RegisterUtility<IFileStorage>(storage);
// 注册存档仓库
var saveConfig = new SaveConfiguration
{
SaveRoot = "",
SaveSlotPrefix = "save_",
SaveFileName = "data.json"
};
var saveRepo = new SaveRepository<GameSaveData>(storage, saveConfig);
RegisterUtility<ISaveRepository<GameSaveData>>(saveRepo);
// 注册 Model
RegisterModel(new PlayerModel());
RegisterModel(new GameModel());
// 注册 System
RegisterSystem(new GameplaySystem());
RegisterSystem(new SpawnSystem());
GD.Print("游戏架构初始化完成");
}
}
}
```
**代码说明**
- `PlayerModel` 管理玩家状态
- `GameModel` 管理游戏状态
- `GameSaveData` 定义存档结构
- `GameArchitecture` 注册所有组件
## 步骤 3实现游戏系统
创建游戏逻辑系统。
### 3.1 游戏逻辑系统
```csharp
// Scripts/Systems/GameplaySystem.cs
using GFramework.Core.system;
using GFramework.Core.extensions;
using MyShooterGame.Models;
using Godot;
namespace MyShooterGame.Systems
{
public class GameplaySystem : AbstractSystem
{
public void StartNewGame()
{
GD.Print("开始新游戏");
var playerModel = this.GetModel<PlayerModel>();
var gameModel = this.GetModel<GameModel>();
// 重置数据
playerModel.Reset();
gameModel.StartGame();
}
public void GameOver()
{
GD.Print("游戏结束");
var gameModel = this.GetModel<GameModel>();
gameModel.EndGame();
// 保存最高分
SaveHighScore();
}
public void PauseGame()
{
var gameModel = this.GetModel<GameModel>();
gameModel.PauseGame();
GetTree().Paused = true;
}
public void ResumeGame()
{
var gameModel = this.GetModel<GameModel>();
gameModel.ResumeGame();
GetTree().Paused = false;
}
private void SaveHighScore()
{
// 实现最高分保存逻辑
}
private SceneTree GetTree()
{
return (SceneTree)Engine.GetMainLoop();
}
}
}
```
### 3.2 敌人生成系统
```csharp
// Scripts/Systems/SpawnSystem.cs
using GFramework.Core.system;
using GFramework.Core.extensions;
using GFramework.Core.Abstractions.coroutine;
using GFramework.Core.coroutine;
using MyShooterGame.Models;
using Godot;
using System.Collections.Generic;
namespace MyShooterGame.Systems
{
public class SpawnSystem : AbstractSystem
{
private PackedScene _enemyScene;
private Node2D _spawnRoot;
private CoroutineHandle? _spawnCoroutine;
public void Initialize(Node2D spawnRoot, PackedScene enemyScene)
{
_spawnRoot = spawnRoot;
_enemyScene = enemyScene;
}
public void StartSpawning()
{
if (_spawnCoroutine.HasValue)
{
this.StopCoroutine(_spawnCoroutine.Value);
}
_spawnCoroutine = this.StartCoroutine(SpawnEnemiesCoroutine());
}
public void StopSpawning()
{
if (_spawnCoroutine.HasValue)
{
this.StopCoroutine(_spawnCoroutine.Value);
_spawnCoroutine = null;
}
}
private IEnumerator<IYieldInstruction> SpawnEnemiesCoroutine()
{
var gameModel = this.GetModel<GameModel>();
while (gameModel.IsPlaying.Value)
{
// 等待 2 秒
yield return CoroutineHelper.WaitForSeconds(2.0);
// 生成敌人
if (!gameModel.IsPaused.Value)
{
SpawnEnemy();
}
}
}
private void SpawnEnemy()
{
if (_enemyScene == null || _spawnRoot == null)
return;
var enemy = _enemyScene.Instantiate<Node2D>();
_spawnRoot.AddChild(enemy);
// 随机位置
var random = new Random();
enemy.Position = new Vector2(
random.Next(100, 900),
-50
);
var gameModel = this.GetModel<GameModel>();
gameModel.EnemiesAlive.Value++;
GD.Print($"生成敌人,当前数量: {gameModel.EnemiesAlive.Value}");
}
}
}
```
**代码说明**
- `GameplaySystem` 管理游戏流程
- `SpawnSystem` 使用协程定时生成敌人
- 系统之间通过 Model 共享数据
## 步骤 4创建玩家控制器
实现玩家的移动和射击。
```csharp
// Scripts/Controllers/PlayerController.cs
using GFramework.Core.Abstractions.controller;
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.extensions;
using MyShooterGame.Architecture;
using MyShooterGame.Models;
using Godot;
namespace MyShooterGame.Controllers
{
public partial class PlayerController : CharacterBody2D, IController
{
[Export] public float Speed = 300f;
[Export] public PackedScene BulletScene;
private float _shootCooldown = 0f;
private const float ShootInterval = 0.2f;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public override void _Ready()
{
// 监听玩家死亡
var playerModel = this.GetModel<PlayerModel>();
playerModel.IsAlive.RegisterOnValueChanged(isAlive =>
{
if (!isAlive)
{
OnPlayerDied();
}
});
}
public override void _Process(double delta)
{
_shootCooldown -= (float)delta;
// 射击
if (Input.IsActionPressed("shoot") && _shootCooldown <= 0)
{
Shoot();
_shootCooldown = ShootInterval;
}
}
public override void _PhysicsProcess(double delta)
{
// 移动
var velocity = Vector2.Zero;
if (Input.IsActionPressed("move_left"))
velocity.X -= 1;
if (Input.IsActionPressed("move_right"))
velocity.X += 1;
if (Input.IsActionPressed("move_up"))
velocity.Y -= 1;
if (Input.IsActionPressed("move_down"))
velocity.Y += 1;
Velocity = velocity.Normalized() * Speed;
MoveAndSlide();
// 限制在屏幕内
var screenSize = GetViewportRect().Size;
Position = new Vector2(
Mathf.Clamp(Position.X, 0, screenSize.X),
Mathf.Clamp(Position.Y, 0, screenSize.Y)
);
}
private void Shoot()
{
if (BulletScene == null)
return;
var bullet = BulletScene.Instantiate<Node2D>();
GetParent().AddChild(bullet);
bullet.GlobalPosition = GlobalPosition + new Vector2(0, -20);
GD.Print("发射子弹");
}
public void TakeDamage(int damage)
{
var playerModel = this.GetModel<PlayerModel>();
playerModel.TakeDamage(damage);
GD.Print($"玩家受伤,剩余生命: {playerModel.Health.Value}");
}
private void OnPlayerDied()
{
GD.Print("玩家死亡");
var playerModel = this.GetModel<PlayerModel>();
playerModel.LoseLife();
if (playerModel.Lives.Value > 0)
{
// 重生
Position = new Vector2(400, 500);
}
else
{
// 游戏结束
var gameplaySystem = this.GetSystem<GameplaySystem>();
gameplaySystem.GameOver();
}
}
}
}
```
**代码说明**
- 实现 `IController` 接口访问架构
- 使用 Godot 的输入系统
- 通过 Model 更新游戏状态
- 监听属性变化响应事件
## 步骤 5创建游戏场景
### 5.1 主场景 (Main.tscn)
创建主场景并添加架构初始化脚本:
```csharp
// Scripts/Main.cs
using Godot;
using MyShooterGame.Architecture;
public partial class Main : Node
{
private GameArchitecture _architecture;
public override async void _Ready()
{
GD.Print("初始化游戏");
// 创建并初始化架构
_architecture = new GameArchitecture();
await _architecture.InitializeAsync();
GD.Print("架构初始化完成,切换到菜单");
// 加载菜单场景
GetTree().ChangeSceneToFile("res://Scenes/Menu.tscn");
}
}
```
### 5.2 菜单场景 (Menu.tscn)
创建菜单UI并添加控制脚本
```csharp
// Scripts/UI/MenuController.cs
using Godot;
using GFramework.Core.Abstractions.controller;
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.extensions;
using MyShooterGame.Architecture;
using MyShooterGame.Systems;
public partial class MenuController : Control, IController
{
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public override void _Ready()
{
// 连接按钮信号
GetNode<Button>("VBoxContainer/StartButton").Pressed += OnStartPressed;
GetNode<Button>("VBoxContainer/QuitButton").Pressed += OnQuitPressed;
}
private void OnStartPressed()
{
GD.Print("开始游戏");
// 初始化游戏
var gameplaySystem = this.GetSystem<GameplaySystem>();
gameplaySystem.StartNewGame();
// 切换到游戏场景
GetTree().ChangeSceneToFile("res://Scenes/Game.tscn");
}
private void OnQuitPressed()
{
GetTree().Quit();
}
}
```
### 5.3 游戏场景 (Game.tscn)
创建游戏场景并添加控制脚本:
```csharp
// Scripts/GameScene.cs
using Godot;
using GFramework.Core.Abstractions.controller;
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.extensions;
using MyShooterGame.Architecture;
using MyShooterGame.Systems;
using MyShooterGame.Models;
public partial class GameScene : Node2D, IController
{
[Export] public PackedScene EnemyScene;
private SpawnSystem _spawnSystem;
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public override void _Ready()
{
// 初始化生成系统
_spawnSystem = this.GetSystem<SpawnSystem>();
_spawnSystem.Initialize(this, EnemyScene);
_spawnSystem.StartSpawning();
// 监听游戏状态
var gameModel = this.GetModel<GameModel>();
gameModel.IsPlaying.RegisterOnValueChanged(isPlaying =>
{
if (!isPlaying)
{
OnGameOver();
}
});
GD.Print("游戏场景已加载");
}
public override void _Process(double delta)
{
// 更新游戏时间
var gameModel = this.GetModel<GameModel>();
if (gameModel.IsPlaying.Value && !gameModel.IsPaused.Value)
{
gameModel.GameTime.Value += (float)delta;
}
// 暂停
if (Input.IsActionJustPressed("ui_cancel"))
{
TogglePause();
}
}
private void TogglePause()
{
var gameplaySystem = this.GetSystem<GameplaySystem>();
var gameModel = this.GetModel<GameModel>();
if (gameModel.IsPaused.Value)
{
gameplaySystem.ResumeGame();
}
else
{
gameplaySystem.PauseGame();
}
}
private void OnGameOver()
{
GD.Print("游戏结束,返回菜单");
_spawnSystem.StopSpawning();
// 延迟返回菜单
GetTree().CreateTimer(2.0).Timeout += () =>
{
GetTree().ChangeSceneToFile("res://Scenes/Menu.tscn");
};
}
}
```
**代码说明**
- `Main` 初始化架构
- `MenuController` 处理菜单交互
- `GameScene` 管理游戏场景
- 所有脚本通过架构访问系统和模型
## 完整代码
项目结构和所有代码文件已在上述步骤中提供。
## 运行结果
运行游戏后,你将看到:
1. **启动**
- 架构初始化
- 自动进入主菜单
2. **主菜单**
- 显示开始和退出按钮
- 点击开始进入游戏
3. **游戏场景**
- 玩家可以移动和射击
- 敌人定时生成
- HUD 显示分数和生命值
- 按 ESC 暂停游戏
4. **游戏结束**
- 玩家生命值为 0 时游戏结束
- 显示最终分数
- 自动返回主菜单
**验证步骤**
1. 架构正确初始化
2. 场景切换正常
3. 玩家控制响应
4. 敌人生成系统工作
5. 数据模型正确更新
6. 暂停功能正常
## 下一步
恭喜!你已经完成了一个基础的 Godot 游戏项目。接下来可以:
- 添加更多游戏功能(道具、关卡等)
- 实现完整的 UI 系统
- 添加音效和音乐
- 优化性能和体验
- 发布到不同平台
## 相关文档
- [Godot 架构集成](/zh-CN/godot/architecture) - 架构详细说明
- [Godot 场景系统](/zh-CN/godot/scene) - 场景管理
- [Godot UI 系统](/zh-CN/godot/ui) - UI 管理
- [Godot 扩展](/zh-CN/godot/extensions) - 扩展功能

View File

@ -71,6 +71,50 @@
---
### 系统实现教程
#### [使用协程系统](./coroutine-tutorial.md)
> 学习如何使用协程系统实现异步操作和时间控制。
**学习内容**:创建协程、等待指令、事件等待、协程组合
**预计时间**1-2 小时
#### [实现状态机](./state-machine-tutorial.md)
> 学习如何使用状态机系统管理游戏状态和场景切换。
**学习内容**:定义状态、状态转换、异步状态、状态历史
**预计时间**1-2 小时
#### [实现暂停系统](./pause-system.md)
> 学习如何使用暂停系统实现多层暂停管理和游戏流程控制。
**学习内容**:基本暂停、分组暂停、暂停栈、自定义处理器
**预计时间**1-2 小时
#### [资源管理最佳实践](./resource-management.md)
> 学习如何高效管理游戏资源的加载、缓存和释放。
**学习内容**:资源加载、缓存策略、释放策略、内存优化
**预计时间**1-2 小时
#### [实现存档系统](./save-system.md)
> 学习如何实现完整的游戏存档和读档系统。
**学习内容**:数据序列化、存档管理、版本控制、加密保护
**预计时间**1-2 小时
---
## 🎯 学习路径建议
### 路径一:快速上手(推荐新手)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,814 @@
---
title: 资源管理最佳实践
description: 学习如何高效管理游戏资源,避免内存泄漏和性能问题
---
# 资源管理最佳实践
## 学习目标
完成本教程后,你将能够:
- 理解资源管理的核心概念和重要性
- 实现自定义资源加载器
- 使用资源句柄管理资源生命周期
- 实现资源预加载和延迟加载
- 选择合适的资源释放策略
- 避免常见的资源管理陷阱
## 前置条件
- 已安装 GFramework.Core NuGet 包
- 了解 C# 基础语法和 async/await
- 阅读过[快速开始](/zh-CN/getting-started/quick-start)
- 了解[协程系统](/zh-CN/core/coroutine)
## 步骤 1创建资源类型和加载器
首先,让我们定义游戏中常用的资源类型,并为它们实现加载器。
```csharp
using GFramework.Core.Abstractions.resource;
using System;
using System.IO;
using System.Threading.Tasks;
namespace MyGame.Resources
{
// ===== 资源类型定义 =====
/// <summary>
/// 纹理资源
/// </summary>
public class Texture : IDisposable
{
public string Path { get; set; } = string.Empty;
public int Width { get; set; }
public int Height { get; set; }
public byte[]? Data { get; set; }
public void Dispose()
{
Data = null;
Console.WriteLine($"纹理已释放: {Path}");
}
}
/// <summary>
/// 音频资源
/// </summary>
public class AudioClip : IDisposable
{
public string Path { get; set; } = string.Empty;
public double Duration { get; set; }
public byte[]? Data { get; set; }
public void Dispose()
{
Data = null;
Console.WriteLine($"音频已释放: {Path}");
}
}
/// <summary>
/// 配置文件资源
/// </summary>
public class ConfigData
{
public string Path { get; set; } = string.Empty;
public Dictionary<string, string> Data { get; set; } = new();
}
// ===== 资源加载器实现 =====
/// <summary>
/// 纹理加载器
/// </summary>
public class TextureLoader : IResourceLoader<Texture>
{
public Texture Load(string path)
{
Console.WriteLine($"同步加载纹理: {path}");
// 模拟加载纹理
Thread.Sleep(100); // 模拟 I/O 延迟
return new Texture
{
Path = path,
Width = 512,
Height = 512,
Data = new byte[512 * 512 * 4] // RGBA
};
}
public async Task<Texture> LoadAsync(string path)
{
Console.WriteLine($"异步加载纹理: {path}");
// 模拟异步加载
await Task.Delay(100);
return new Texture
{
Path = path,
Width = 512,
Height = 512,
Data = new byte[512 * 512 * 4]
};
}
public void Unload(Texture resource)
{
resource?.Dispose();
}
}
/// <summary>
/// 音频加载器
/// </summary>
public class AudioLoader : IResourceLoader<AudioClip>
{
public AudioClip Load(string path)
{
Console.WriteLine($"同步加载音频: {path}");
Thread.Sleep(150);
return new AudioClip
{
Path = path,
Duration = 30.0,
Data = new byte[1024 * 1024] // 1MB
};
}
public async Task<AudioClip> LoadAsync(string path)
{
Console.WriteLine($"异步加载音频: {path}");
await Task.Delay(150);
return new AudioClip
{
Path = path,
Duration = 30.0,
Data = new byte[1024 * 1024]
};
}
public void Unload(AudioClip resource)
{
resource?.Dispose();
}
}
/// <summary>
/// 配置文件加载器
/// </summary>
public class ConfigLoader : IResourceLoader<ConfigData>
{
public ConfigData Load(string path)
{
Console.WriteLine($"加载配置: {path}");
// 模拟解析配置文件
return new ConfigData
{
Path = path,
Data = new Dictionary<string, string>
{
["version"] = "1.0",
["difficulty"] = "normal"
}
};
}
public async Task<ConfigData> LoadAsync(string path)
{
await Task.Delay(50);
return Load(path);
}
public void Unload(ConfigData resource)
{
resource.Data.Clear();
Console.WriteLine($"配置已释放: {resource.Path}");
}
}
}
```
**代码说明**
- 定义了三种常见资源类型:纹理、音频、配置
- 实现 `IResourceLoader<T>` 接口提供加载逻辑
- 同步和异步加载方法分别处理不同场景
- `Unload` 方法负责资源清理
## 步骤 2注册资源管理器
在架构中注册资源管理器和所有加载器。
```csharp
using GFramework.Core.architecture;
using GFramework.Core.Abstractions.resource;
using GFramework.Core.resource;
using MyGame.Resources;
namespace MyGame
{
public class GameArchitecture : Architecture
{
public static IArchitecture Interface { get; private set; }
protected override void Init()
{
Interface = this;
// 创建资源管理器
var resourceManager = new ResourceManager();
// 注册资源加载器
resourceManager.RegisterLoader(new TextureLoader());
resourceManager.RegisterLoader(new AudioLoader());
resourceManager.RegisterLoader(new ConfigLoader());
// 设置释放策略(默认手动释放)
resourceManager.SetReleaseStrategy(new ManualReleaseStrategy());
// 注册到架构
RegisterUtility<IResourceManager>(resourceManager);
Console.WriteLine("资源管理器初始化完成");
}
}
}
```
**代码说明**
- 创建 `ResourceManager` 实例
- 为每种资源类型注册对应的加载器
- 设置资源释放策略
- 将资源管理器注册为 Utility
## 步骤 3实现资源预加载系统
创建一个系统来管理资源的预加载和卸载。
```csharp
using GFramework.Core.system;
using GFramework.Core.Abstractions.resource;
using GFramework.Core.extensions;
using MyGame.Resources;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyGame.Systems
{
/// <summary>
/// 资源预加载系统
/// </summary>
public class ResourcePreloadSystem : AbstractSystem
{
// 场景资源配置
private readonly Dictionary<string, List<string>> _sceneResources = new()
{
["Menu"] = new List<string>
{
"textures/menu_bg.png",
"textures/button.png",
"audio/menu_bgm.mp3"
},
["Gameplay"] = new List<string>
{
"textures/player.png",
"textures/enemy.png",
"textures/bullet.png",
"audio/game_bgm.mp3",
"audio/shoot.mp3",
"config/level_1.cfg"
},
["GameOver"] = new List<string>
{
"textures/gameover_bg.png",
"audio/gameover.mp3"
}
};
// 当前场景的资源句柄
private readonly List<IDisposable> _currentHandles = new();
/// <summary>
/// 预加载场景资源
/// </summary>
public async Task PreloadSceneAsync(string sceneName)
{
if (!_sceneResources.TryGetValue(sceneName, out var resources))
{
Console.WriteLine($"场景 {sceneName} 没有配置资源");
return;
}
Console.WriteLine($"\n=== 开始预加载场景: {sceneName} ===");
var resourceManager = this.GetUtility<IResourceManager>();
// 并行加载所有资源
var tasks = new List<Task>();
foreach (var path in resources)
{
if (path.EndsWith(".png"))
{
tasks.Add(resourceManager.PreloadAsync<Texture>(path));
}
else if (path.EndsWith(".mp3"))
{
tasks.Add(resourceManager.PreloadAsync<AudioClip>(path));
}
else if (path.EndsWith(".cfg"))
{
tasks.Add(resourceManager.PreloadAsync<ConfigData>(path));
}
}
await Task.WhenAll(tasks);
Console.WriteLine($"场景 {sceneName} 资源预加载完成\n");
}
/// <summary>
/// 预加载场景资源(带进度)
/// </summary>
public async Task PreloadSceneWithProgressAsync(
string sceneName,
Action<float> onProgress)
{
if (!_sceneResources.TryGetValue(sceneName, out var resources))
return;
Console.WriteLine($"\n=== 开始预加载场景: {sceneName} ===");
var resourceManager = this.GetUtility<IResourceManager>();
int totalCount = resources.Count;
int loadedCount = 0;
foreach (var path in resources)
{
// 根据扩展名加载不同类型的资源
if (path.EndsWith(".png"))
{
await resourceManager.PreloadAsync<Texture>(path);
}
else if (path.EndsWith(".mp3"))
{
await resourceManager.PreloadAsync<AudioClip>(path);
}
else if (path.EndsWith(".cfg"))
{
await resourceManager.PreloadAsync<ConfigData>(path);
}
loadedCount++;
float progress = (float)loadedCount / totalCount;
onProgress?.Invoke(progress);
Console.WriteLine($"加载进度: {progress * 100:F0}% ({loadedCount}/{totalCount})");
}
Console.WriteLine($"场景 {sceneName} 资源预加载完成\n");
}
/// <summary>
/// 获取场景资源句柄
/// </summary>
public void AcquireSceneResources(string sceneName)
{
if (!_sceneResources.TryGetValue(sceneName, out var resources))
return;
Console.WriteLine($"获取场景 {sceneName} 的资源句柄");
var resourceManager = this.GetUtility<IResourceManager>();
// 清理旧句柄
ReleaseCurrentResources();
// 获取新句柄
foreach (var path in resources)
{
IDisposable? handle = null;
if (path.EndsWith(".png"))
{
handle = resourceManager.GetHandle<Texture>(path);
}
else if (path.EndsWith(".mp3"))
{
handle = resourceManager.GetHandle<AudioClip>(path);
}
else if (path.EndsWith(".cfg"))
{
handle = resourceManager.GetHandle<ConfigData>(path);
}
if (handle != null)
{
_currentHandles.Add(handle);
}
}
Console.WriteLine($"已获取 {_currentHandles.Count} 个资源句柄");
}
/// <summary>
/// 释放当前场景资源
/// </summary>
public void ReleaseCurrentResources()
{
Console.WriteLine($"释放 {_currentHandles.Count} 个资源句柄");
foreach (var handle in _currentHandles)
{
handle.Dispose();
}
_currentHandles.Clear();
}
/// <summary>
/// 卸载场景资源
/// </summary>
public void UnloadSceneResources(string sceneName)
{
if (!_sceneResources.TryGetValue(sceneName, out var resources))
return;
Console.WriteLine($"\n卸载场景 {sceneName} 的资源");
var resourceManager = this.GetUtility<IResourceManager>();
foreach (var path in resources)
{
resourceManager.Unload(path);
}
Console.WriteLine($"场景 {sceneName} 资源已卸载\n");
}
/// <summary>
/// 显示资源状态
/// </summary>
public void ShowResourceStatus()
{
var resourceManager = this.GetUtility<IResourceManager>();
Console.WriteLine("\n=== 资源状态 ===");
Console.WriteLine($"已加载资源数: {resourceManager.LoadedResourceCount}");
Console.WriteLine("已加载资源列表:");
foreach (var path in resourceManager.GetLoadedResourcePaths())
{
Console.WriteLine($" - {path}");
}
Console.WriteLine();
}
}
}
```
**代码说明**
- 使用字典配置每个场景需要的资源
- `PreloadSceneAsync` 并行预加载所有资源
- `PreloadSceneWithProgressAsync` 提供加载进度回调
- `AcquireSceneResources` 获取资源句柄防止被释放
- `ReleaseCurrentResources` 释放不再使用的资源
## 步骤 4实现资源使用示例
创建一个游戏系统展示如何正确使用资源。
```csharp
using GFramework.Core.system;
using GFramework.Core.Abstractions.resource;
using GFramework.Core.extensions;
using MyGame.Resources;
namespace MyGame.Systems
{
/// <summary>
/// 游戏系统示例
/// </summary>
public class GameplaySystem : AbstractSystem
{
private IResourceHandle<Texture>? _playerTexture;
private IResourceHandle<AudioClip>? _bgmClip;
/// <summary>
/// 初始化游戏
/// </summary>
public void InitializeGame()
{
Console.WriteLine("\n=== 初始化游戏 ===");
var resourceManager = this.GetUtility<IResourceManager>();
// 获取玩家纹理句柄
_playerTexture = resourceManager.GetHandle<Texture>("textures/player.png");
if (_playerTexture?.Resource != null)
{
Console.WriteLine($"玩家纹理已加载: {_playerTexture.Resource.Width}x{_playerTexture.Resource.Height}");
}
// 获取背景音乐句柄
_bgmClip = resourceManager.GetHandle<AudioClip>("audio/game_bgm.mp3");
if (_bgmClip?.Resource != null)
{
Console.WriteLine($"背景音乐已加载: {_bgmClip.Resource.Duration}秒");
}
Console.WriteLine("游戏初始化完成\n");
}
/// <summary>
/// 使用临时资源(使用 using 语句)
/// </summary>
public void SpawnBullet()
{
var resourceManager = this.GetUtility<IResourceManager>();
// 使用 using 语句自动管理资源生命周期
using var bulletTexture = resourceManager.GetHandle<Texture>("textures/bullet.png");
if (bulletTexture?.Resource != null)
{
Console.WriteLine("创建子弹,使用纹理");
// 使用纹理创建子弹...
}
// 离开作用域后自动释放句柄
}
/// <summary>
/// 播放音效(临时资源)
/// </summary>
public void PlayShootSound()
{
var resourceManager = this.GetUtility<IResourceManager>();
using var shootSound = resourceManager.GetHandle<AudioClip>("audio/shoot.mp3");
if (shootSound?.Resource != null)
{
Console.WriteLine("播放射击音效");
// 播放音效...
}
}
/// <summary>
/// 清理游戏资源
/// </summary>
public void CleanupGame()
{
Console.WriteLine("\n=== 清理游戏资源 ===");
// 释放长期持有的资源句柄
_playerTexture?.Dispose();
_playerTexture = null;
_bgmClip?.Dispose();
_bgmClip = null;
Console.WriteLine("游戏资源已清理\n");
}
}
}
```
**代码说明**
- 长期使用的资源玩家纹理、BGM保存句柄
- 临时资源(子弹纹理、音效)使用 `using` 语句
- 在清理时释放所有持有的句柄
- 展示了正确的资源生命周期管理
## 步骤 5测试资源管理
编写测试代码验证资源管理功能。
```csharp
using MyGame;
using MyGame.Systems;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("=== 资源管理最佳实践测试 ===\n");
// 1. 初始化架构
var architecture = new GameArchitecture();
architecture.Initialize();
await architecture.WaitUntilReadyAsync();
// 2. 获取系统
var preloadSystem = architecture.GetSystem<ResourcePreloadSystem>();
var gameplaySystem = architecture.GetSystem<GameplaySystem>();
// 3. 测试场景资源预加载
Console.WriteLine("--- 测试 1: 预加载菜单场景 ---");
await preloadSystem.PreloadSceneWithProgressAsync("Menu", progress =>
{
// 进度回调
});
preloadSystem.ShowResourceStatus();
await Task.Delay(500);
// 4. 切换到游戏场景
Console.WriteLine("--- 测试 2: 切换到游戏场景 ---");
await preloadSystem.PreloadSceneWithProgressAsync("Gameplay", progress =>
{
// 进度回调
});
preloadSystem.ShowResourceStatus();
await Task.Delay(500);
// 5. 获取场景资源句柄
Console.WriteLine("--- 测试 3: 获取游戏场景资源句柄 ---");
preloadSystem.AcquireSceneResources("Gameplay");
await Task.Delay(500);
// 6. 初始化游戏
Console.WriteLine("--- 测试 4: 初始化游戏 ---");
gameplaySystem.InitializeGame();
await Task.Delay(500);
// 7. 使用临时资源
Console.WriteLine("--- 测试 5: 使用临时资源 ---");
gameplaySystem.SpawnBullet();
gameplaySystem.PlayShootSound();
preloadSystem.ShowResourceStatus();
await Task.Delay(500);
// 8. 清理游戏
Console.WriteLine("--- 测试 6: 清理游戏 ---");
gameplaySystem.CleanupGame();
await Task.Delay(500);
// 9. 释放场景资源句柄
Console.WriteLine("--- 测试 7: 释放场景资源句柄 ---");
preloadSystem.ReleaseCurrentResources();
preloadSystem.ShowResourceStatus();
await Task.Delay(500);
// 10. 卸载旧场景资源
Console.WriteLine("--- 测试 8: 卸载菜单场景资源 ---");
preloadSystem.UnloadSceneResources("Menu");
preloadSystem.ShowResourceStatus();
Console.WriteLine("=== 测试完成 ===");
}
}
```
**代码说明**
- 测试资源预加载和进度回调
- 测试场景切换时的资源管理
- 测试资源句柄的获取和释放
- 测试临时资源的自动管理
- 验证资源状态和内存清理
## 完整代码
所有代码文件已在上述步骤中提供。项目结构如下:
```
MyGame/
├── Resources/
│ ├── Texture.cs
│ ├── AudioClip.cs
│ ├── ConfigData.cs
│ ├── TextureLoader.cs
│ ├── AudioLoader.cs
│ └── ConfigLoader.cs
├── Systems/
│ ├── ResourcePreloadSystem.cs
│ └── GameplaySystem.cs
├── GameArchitecture.cs
└── Program.cs
```
## 运行结果
运行程序后,你将看到类似以下的输出:
```
=== 资源管理最佳实践测试 ===
资源管理器初始化完成
--- 测试 1: 预加载菜单场景 ---
=== 开始预加载场景: Menu ===
异步加载纹理: textures/menu_bg.png
异步加载纹理: textures/button.png
异步加载音频: audio/menu_bgm.mp3
加载进度: 33% (1/3)
加载进度: 67% (2/3)
加载进度: 100% (3/3)
场景 Menu 资源预加载完成
=== 资源状态 ===
已加载资源数: 3
已加载资源列表:
- textures/menu_bg.png
- textures/button.png
- audio/menu_bgm.mp3
--- 测试 2: 切换到游戏场景 ---
=== 开始预加载场景: Gameplay ===
异步加载纹理: textures/player.png
异步加载纹理: textures/enemy.png
异步加载纹理: textures/bullet.png
异步加载音频: audio/game_bgm.mp3
异步加载音频: audio/shoot.mp3
加载配置: config/level_1.cfg
加载进度: 17% (1/6)
加载进度: 33% (2/6)
加载进度: 50% (3/6)
加载进度: 67% (4/6)
加载进度: 83% (5/6)
加载进度: 100% (6/6)
场景 Gameplay 资源预加载完成
=== 资源状态 ===
已加载资源数: 9
--- 测试 3: 获取游戏场景资源句柄 ---
获取场景 Gameplay 的资源句柄
已获取 6 个资源句柄
--- 测试 4: 初始化游戏 ===
=== 初始化游戏 ===
玩家纹理已加载: 512x512
背景音乐已加载: 30秒
游戏初始化完成
--- 测试 5: 使用临时资源 ---
创建子弹,使用纹理
播放射击音效
--- 测试 6: 清理游戏 ---
=== 清理游戏资源 ===
游戏资源已清理
--- 测试 7: 释放场景资源句柄 ---
释放 6 个资源句柄
--- 测试 8: 卸载菜单场景资源 ---
卸载场景 Menu 的资源
纹理已释放: textures/menu_bg.png
纹理已释放: textures/button.png
音频已释放: audio/menu_bgm.mp3
场景 Menu 资源已卸载
=== 资源状态 ===
已加载资源数: 6
=== 测试完成 ===
```
**验证步骤**
1. 资源预加载正常工作
2. 加载进度正确显示
3. 资源句柄管理正确
4. 临时资源自动释放
5. 资源卸载成功执行
6. 内存正确清理
## 下一步
恭喜!你已经掌握了资源管理的最佳实践。接下来可以学习:
- [使用协程系统](/zh-CN/tutorials/coroutine-tutorial) - 在协程中加载资源
- [实现状态机](/zh-CN/tutorials/state-machine-tutorial) - 在状态切换时管理资源
- [实现存档系统](/zh-CN/tutorials/save-system) - 保存和加载游戏数据
## 相关文档
- [资源管理系统](/zh-CN/core/resource) - 资源系统详细说明
- [对象池系统](/zh-CN/core/pool) - 结合对象池复用资源
- [协程系统](/zh-CN/core/coroutine) - 异步加载资源
- [System 层](/zh-CN/core/system) - System 详细说明

View File

@ -0,0 +1,966 @@
---
title: 实现存档系统
description: 学习如何实现完整的游戏存档系统,支持多槽位和自动保存
---
# 实现存档系统
## 学习目标
完成本教程后,你将能够:
- 理解游戏存档系统的设计原则
- 定义存档数据结构
- 实现多槽位存档管理
- 实现自动保存功能
- 处理存档加载和保存错误
- 实现存档列表和删除功能
## 前置条件
- 已安装 GFramework.Game NuGet 包
- 了解 C# 基础语法和 async/await
- 阅读过[快速开始](/zh-CN/getting-started/quick-start)
- 了解[数据与存档系统](/zh-CN/game/data)
## 步骤 1定义存档数据结构
首先,让我们定义游戏存档需要保存的数据结构。
```csharp
using GFramework.Game.Abstractions.data;
using System;
using System.Collections.Generic;
namespace MyGame.Data
{
/// <summary>
/// 玩家数据
/// </summary>
public class PlayerData
{
public string Name { get; set; } = "Player";
public int Level { get; set; } = 1;
public int Experience { get; set; } = 0;
public int Health { get; set; } = 100;
public int MaxHealth { get; set; } = 100;
public int Gold { get; set; } = 0;
public Vector3 Position { get; set; } = new();
}
/// <summary>
/// 位置数据
/// </summary>
public class Vector3
{
public float X { get; set; }
public float Y { get; set; }
public float Z { get; set; }
}
/// <summary>
/// 关卡进度数据
/// </summary>
public class ProgressData
{
public int CurrentLevel { get; set; } = 1;
public List<int> CompletedLevels { get; set; } = new();
public Dictionary<string, bool> Achievements { get; set; } = new();
public float PlayTime { get; set; } = 0f;
}
/// <summary>
/// 物品数据
/// </summary>
public class InventoryData
{
public List<ItemData> Items { get; set; } = new();
public List<string> EquippedItems { get; set; } = new();
}
/// <summary>
/// 单个物品数据
/// </summary>
public class ItemData
{
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public int Quantity { get; set; } = 1;
}
/// <summary>
/// 完整的存档数据
/// </summary>
public class GameSaveData : IVersionedData
{
// 数据版本号
public int Version { get; set; } = 1;
// 存档元数据
public DateTime SaveTime { get; set; }
public string SaveName { get; set; } = "New Save";
public float TotalPlayTime { get; set; }
// 游戏数据
public PlayerData Player { get; set; } = new();
public ProgressData Progress { get; set; } = new();
public InventoryData Inventory { get; set; } = new();
}
}
```
**代码说明**
- `GameSaveData` 实现 `IVersionedData` 支持版本管理
- 将数据分为玩家、进度、物品等模块
- 包含存档元数据(保存时间、名称等)
- 使用属性初始化器设置默认值
## 步骤 2创建存档管理系统
实现一个系统来管理存档的创建、加载和保存。
```csharp
using GFramework.Core.system;
using GFramework.Core.Abstractions.resource;
using GFramework.Core.extensions;
using GFramework.Game.Abstractions.data;
using MyGame.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyGame.Systems
{
/// <summary>
/// 存档管理系统
/// </summary>
public class SaveSystem : AbstractSystem
{
private GameSaveData? _currentSave;
private int _currentSlot = -1;
/// <summary>
/// 创建新存档
/// </summary>
public GameSaveData CreateNewSave(string saveName)
{
Console.WriteLine($"创建新存档: {saveName}");
var saveData = new GameSaveData
{
SaveName = saveName,
SaveTime = DateTime.Now,
TotalPlayTime = 0f,
Player = new PlayerData
{
Name = "Player",
Level = 1,
Experience = 0,
Health = 100,
MaxHealth = 100,
Gold = 0
},
Progress = new ProgressData
{
CurrentLevel = 1,
CompletedLevels = new List<int>(),
Achievements = new Dictionary<string, bool>(),
PlayTime = 0f
},
Inventory = new InventoryData
{
Items = new List<ItemData>(),
EquippedItems = new List<string>()
}
};
_currentSave = saveData;
return saveData;
}
/// <summary>
/// 保存游戏
/// </summary>
public async Task<bool> SaveGameAsync(int slot)
{
if (_currentSave == null)
{
Console.WriteLine("错误: 没有当前存档数据");
return false;
}
try
{
Console.WriteLine($"\n=== 保存游戏到槽位 {slot} ===");
var saveRepo = this.GetUtility<ISaveRepository<GameSaveData>>();
// 更新保存时间
_currentSave.SaveTime = DateTime.Now;
_currentSlot = slot;
// 保存到存储
await saveRepo.SaveAsync(slot, _currentSave);
Console.WriteLine($"存档已保存: {_currentSave.SaveName}");
Console.WriteLine($"玩家: {_currentSave.Player.Name}, 等级 {_currentSave.Player.Level}");
Console.WriteLine($"游戏时间: {_currentSave.TotalPlayTime:F1} 小时");
Console.WriteLine($"保存时间: {_currentSave.SaveTime}\n");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"保存失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 加载游戏
/// </summary>
public async Task<bool> LoadGameAsync(int slot)
{
try
{
Console.WriteLine($"\n=== 从槽位 {slot} 加载游戏 ===");
var saveRepo = this.GetUtility<ISaveRepository<GameSaveData>>();
// 检查存档是否存在
if (!await saveRepo.ExistsAsync(slot))
{
Console.WriteLine($"槽位 {slot} 不存在存档\n");
return false;
}
// 加载存档
_currentSave = await saveRepo.LoadAsync(slot);
_currentSlot = slot;
Console.WriteLine($"存档已加载: {_currentSave.SaveName}");
Console.WriteLine($"玩家: {_currentSave.Player.Name}, 等级 {_currentSave.Player.Level}");
Console.WriteLine($"当前关卡: {_currentSave.Progress.CurrentLevel}");
Console.WriteLine($"金币: {_currentSave.Player.Gold}");
Console.WriteLine($"物品数量: {_currentSave.Inventory.Items.Count}");
Console.WriteLine($"保存时间: {_currentSave.SaveTime}\n");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"加载失败: {ex.Message}\n");
return false;
}
}
/// <summary>
/// 删除存档
/// </summary>
public async Task<bool> DeleteSaveAsync(int slot)
{
try
{
Console.WriteLine($"\n删除槽位 {slot} 的存档");
var saveRepo = this.GetUtility<ISaveRepository<GameSaveData>>();
if (!await saveRepo.ExistsAsync(slot))
{
Console.WriteLine($"槽位 {slot} 不存在存档\n");
return false;
}
await saveRepo.DeleteAsync(slot);
if (_currentSlot == slot)
{
_currentSave = null;
_currentSlot = -1;
}
Console.WriteLine($"槽位 {slot} 的存档已删除\n");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"删除失败: {ex.Message}\n");
return false;
}
}
/// <summary>
/// 列出所有存档
/// </summary>
public async Task<List<SaveSlotInfo>> ListSavesAsync()
{
var result = new List<SaveSlotInfo>();
try
{
var saveRepo = this.GetUtility<ISaveRepository<GameSaveData>>();
var slots = await saveRepo.ListSlotsAsync();
Console.WriteLine($"\n=== 存档列表 ({slots.Count} 个) ===");
foreach (var slot in slots)
{
var saveData = await saveRepo.LoadAsync(slot);
var info = new SaveSlotInfo
{
Slot = slot,
SaveName = saveData.SaveName,
PlayerName = saveData.Player.Name,
Level = saveData.Player.Level,
SaveTime = saveData.SaveTime,
PlayTime = saveData.TotalPlayTime
};
result.Add(info);
Console.WriteLine($"槽位 {slot}: {info.SaveName}");
Console.WriteLine($" 玩家: {info.PlayerName}, 等级 {info.Level}");
Console.WriteLine($" 保存时间: {info.SaveTime}");
Console.WriteLine($" 游戏时间: {info.PlayTime:F1} 小时");
}
Console.WriteLine();
}
catch (Exception ex)
{
Console.WriteLine($"列出存档失败: {ex.Message}\n");
}
return result;
}
/// <summary>
/// 获取当前存档
/// </summary>
public GameSaveData? GetCurrentSave() => _currentSave;
/// <summary>
/// 获取当前槽位
/// </summary>
public int GetCurrentSlot() => _currentSlot;
}
/// <summary>
/// 存档槽位信息
/// </summary>
public class SaveSlotInfo
{
public int Slot { get; set; }
public string SaveName { get; set; } = string.Empty;
public string PlayerName { get; set; } = string.Empty;
public int Level { get; set; }
public DateTime SaveTime { get; set; }
public float PlayTime { get; set; }
}
}
```
**代码说明**
- `CreateNewSave` 创建新的存档数据
- `SaveGameAsync` 保存当前存档到指定槽位
- `LoadGameAsync` 从槽位加载存档
- `DeleteSaveAsync` 删除指定槽位的存档
- `ListSavesAsync` 列出所有存档信息
- 使用 try-catch 处理异常
## 步骤 3实现自动保存功能
创建自动保存系统,定期保存游戏进度。
```csharp
using GFramework.Core.system;
using GFramework.Core.extensions;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MyGame.Systems
{
/// <summary>
/// 自动保存系统
/// </summary>
public class AutoSaveSystem : AbstractSystem
{
private CancellationTokenSource? _autoSaveCts;
private bool _isAutoSaveEnabled;
private TimeSpan _autoSaveInterval = TimeSpan.FromMinutes(5);
/// <summary>
/// 启动自动保存
/// </summary>
public void StartAutoSave(int slot, TimeSpan? interval = null)
{
if (_isAutoSaveEnabled)
{
Console.WriteLine("自动保存已在运行");
return;
}
if (interval.HasValue)
{
_autoSaveInterval = interval.Value;
}
_autoSaveCts = new CancellationTokenSource();
_isAutoSaveEnabled = true;
Console.WriteLine($"\n启动自动保存 (间隔: {_autoSaveInterval.TotalSeconds} 秒)");
// 在后台线程运行自动保存
Task.Run(async () =>
{
while (!_autoSaveCts.Token.IsCancellationRequested)
{
try
{
// 等待指定间隔
await Task.Delay(_autoSaveInterval, _autoSaveCts.Token);
// 执行自动保存
await PerformAutoSaveAsync(slot);
}
catch (OperationCanceledException)
{
// 正常取消
break;
}
catch (Exception ex)
{
Console.WriteLine($"自动保存错误: {ex.Message}");
}
}
}, _autoSaveCts.Token);
}
/// <summary>
/// 停止自动保存
/// </summary>
public void StopAutoSave()
{
if (!_isAutoSaveEnabled)
{
return;
}
Console.WriteLine("停止自动保存");
_autoSaveCts?.Cancel();
_autoSaveCts?.Dispose();
_autoSaveCts = null;
_isAutoSaveEnabled = false;
}
/// <summary>
/// 执行自动保存
/// </summary>
private async Task PerformAutoSaveAsync(int slot)
{
Console.WriteLine($"\n[自动保存] 保存到槽位 {slot}...");
var saveSystem = this.GetSystem<SaveSystem>();
var success = await saveSystem.SaveGameAsync(slot);
if (success)
{
Console.WriteLine("[自动保存] 完成");
}
else
{
Console.WriteLine("[自动保存] 失败");
}
}
/// <summary>
/// 检查自动保存是否启用
/// </summary>
public bool IsAutoSaveEnabled() => _isAutoSaveEnabled;
/// <summary>
/// 设置自动保存间隔
/// </summary>
public void SetAutoSaveInterval(TimeSpan interval)
{
_autoSaveInterval = interval;
Console.WriteLine($"自动保存间隔已设置为 {interval.TotalSeconds} 秒");
}
}
}
```
**代码说明**
- 使用 `CancellationTokenSource` 控制后台任务
- `StartAutoSave` 启动定时保存任务
- `StopAutoSave` 停止自动保存
- 使用 `Task.Delay` 实现定时触发
- 捕获并处理异常,避免自动保存失败影响游戏
## 步骤 4实现游戏数据更新
创建一个系统来模拟游戏数据的变化。
```csharp
using GFramework.Core.system;
using GFramework.Core.extensions;
using MyGame.Data;
using System;
namespace MyGame.Systems
{
/// <summary>
/// 游戏逻辑系统(模拟)
/// </summary>
public class GameLogicSystem : AbstractSystem
{
private Random _random = new();
/// <summary>
/// 模拟玩家升级
/// </summary>
public void LevelUp()
{
var saveSystem = this.GetSystem<SaveSystem>();
var save = saveSystem.GetCurrentSave();
if (save == null)
{
Console.WriteLine("没有当前存档");
return;
}
save.Player.Level++;
save.Player.Experience = 0;
save.Player.MaxHealth += 10;
save.Player.Health = save.Player.MaxHealth;
Console.WriteLine($"\n玩家升级! 当前等级: {save.Player.Level}");
}
/// <summary>
/// 模拟获得金币
/// </summary>
public void AddGold(int amount)
{
var saveSystem = this.GetSystem<SaveSystem>();
var save = saveSystem.GetCurrentSave();
if (save == null) return;
save.Player.Gold += amount;
Console.WriteLine($"\n获得金币 +{amount}, 当前: {save.Player.Gold}");
}
/// <summary>
/// 模拟完成关卡
/// </summary>
public void CompleteLevel(int level)
{
var saveSystem = this.GetSystem<SaveSystem>();
var save = saveSystem.GetCurrentSave();
if (save == null) return;
if (!save.Progress.CompletedLevels.Contains(level))
{
save.Progress.CompletedLevels.Add(level);
Console.WriteLine($"\n完成关卡 {level}!");
}
save.Progress.CurrentLevel = level + 1;
}
/// <summary>
/// 模拟获得物品
/// </summary>
public void AddItem(string itemId, string itemName, int quantity = 1)
{
var saveSystem = this.GetSystem<SaveSystem>();
var save = saveSystem.GetCurrentSave();
if (save == null) return;
// 查找已有物品
var existingItem = save.Inventory.Items.Find(i => i.Id == itemId);
if (existingItem != null)
{
existingItem.Quantity += quantity;
}
else
{
save.Inventory.Items.Add(new ItemData
{
Id = itemId,
Name = itemName,
Quantity = quantity
});
}
Console.WriteLine($"\n获得物品: {itemName} x{quantity}");
}
/// <summary>
/// 模拟游戏进行
/// </summary>
public void SimulateGameplay()
{
Console.WriteLine("\n=== 模拟游戏进行 ===");
// 随机事件
int eventType = _random.Next(0, 4);
switch (eventType)
{
case 0:
AddGold(_random.Next(10, 100));
break;
case 1:
LevelUp();
break;
case 2:
CompleteLevel(_random.Next(1, 10));
break;
case 3:
AddItem($"item_{_random.Next(1, 5)}", $"物品 {_random.Next(1, 5)}", 1);
break;
}
// 增加游戏时间
var saveSystem = this.GetSystem<SaveSystem>();
var save = saveSystem.GetCurrentSave();
if (save != null)
{
save.TotalPlayTime += 0.1f;
save.Progress.PlayTime += 0.1f;
}
}
}
}
```
**代码说明**
- 提供各种游戏事件的模拟方法
- 直接修改当前存档数据
- 用于测试存档系统功能
## 步骤 5注册系统并测试
在架构中注册所有系统并进行测试。
```csharp
using GFramework.Core.architecture;
using GFramework.Game.Abstractions.data;
using GFramework.Game.Abstractions.storage;
using GFramework.Game.data;
using GFramework.Game.storage;
using MyGame.Data;
using MyGame.Systems;
namespace MyGame
{
public class GameArchitecture : Architecture
{
public static IArchitecture Interface { get; private set; }
protected override void Init()
{
Interface = this;
// 注册存储系统
var storage = new FileStorage("./game_data");
RegisterUtility<IFileStorage>(storage);
// 注册存档仓库
var saveConfig = new SaveConfiguration
{
SaveRoot = "saves",
SaveSlotPrefix = "slot_",
SaveFileName = "save.json"
};
var saveRepo = new SaveRepository<GameSaveData>(storage, saveConfig);
RegisterUtility<ISaveRepository<GameSaveData>>(saveRepo);
// 注册系统
RegisterSystem(new SaveSystem());
RegisterSystem(new AutoSaveSystem());
RegisterSystem(new GameLogicSystem());
Console.WriteLine("游戏架构初始化完成");
}
}
}
```
### 测试代码
```csharp
using MyGame;
using MyGame.Systems;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("=== 存档系统测试 ===\n");
// 1. 初始化架构
var architecture = new GameArchitecture();
architecture.Initialize();
await architecture.WaitUntilReadyAsync();
// 2. 获取系统
var saveSystem = architecture.GetSystem<SaveSystem>();
var autoSaveSystem = architecture.GetSystem<AutoSaveSystem>();
var gameLogic = architecture.GetSystem<GameLogicSystem>();
// 3. 创建新存档
Console.WriteLine("--- 测试 1: 创建新存档 ---");
saveSystem.CreateNewSave("我的冒险");
await saveSystem.SaveGameAsync(1);
await Task.Delay(1000);
// 4. 模拟游戏进行
Console.WriteLine("--- 测试 2: 游戏进行 ---");
for (int i = 0; i < 5; i++)
{
gameLogic.SimulateGameplay();
await Task.Delay(500);
}
// 5. 手动保存
Console.WriteLine("\n--- 测试 3: 手动保存 ---");
await saveSystem.SaveGameAsync(1);
await Task.Delay(1000);
// 6. 创建第二个存档
Console.WriteLine("--- 测试 4: 创建第二个存档 ---");
saveSystem.CreateNewSave("新的旅程");
gameLogic.AddGold(500);
gameLogic.LevelUp();
await saveSystem.SaveGameAsync(2);
await Task.Delay(1000);
// 7. 列出所有存档
Console.WriteLine("--- 测试 5: 列出所有存档 ---");
await saveSystem.ListSavesAsync();
await Task.Delay(1000);
// 8. 加载第一个存档
Console.WriteLine("--- 测试 6: 加载存档 ---");
await saveSystem.LoadGameAsync(1);
await Task.Delay(1000);
// 9. 启动自动保存
Console.WriteLine("--- 测试 7: 启动自动保存 ---");
autoSaveSystem.StartAutoSave(1, TimeSpan.FromSeconds(3));
// 模拟游戏进行
for (int i = 0; i < 10; i++)
{
gameLogic.SimulateGameplay();
await Task.Delay(1000);
}
// 10. 停止自动保存
autoSaveSystem.StopAutoSave();
await Task.Delay(1000);
// 11. 删除存档
Console.WriteLine("--- 测试 8: 删除存档 ---");
await saveSystem.DeleteSaveAsync(2);
await Task.Delay(1000);
// 12. 最终存档列表
Console.WriteLine("--- 测试 9: 最终存档列表 ---");
await saveSystem.ListSavesAsync();
Console.WriteLine("=== 测试完成 ===");
}
}
```
**代码说明**
- 注册存储系统和存档仓库
- 注册所有游戏系统
- 测试存档的创建、保存、加载、删除
- 测试自动保存功能
- 测试多槽位管理
## 完整代码
所有代码文件已在上述步骤中提供。项目结构如下:
```
MyGame/
├── Data/
│ ├── PlayerData.cs
│ ├── ProgressData.cs
│ ├── InventoryData.cs
│ └── GameSaveData.cs
├── Systems/
│ ├── SaveSystem.cs
│ ├── AutoSaveSystem.cs
│ └── GameLogicSystem.cs
├── GameArchitecture.cs
└── Program.cs
```
## 运行结果
运行程序后,你将看到类似以下的输出:
```
=== 存档系统测试 ===
游戏架构初始化完成
--- 测试 1: 创建新存档 ---
创建新存档: 我的冒险
=== 保存游戏到槽位 1 ===
存档已保存: 我的冒险
玩家: Player, 等级 1
游戏时间: 0.0 小时
保存时间: 2026-03-07 10:30:00
--- 测试 2: 游戏进行 ---
=== 模拟游戏进行 ===
获得金币 +45, 当前: 45
=== 模拟游戏进行 ===
玩家升级! 当前等级: 2
=== 模拟游戏进行 ===
完成关卡 3!
=== 模拟游戏进行 ===
获得物品: 物品 2 x1
=== 模拟游戏进行 ===
获得金币 +78, 当前: 123
--- 测试 3: 手动保存 ---
=== 保存游戏到槽位 1 ===
存档已保存: 我的冒险
玩家: Player, 等级 2
游戏时间: 0.5 小时
保存时间: 2026-03-07 10:30:05
--- 测试 4: 创建第二个存档 ---
创建新存档: 新的旅程
获得金币 +500, 当前: 500
玩家升级! 当前等级: 2
=== 保存游戏到槽位 2 ===
存档已保存: 新的旅程
玩家: Player, 等级 2
游戏时间: 0.0 小时
保存时间: 2026-03-07 10:30:06
--- 测试 5: 列出所有存档 ---
=== 存档列表 (2 个) ===
槽位 1: 我的冒险
玩家: Player, 等级 2
保存时间: 2026-03-07 10:30:05
游戏时间: 0.5 小时
槽位 2: 新的旅程
玩家: Player, 等级 2
保存时间: 2026-03-07 10:30:06
游戏时间: 0.0 小时
--- 测试 6: 加载存档 ---
=== 从槽位 1 加载游戏 ===
存档已加载: 我的冒险
玩家: Player, 等级 2
当前关卡: 4
金币: 123
物品数量: 1
保存时间: 2026-03-07 10:30:05
--- 测试 7: 启动自动保存 ---
启动自动保存 (间隔: 3 秒)
=== 模拟游戏进行 ===
...
[自动保存] 保存到槽位 1...
=== 保存游戏到槽位 1 ===
存档已保存: 我的冒险
...
[自动保存] 完成
停止自动保存
--- 测试 8: 删除存档 ---
删除槽位 2 的存档
槽位 2 的存档已删除
--- 测试 9: 最终存档列表 ---
=== 存档列表 (1 个) ===
槽位 1: 我的冒险
玩家: Player, 等级 3
保存时间: 2026-03-07 10:30:15
游戏时间: 1.5 小时
=== 测试完成 ===
```
**验证步骤**
1. 存档创建和保存成功
2. 游戏数据正确更新
3. 多槽位管理正常
4. 存档加载恢复数据
5. 自动保存定时触发
6. 存档删除功能正常
7. 存档列表显示正确
## 下一步
恭喜!你已经实现了一个完整的存档系统。接下来可以学习:
- [Godot 完整项目搭建](/zh-CN/tutorials/godot-complete-project) - 在 Godot 中使用存档系统
- [使用协程系统](/zh-CN/tutorials/coroutine-tutorial) - 异步加载存档
- [数据与存档系统](/zh-CN/game/data) - 数据系统详细说明
## 相关文档
- [数据与存档系统](/zh-CN/game/data) - 数据系统详细说明
- [对象池系统](/zh-CN/core/pool) - 结合对象池复用资源
- [协程系统](/zh-CN/core/coroutine) - 异步加载资源
- [System 层](/zh-CN/core/system) - System 详细说明

View File

@ -0,0 +1,746 @@
---
title: 实现状态机
description: 学习如何使用状态机系统管理游戏状态和场景切换
---
# 实现状态机
## 学习目标
完成本教程后,你将能够:
- 理解状态机的概念和应用场景
- 创建自定义游戏状态
- 实现状态之间的转换和验证
- 使用异步状态处理加载操作
- 在状态中访问架构组件
- 实现完整的游戏流程控制
## 前置条件
- 已安装 GFramework.Core NuGet 包
- 了解 C# 基础语法和 async/await
- 阅读过[快速开始](/zh-CN/getting-started/quick-start)
- 了解[生命周期管理](/zh-CN/core/lifecycle)
## 步骤 1定义游戏状态
首先,让我们为一个简单的游戏定义几个基本状态:主菜单、加载、游戏中、暂停和游戏结束。
```csharp
using GFramework.Core.Abstractions.state;
using GFramework.Core.state;
namespace MyGame.States
{
/// <summary>
/// 主菜单状态
/// </summary>
public class MenuState : ContextAwareStateBase
{
public override void OnEnter(IState? from)
{
Console.WriteLine("=== 进入主菜单 ===");
// 显示菜单 UI
ShowMenuUI();
// 播放菜单音乐
PlayMenuMusic();
}
public override void OnExit(IState? to)
{
Console.WriteLine("退出主菜单");
// 隐藏菜单 UI
HideMenuUI();
}
public override bool CanTransitionTo(IState target)
{
// 菜单只能切换到加载状态
return target is LoadingState;
}
private void ShowMenuUI()
{
Console.WriteLine("显示菜单界面");
}
pd HideMenuUI()
{
Console.WriteLine("隐藏菜单界面");
}
private void PlayMenuMusic()
{
Console.WriteLine("播放菜单音乐");
}
}
/// <summary>
/// 游戏中状态
/// </summary>
public class GameplayState : ContextAwareStateBase
{
public override void OnEnter(IState? from)
{
Console.WriteLine("=== 开始游戏 ===");
// 初始化游戏场景
InitializeGameScene();
// 重置玩家数据
ResetPlayerData();
// 播放游戏音乐
PlayGameMusic();
}
public override void OnExit(IState? to)
{
Console.WriteLine("结束游戏");
// 保存游戏进度(如果不是游戏结束)
if (to is not GameOverState)
{
SaveGameProgress();
}
}
public override bool CanTransitionTo(IState target)
{
// 游戏中可以切换到暂停或游戏结束状态
return target is PauseState or GameOverState;
}
private void InitializeGameScene()
{
Console.WriteLine("初始化游戏场景");
}
private void ResetPlayerData()
{
Console.WriteLine("重置玩家数据");
}
private void PlayGameMusic()
{
Console.WriteLine("播放游戏音乐");
}
private void SaveGameProgress()
{
Console.WriteLine("保存游戏进度");
}
}
/// <summary>
/// 暂停状态
/// </summary>
public class PauseState : ContextAwareStateBase
{
public override void OnEnter(IState? from)
{
Console.WriteLine("=== 游戏暂停 ===");
// 显示暂停菜单
ShowPauseMenu();
// 暂停游戏逻辑
PauseGameLogic();
}
public override void OnExit(IState? to)
{
Console.WriteLine("取消暂停");
// 隐藏暂停菜单
HidePauseMenu();
// 恢复游戏逻辑
ResumeGameLogic();
}
public override bool CanTransitionTo(IState target)
{
// 暂停状态可以返回游戏或退出到菜单
return target is GameplayState or MenuState;
}
private void ShowPauseMenu()
{
Console.WriteLine("显示暂停菜单");
}
private void HidePauseMenu()
{
Console.WriteLine("隐藏暂停菜单");
}
private void PauseGameLogic()
{
Console.WriteLine("暂停游戏逻辑");
}
private void ResumeGameLogic()
{
Console.WriteLine("恢复游戏逻辑");
}
}
/// <summary>
/// 游戏结束状态
/// </summary>
public class GameOverState : ContextAwareStateBase
{
public bool IsVictory { get; set; }
public override void OnEnter(IState? from)
{
Console.WriteLine(IsVictory
? "=== 游戏胜利 ==="
: "=== 游戏失败 ===");
// 显示结算界面
ShowGameOverUI();
// 播放结算音乐
PlayGameOverMusic();
}
public override void OnExit(IState? to)
{
Console.WriteLine("退出结算界面");
// 隐藏结算界面
HideGameOverUI();
}
public override bool CanTransitionTo(IState target)
{
// 游戏结束只能返回菜单
return target is MenuState;
}
private void ShowGameOverUI()
{
Console.WriteLine("显示结算界面");
}
private void HideGameOverUI()
{
Console.WriteLine("隐藏结算界面");
}
private void PlayGameOverMusic()
{
Console.WriteLine("播放结算音乐");
}
}
}
```
**代码说明**
- 继承 `ContextAwareStateBase` 创建状态
- `OnEnter` 在进入状态时调用,用于初始化
- `OnExit` 在退出状态时调用,用于清理
- `CanTransitionTo` 定义允许的状态转换规则
- 每个状态职责单一,逻辑清晰
## 步骤 2创建异步加载状态
实现一个异步加载状态,用于加载游戏资源。
```csharp
using GFramework.Core.state;
using System.Threading.Tasks;
namespace MyGame.States
{
/// <summary>
/// 加载状态(异步)
/// </summary>
public class LoadingState : AsyncContextAwareStateBase
{
public int TargetLevel { get; set; } = 1;
public override async Task OnEnterAsync(IState? from)
{
Console.WriteLine($"=== 开始加载关卡 {TargetLevel} ===");
// 显示加载界面
ShowLoadingUI();
// 异步加载资源
await LoadResourcesAsync();
// 加载完成后自动切换到游戏状态
Console.WriteLine("加载完成,进入游戏");
var stateMachine = this.GetSystem<IStateMachineSystem>();
await stateMachine.ChangeToAsync<GameplayState>();
}
public override async Task OnExitAsync(IState? to)
{
Console.WriteLine("退出加载状态");
// 隐藏加载界面
HideLoadingUI();
await Task.CompletedTask;
}
public override bool CanTransitionTo(IState target)
{
// 加载状态只能切换到游戏状态
return target is GameplayState;
}
/// <summary>
/// 异步加载资源
/// </summary>
private async Task LoadResourcesAsync()
{
// 加载纹理
Console.WriteLine("加载纹理资源...");
await Task.Delay(500);
Console.WriteLine("纹理加载完成 (33%)");
// 加载音频
Console.WriteLine("加载音频资源...");
await Task.Delay(500);
Console.WriteLine("音频加载完成 (66%)");
// 加载关卡数据
Console.WriteLine("加载关卡数据...");
await Task.Delay(500);
Console.WriteLine("关卡数据加载完成 (100%)");
}
private void ShowLoadingUI()
{
Console.WriteLine("显示加载界面");
}
private void HideLoadingUI()
{
Console.WriteLine("隐藏加载界面");
}
}
}
```
**代码说明**
- 继承 `AsyncContextAwareStateBase` 支持异步操作
- `OnEnterAsync``OnExitAsync` 是异步方法
- 可以在状态中使用 `await` 等待异步操作
- 加载完成后自动切换到下一个状态
## 步骤 3注册状态机系统
在架构中注册状态机系统和所有状态。
```csharp
using GFramework.Core.architecture;
using GFramework.Core.Abstractions.state;
using GFramework.Core.state;
using MyGame.States;
namespace MyGame
{
public class GameArchitecture : Architecture
{
public static IArchitecture Interface { get; private set; }
protected override void Init()
{
Interface = this;
// 创建状态机系统
var stateMachine = new StateMachineSystem();
// 注册所有状态
stateMachine
.Register(new MenuState())
.Register(new LoadingState())
.Register(new GameplayState())
.Register(new PauseState())
.Register(new GameOverState());
// 注册状态机系统到架构
RegisterSystem<IStateMachineSystem>(stateMachine);
Console.WriteLine("状态机系统初始化完成");
}
}
}
```
**代码说明**
- 创建 `StateMachineSystem` 实例
- 使用链式调用注册所有状态
- 将状态机注册为 `IStateMachineSystem` 服务
- 状态会自动获得架构上下文
## 步骤 4创建游戏控制器
创建控制器来管理游戏流程和状态切换。
```csharp
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.Abstractions.controller;
using GFramework.Core.Abstractions.state;
using GFramework.Core.extensions;
using MyGame.States;
using System.Threading.Tasks;
namespace MyGame.Controllers
{
public class GameFlowController : IController
{
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
/// <summary>
/// 开始游戏
/// </summary>
public async Task StartGame(int level = 1)
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
// 设置加载状态的目标关卡
var loadingState = stateMachine.GetState<LoadingState>();
if (loadingState != null)
{
loadingState.TargetLevel = level;
}
// 切换到加载状态
var success = await stateMachine.ChangeToAsync<LoadingState>();
if (!success)
{
Console.WriteLine("无法开始游戏:状态切换失败");
}
}
/// <summary>
/// 暂停游戏
/// </summary>
public async Task PauseGame()
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
// 检查当前是否在游戏状态
if (stateMachine.Current is not GameplayState)
{
Console.WriteLine("当前不在游戏中,无法暂停");
return;
}
// 切换到暂停状态
await stateMachine.ChangeToAsync<PauseState>();
}
/// <summary>
/// 恢复游戏
/// </summary>
public async Task ResumeGame()
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
// 检查当前是否在暂停状态
if (stateMachine.Current is not PauseState)
{
Console.WriteLine("当前不在暂停状态");
return;
}
// 返回游戏状态
await stateMachine.ChangeToAsync<GameplayState>();
}
/// <summary>
/// 游戏结束
/// </summary>
public async Task EndGame(bool isVictory)
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
// 设置游戏结束状态的数据
var gameOverState = stateMachine.GetState<GameOverState>();
if (gameOverState != null)
{
gameOverState.IsVictory = isVictory;
}
// 切换到游戏结束状态
await stateMachine.ChangeToAsync<GameOverState>();
}
/// <summary>
/// 返回主菜单
/// </summary>
public async Task ReturnToMenu()
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
// 切换到菜单状态
var success = await stateMachine.ChangeToAsync<MenuState>();
if (!success)
{
Console.WriteLine("无法返回菜单:状态切换被拒绝");
}
}
/// <summary>
/// 显示当前状态
/// </summary>
public void ShowCurrentState()
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
var currentState = stateMachine.Current;
if (currentState != null)
{
Console.WriteLine($"当前状态: {currentState.GetType().Name}");
}
else
{
Console.WriteLine("当前没有活动状态");
}
}
/// <summary>
/// 显示状态历史
/// </summary>
public void ShowStateHistory()
{
var stateMachine = this.GetSystem<IStateMachineSystem>();
var history = stateMachine.GetStateHistory();
Console.WriteLine("状态历史:");
foreach (var state in history)
{
Console.WriteLine($" - {state.GetType().Name}");
}
}
}
}
```
**代码说明**
- 实现 `IController` 接口
- 提供各种游戏流程控制方法
- 使用 `GetState<T>()` 获取状态实例并设置数据
- 使用 `ChangeToAsync<T>()` 切换状态
- 检查切换结果并处理失败情况
## 步骤 5测试状态机
编写测试代码验证状态机功能。
```csharp
using MyGame;
using MyGame.Controllers;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("=== 状态机系统测试 ===\n");
// 1. 初始化架构
var architecture = new GameArchitecture();
architecture.Initialize();
await architecture.WaitUntilReadyAsync();
// 2. 创建游戏流程控制器
var gameFlow = new GameFlowController();
// 3. 切换到主菜单
Console.WriteLine("\n--- 测试 1: 进入主菜单 ---");
await gameFlow.ReturnToMenu();
gameFlow.ShowCurrentState();
await Task.Delay(1000);
// 4. 开始游戏(会经过加载状态)
Console.WriteLine("\n--- 测试 2: 开始游戏 ---");
await gameFlow.StartGame(level: 1);
await Task.Delay(2000); // 等待加载完成
gameFlow.ShowCurrentState();
await Task.Delay(1000);
// 5. 暂停游戏
Console.WriteLine("\n--- 测试 3: 暂停游戏 ---");
await gameFlow.PauseGame();
gameFlow.ShowCurrentState();
await Task.Delay(1000);
// 6. 恢复游戏
Console.WriteLine("\n--- 测试 4: 恢复游戏 ---");
await gameFlow.ResumeGame();
gameFlow.ShowCurrentState();
await Task.Delay(1000);
// 7. 游戏胜利
Console.WriteLine("\n--- 测试 5: 游戏胜利 ---");
await gameFlow.EndGame(isVictory: true);
gameFlow.ShowCurrentState();
await Task.Delay(1000);
// 8. 返回菜单
Console.WriteLine("\n--- 测试 6: 返回菜单 ---");
await gameFlow.ReturnToMenu();
gameFlow.ShowCurrentState();
// 9. 显示状态历史
Console.WriteLine("\n--- 状态历史 ---");
gameFlow.ShowStateHistory();
Console.WriteLine("\n=== 测试完成 ===");
}
}
```
**代码说明**
- 初始化架构并等待就绪
- 按顺序测试各种状态切换
- 使用 `Task.Delay` 模拟游戏运行
- 验证状态转换规则和历史记录
## 完整代码
所有代码文件已在上述步骤中提供。项目结构如下:
```
MyGame/
├── States/
│ ├── MenuState.cs
│ ├── LoadingState.cs
│ ├── GameplayState.cs
│ ├── PauseState.cs
│ └── GameOverState.cs
├── Controllers/
│ └── GameFlowController.cs
├── GameArchitecture.cs
└── Program.cs
```
## 运行结果
运行程序后,你将看到类似以下的输出:
```
=== 状态机系统测试 ===
状态机系统初始化完成
--- 测试 1: 进入主菜单 ---
=== 进入主菜单 ===
显示菜单界面
播放菜单音乐
当前状态: MenuState
--- 测试 2: 开始游戏 ---
退出主菜单
隐藏菜单界面
=== 开始加载关卡 1 ===
显示加载界面
加载纹理资源...
纹理加载完成 (33%)
加载音频资源...
音频加载完成 (66%)
加载关卡数据...
关卡数据加载完成 (100%)
加载完成,进入游戏
退出加载状态
隐藏加载界面
=== 开始游戏 ===
初始化游戏场景
重置玩家数据
播放游戏音乐
当前状态: GameplayState
--- 测试 3: 暂停游戏 ---
结束游戏
保存游戏进度
=== 游戏暂停 ===
显示暂停菜单
暂停游戏逻辑
当前状态: PauseState
--- 测试 4: 恢复游戏 ---
取消暂停
隐藏暂停菜单
恢复游戏逻辑
=== 开始游戏 ===
初始化游戏场景
重置玩家数据
播放游戏音乐
当前状态: GameplayState
--- 测试 5: 游戏胜利 ---
结束游戏
=== 游戏胜利 ===
显示结算界面
播放结算音乐
当前状态: GameOverState
--- 测试 6: 返回菜单 ---
退出结算界面
隐藏结算界面
=== 进入主菜单 ===
显示菜单界面
播放菜单音乐
当前状态: MenuState
--- 状态历史 ---
状态历史:
- MenuState
- LoadingState
- GameplayState
- PauseState
- GameplayState
- GameOverState
- MenuState
=== 测试完成 ===
```
**验证步骤**
1. 所有状态切换成功执行
2. 状态转换规则正确生效
3. 异步加载状态正常工作
4. 状态历史记录完整
5. 状态数据传递正确
## 下一步
恭喜!你已经掌握了状态机系统的使用。接下来可以学习:
- [使用协程系统](/zh-CN/tutorials/coroutine-tutorial) - 在状态中使用协程
- [资源管理最佳实践](/zh-CN/tutorials/resource-management) - 在加载状态中管理资源
- [实现存档系统](/zh-CN/tutorials/save-system) - 保存和恢复游戏状态
## 相关文档
- [状态机系统](/zh-CN/core/state-machine) - 状态机详细说明
- [生命周期管理](/zh-CN/core/lifecycle) - 组件生命周期
- [System 层](/zh-CN/core/system) - System 详细说明
- [架构组件](/zh-CN/core/architecture) - 架构基础

File diff suppressed because it is too large Load Diff