docs(tutorials): 添加系统实现教程并完善核心组件文档

- 新增协程系统、状态机、暂停系统、资源管理和存档系统教程
- 添加 Configuration 包详细使用说明文档
- 创建 ECS 系统集成指南,介绍 Arch.Core 集成方案
- 提供完整的组件定义、系统创建和实体管理示例
- 包含性能优化建议和最佳实践指导
This commit is contained in:
GeWuYou 2026-03-07 15:44:34 +08:00
parent b4ef62c731
commit 84d7408bef
14 changed files with 10500 additions and 0 deletions

View File

@ -0,0 +1,938 @@
# Configuration 包使用说明
## 概述
Configuration 包提供了线程安全的配置管理系统,支持类型安全的配置存储、访问、监听和持久化。配置管理器可以用于管理游戏设置、运行时参数、开发配置等各种键值对数据。
配置系统是 GFramework 架构中的实用工具Utility可以在架构的任何层级中使用提供统一的配置管理能力。
## 核心接口
### IConfigurationManager
配置管理器接口,提供类型安全的配置存储和访问。所有方法都是线程安全的。
**核心方法:**
```csharp
// 配置访问
T? GetConfig<T>(string key); // 获取配置值
T GetConfig<T>(string key, T defaultValue); // 获取配置值(带默认值)
void SetConfig<T>(string key, T value); // 设置配置值
bool HasConfig(string key); // 检查配置是否存在
bool RemoveConfig(string key); // 移除配置
void Clear(); // 清空所有配置
// 配置监听
IUnRegister WatchConfig<T>(string key, Action<T> 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) - 配置管理器内部使用日志记录

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<T>` 表示可能存在或不存在的值,用于替代 null 引用。它有两种状态:
- **Some**:包含一个值
- **None**:不包含值
使用 Option 可以在编译时强制处理"无值"的情况,避免空引用异常。
### Result 类型
`Result<T>` 表示操作的结果,可能是成功值或失败异常。它有三种状态:
- **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<T> 有什么区别?**
A:
- `Nullable<T>` 只能用于值类型,`Option<T>` 可用于任何类型
- `Option<T>` 提供丰富的函数式操作Map、Bind、Filter 等)
- `Option<T>` 强制显式处理"无值"情况,更安全
- `Option<T>` 可以与 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`

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 引擎集成

View File

@ -0,0 +1,756 @@
---
title: 序列化系统
description: 序列化系统提供了统一的对象序列化和反序列化接口,支持 JSON 格式和运行时类型处理。
---
# 序列化系统
## 概述
序列化系统是 GFramework.Game 中用于对象序列化和反序列化的核心组件。它提供了统一的序列化接口,支持将对象转换为字符串格式(如
JSON进行存储或传输并能够将字符串数据还原为对象。
序列化系统与数据存储、配置管理、存档系统等模块深度集成,为游戏数据的持久化提供了基础支持。
**主要特性**
- 统一的序列化接口
- JSON 格式支持
- 运行时类型序列化
- 泛型和非泛型 API
- 与存储系统无缝集成
- 类型安全的反序列化
## 核心概念
### 序列化器接口
`ISerializer` 定义了基本的序列化操作:
```csharp
public interface ISerializer : IUtility
{
// 将对象序列化为字符串
string Serialize<T>(T value);
// 将字符串反序列化为对象
T Deserialize<T>(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<T>(T value);
T Deserialize<T>(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<T>(string key);
T Read<T>(string key, T defaultValue);
Task<T> ReadAsync<T>(string key);
// 写入数据
void Write<T>(string key, T value);
Task WriteAsync<T>(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<T>(string key)
{
// 先从缓存读取
if (_cache.TryGetValue(key, out var cached))
{
return (T)cached;
}
// 从存储读取并缓存
var value = _innerStorage.Read<T>(key);
_cache[key] = value;
return value;
}
public void Write<T>(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<T>(string key, T value)
{
var json = JsonSerializer.Serialize(value);
var encrypted = _encryption.Encrypt(json);
_innerStorage.Write(key, encrypted);
}
public T Read<T>(string key)
{
var encrypted = _innerStorage.Read<byte[]>(key);
var json = _encryption.Decrypt(encrypted);
return JsonSerializer.Deserialize<T>(json);
}
}
```
### 问题:如何限制存储大小?
**解答**
实现配额管理:
```csharp
public class QuotaStorage : IStorage
{
private readonly IStorage _innerStorage;
private readonly long _maxSize;
private long _currentSize;
public void Write<T>(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<T>(T value)
{
var json = _innerSerializer.Serialize(value);
var bytes = Encoding.UTF8.GetBytes(json);
var compressed = Compress(bytes);
return Convert.ToBase64String(compressed);
}
public T Deserialize<T>(string data)
{
var compressed = Convert.FromBase64String(data);
var bytes = Decompress(compressed);
var json = Encoding.UTF8.GetString(bytes);
return _innerSerializer.Deserialize<T>(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<T>(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<T>(string key)
{
var stopwatch = Stopwatch.StartNew();
try
{
var value = _innerStorage.Read<T>(key);
_logger.Info($"读取成功: {key}, 耗时: {stopwatch.ElapsedMilliseconds}ms");
return value;
}
catch (Exception ex)
{
_logger.Error($"读取失败: {key}, 错误: {ex.Message}");
throw;
}
}
}
```
## 相关文档
- [数据与存档系统](/zh-CN/game/data) - 数据持久化
- [序列化系统](/zh-CN/core/serializer) - 数据序列化
- [Godot 集成](/zh-CN/godot/index) - Godot 中的存储
- [存档系统教程](/zh-CN/tutorials/save-system) - 完整示例

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/best-practices/performance) - 性能优化最佳实践

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) - 核心资源管理

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<T>` 明确表示值可能不存在,避免 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<T>` 将错误作为值返回,而不是抛出异常
- `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

@ -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