From b3838ce8c7e556e0999f44e0dfd10695b8680b8e Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sat, 7 Mar 2026 13:02:19 +0800 Subject: [PATCH] =?UTF-8?q?docs(godot):=20=E6=B7=BB=E5=8A=A0=20Godot=20?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E9=9B=86=E6=88=90=E5=92=8C=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Godot 架构集成文档,介绍 AbstractArchitecture 和 ArchitectureAnchor - 添加 Godot 场景系统文档,涵盖 SceneBehavior 和场景生命周期管理 - 包含数据与存档系统文档,介绍 IDataRepository 和 ISaveRepository 接口 - 提供完整的代码示例和最佳实践指南 - 覆盖多架构支持、热重载和场景参数传递等高级功能 - 包含常见问题解答和相关文档链接 --- docs/zh-CN/game/data.md | 588 +++++++++++ docs/zh-CN/godot/architecture.md | 590 +++++++++++ docs/zh-CN/godot/scene.md | 583 +++++++++++ docs/zh-CN/godot/ui.md | 643 ++++++++++++ docs/zh-CN/tutorials/coroutine-tutorial.md | 598 +++++++++++ .../zh-CN/tutorials/godot-complete-project.md | 813 +++++++++++++++ docs/zh-CN/tutorials/resource-management.md | 814 +++++++++++++++ docs/zh-CN/tutorials/save-system.md | 966 ++++++++++++++++++ .../zh-CN/tutorials/state-machine-tutorial.md | 746 ++++++++++++++ 9 files changed, 6341 insertions(+) create mode 100644 docs/zh-CN/game/data.md create mode 100644 docs/zh-CN/godot/architecture.md create mode 100644 docs/zh-CN/godot/scene.md create mode 100644 docs/zh-CN/godot/ui.md create mode 100644 docs/zh-CN/tutorials/coroutine-tutorial.md create mode 100644 docs/zh-CN/tutorials/godot-complete-project.md create mode 100644 docs/zh-CN/tutorials/resource-management.md create mode 100644 docs/zh-CN/tutorials/save-system.md create mode 100644 docs/zh-CN/tutorials/state-machine-tutorial.md diff --git a/docs/zh-CN/game/data.md b/docs/zh-CN/game/data.md new file mode 100644 index 0000000..f594fd6 --- /dev/null +++ b/docs/zh-CN/game/data.md @@ -0,0 +1,588 @@ +--- +title: 数据与存档系统 +description: 数据与存档系统提供了完整的数据持久化解决方案,支持多槽位存档、版本管理和数据迁移。 +--- + +# 数据与存档系统 + +## 概述 + +数据与存档系统是 GFramework.Game 中用于管理游戏数据持久化的核心组件。它提供了统一的数据加载和保存接口,支持多槽位存档管理、数据版本控制和自动迁移,让你可以轻松实现游戏存档、设置保存等功能。 + +通过数据系统,你可以将游戏数据保存到本地存储,支持多个存档槽位,并在数据结构变化时自动进行版本迁移。 + +**主要特性**: + +- 统一的数据持久化接口 +- 多槽位存档管理 +- 数据版本控制和迁移 +- 异步加载和保存 +- 批量数据操作 +- 与存储系统集成 + +## 核心概念 + +### 数据接口 + +`IData` 标记数据类型: + +```csharp +public interface IData +{ + // 标记接口,用于标识可持久化的数据 +} +``` + +### 数据仓库 + +`IDataRepository` 提供通用的数据操作: + +```csharp +public interface IDataRepository : IUtility +{ + Task LoadAsync(IDataLocation location) where T : class, IData, new(); + Task SaveAsync(IDataLocation location, T data) where T : class, IData; + Task ExistsAsync(IDataLocation location); + Task DeleteAsync(IDataLocation location); + Task SaveAllAsync(IEnumerable<(IDataLocation, IData)> dataList); +} +``` + +### 存档仓库 + +`ISaveRepository` 专门用于管理游戏存档: + +```csharp +public interface ISaveRepository : IUtility + where TSaveData : class, IData, new() +{ + Task ExistsAsync(int slot); + Task LoadAsync(int slot); + Task SaveAsync(int slot, TSaveData data); + Task DeleteAsync(int slot); + Task> 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>(); + + // 创建存档数据 + 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>(); + + // 检查存档是否存在 + 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>(); + + // 删除存档 + await saveRepo.DeleteAsync(slot); + Console.WriteLine($"已删除槽位 {slot} 的存档"); + } +} +``` + +### 注册存档仓库 + +```csharp +using GFramework.Game.data; + +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 获取存储系统 + var storage = this.GetUtility(); + + // 创建存档配置 + var saveConfig = new SaveConfiguration + { + SaveRoot = "saves", + SaveSlotPrefix = "slot_", + SaveFileName = "save.json" + }; + + // 注册存档仓库 + var saveRepo = new SaveRepository(storage, saveConfig); + RegisterUtility>(saveRepo); + } +} +``` + +## 高级用法 + +### 列出所有存档 + +```csharp +public async Task ShowSaveList() +{ + var saveRepo = this.GetUtility>(); + + // 获取所有存档槽位 + 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>(); + 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 LoadWithMigration(int slot) +{ + var saveRepo = this.GetUtility>(); + 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(); + + 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 LoadSettings() + { + var dataRepo = this.GetUtility(); + var location = new DataLocation("settings", "game_settings.json"); + + // 检查是否存在 + if (!await dataRepo.ExistsAsync(location)) + { + return new GameSettings(); // 返回默认设置 + } + + // 加载设置 + return await dataRepo.LoadAsync(location); + } +} +``` + +### 批量保存数据 + +```csharp +public async Task SaveAllGameData() +{ + var dataRepo = this.GetUtility(); + + 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>(); + + 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>(); + + 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` 的槽位参数: + +```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 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 LoadEncrypted(int slot) +{ + var encrypted = await storage.ReadAsync(path); + var json = Decrypt(encrypted); + return JsonSerializer.Deserialize(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) - 完整示例 diff --git a/docs/zh-CN/godot/architecture.md b/docs/zh-CN/godot/architecture.md new file mode 100644 index 0000000..7ca9a50 --- /dev/null +++ b/docs/zh-CN/godot/architecture.md @@ -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 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(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(); + var gameplaySystem = this.GetSystem(); + + // 发送事件 + this.SendEvent(new PlayerSpawnedEvent()); + + // 执行命令 + this.SendCommand(new InitPlayerCommand()); + } + + public override void _Process(double delta) + { + // 在 Process 中使用架构组件 + var inputSystem = this.GetSystem(); + 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(); // 架构可能未就绪 + } + + ✓ public override void _Ready() + { + var model = this.GetModel(); // 安全 + } + ``` + +## 常见问题 + +### 问题:架构什么时候初始化? + +**解答**: +在根节点的 `_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(); + } +} + +// 方式 2: 直接使用单例 +public partial class Enemy : Node +{ + public override void _Ready() + { + var model = GameArchitecture.Interface.GetModel(); + } +} +``` + +### 问题:架构锚点节点是什么? + +**解答**: +架构锚点是一个隐藏的节点,用于将架构绑定到 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 扩展方法 diff --git a/docs/zh-CN/godot/scene.md b/docs/zh-CN/godot/scene.md new file mode 100644 index 0000000..2555943 --- /dev/null +++ b/docs/zh-CN/godot/scene.md @@ -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` 封装了 Godot 节点的场景行为: + +```csharp +public abstract class SceneBehaviorBase : 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("res://scenes/MainMenu.tscn")); + Register("Gameplay", GD.Load("res://scenes/Gameplay.tscn")); + Register("Pause", GD.Load("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(sceneRegistry); + + // 注册场景工厂 + var sceneFactory = new GodotSceneFactory(); + RegisterUtility(sceneFactory); + + // 注册场景路由 + var sceneRouter = new GodotSceneRouter(); + RegisterSystem(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(); + await sceneRouter.ReplaceAsync("MainMenu"); + } + + private async void StartGame() + { + var sceneRouter = this.GetSystem(); + await sceneRouter.ReplaceAsync("Gameplay"); + } + + private async void ShowPause() + { + var sceneRouter = this.GetSystem(); + 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 +{ + public Node2DSceneBehavior(Node2D owner, string key) : base(owner, key) + { + } +} + +// Node3D 场景 +public class Node3DSceneBehavior : SceneBehaviorBase +{ + public Node3DSceneBehavior(Node3D owner, string key) : base(owner, key) + { + } +} + +// Control 场景(UI) +public class ControlSceneBehavior : SceneBehaviorBase +{ + 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(); +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(); + await sceneRouter.ReplaceAsync("Gameplay"); + } + + private async Task PreloadNextScene() + { + var sceneFactory = this.GetUtility(); + 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(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("res://scenes/MainMenu.tscn")); + Register("Gameplay", GD.Load("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(); +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(); + var gameSystem = this.GetSystem(); + 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 扩展方法 diff --git a/docs/zh-CN/godot/ui.md b/docs/zh-CN/godot/ui.md new file mode 100644 index 0000000..aca8a08 --- /dev/null +++ b/docs/zh-CN/godot/ui.md @@ -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` 封装了 Godot Control 节点的 UI 行为: + +```csharp +public abstract class CanvasItemUiPageBehaviorBase : 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 +{ + public override UiLayer Layer => UiLayer.Page; + public override bool IsReentrant => false; +} + +// Modal 层(模态对话框) +public class ModalLayerUiPageBehavior : CanvasItemUiPageBehaviorBase +{ + 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("res://ui/MainMenu.tscn")); + Register("Settings", GD.Load("res://ui/Settings.tscn")); + Register("ConfirmDialog", GD.Load("res://ui/ConfirmDialog.tscn")); + Register("Toast", GD.Load("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(uiRegistry); + + // 注册 UI 工厂 + var uiFactory = new GodotUiFactory(); + RegisterUtility(uiFactory); + + // 注册 UI 路由 + var uiRouter = new GodotUiRouter(); + RegisterSystem(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(); + await uiRouter.PushAsync("MainMenu"); + } + + private async void ShowSettings() + { + var uiRouter = this.GetSystem(); + await uiRouter.PushAsync("Settings"); + } + + private void ShowDialog() + { + var uiRouter = this.GetSystem(); + uiRouter.Show("ConfirmDialog", UiLayer.Modal); + } + + private void ShowToast(string message) + { + var uiRouter = this.GetSystem(); + 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