docs(godot): 添加 Godot 架构集成和场景系统文档

- 新增 Godot 架构集成文档,介绍 AbstractArchitecture 和 ArchitectureAnchor
- 添加 Godot 场景系统文档,涵盖 SceneBehavior 和场景生命周期管理
- 包含数据与存档系统文档,介绍 IDataRepository 和 ISaveRepository 接口
- 提供完整的代码示例和最佳实践指南
- 覆盖多架构支持、热重载和场景参数传递等高级功能
- 包含常见问题解答和相关文档链接
This commit is contained in:
GeWuYou 2026-03-07 13:02:19 +08:00
parent bbb91d597a
commit b3838ce8c7
9 changed files with 6341 additions and 0 deletions

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

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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