diff --git a/GFramework.Core.Abstractions/controller/IController.cs b/GFramework.Core.Abstractions/controller/IController.cs index 9372fd7..3fd94f3 100644 --- a/GFramework.Core.Abstractions/controller/IController.cs +++ b/GFramework.Core.Abstractions/controller/IController.cs @@ -1,10 +1,38 @@ -namespace GFramework.Core.Abstractions.controller; - -/// -/// 控制器接口,定义了控制器的基本契约和行为规范 -/// -/// -/// 该接口为框架中的控制器组件提供统一的抽象定义, -/// 用于实现控制器的标准功能和生命周期管理 -/// +namespace GFramework.Core.Abstractions.controller; + +/// +/// 控制器标记接口,用于标识控制器组件 +/// +/// +/// +/// IController 是一个标记接口(Marker Interface),不包含任何方法或属性。 +/// 它的作用是标识一个类是控制器,用于协调 Model、System 和 UI 之间的交互。 +/// +/// +/// 架构访问 :控制器通常需要访问架构上下文。使用 [ContextAware] 特性 +/// 自动生成上下文访问能力: +/// +/// +/// using GFramework.SourceGenerators.Abstractions.rule; +/// +/// [ContextAware] +/// public partial class PlayerController : IController +/// { +/// public void Initialize() +/// { +/// // [ContextAware] 实现 IContextAware 接口,可使用扩展方法 +/// var playerModel = this.GetModel<PlayerModel>(); +/// var gameSystem = this.GetSystem<GameSystem>(); +/// } +/// } +/// +/// +/// 注意: +/// +/// +/// 必须添加 partial 关键字 +/// [ContextAware] 特性会自动实现 IContextAware 接口 +/// 可使用 this.GetModel()、this.GetSystem() 等扩展方法访问架构 +/// +/// public interface IController; \ No newline at end of file diff --git a/docs/zh-CN/best-practices/architecture-patterns.md b/docs/zh-CN/best-practices/architecture-patterns.md index 7067b15..e743ca3 100644 --- a/docs/zh-CN/best-practices/architecture-patterns.md +++ b/docs/zh-CN/best-practices/architecture-patterns.md @@ -1,3445 +1,3443 @@ -# 架构设计模式指南 - -> 全面介绍 GFramework 中的架构设计模式,帮助你构建清晰、可维护、可扩展的游戏架构。 - -## 📋 目录 - -- [概述](#概述) -- [MVC 模式](#mvc-模式) -- [MVVM 模式](#mvvm-模式) -- [命令模式](#命令模式) -- [查询模式](#查询模式) -- [事件驱动模式](#事件驱动模式) -- [依赖注入模式](#依赖注入模式) -- [服务定位器模式](#服务定位器模式) -- [对象池模式](#对象池模式) -- [状态模式](#状态模式) -- [设计原则](#设计原则) -- [架构分层](#架构分层) -- [依赖管理](#依赖管理) -- [事件系统设计](#事件系统设计) -- [模块化架构](#模块化架构) -- [错误处理策略](#错误处理策略) -- [测试策略](#测试策略) -- [重构指南](#重构指南) -- [模式选择与组合](#模式选择与组合) -- [常见问题](#常见问题) - -## 概述 - -架构设计模式是经过验证的解决方案,用于解决软件开发中的常见问题。GFramework 内置了多种设计模式,帮助你构建高质量的游戏应用。 - -### 为什么需要架构设计模式? - -1. **提高代码质量**:遵循最佳实践,减少 bug -2. **增强可维护性**:清晰的结构,易于理解和修改 -3. **促进团队协作**:统一的代码风格和架构 -4. **提升可扩展性**:轻松添加新功能 -5. **简化测试**:解耦的组件更容易测试 - -### GFramework 支持的核心模式 - -| 模式 | 用途 | 核心组件 | -|-----------|--------------|-------------------------| -| **MVC** | 分离数据、视图和控制逻辑 | Model, Controller | -| **MVVM** | 数据绑定和响应式 UI | Model, BindableProperty | -| **命令模式** | 封装操作请求 | ICommand, CommandBus | -| **查询模式** | 分离读操作 | IQuery, QueryBus | -| **事件驱动** | 松耦合通信 | IEventBus, Event | -| **依赖注入** | 控制反转 | IIocContainer | -| **服务定位器** | 服务查找 | Architecture.GetSystem | -| **对象池** | 对象复用 | IObjectPoolSystem | -| **状态模式** | 状态管理 | IStateMachine | - -## MVC 模式 - -### 概念 - -MVC(Model-View-Controller)是一种将应用程序分为三个核心组件的架构模式: - -- **Model(模型)**:管理数据和业务逻辑 -- **View(视图)**:显示数据给用户 -- **Controller(控制器)**:处理用户输入,协调 Model 和 View - -### 在 GFramework 中的实现 - -```csharp -// Model - 数据层 -public class PlayerModel : AbstractModel -{ - public BindableProperty<int> Health { get; } = new(100); - public BindableProperty<int> Score { get; } = new(0); - public BindableProperty<string> Name { get; } = new("Player"); - - protected override void OnInit() - { - // 监听数据变化 - Health.Register(newHealth => - { - if (newHealth <= 0) - { - this.SendEvent(new PlayerDiedEvent()); - } - }); - } -} - -// Controller - 控制层 -[ContextAware] -public partial class PlayerController : Node, IController -{ - private PlayerModel _playerModel; - - public override void _Ready() - { - _playerModel = Context.GetModel<PlayerModel>(); - - // 监听数据变化,更新视图 - _playerModel.Health.Register(UpdateHealthUI); - _playerModel.Score.Register(UpdateScoreUI); - } - - // 处理用户输入 - public override void _Input(InputEvent @event) - { - if (@event is InputEventKey keyEvent && keyEvent.Pressed) - { - if (keyEvent.Keycode == Key.Space) - { - // 发送命令修改 Model - Context.SendCommand(new AttackCommand()); - } - } - } - - // 更新视图 - private void UpdateHealthUI(int health) - { - var healthBar = GetNode<ProgressBar>("HealthBar"); - healthBar.Value = health; - } - - private void UpdateScoreUI(int score) - { - var scoreLabel = GetNode<Label>("ScoreLabel"); - scoreLabel.Text = $"Score: {score}"; - } -} - -// System - 业务逻辑层(可选) -public class CombatSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent<AttackCommand>(OnAttack); - } - - private void OnAttack(AttackCommand cmd) - { - var playerModel = this.GetModel<PlayerModel>(); - var enemyModel = this.GetModel<EnemyModel>(); - - // 计算伤害 - int damage = CalculateDamage(playerModel, enemyModel); - enemyModel.Health.Value -= damage; - - // 增加分数 - if (enemyModel.Health.Value <= 0) - { - playerModel.Score.Value += 100; - } - } - - private int CalculateDamage(PlayerModel player, EnemyModel enemy) - { - return 10; // 简化示例 - } -} -``` - -### MVC 优势 - -- ✅ **职责分离**:Model、View、Controller 各司其职 -- ✅ **易于测试**:可以独立测试每个组件 -- ✅ **可维护性高**:修改一个组件不影响其他组件 -- ✅ **支持多视图**:同一个 Model 可以有多个 View - -### 最佳实践 - -1. **Model 只负责数据**:不包含 UI 逻辑 -2. **Controller 协调交互**:不直接操作 UI 细节 -3. **View 只负责显示**:不包含业务逻辑 -4. **使用事件通信**:Model 变化通过事件通知 Controller - -## MVVM 模式 - -### 概念 - -MVVM(Model-View-ViewModel)是 MVC 的变体,强调数据绑定和响应式编程: - -- **Model**:数据和业务逻辑 -- **View**:用户界面 -- **ViewModel**:View 的抽象,提供数据绑定 - -### 在 GFramework 中的实现 - -```csharp -// Model - 数据层 -public class GameModel : AbstractModel -{ - public BindableProperty<int> CurrentLevel { get; } = new(1); - public BindableProperty<float> Progress { get; } = new(0f); - public BindableProperty<bool> IsLoading { get; } = new(false); - - protected override void OnInit() - { - Progress.Register(progress => - { - if (progress >= 1.0f) - { - this.SendEvent(new LevelCompletedEvent()); - } - }); - } -} - -// ViewModel - 视图模型(在 GFramework 中,Model 本身就是 ViewModel) -public class PlayerViewModel : AbstractModel -{ - private PlayerModel _playerModel; - - // 计算属性 - public BindableProperty<string> HealthText { get; } = new(""); - public BindableProperty<float> HealthPercentage { get; } = new(1.0f); - public BindableProperty<bool> IsAlive { get; } = new(true); - - protected override void OnInit() - { - _playerModel = this.GetModel<PlayerModel>(); - - // 绑定数据转换 - _playerModel.Health.Register(health => - { - HealthText.Value = $"{health} / {_playerModel.MaxHealth.Value}"; - HealthPercentage.Value = (float)health / _playerModel.MaxHealth.Value; - IsAlive.Value = health > 0; - }); - } -} - -// View - 视图层 -[ContextAware] -public partial class PlayerView : Control, IController -{ - private PlayerViewModel _viewModel; - private Label _healthLabel; - private ProgressBar _healthBar; - private Panel _deathPanel; - - public override void _Ready() - { - _viewModel = Context.GetModel<PlayerViewModel>(); - - _healthLabel = GetNode<Label>("HealthLabel"); - _healthBar = GetNode<ProgressBar>("HealthBar"); - _deathPanel = GetNode<Panel>("DeathPanel"); - - // 数据绑定 - _viewModel.HealthText.Register(text => _healthLabel.Text = text); - _viewModel.HealthPercentage.Register(pct => _healthBar.Value = pct * 100); - _viewModel.IsAlive.Register(alive => _deathPanel.Visible = !alive); - } -} -``` - -### MVVM 优势 - -- ✅ **自动更新 UI**:数据变化自动反映到界面 -- ✅ **减少样板代码**:不需要手动更新 UI -- ✅ **易于测试**:ViewModel 可以独立测试 -- ✅ **支持复杂 UI**:适合数据驱动的界面 - -### 最佳实践 - -1. **使用 BindableProperty**:实现响应式数据 -2. **ViewModel 不依赖 View**:保持单向依赖 -3. **计算属性放在 ViewModel**:如百分比、格式化文本 -4. **避免在 View 中写业务逻辑**:只负责数据绑定 - -## 命令模式 - -### 概念 - -命令模式将请求封装为对象,从而支持参数化、队列化、日志记录和撤销操作。 - -### 在 GFramework 中的实现 - -```csharp -// 定义命令输入 -public class BuyItemInput : ICommandInput -{ - public string ItemId { get; set; } - public int Quantity { get; set; } -} - -// 实现命令 -public class BuyItemCommand : AbstractCommand<BuyItemInput> -{ - protected override void OnExecute(BuyItemInput input) - { - var playerModel = this.GetModel<PlayerModel>(); - var inventoryModel = this.GetModel<InventoryModel>(); - var shopModel = this.GetModel<ShopModel>(); - - // 获取物品信息 - var item = shopModel.GetItem(input.ItemId); - var totalCost = item.Price * input.Quantity; - - // 检查金币 - if (playerModel.Gold.Value < totalCost) - { - this.SendEvent(new InsufficientGoldEvent()); - return; - } - - // 扣除金币 - playerModel.Gold.Value -= totalCost; - - // 添加物品 - inventoryModel.AddItem(input.ItemId, input.Quantity); - - // 发送事件 - this.SendEvent(new ItemPurchasedEvent - { - ItemId = input.ItemId, - Quantity = input.Quantity, - Cost = totalCost - }); - } -} - -// 使用命令 -public class ShopController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void OnBuyButtonClicked(string itemId, int quantity) - { - // 创建并发送命令 - var input = new BuyItemInput - { - ItemId = itemId, - Quantity = quantity - }; - - this.SendCommand(new BuyItemCommand { Input = input }); - } -} -``` - -### 支持撤销的命令 - -```csharp -public interface IUndoableCommand : ICommand -{ - void Undo(); -} - -public class MoveCommand : AbstractCommand, IUndoableCommand -{ - private Vector2 _previousPosition; - private Vector2 _newPosition; - - public MoveCommand(Vector2 newPosition) - { - _newPosition = newPosition; - } - - protected override void OnExecute() - { - var playerModel = this.GetModel<PlayerModel>(); - _previousPosition = playerModel.Position.Value; - playerModel.Position.Value = _newPosition; - } - - public void Undo() - { - var playerModel = this.GetModel<PlayerModel>(); - playerModel.Position.Value = _previousPosition; - } -} - -// 命令历史管理器 -public class CommandHistory -{ - private readonly Stack<IUndoableCommand> _history = new(); - - public void Execute(IUndoableCommand command) - { - command.Execute(); - _history.Push(command); - } - - public void Undo() - { - if (_history.Count > 0) - { - var command = _history.Pop(); - command.Undo(); - } - } -} -``` - -### 命令模式优势 - -- ✅ **解耦发送者和接收者**:调用者不需要知道实现细节 -- ✅ **支持撤销/重做**:保存命令历史 -- ✅ **支持队列和日志**:可以记录所有操作 -- ✅ **易于扩展**:添加新命令不影响现有代码 - -### 最佳实践 - -1. **命令保持原子性**:一个命令完成一个完整操作 -2. **使用输入对象传参**:避免构造函数参数过多 -3. **命令无状态**:执行完即可丢弃 -4. **发送事件通知结果**:而不是返回值 - -## 查询模式 - -### 概念 - -查询模式(CQRS 的一部分)将读操作与写操作分离,查询只读取数据,不修改状态。 - -### 在 GFramework 中的实现 - -```csharp -// 定义查询输入 -public class GetPlayerStatsInput : IQueryInput -{ - public string PlayerId { get; set; } -} - -// 定义查询结果 -public class PlayerStats -{ - public int Level { get; set; } - public int Health { get; set; } - public int MaxHealth { get; set; } - public int Attack { get; set; } - public int Defense { get; set; } - public int TotalPower { get; set; } -} - -// 实现查询 -public class GetPlayerStatsQuery : AbstractQuery<GetPlayerStatsInput, PlayerStats> -{ - protected override PlayerStats OnDo(GetPlayerStatsInput input) - { - var playerModel = this.GetModel<PlayerModel>(); - var equipmentModel = this.GetModel<EquipmentModel>(); - - // 计算总战力 - int basePower = playerModel.Level.Value * 10; - int equipmentPower = equipmentModel.GetTotalPower(); - - return new PlayerStats - { - Level = playerModel.Level.Value, - Health = playerModel.Health.Value, - MaxHealth = playerModel.MaxHealth.Value, - Attack = playerModel.Attack.Value + equipmentModel.GetAttackBonus(), - Defense = playerModel.Defense.Value + equipmentModel.GetDefenseBonus(), - TotalPower = basePower + equipmentPower - }; - } -} - -// 使用查询 -public class CharacterPanelController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void ShowCharacterStats() - { - var input = new GetPlayerStatsInput { PlayerId = "player1" }; - var query = new GetPlayerStatsQuery { Input = input }; - var stats = this.SendQuery(query); - - // 显示统计信息 - DisplayStats(stats); - } - - private void DisplayStats(PlayerStats stats) - { - Console.WriteLine($"Level: {stats.Level}"); - Console.WriteLine($"Health: {stats.Health}/{stats.MaxHealth}"); - Console.WriteLine($"Attack: {stats.Attack}"); - Console.WriteLine($"Defense: {stats.Defense}"); - Console.WriteLine($"Total Power: {stats.TotalPower}"); - } -} -``` - -### 复杂查询示例 - -```csharp -// 查询背包中可装备的物品 -public class GetEquippableItemsQuery : AbstractQuery<EmptyQueryInput, List<Item>> -{ - protected override List<Item> OnDo(EmptyQueryInput input) - { - var inventoryModel = this.GetModel<InventoryModel>(); - var playerModel = this.GetModel<PlayerModel>(); - - return inventoryModel.GetAllItems() - .Where(item => item.Type == ItemType.Equipment) - .Where(item => item.RequiredLevel <= playerModel.Level.Value) - .OrderByDescending(item => item.Power) - .ToList(); - } -} - -// 组合查询 -public class CanUseSkillQuery : AbstractQuery<CanUseSkillInput, bool> -{ - protected override bool OnDo(CanUseSkillInput input) - { - var playerModel = this.GetModel<PlayerModel>(); - - // 查询技能消耗 - var costQuery = new GetSkillCostQuery { Input = new GetSkillCostInput { SkillId = input.SkillId } }; - var cost = this.SendQuery(costQuery); - - // 查询冷却状态 - var cooldownQuery = new IsSkillOnCooldownQuery { Input = new IsSkillOnCooldownInput { SkillId = input.SkillId } }; - var onCooldown = this.SendQuery(cooldownQuery); - - // 综合判断 - return playerModel.Mana.Value >= cost.ManaCost - && !onCooldown - && playerModel.Health.Value > 0; - } -} -``` - -### 查询模式优势 - -- ✅ **职责分离**:读写操作明确分离 -- ✅ **易于优化**:可以针对查询进行缓存优化 -- ✅ **提高可读性**:查询意图清晰 -- ✅ **支持复杂查询**:可以组合多个简单查询 - -### 最佳实践 - -1. **查询只读取,不修改**:保持查询的纯粹性 -2. **使用清晰的命名**:Get、Is、Can、Has 等前缀 -3. **避免过度查询**:频繁查询考虑使用 BindableProperty -4. **合理使用缓存**:复杂计算结果可以缓存 - -## 事件驱动模式 - -### 概念 - -事件驱动模式通过事件实现组件间的松耦合通信。发送者不需要知道接收者,接收者通过订阅事件来响应变化。 - -### 在 GFramework 中的实现 - -```csharp -// 定义事件 -public struct PlayerDiedEvent -{ - public Vector3 Position { get; set; } - public string Cause { get; set; } - public int FinalScore { get; set; } -} - -public struct EnemyKilledEvent -{ - public string EnemyId { get; set; } - public int Reward { get; set; } -} - -// Model 发送事件 -public class PlayerModel : AbstractModel -{ - public BindableProperty<int> Health { get; } = new(100); - public BindableProperty<Vector3> Position { get; } = new(Vector3.Zero); - - protected override void OnInit() - { - Health.Register(newHealth => - { - if (newHealth <= 0) - { - // 发送玩家死亡事件 - this.SendEvent(new PlayerDiedEvent - { - Position = Position.Value, - Cause = "Health depleted", - FinalScore = this.GetModel<GameModel>().Score.Value - }); - } - }); - } -} - -// System 监听和发送事件 -public class AchievementSystem : AbstractSystem -{ - private int _enemyKillCount = 0; - - protected override void OnInit() - { - // 监听敌人被杀事件 - this.RegisterEvent<EnemyKilledEvent>(OnEnemyKilled); - - // 监听玩家死亡事件 - this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied); - } - - private void OnEnemyKilled(EnemyKilledEvent e) - { - _enemyKillCount++; - - // 检查成就条件 - if (_enemyKillCount == 10) - { - this.SendEvent(new AchievementUnlockedEvent - { - AchievementId = "first_blood_10", - Title = "新手猎人", - Description = "击败10个敌人" - }); - } - } - - private void OnPlayerDied(PlayerDiedEvent e) - { - // 记录统计数据 - var statsModel = this.GetModel<StatisticsModel>(); - statsModel.RecordDeath(e.Position, e.Cause); - } -} - -// Controller 监听事件 -public class UIController : IController -{ - private IUnRegisterList _unregisterList = new UnRegisterList(); - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void Initialize() - { - // 监听成就解锁事件 - this.RegisterEvent<AchievementUnlockedEvent>(OnAchievementUnlocked) - .AddToUnregisterList(_unregisterList); - - // 监听玩家死亡事件 - this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied) - .AddToUnregisterList(_unregisterList); - } - - private void OnAchievementUnlocked(AchievementUnlockedEvent e) - { - ShowAchievementNotification(e.Title, e.Description); - } - - private void OnPlayerDied(PlayerDiedEvent e) - { - ShowGameOverScreen(e.FinalScore); - } - - public void Cleanup() - { - _unregisterList.UnRegisterAll(); - } -} -``` - -### 事件组合 - -```csharp -// 使用 OrEvent 组合多个事件 -public class InputController : IController -{ - public void Initialize() - { - var onAnyInput = new OrEvent() - .Or(keyboardEvent) - .Or(mouseEvent) - .Or(gamepadEvent); - - onAnyInput.Register(() => - { - ResetIdleTimer(); - }); - } -} -``` - -### 事件驱动模式优势 - -- ✅ **松耦合**:发送者和接收者互不依赖 -- ✅ **一对多通信**:一个事件可以有多个监听者 -- ✅ **易于扩展**:添加新监听者不影响现有代码 -- ✅ **支持异步**:事件可以异步处理 - -### 最佳实践 - -1. **事件命名使用过去式**:PlayerDiedEvent、LevelCompletedEvent -2. **事件使用结构体**:减少内存分配 -3. **及时注销事件**:使用 IUnRegisterList 管理 -4. **避免事件循环**:事件处理器中谨慎发送新事件 - -## 依赖注入模式 - -### 概念 - -依赖注入(DI)是一种实现控制反转(IoC)的技术,通过外部注入依赖而不是在类内部创建。 - -### 在 GFramework 中的实现 - -```csharp -// 定义接口 -public interface IStorageService -{ - Task SaveAsync<T>(string key, T data); - Task<T> LoadAsync<T>(string key); -} - -public interface IAudioService -{ - void PlaySound(string soundId); - void PlayMusic(string musicId); -} - -// 实现服务 -public class LocalStorageService : IStorageService -{ - public async Task SaveAsync<T>(string key, T data) - { - var json = JsonSerializer.Serialize(data); - await File.WriteAllTextAsync($"saves/{key}.json", json); - } - - public async Task<T> LoadAsync<T>(string key) - { - var json = await File.ReadAllTextAsync($"saves/{key}.json"); - return JsonSerializer.Deserialize<T>(json); - } -} - -public class GodotAudioService : IAudioService -{ - private AudioStreamPlayer _soundPlayer; - private AudioStreamPlayer _musicPlayer; - - public void PlaySound(string soundId) - { - var sound = GD.Load<AudioStream>($"res://sounds/{soundId}.ogg"); - _soundPlayer.Stream = sound; - _soundPlayer.Play(); - } - - public void PlayMusic(string musicId) - { - var music = GD.Load<AudioStream>($"res://music/{musicId}.ogg"); - _musicPlayer.Stream = music; - _musicPlayer.Play(); - } -} - -// 在架构中注册服务 -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册服务实现 - RegisterUtility<IStorageService>(new LocalStorageService()); - RegisterUtility<IAudioService>(new GodotAudioService()); - - // 注册 System(System 会自动获取依赖) - RegisterSystem(new SaveSystem()); - RegisterSystem(new AudioSystem()); - } -} - -// System 使用依赖注入 -public class SaveSystem : AbstractSystem -{ - private IStorageService _storageService; - - protected override void OnInit() - { - // 从容器获取依赖 - _storageService = this.GetUtility<IStorageService>(); - - this.RegisterEvent<SaveGameEvent>(OnSaveGame); - } - - private async void OnSaveGame(SaveGameEvent e) - { - var playerModel = this.GetModel<PlayerModel>(); - var saveData = new SaveData - { - PlayerName = playerModel.Name.Value, - Level = playerModel.Level.Value, - Health = playerModel.Health.Value - }; - - await _storageService.SaveAsync("current_save", saveData); - } -} -``` - -### 构造函数注入(推荐) - -```csharp -public class SaveSystem : AbstractSystem -{ - private readonly IStorageService _storageService; - private readonly IAudioService _audioService; - - // 通过构造函数注入依赖 - public SaveSystem(IStorageService storageService, IAudioService audioService) - { - _storageService = storageService; - _audioService = audioService; - } - - protected override void OnInit() - { - this.RegisterEvent<SaveGameEvent>(OnSaveGame); - } - - private async void OnSaveGame(SaveGameEvent e) - { - await _storageService.SaveAsync("save", e.Data); - _audioService.PlaySound("save_success"); - } -} - -// 注册时传入依赖 -public class GameArchitecture : Architecture -{ - protected override void Init() - { - var storageService = new LocalStorageService(); - var audioService = new GodotAudioService(); - - RegisterUtility<IStorageService>(storageService); - RegisterUtility<IAudioService>(audioService); - - // 构造函数注入 - RegisterSystem(new SaveSystem(storageService, audioService)); - } -} -``` - -### 依赖注入优势 - -- ✅ **易于测试**:可以注入模拟对象 -- ✅ **松耦合**:依赖接口而非实现 -- ✅ **灵活配置**:运行时选择实现 -- ✅ **提高可维护性**:依赖关系清晰 - -### 最佳实践 - -1. **依赖接口而非实现**:使用 IStorageService 而非 LocalStorageService -2. **优先使用构造函数注入**:依赖关系更明确 -3. **避免循环依赖**:System 不应相互依赖 -4. **使用 IoC 容器管理生命周期**:让框架管理对象创建 - -## 服务定位器模式 - -### 概念 - -服务定位器模式提供一个全局访问点来获取服务,是依赖注入的替代方案。 - -### 在 GFramework 中的实现 - -```csharp -// GFramework 的 Architecture 本身就是服务定位器 -public class GameplaySystem : AbstractSystem -{ - protected override void OnInit() - { - // 通过服务定位器获取服务 - var playerModel = this.GetModel<PlayerModel>(); - var audioSystem = this.GetSystem<AudioSystem>(); - var storageUtility = this.GetUtility<IStorageUtility>(); - - // 使用服务 - playerModel.Health.Value = 100; - audioSystem.PlayBGM("gameplay"); - } -} - -// 在 Controller 中使用 -public class MenuController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void OnStartButtonClicked() - { - // 通过架构获取服务 - var gameModel = this.GetModel<GameModel>(); - gameModel.GameState.Value = GameState.Playing; - - // 发送命令 - this.SendCommand(new StartGameCommand()); - } -} -``` - -### 自定义服务定位器 - -```csharp -// 创建专门的服务定位器 -public static class ServiceLocator -{ - private static readonly Dictionary<Type, object> _services = new(); - - public static void Register<T>(T service) - { - _services[typeof(T)] = service; - } - - public static T Get<T>() - { - if (_services.TryGetValue(typeof(T), out var service)) - { - return (T)service; - } - throw new InvalidOperationException($"Service {typeof(T)} not registered"); - } - - public static void Clear() - { - _services.Clear(); - } -} - -// 使用自定义服务定位器 -public class GameInitializer -{ - public void Initialize() - { - // 注册服务 - ServiceLocator.Register<IAnalyticsService>(new AnalyticsService()); - ServiceLocator.Register<ILeaderboardService>(new LeaderboardService()); - } -} - -public class GameOverScreen -{ - public void SubmitScore(int score) - { - // 获取服务 - var leaderboard = ServiceLocator.Get<ILeaderboardService>(); - leaderboard.SubmitScore(score); - - var analytics = ServiceLocator.Get<IAnalyticsService>(); - analytics.TrackEvent("game_over", new { score }); - } -} -``` - -### 服务定位器 vs 依赖注入 - -| 特性 | 服务定位器 | 依赖注入 | -|-----------|--------------|------------| -| **依赖可见性** | 隐式(运行时获取) | 显式(构造函数参数) | -| **易用性** | 简单直接 | 需要配置 | -| **测试性** | 较难(需要模拟全局状态) | 容易(注入模拟对象) | -| **编译时检查** | 无 | 有 | -| **适用场景** | 快速原型、小项目 | 大型项目、团队协作 | - -### 最佳实践 - -1. **小项目使用服务定位器**:简单直接 -2. **大项目使用依赖注入**:更易维护 -3. **避免过度使用**:不要把所有东西都放入定位器 -4. **提供清晰的 API**:GetModel、GetSystem、GetUtility - -## 对象池模式 - -### 概念 - -对象池模式通过复用对象来减少内存分配和垃圾回收,提高性能。 - -### 在 GFramework 中的实现 - -```csharp -// 定义可池化对象 -public class Bullet : Node2D, IPoolableNode -{ - public bool IsInPool { get; set; } - - public void OnSpawn() - { - // 从池中取出时调用 - Visible = true; - IsInPool = false; - } - - public void OnRecycle() - { - // 回收到池中时调用 - Visible = false; - IsInPool = true; - Position = Vector2.Zero; - Rotation = 0; - } -} - -// 创建对象池系统 -public class BulletPoolSystem : AbstractNodePoolSystem<Bullet> -{ - protected override Bullet CreateInstance() - { - var bullet = new Bullet(); - // 初始化子弹 - return bullet; - } - - protected override void OnInit() - { - // 预创建对象 - PrewarmPool(50); - } -} - -// 使用对象池 -public class WeaponSystem : AbstractSystem -{ - private BulletPoolSystem _bulletPool; - - protected override void OnInit() - { - _bulletPool = this.GetSystem<BulletPoolSystem>(); - this.RegisterEvent<FireWeaponEvent>(OnFireWeapon); - } - - private void OnFireWeapon(FireWeaponEvent e) - { - // 从池中获取子弹 - var bullet = _bulletPool.Spawn(); - bullet.Position = e.Position; - bullet.Rotation = e.Direction; - - // 3秒后回收 - ScheduleRecycle(bullet, 3.0f); - } - - private async void ScheduleRecycle(Bullet bullet, float delay) - { - await Task.Delay((int)(delay * 1000)); - _bulletPool.Recycle(bullet); - } -} -``` - -### 通用对象池 - -```csharp -// 通用对象池实现 -public class ObjectPool<T> where T : class, new() -{ - private readonly Stack<T> _pool = new(); - private readonly Action<T> _onSpawn; - private readonly Action<T> _onRecycle; - private readonly int _maxSize; - - public ObjectPool(int initialSize = 10, int maxSize = 100, - Action<T> onSpawn = null, Action<T> onRecycle = null) - { - _maxSize = maxSize; - _onSpawn = onSpawn; - _onRecycle = onRecycle; - - // 预创建对象 - for (int i = 0; i < initialSize; i++) - { - _pool.Push(new T()); - } - } - - public T Spawn() - { - T obj = _pool.Count > 0 ? _pool.Pop() : new T(); - _onSpawn?.Invoke(obj); - return obj; - } - - public void Recycle(T obj) - { - if (_pool.Count < _maxSize) - { - _onRecycle?.Invoke(obj); - _pool.Push(obj); - } - } - - public void Clear() - { - _pool.Clear(); - } -} - -// 使用通用对象池 -public class ParticleSystem : AbstractSystem -{ - private ObjectPool<Particle> _particlePool; - - protected override void OnInit() - { - _particlePool = new ObjectPool<Particle>( - initialSize: 100, - maxSize: 500, - onSpawn: p => p.Reset(), - onRecycle: p => p.Clear() - ); - } - - public void EmitParticles(Vector3 position, int count) - { - for (int i = 0; i < count; i++) - { - var particle = _particlePool.Spawn(); - particle.Position = position; - particle.Velocity = GetRandomVelocity(); - } - } -} -``` - -### 对象池优势 - -- ✅ **减少 GC 压力**:复用对象,减少内存分配 -- ✅ **提高性能**:避免频繁创建销毁对象 -- ✅ **稳定帧率**:减少 GC 导致的卡顿 -- ✅ **适合高频对象**:子弹、粒子、特效等 - -### 最佳实践 - -1. **预热池**:提前创建对象避免运行时分配 -2. **设置最大容量**:防止池无限增长 -3. **重置对象状态**:OnRecycle 中清理状态 -4. **监控池使用情况**:记录 Spawn/Recycle 次数 - -## 状态模式 - -### 概念 - -状态模式允许对象在内部状态改变时改变其行为,将状态相关的行为封装到独立的状态类中。 - -### 在 GFramework 中的实现 - -```csharp -// 定义游戏状态 -public class MenuState : ContextAwareStateBase -{ - public override void OnEnter(IState from) - { - Console.WriteLine("进入菜单状态"); - - // 显示菜单 UI - var uiSystem = this.GetSystem<UISystem>(); - uiSystem.ShowMenu(); - - // 播放菜单音乐 - var audioSystem = this.GetSystem<AudioSystem>(); - audioSystem.PlayBGM("menu_theme"); - } - - public override void OnExit(IState to) - { - Console.WriteLine("退出菜单状态"); - - // 隐藏菜单 UI - var uiSystem = this.GetSystem<UISystem>(); - uiSystem.HideMenu(); - } - - public override bool CanTransitionTo(IState target) - { - // 菜单可以转换到游戏或设置状态 - return target is GameplayState or SettingsState; - } -} - -public class GameplayState : ContextAwareStateBase -{ - public override void OnEnter(IState from) - { - Console.WriteLine("进入游戏状态"); - - // 初始化游戏 - var gameModel = this.GetModel<GameModel>(); - gameModel.Reset(); - - // 加载关卡 - this.SendCommand(new LoadLevelCommand { LevelId = 1 }); - - // 播放游戏音乐 - var audioSystem = this.GetSystem<AudioSystem>(); - audioSystem.PlayBGM("gameplay_theme"); - } - - public override void OnExit(IState to) - { - Console.WriteLine("退出游戏状态"); - - // 保存游戏进度 - this.SendCommand(new SaveGameCommand()); - } - - public override bool CanTransitionTo(IState target) - { - // 游戏中可以暂停或结束 - return target is PauseState or GameOverState; - } -} - -public class PauseState : ContextAwareStateBase -{ - public override void OnEnter(IState from) - { - Console.WriteLine("进入暂停状态"); - - // 暂停游戏 - var timeSystem = this.GetSystem<TimeSystem>(); - timeSystem.Pause(); - - // 显示暂停菜单 - var uiSystem = this.GetSystem<UISystem>(); - uiSystem.ShowPauseMenu(); - } - - public override void OnExit(IState to) - { - Console.WriteLine("退出暂停状态"); - - // 恢复游戏 - var timeSystem = this.GetSystem<TimeSystem>(); - timeSystem.Resume(); - - // 隐藏暂停菜单 - var uiSystem = this.GetSystem<UISystem>(); - uiSystem.HidePauseMenu(); - } - - public override bool CanTransitionTo(IState target) - { - // 暂停只能返回游戏或退出到菜单 - return target is GameplayState or MenuState; - } -} - -// 注册状态机 -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 创建状态机系统 - var stateMachine = new StateMachineSystem(); - - // 注册所有状态 - stateMachine - .Register(new MenuState()) - .Register(new GameplayState()) - .Register(new PauseState()) - .Register(new GameOverState()) - .Register(new SettingsState()); - - RegisterSystem<IStateMachineSystem>(stateMachine); - } -} - -// 使用状态机 -public class GameController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public async Task StartGame() - { - var stateMachine = this.GetSystem<IStateMachineSystem>(); - await stateMachine.ChangeToAsync<GameplayState>(); - } - - public async Task PauseGame() - { - var stateMachine = this.GetSystem<IStateMachineSystem>(); - await stateMachine.ChangeToAsync<PauseState>(); - } - - public async Task ResumeGame() - { - var stateMachine = this.GetSystem<IStateMachineSystem>(); - await stateMachine.ChangeToAsync<GameplayState>(); - } -} -``` - -### 异步状态 - -```csharp -public class LoadingState : AsyncContextAwareStateBase -{ - public override async Task OnEnterAsync(IState from) - { - Console.WriteLine("开始加载..."); - - // 显示加载界面 - var uiSystem = this.GetSystem<UISystem>(); - uiSystem.ShowLoadingScreen(); - - // 异步加载资源 - await LoadResourcesAsync(); - - Console.WriteLine("加载完成"); - - // 自动切换到游戏状态 - var stateMachine = this.GetSystem<IStateMachineSystem>(); - await stateMachine.ChangeToAsync<GameplayState>(); - } - - private async Task LoadResourcesAsync() - { - // 模拟异步加载 - await Task.Delay(2000); - } - - public override async Task OnExitAsync(IState to) - { - // 隐藏加载界面 - var uiSystem = this.GetSystem<UISystem>(); - uiSystem.HideLoadingScreen(); - - await Task.CompletedTask; - } -} -``` - -### 状态模式优势 - -- ✅ **清晰的状态管理**:每个状态独立封装 -- ✅ **易于扩展**:添加新状态不影响现有代码 -- ✅ **状态转换验证**:CanTransitionTo 控制合法转换 -- ✅ **支持异步操作**:异步状态处理加载等操作 - -### 最佳实践 - -1. **状态保持单一职责**:每个状态只负责一个场景 -2. **使用转换验证**:防止非法状态转换 -3. **在 OnEnter 初始化,OnExit 清理**:保持状态独立 -4. **异步操作使用异步状态**:避免阻塞主线程 - -## 设计原则 - -### 1. 单一职责原则 (SRP) - -确保每个类只负责一个功能领域: - -```csharp -// ✅ 好的做法:职责单一 -public class PlayerMovementController : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnPlayerInput); - } - - private void OnPlayerInput(PlayerInputEvent e) - { - // 只负责移动逻辑 - ProcessMovement(e.Direction); - } - - private void ProcessMovement(Vector2 direction) - { - // 移动相关的业务逻辑 - } -} - -public class PlayerCombatController : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnAttackInput); - } - - private void OnAttackInput(AttackInputEvent e) - { - // 只负责战斗逻辑 - ProcessAttack(e.Target); - } - - private void ProcessAttack(Entity target) - { - // 战斗相关的业务逻辑 - } -} - -// ❌ 避免:职责混乱 -public class PlayerController : AbstractSystem -{ - private void OnPlayerInput(PlayerInputEvent e) - { - // 移动逻辑 - ProcessMovement(e.Direction); - - // 战斗逻辑 - if (e.IsAttacking) - { - ProcessAttack(e.Target); - } - - // UI逻辑 - UpdateHealthBar(); - - // 音效逻辑 - PlaySoundEffect(); - - // 存档逻辑 - SaveGame(); - - // 职责太多,难以维护 - } -} -``` - -### 2. 开闭原则 (OCP) - -设计应该对扩展开放,对修改封闭: - -```csharp -// ✅ 好的做法:使用接口和策略模式 -public interface IWeaponStrategy -{ - void Attack(Entity attacker, Entity target); - int CalculateDamage(Entity attacker, Entity target); -} - -public class SwordWeaponStrategy : IWeaponStrategy -{ - public void Attack(Entity attacker, Entity target) - { - var damage = CalculateDamage(attacker, target); - target.TakeDamage(damage); - PlaySwingAnimation(); - } - - public int CalculateDamage(Entity attacker, Entity target) - { - return attacker.Strength + GetSwordBonus() - target.Armor; - } -} - -public class MagicWeaponStrategy : IWeaponStrategy -{ - public void Attack(Entity attacker, Entity target) - { - var damage = CalculateDamage(attacker, target); - target.TakeDamage(damage); - CastMagicEffect(); - } - - public int CalculateDamage(Entity attacker, Entity target) - { - return attacker.Intelligence * 2 + GetMagicBonus() - target.MagicResistance; - } -} - -public class CombatSystem : AbstractSystem -{ - private readonly Dictionary _weaponStrategies; - - public CombatSystem() - { - _weaponStrategies = new() - { - { WeaponType.Sword, new SwordWeaponStrategy() }, - { WeaponType.Magic, new MagicWeaponStrategy() } - }; - } - - public void Attack(Entity attacker, Entity target) - { - var weaponType = attacker.EquippedWeapon.Type; - - if (_weaponStrategies.TryGetValue(weaponType, out var strategy)) - { - strategy.Attack(attacker, target); - } - } - - // 添加新武器类型时,只需要添加新的策略,不需要修改现有代码 - public void RegisterWeaponStrategy(WeaponType type, IWeaponStrategy strategy) - { - _weaponStrategies[type] = strategy; - } -} - -// ❌ 避免:需要修改现有代码来扩展 -public class CombatSystem : AbstractSystem -{ - public void Attack(Entity attacker, Entity target) - { - var weaponType = attacker.EquippedWeapon.Type; - - switch (weaponType) - { - case WeaponType.Sword: - // 剑的攻击逻辑 - break; - case WeaponType.Bow: - // 弓的攻击逻辑 - break; - default: - throw new NotSupportedException($"Weapon type {weaponType} not supported"); - } - - // 添加新武器类型时需要修改这里的 switch 语句 - } -} -``` - -### 3. 依赖倒置原则 (DIP) - -高层模块不应该依赖低层模块,两者都应该依赖抽象: - -```csharp -// ✅ 好的做法:依赖抽象 -public interface IDataStorage -{ - Task SaveAsync<T>(string key, T data); - Task<T> LoadAsync<T>(string key, T defaultValue = default); - Task ExistsAsync(string key); -} - -public class FileStorage : IDataStorage -{ - public async Task SaveAsync<T>(string key, T data) - { - var json = JsonConvert.SerializeObject(data); - await File.WriteAllTextAsync(GetFilePath(key), json); - } - - public async Task<T> LoadAsync<T>(string key, T defaultValue = default) - { - var filePath = GetFilePath(key); - if (!File.Exists(filePath)) - return defaultValue; - - var json = await File.ReadAllTextAsync(filePath); - return JsonConvert.DeserializeObject<T>(json); - } - - public async Task ExistsAsync(string key) - { - return File.Exists(GetFilePath(key)); - } - - private string GetFilePath(string key) - { - return $"saves/{key}.json"; - } -} - -public class CloudStorage : IDataStorage -{ - public async Task SaveAsync<T>(string key, T data) - { - // 云存储实现 - await UploadToCloud(key, data); - } - - public async Task<T> LoadAsync<T>(string key, T defaultValue = default) - { - // 云存储实现 - return await DownloadFromCloud<T>(key, defaultValue); - } - - public async Task ExistsAsync(string key) - { - // 云存储实现 - return await CheckCloudExists(key); - } -} - -// 高层模块依赖抽象 -public class SaveSystem : AbstractSystem -{ - private readonly IDataStorage _storage; - - public SaveSystem(IDataStorage storage) - { - _storage = storage; - } - - public async Task SaveGameAsync(SaveData data) - { - await _storage.SaveAsync("current_save", data); - } - - public async Task LoadGameAsync() - { - return await _storage.LoadAsync("current_save"); - } -} - -// ❌ 避免:依赖具体实现 -public class SaveSystem : AbstractSystem -{ - private readonly FileStorage _storage; // 直接依赖具体实现 - - public SaveSystem() - { - _storage = new FileStorage(); // 硬编码依赖 - } - - // 无法轻松切换到其他存储方式 -} -``` - -## 架构分层 - -### 1. 清晰的层次结构 - -```csharp -// ✅ 好的做法:清晰的分层架构 -namespace Game.Models -{ - // 数据层:只负责存储状态 - public class PlayerModel : AbstractModel - { - public BindableProperty Health { get; } = new(100); - public BindableProperty MaxHealth { get; } = new(100); - public BindableProperty Position { get; } = new(Vector2.Zero); - public BindableProperty State { get; } = new(PlayerState.Idle); - - protected override void OnInit() - { - // 只处理数据相关的逻辑 - Health.Register(OnHealthChanged); - } - - private void OnHealthChanged(int newHealth) - { - if (newHealth <= 0) - { - State.Value = PlayerState.Dead; - SendEvent(new PlayerDeathEvent()); - } - } - } - - public enum PlayerState - { - Idle, - Moving, - Attacking, - Dead - } -} - -namespace Game.Systems -{ - // 业务逻辑层:处理游戏逻辑 - public class PlayerMovementSystem : AbstractSystem - { - private PlayerModel _playerModel; - private GameModel _gameModel; - - protected override void OnInit() - { - _playerModel = GetModel(); - _gameModel = GetModel(); - - this.RegisterEvent(OnPlayerInput); - } - - private void OnPlayerInput(PlayerInputEvent e) - { - if (_gameModel.State.Value != GameState.Playing) - return; - - if (_playerModel.State.Value == PlayerState.Dead) - return; - - // 处理移动逻辑 - ProcessMovement(e.Direction); - } - - private void ProcessMovement(Vector2 direction) - { - if (direction != Vector2.Zero) - { - _playerModel.Position.Value += direction.Normalized() * GetMovementSpeed(); - _playerModel.State.Value = PlayerState.Moving; - - SendEvent(new PlayerMovedEvent { - NewPosition = _playerModel.Position.Value, - Direction = direction - }); - } - else - { - _playerModel.State.Value = PlayerState.Idle; - } - } - - private float GetMovementSpeed() - { - // 从玩家属性或其他地方获取速度 - return 5.0f; - } - } -} - -namespace Game.Controllers -{ - // 控制层:连接用户输入和业务逻辑 - [ContextAware] - public partial class PlayerController : Node, IController - { - private PlayerModel _playerModel; - - public override void _Ready() - { - _playerModel = Context.GetModel(); - - // 监听用户输入 - SetProcessInput(true); - - // 监听数据变化,更新UI - _playerModel.Health.Register(UpdateHealthUI); - _playerModel.Position.Register(UpdatePosition); - } - - public override void _Input(InputEvent @event) - { - if (@event is InputEventKey keyEvent && keyEvent.Pressed) - { - var direction = GetInputDirection(keyEvent); - Context.SendEvent(new PlayerInputEvent { Direction = direction }); - } - } - - private void UpdateHealthUI(int health) - { - // 更新UI显示 - var healthBar = GetNode("UI/HealthBar"); - healthBar.Value = (float)health / _playerModel.MaxHealth.Value * 100; - } - - private void UpdatePosition(Vector2 position) - { - // 更新玩家位置 - Position = position; - } - - private Vector2 GetInputDirection(InputEventKey keyEvent) - { - return keyEvent.Keycode switch - { - Key.W => Vector2.Up, - Key.S => Vector2.Down, - Key.A => Vector2.Left, - Key.D => Vector2.Right, - _ => Vector2.Zero - }; - } - } -} -``` - -### 2. 避免层次混乱 - -```csharp -// ❌ 避免:层次混乱 -public class PlayerController : Node, IController -{ - // 混合了数据层、业务逻辑层和控制层的职责 - public BindableProperty Health { get; } = new(100); // 数据层职责 - - public override void _Input(InputEvent @event) // 控制层职责 - { - if (@event is InputEventKey keyEvent && keyEvent.Pressed) - { - if (keyEvent.Keycode == Key.W) - { - Position += Vector2.Up * MovementSpeed; // 业务逻辑层职责 - } - - if (keyEvent.Keycode == Key.Space) - { - Health -= 10; // 业务逻辑层职责 - PlaySoundEffect(); // 业务逻辑层职责 - } - } - } - - // 这样会导致代码难以测试和维护 -} -``` - -## 依赖管理 - -### 1. 构造函数注入 - -```csharp -// ✅ 好的做法:构造函数注入 -public class PlayerCombatSystem : AbstractSystem -{ - private readonly PlayerModel _playerModel; - private readonly IWeaponService _weaponService; - private readonly ISoundService _soundService; - private readonly IEffectService _effectService; - - // 通过构造函数注入依赖 - public PlayerCombatSystem( - PlayerModel playerModel, - IWeaponService weaponService, - ISoundService soundService, - IEffectService effectService) - { - _playerModel = playerModel; - _weaponService = weaponService; - _soundService = soundService; - _effectService = effectService; - } - - protected override void OnInit() - { - this.RegisterEvent(OnAttack); - } - - private void OnAttack(AttackEvent e) - { - var weapon = _weaponService.GetEquippedWeapon(_playerModel); - var damage = _weaponService.CalculateDamage(weapon, e.Target); - - e.Target.TakeDamage(damage); - _soundService.PlayAttackSound(weapon.Type); - _effectService.PlayAttackEffect(_playerModel.Position, weapon.Type); - } -} - -// ❌ 避免:依赖注入容器 -public class PlayerCombatSystem : AbstractSystem -{ - private PlayerModel _playerModel; - private IWeaponService _weaponService; - private ISoundService _soundService; - private IEffectService _effectService; - - protected override void OnInit() - { - // 在运行时获取依赖,难以测试 - _playerModel = GetModel(); - _weaponService = GetService(); - _soundService = GetService(); - _effectService = GetService(); - } - - // 测试时难以模拟依赖 -} -``` - -### 2. 接口隔离 - -```csharp -// ✅ 好的做法:小而专注的接口 -public interface IMovementController -{ - void Move(Vector2 direction); - void Stop(); - bool CanMove(); -} - -public interface ICombatController -{ - void Attack(Entity target); - void Defend(); - bool CanAttack(); -} - -public interface IUIController -{ - void ShowHealthBar(); - void HideHealthBar(); - void UpdateHealthDisplay(int currentHealth, int maxHealth); -} - -public class PlayerController : Node, IMovementController, ICombatController, IUIController -{ - // 实现各个接口,职责清晰 -} - -// ❌ 避免:大而全的接口 -public interface IPlayerController -{ - void Move(Vector2 direction); - void Stop(); - void Attack(Entity target); - void Defend(); - void ShowHealthBar(); - void HideHealthBar(); - void UpdateHealthDisplay(int currentHealth, int maxHealth); - void SaveGame(); - void LoadGame(); - void Respawn(); - void PlayAnimation(string animationName); - void StopAnimation(); - // ... 更多方法,接口过于庞大 -} -``` - -## 事件系统设计 - -### 1. 事件命名和结构 - -```csharp -// ✅ 好的做法:清晰的事件命名和结构 -public struct PlayerHealthChangedEvent -{ - public int PreviousHealth { get; } - public int NewHealth { get; } - public int MaxHealth { get; } - public Vector3 DamagePosition { get; } - public DamageType DamageType { get; } -} - -public struct PlayerDiedEvent -{ - public Vector3 DeathPosition { get; } - public string CauseOfDeath { get; } - public TimeSpan SurvivalTime { get; } -} - -public struct WeaponEquippedEvent -{ - public string PlayerId { get; } - public WeaponType WeaponType { get; } - public string WeaponId { get; } -} - -// ❌ 避免:模糊的事件命名和结构 -public struct PlayerEvent -{ - public EventType Type { get; } - public object Data { get; } // 类型不安全 - public Dictionary Properties { get; } // 难以理解 -} -``` - -### 2. 事件处理职责 - -```csharp -// ✅ 好的做法:单一职责的事件处理 -public class UIHealthBarController : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnPlayerHealthChanged); - this.RegisterEvent(OnPlayerDied); - } - - private void OnPlayerHealthChanged(PlayerHealthChangedEvent e) - { - UpdateHealthBar(e.NewHealth, e.MaxHealth); - - if (e.NewHealth < e.PreviousHealth) - { - ShowDamageEffect(e.DamagePosition, e.PreviousHealth - e.NewHealth); - } - } - - private void OnPlayerDied(PlayerDiedEvent e) - { - HideHealthBar(); - ShowDeathScreen(e.CauseOfDeath); - } -} - -public class AudioController : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnPlayerHealthChanged); - this.RegisterEvent(OnPlayerDied); - } - - private void OnPlayerHealthChanged(PlayerHealthChangedEvent e) - { - if (e.NewHealth < e.PreviousHealth) - { - PlayHurtSound(e.DamageType); - } - } - - private void OnPlayerDied(PlayerDiedEvent e) - { - PlayDeathSound(); - } -} - -// ❌ 避免:一个处理器处理多种不相关的事件 -public class PlayerEventHandler : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnPlayerHealthChanged); - this.RegisterEvent(OnPlayerDied); - this.RegisterEvent(OnWeaponEquipped); - this.RegisterEvent(OnLevelUp); - // 注册太多事件,职责混乱 - } - - private void OnPlayerHealthChanged(PlayerHealthChangedEvent e) - { - UpdateUI(); // UI职责 - PlayAudio(); // 音频职责 - SaveStatistics(); // 存档职责 - UpdateAchievements(); // 成就系统职责 - // 一个事件处理器承担太多职责 - } -} -``` - -## 模块化架构 - -### 1. 模块边界清晰 - -```csharp -// ✅ 好的做法:清晰的模块边界 -public class AudioModule : AbstractModule -{ - // 模块只负责音频相关的功能 - public override void Install(IArchitecture architecture) - { - architecture.RegisterSystem(new AudioSystem()); - architecture.RegisterSystem(new MusicSystem()); - architecture.RegisterUtility(new AudioUtility()); - } -} - -public class InputModule : AbstractModule -{ - // 模块只负责输入相关的功能 - public override void Install(IArchitecture architecture) - { - architecture.RegisterSystem(new InputSystem()); - architecture.RegisterSystem(new InputMappingSystem()); - architecture.RegisterUtility(new InputUtility()); - } -} - -public class UIModule : AbstractModule -{ - // 模块只负责UI相关的功能 - public override void Install(IArchitecture architecture) - { - architecture.RegisterSystem(new UISystem()); - architecture.RegisterSystem(new HUDSystem()); - architecture.RegisterSystem(new MenuSystem()); - architecture.RegisterUtility(new UIUtility()); - } -} - -// ❌ 避免:模块职责混乱 -public class GameModule : AbstractModule -{ - public override void Install(IArchitecture architecture) - { - // 一个模块包含所有功能 - architecture.RegisterSystem(new AudioSystem()); // 音频 - architecture.RegisterSystem(new InputSystem()); // 输入 - architecture.RegisterSystem(new UISystem()); // UI - architecture.RegisterSystem(new CombatSystem()); // 战斗 - architecture.RegisterSystem(new InventorySystem()); // 背包 - architecture.RegisterSystem(new QuestSystem()); // 任务 - // 模块过于庞大,难以维护 - } -} -``` - -### 2. 模块间通信 - -```csharp -// ✅ 好的做法:通过事件进行模块间通信 -public class AudioModule : AbstractModule -{ - public override void Install(IArchitecture architecture) - { - architecture.RegisterSystem(new AudioSystem()); - } -} - -public class AudioSystem : AbstractSystem -{ - protected override void OnInit() - { - // 监听其他模块发送的事件 - this.RegisterEvent(OnPlayerAttack); - this.RegisterEvent(OnPlayerDied); - this.RegisterEvent(OnWeaponEquipped); - } - - private void OnPlayerAttack(PlayerAttackEvent e) - { - PlayAttackSound(e.WeaponType); - } - - private void OnPlayerDied(PlayerDiedEvent e) - { - PlayDeathSound(); - } - - private void OnWeaponEquipped(WeaponEquippedEvent e) - { - PlayEquipSound(e.WeaponType); - } -} - -public class CombatModule : AbstractModule -{ - public override void Install(IArchitecture architecture) - { - architecture.RegisterSystem(new CombatSystem()); - } -} - -public class CombatSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnAttackInput); - } - - private void OnAttackInput(AttackInputEvent e) - { - ProcessAttack(e); - - // 发送事件通知其他模块 - SendEvent(new PlayerAttackEvent { - PlayerId = e.PlayerId, - WeaponType = GetPlayerWeaponType(e.PlayerId) - }); - } -} - -// ❌ 避免:模块间直接依赖 -public class CombatSystem : AbstractSystem -{ - private AudioSystem _audioSystem; // 直接依赖其他模块 - - protected override void OnInit() - { - // 直接获取其他模块的系统 - _audioSystem = GetSystem(); - } - - private void OnAttackInput(AttackInputEvent e) - { - ProcessAttack(e); - - // 直接调用其他模块的方法 - _audioSystem.PlayAttackSound(weaponType); - } -} -``` - -## 错误处理策略 - -### 1. 异常处理层次 - -```csharp -// ✅ 好的做法:分层异常处理 -public class GameApplicationException : Exception -{ - public string ErrorCode { get; } - public Dictionary Context { get; } - - public GameApplicationException(string message, string errorCode, - Dictionary context = null, Exception innerException = null) - : base(message, innerException) - { - ErrorCode = errorCode; - Context = context ?? new Dictionary(); - } -} - -public class PlayerException : GameApplicationException -{ - public PlayerException(string message, string errorCode, - Dictionary context = null, Exception innerException = null) - : base(message, errorCode, context, innerException) - { - } -} - -public class InventoryException : GameApplicationException -{ - public InventoryException(string message, string errorCode, - Dictionary context = null, Exception innerException = null) - : base(message, errorCode, context, innerException) - { - } -} - -// 在系统中的使用 -public class PlayerInventorySystem : AbstractSystem -{ - public void AddItem(string playerId, Item item) - { - try - { - ValidateItem(item); - CheckInventorySpace(playerId, item); - - AddItemToInventory(playerId, item); - - SendEvent(new ItemAddedEvent { PlayerId = playerId, Item = item }); - } - catch (ItemValidationException ex) - { - throw new InventoryException( - $"Failed to add item {item.Id} to player {playerId}", - "ITEM_VALIDATION_FAILED", - new Dictionary - { - ["playerId"] = playerId, - ["itemId"] = item.Id, - ["validationError"] = ex.Message - }, - ex - ); - } - catch (InventoryFullException ex) - { - throw new InventoryException( - $"Player {playerId} inventory is full", - "INVENTORY_FULL", - new Dictionary - { - ["playerId"] = playerId, - ["itemId"] = item.Id, - ["maxSlots"] = ex.MaxSlots, - ["currentSlots"] = ex.CurrentSlots - }, - ex - ); - } - catch (Exception ex) - { - // 捕获未知异常并包装 - throw new InventoryException( - $"Unexpected error adding item {item.Id} to player {playerId}", - "UNKNOWN_ERROR", - new Dictionary - { - ["playerId"] = playerId, - ["itemId"] = item.Id, - ["originalError"] = ex.Message - }, - ex - ); - } - } - - private void ValidateItem(Item item) - { - if (item == null) - throw new ItemValidationException("Item cannot be null"); - - if (string.IsNullOrEmpty(item.Id)) - throw new ItemValidationException("Item ID cannot be empty"); - - if (item.StackSize <= 0) - throw new ItemValidationException("Item stack size must be positive"); - } - - private void CheckInventorySpace(string playerId, Item item) - { - var inventory = GetPlayerInventory(playerId); - var requiredSpace = CalculateRequiredSpace(item); - - if (inventory.FreeSpace < requiredSpace) - { - throw new InventoryFullException( - inventory.FreeSpace, - inventory.MaxSlots - ); - } - } -} -``` - -### 2. 错误恢复策略 - -```csharp -// ✅ 好的做法:优雅的错误恢复 -public class SaveSystem : AbstractSystem -{ - private readonly IStorage _primaryStorage; - private readonly IStorage _backupStorage; - - public SaveSystem(IStorage primaryStorage, IStorage backupStorage = null) - { - _primaryStorage = primaryStorage; - _backupStorage = backupStorage ?? new LocalStorage("backup"); - } - - public async Task LoadSaveDataAsync(string saveId) - { - try - { - // 尝试从主存储加载 - return await _primaryStorage.ReadAsync(saveId); - } - catch (StorageException ex) - { - Logger.Warning($"Failed to load from primary storage: {ex.Message}"); - - try - { - // 尝试从备份存储加载 - var backupData = await _backupStorage.ReadAsync(saveId); - Logger.Info($"Successfully loaded from backup storage: {saveId}"); - - // 恢复到主存储 - await _primaryStorage.WriteAsync(saveId, backupData); - - return backupData; - } - catch (Exception backupEx) - { - Logger.Error($"Failed to load from backup storage: {backupEx.Message}"); - - // 返回默认存档数据 - return GetDefaultSaveData(); - } - } - } - - private SaveData GetDefaultSaveData() - { - Logger.Warning("Returning default save data due to loading failures"); - return new SaveData - { - PlayerId = "default", - Level = 1, - Health = 100, - Position = Vector3.Zero, - CreatedAt = DateTime.UtcNow - }; - } -} - -// ❌ 避免:粗暴的错误处理 -public class SaveSystem : AbstractSystem -{ - public async Task LoadSaveDataAsync(string saveId) - { - try - { - return await _storage.ReadAsync(saveId); - } - catch (Exception ex) - { - // 直接抛出异常,不提供恢复机制 - throw new Exception($"Failed to load save: {ex.Message}", ex); - } - } -} -``` - -## 测试策略 - -### 1. 可测试的架构设计 - -```csharp -// ✅ 好的做法:可测试的架构 -public interface IPlayerMovementService -{ - void MovePlayer(string playerId, Vector2 direction); - bool CanPlayerMove(string playerId); -} - -public class PlayerMovementService : IPlayerMovementService -{ - private readonly IPlayerRepository _playerRepository; - private readonly ICollisionService _collisionService; - private readonly IMapService _mapService; - - public PlayerMovementService( - IPlayerRepository playerRepository, - ICollisionService collisionService, - IMapService mapService) - { - _playerRepository = playerRepository; - _collisionService = collisionService; - _mapService = mapService; - } - - public void MovePlayer(string playerId, Vector2 direction) - { - if (!CanPlayerMove(playerId)) - return; - - var player = _playerRepository.GetById(playerId); - var newPosition = player.Position + direction * player.Speed; - - if (_collisionService.CanMoveTo(newPosition)) - { - player.Position = newPosition; - _playerRepository.Update(player); - } - } - - public bool CanPlayerMove(string playerId) - { - var player = _playerRepository.GetById(playerId); - return player != null && player.IsAlive && !player.IsStunned; - } -} - -// 测试代码 -[TestFixture] -public class PlayerMovementServiceTests -{ - private Mock _mockPlayerRepository; - private Mock _mockCollisionService; - private Mock _mockMapService; - private PlayerMovementService _movementService; - - [SetUp] - public void Setup() - { - _mockPlayerRepository = new Mock(); - _mockCollisionService = new Mock(); - _mockMapService = new Mock(); - - _movementService = new PlayerMovementService( - _mockPlayerRepository.Object, - _mockCollisionService.Object, - _mockMapService.Object - ); - } - - [Test] - public void MovePlayer_ValidMovement_ShouldUpdatePlayerPosition() - { - // Arrange - var playerId = "player1"; - var player = new Player { Id = playerId, Position = Vector2.Zero, Speed = 5.0f }; - var direction = Vector2.Right; - - _mockPlayerRepository.Setup(r => r.GetById(playerId)).Returns(player); - _mockCollisionService.Setup(c => c.CanMoveTo(It.IsAny())).Returns(true); - - // Act - _movementService.MovePlayer(playerId, direction); - - // Assert - _mockPlayerRepository.Verify(r => r.Update(It.Is(p => p.Position == Vector2.Right * 5.0f)), Times.Once); - } - - [Test] - public void MovePlayer_CollisionBlocked_ShouldNotUpdatePlayerPosition() - { - // Arrange - var playerId = "player1"; - var player = new Player { Id = playerId, Position = Vector2.Zero, Speed = 5.0f }; - var direction = Vector2.Right; - - _mockPlayerRepository.Setup(r => r.GetById(playerId)).Returns(player); - _mockCollisionService.Setup(c => c.CanMoveTo(It.IsAny())).Returns(false); - - // Act - _movementService.MovePlayer(playerId, direction); - - // Assert - _mockPlayerRepository.Verify(r => r.Update(It.IsAny()), Times.Never); - } -} - -// ❌ 避免:难以测试的设计 -public class PlayerMovementSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnMovementInput); - } - - private void OnMovementInput(MovementInputEvent e) - { - var player = GetModel(); // 依赖架构,难以测试 - var newPosition = player.Position + e.Direction * player.Speed; - - if (CanMoveTo(newPosition)) // 私有方法,难以直接测试 - { - player.Position = newPosition; - } - } - - private bool CanMoveTo(Vector2 position) - { - // 复杂的碰撞检测逻辑,难以测试 - return true; - } -} -``` - -## 重构指南 - -### 1. 识别代码异味 - -```csharp -// ❌ 代码异味:长方法、重复代码、上帝类 -public class GameManager : Node -{ - public void ProcessPlayerInput(InputEvent @event) - { - // 长方法 - 做太多事情 - if (@event is InputEventKey keyEvent && keyEvent.Pressed) - { - switch (keyEvent.Keycode) - { - case Key.W: - MovePlayer(Vector2.Up); - PlayFootstepSound(); - UpdatePlayerAnimation("walk_up"); - CheckPlayerCollisions(); - UpdateCameraPosition(); - SavePlayerPosition(); - break; - case Key.S: - MovePlayer(Vector2.Down); - PlayFootstepSound(); - UpdatePlayerAnimation("walk_down"); - CheckPlayerCollisions(); - UpdateCameraPosition(); - SavePlayerPosition(); - break; - // 重复代码 - } - } - } - - private void MovePlayer(Vector2 direction) - { - Player.Position += direction * Player.Speed; - } - - private void PlayFootstepSound() - { - AudioPlayer.Play("footstep.wav"); - } - - // ... 更多方法,类过于庞大 -} - -// ✅ 重构后:职责分离 -public class PlayerInputController : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnInput); - } - - private void OnInput(InputEvent e) - { - if (e is InputEventKey keyEvent && keyEvent.Pressed) - { - var direction = GetDirectionFromKey(keyEvent.Keycode); - if (direction != Vector2.Zero) - { - SendEvent(new PlayerMoveEvent { Direction = direction }); - } - } - } - - private Vector2 GetDirectionFromKey(Key keycode) - { - return keycode switch - { - Key.W => Vector2.Up, - Key.S => Vector2.Down, - Key.A => Vector2.Left, - Key.D => Vector2.Right, - _ => Vector2.Zero - }; - } -} - -public class PlayerMovementSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnPlayerMove); - } - - private void OnPlayerMove(PlayerMoveEvent e) - { - var playerModel = GetModel(); - var newPosition = playerModel.Position + e.Direction * playerModel.Speed; - - if (CanMoveTo(newPosition)) - { - playerModel.Position = newPosition; - SendEvent(new PlayerMovedEvent { NewPosition = newPosition }); - } - } - - private bool CanMoveTo(Vector2 position) - { - var collisionService = GetUtility(); - return collisionService.CanMoveTo(position); - } -} - -public class AudioSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnPlayerMoved); - } - - private void OnPlayerMoved(PlayerMovedEvent e) - { - PlayFootstepSound(); - } - - private void PlayFootstepSound() - { - var audioUtility = GetUtility(); - audioUtility.PlaySound("footstep.wav"); - } -} - -public class AnimationSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnPlayerMoved); - } - - private void OnPlayerMoved(PlayerMovedEvent e) - { - var animationName = GetAnimationNameFromDirection(e.Direction); - SendEvent(new PlayAnimationEvent { AnimationName = animationName }); - } - - private string GetAnimationNameFromDirection(Vector2 direction) - { - if (direction == Vector2.Up) return "walk_up"; - if (direction == Vector2.Down) return "walk_down"; - if (direction == Vector2.Left) return "walk_left"; - if (direction == Vector2.Right) return "walk_right"; - return "idle"; - } -} -``` - -### 2. 渐进式重构 - -```csharp -// 第一步:提取重复代码 -public class PlayerController : Node -{ - public void ProcessInput(InputEvent @event) - { - if (@event is InputEventKey keyEvent && keyEvent.Pressed) - { - Vector2 direction; - switch (keyEvent.Keycode) - { - case Key.W: - direction = Vector2.Up; - break; - case Key.S: - direction = Vector2.Down; - break; - case Key.A: - direction = Vector2.Left; - break; - case Key.D: - direction = Vector2.Right; - break; - default: - return; - } - - MovePlayer(direction); - } - } -} - -// 第二步:提取方法 -public class PlayerController : Node -{ - public void ProcessInput(InputEvent @event) - { - if (@event is InputEventKey keyEvent && keyEvent.Pressed) - { - var direction = GetDirectionFromKey(keyEvent.Keycode); - if (direction != Vector2.Zero) - { - MovePlayer(direction); - } - } - } - - private Vector2 GetDirectionFromKey(Key keycode) - { - return keycode switch - { - Key.W => Vector2.Up, - Key.S => Vector2.Down, - Key.A => Vector2.Left, - Key.D => Vector2.Right, - _ => Vector2.Zero - }; - } -} - -// 第三步:引入系统和事件 -public class PlayerController : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnInput); - } - - private void OnInput(InputEvent e) - { - if (e is InputEventKey keyEvent && keyEvent.Pressed) - { - var direction = GetDirectionFromKey(keyEvent.Keycode); - if (direction != Vector2.Zero) - { - SendEvent(new PlayerMoveEvent { Direction = direction }); - } - } - } - - private Vector2 GetDirectionFromKey(Key keycode) - { - return keycode switch - { - Key.W => Vector2.Up, - Key.S => Vector2.Down, - Key.A => Vector2.Left, - Key.D => Vector2.Right, - _ => Vector2.Zero - }; - } -} -``` - ---- - -## 模式选择与组合 - -### 何时使用哪种模式? - -#### 小型项目(原型、Demo) - -```csharp -// 推荐组合:MVC + 事件驱动 + 服务定位器 -public class SimpleGameArchitecture : Architecture -{ - protected override void Init() - { - // Model - RegisterModel(new PlayerModel()); - RegisterModel(new GameModel()); - - // System - RegisterSystem(new GameplaySystem()); - RegisterSystem(new AudioSystem()); - - // 使用服务定位器模式 - // Controller 通过 this.GetModel/GetSystem 获取服务 - } -} -``` - -**优势**: - -- 快速开发 -- 代码简洁 -- 易于理解 - -#### 中型项目(独立游戏) - -```csharp -// 推荐组合:MVC + MVVM + 命令/查询 + 事件驱动 + 依赖注入 -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册 Utility(依赖注入) - RegisterUtility<IStorageService>(new LocalStorageService()); - RegisterUtility<IAudioService>(new GodotAudioService()); - - // 注册 Model(MVVM) - RegisterModel(new PlayerModel()); - RegisterModel(new PlayerViewModel()); - - // 注册 System(命令/查询处理) - RegisterSystem(new CombatSystem()); - RegisterSystem(new InventorySystem()); - - // 状态机 - var stateMachine = new StateMachineSystem(); - stateMachine - .Register(new MenuState()) - .Register(new GameplayState()); - RegisterSystem<IStateMachineSystem>(stateMachine); - } -} -``` - -**优势**: - -- 职责清晰 -- 易于测试 -- 支持团队协作 - -#### 大型项目(商业游戏) - -```csharp -// 推荐组合:所有模式 + 模块化架构 -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 模块化安装 - InstallModule(new CoreModule()); - InstallModule(new AudioModule()); - InstallModule(new NetworkModule()); - InstallModule(new UIModule()); - InstallModule(new GameplayModule()); - } -} - -// 核心模块 -public class CoreModule : IArchitectureModule -{ - public void Install(IArchitecture architecture) - { - // 依赖注入 - architecture.RegisterUtility<IStorageService>(new CloudStorageService()); - architecture.RegisterUtility<IAnalyticsService>(new AnalyticsService()); - - // 对象池 - architecture.RegisterSystem(new BulletPoolSystem()); - architecture.RegisterSystem(new ParticlePoolSystem()); - } -} - -// 游戏玩法模块 -public class GameplayModule : IArchitectureModule -{ - public void Install(IArchitecture architecture) - { - // Model - architecture.RegisterModel(new PlayerModel()); - architecture.RegisterModel(new EnemyModel()); - - // System(使用命令/查询模式) - architecture.RegisterSystem(new CombatSystem()); - architecture.RegisterSystem(new MovementSystem()); - - // 状态机 - var stateMachine = new StateMachineSystem(); - stateMachine - .Register(new MenuState()) - .Register(new LoadingState()) - .Register(new GameplayState()) - .Register(new PauseState()) - .Register(new GameOverState()); - architecture.RegisterSystem<IStateMachineSystem>(stateMachine); - } -} -``` - -**优势**: - -- 高度模块化 -- 易于维护和扩展 -- 支持大型团队 -- 完善的测试覆盖 - -### 模式组合示例 - -#### 组合 1:MVVM + 命令模式 - -```csharp -// ViewModel -public class ShopViewModel : AbstractModel -{ - public BindableProperty<int> PlayerGold { get; } = new(1000); - public BindableProperty<bool> CanBuy { get; } = new(true); - public BindableProperty<string> StatusMessage { get; } = new(""); - - protected override void OnInit() - { - // 监听购买事件 - this.RegisterEvent<ItemPurchasedEvent>(OnItemPurchased); - this.RegisterEvent<InsufficientGoldEvent>(OnInsufficientGold); - - // 监听金币变化 - PlayerGold.Register(gold => - { - CanBuy.Value = gold >= 100; - }); - } - - private void OnItemPurchased(ItemPurchasedEvent e) - { - PlayerGold.Value -= e.Cost; - StatusMessage.Value = $"购买成功:{e.ItemName}"; - } - - private void OnInsufficientGold(InsufficientGoldEvent e) - { - StatusMessage.Value = "金币不足!"; - } -} - -// View -public class ShopView : Control -{ - private ShopViewModel _viewModel; - - public override void _Ready() - { - _viewModel = GetModel<ShopViewModel>(); - - // 数据绑定 - _viewModel.PlayerGold.Register(gold => - { - GetNode<Label>("GoldLabel").Text = $"金币:{gold}"; - }); - - _viewModel.CanBuy.Register(canBuy => - { - GetNode<Button>("BuyButton").Disabled = !canBuy; - }); - - _viewModel.StatusMessage.Register(msg => - { - GetNode<Label>("StatusLabel").Text = msg; - }); - } - - private void OnBuyButtonPressed() - { - // 发送命令 - this.SendCommand(new BuyItemCommand - { - Input = new BuyItemInput { ItemId = "sword_01" } - }); - } -} -``` - -#### 组合 2:状态模式 + 对象池 - -```csharp -// 游戏状态使用对象池 -public class GameplayState : ContextAwareStateBase -{ - private BulletPoolSystem _bulletPool; - private ParticlePoolSystem _particlePool; - - public override void OnEnter(IState from) - { - // 获取对象池 - _bulletPool = this.GetSystem<BulletPoolSystem>(); - _particlePool = this.GetSystem<ParticlePoolSystem>(); - - // 预热对象池 - _bulletPool.PrewarmPool(100); - _particlePool.PrewarmPool(200); - - // 注册事件 - this.RegisterEvent<FireWeaponEvent>(OnFireWeapon); - } - - private void OnFireWeapon(FireWeaponEvent e) - { - // 从池中获取子弹 - var bullet = _bulletPool.Spawn(); - bullet.Position = e.Position; - bullet.Direction = e.Direction; - - // 生成粒子效果 - var particle = _particlePool.Spawn(); - particle.Position = e.Position; - } - - public override void OnExit(IState to) - { - // 回收所有对象 - _bulletPool.RecycleAll(); - _particlePool.RecycleAll(); - } -} -``` - -#### 组合 3:事件驱动 + 查询模式 - -```csharp -// 成就系统:监听事件,使用查询验证条件 -public class AchievementSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent<EnemyKilledEvent>(OnEnemyKilled); - this.RegisterEvent<LevelCompletedEvent>(OnLevelCompleted); - } - - private void OnEnemyKilled(EnemyKilledEvent e) - { - // 查询击杀总数 - var query = new GetTotalKillsQuery { Input = new EmptyQueryInput() }; - var totalKills = this.SendQuery(query); - - // 检查成就 - if (totalKills == 100) - { - UnlockAchievement("kill_100_enemies"); - } - } - - private void OnLevelCompleted(LevelCompletedEvent e) - { - // 查询是否满足完美通关条件 - var query = new IsPerfectClearQuery - { - Input = new IsPerfectClearInput { LevelId = e.LevelId } - }; - var isPerfect = this.SendQuery(query); - - if (isPerfect) - { - UnlockAchievement($"perfect_clear_level_{e.LevelId}"); - } - } -} -``` - -### 模式选择决策树 - -``` -需要管理游戏状态? -├─ 是 → 使用状态模式 -└─ 否 → 继续 - -需要频繁创建/销毁对象? -├─ 是 → 使用对象池模式 -└─ 否 → 继续 - -需要解耦组件通信? -├─ 是 → 使用事件驱动模式 -└─ 否 → 继续 - -需要封装操作? -├─ 是 → 使用命令模式 -└─ 否 → 继续 - -需要分离读写操作? -├─ 是 → 使用查询模式(CQRS) -└─ 否 → 继续 - -需要数据绑定和响应式 UI? -├─ 是 → 使用 MVVM 模式 -└─ 否 → 使用 MVC 模式 - -需要管理依赖? -├─ 大型项目 → 使用依赖注入 -└─ 小型项目 → 使用服务定位器 -``` - -## 常见问题 - -### Q1: 应该使用 MVC 还是 MVVM? - -**A**: 取决于项目需求: - -- **使用 MVC**: - - 简单的 UI 更新 - - 不需要复杂的数据绑定 - - 快速原型开发 - -- **使用 MVVM**: - - 复杂的数据驱动 UI - - 需要自动更新界面 - - 大量计算属性 - -**推荐**:可以混合使用,简单界面用 MVC,复杂界面用 MVVM。 - -### Q2: 命令模式和查询模式有什么区别? - -**A**: - -| 特性 | 命令模式 | 查询模式 | -|---------|----------------|---------------------| -| **目的** | 修改状态 | 读取数据 | -| **返回值** | 可选 | 必须有 | -| **副作用** | 有 | 无 | -| **示例** | BuyItemCommand | GetPlayerStatsQuery | - -**原则**:命令改变状态,查询读取状态,两者不混用。 - -### Q3: 何时使用事件,何时使用命令? - -**A**: - -- **使用事件**: - - 通知状态变化 - - 一对多通信 - - 跨模块通信 - - 不关心处理结果 - -- **使用命令**: - - 执行具体操作 - - 需要封装逻辑 - - 需要撤销/重做 - - 需要返回结果 - -**示例**: - -```csharp -// 使用命令执行操作 -this.SendCommand(new BuyItemCommand()); - -// 使用事件通知结果 -this.SendEvent(new ItemPurchasedEvent()); -``` - -### Q4: 依赖注入和服务定位器哪个更好? - -**A**: - -- **依赖注入**: - - ✅ 依赖关系明确 - - ✅ 易于测试 - - ✅ 编译时检查 - - ❌ 配置复杂 - -- **服务定位器**: - - ✅ 简单直接 - - ✅ 易于使用 - - ❌ 依赖隐式 - - ❌ 难以测试 - -**推荐**: - -- 小项目:服务定位器 -- 大项目:依赖注入 -- 混合使用:核心服务用依赖注入,辅助服务用服务定位器 - -### Q5: 对象池适合哪些场景? - -**A**: - -**适合**: - -- 频繁创建/销毁的对象(子弹、粒子) -- 创建成本高的对象(网络连接) -- 需要稳定帧率的场景 - -**不适合**: - -- 创建频率低的对象 -- 对象状态复杂难以重置 -- 内存受限的场景 - -**示例**: - -```csharp -// ✅ 适合使用对象池 -- 子弹、导弹 -- 粒子效果 -- UI 元素(列表项) -- 音效播放器 - -// ❌ 不适合使用对象池 -- 玩家角色 -- 关卡数据 -- 配置对象 -``` - -### Q6: 状态机和简单的 if-else 有什么区别? - -**A**: - -**简单 if-else**: - -```csharp -// ❌ 难以维护 -public void Update() -{ - if (gameState == GameState.Menu) - { - UpdateMenu(); - } - else if (gameState == GameState.Playing) - { - UpdateGameplay(); - } - else if (gameState == GameState.Paused) - { - UpdatePause(); - } - // 状态逻辑分散,难以管理 -} -``` - -**状态机**: - -```csharp -// ✅ 清晰易维护 -public class MenuState : ContextAwareStateBase -{ - public override void OnEnter(IState from) - { - // 进入菜单的所有逻辑集中在这里 - } - - public override void OnExit(IState to) - { - // 退出菜单的所有逻辑集中在这里 - } -} -``` - -**优势**: - -- 状态逻辑封装 -- 易于添加新状态 -- 支持状态转换验证 -- 支持状态历史 - -### Q7: 如何避免过度设计? - -**A**: - -**原则**: - -1. **从简单开始**:先用最简单的方案 -2. **按需重构**:遇到问题再优化 -3. **YAGNI 原则**:You Aren't Gonna Need It - -**示例**: - -```csharp -// 第一版:简单直接 -public class Player -{ - public int Health = 100; -} - -// 第二版:需要通知时添加事件 -public class Player -{ - private int _health = 100; - public int Health - { - get => _health; - set - { - _health = value; - OnHealthChanged?.Invoke(value); - } - } - public event Action<int> OnHealthChanged; -} - -// 第三版:需要更多功能时使用 BindableProperty -public class PlayerModel : AbstractModel -{ - public BindableProperty<int> Health { get; } = new(100); -} -``` - -### Q8: 如何在现有项目中引入这些模式? - -**A**: - -**渐进式重构**: - -1. **第一步:引入事件系统** - ```csharp - // 替换直接调用为事件 - // 之前:uiManager.UpdateHealth(health); - // 之后:SendEvent(new HealthChangedEvent { Health = health }); - ``` - -2. **第二步:提取 Model** - ```csharp - // 将数据从各处集中到 Model - public class PlayerModel : AbstractModel - { - public BindableProperty<int> Health { get; } = new(100); - } - ``` - -3. **第三步:引入命令模式** - ```csharp - // 封装操作为命令 - public class HealPlayerCommand : AbstractCommand - { - protected override void OnExecute() - { - var player = this.GetModel<PlayerModel>(); - player.Health.Value = player.MaxHealth.Value; - } - } - ``` - -4. **第四步:添加查询模式** - ```csharp - // 分离读操作 - public class GetPlayerStatsQuery : AbstractQuery<PlayerStats> - { - protected override PlayerStats OnDo() - { - // 查询逻辑 - } - } - ``` - -### Q9: 性能会受到影响吗? - -**A**: - -**影响很小**: - -- 事件系统:微秒级开销 -- 命令/查询:几乎无开销 -- IoC 容器:字典查找,O(1) - -**优化建议**: - -1. **避免频繁事件**:不要每帧发送事件 -2. **缓存查询结果**:复杂查询结果可以缓存 -3. **使用对象池**:减少 GC 压力 -4. **批量操作**:合并多个小操作 - -**性能对比**: - -```csharp -// 直接调用:~1ns -player.Health = 100; - -// 通过命令:~100ns -SendCommand(new SetHealthCommand { Health = 100 }); - -// 差异可以忽略不计,但带来了更好的架构 -``` - -### Q10: 如何测试使用这些模式的代码? - -**A**: - -**单元测试示例**: - -```csharp -[Test] -public void BuyItemCommand_InsufficientGold_ShouldNotBuyItem() -{ - // Arrange - var architecture = new TestArchitecture(); - var playerModel = new PlayerModel(); - playerModel.Gold.Value = 50; // 金币不足 - architecture.RegisterModel(playerModel); - - var command = new BuyItemCommand - { - Input = new BuyItemInput { ItemId = "sword", Price = 100 } - }; - command.SetArchitecture(architecture); - - // Act - command.Execute(); - - // Assert - Assert.AreEqual(50, playerModel.Gold.Value); // 金币未变化 -} - -[Test] -public void GetPlayerStatsQuery_ShouldReturnCorrectStats() -{ - // Arrange - var architecture = new TestArchitecture(); - var playerModel = new PlayerModel(); - playerModel.Level.Value = 10; - playerModel.Health.Value = 80; - architecture.RegisterModel(playerModel); - - var query = new GetPlayerStatsQuery(); - query.SetArchitecture(architecture); - - // Act - var stats = query.Do(); - - // Assert - Assert.AreEqual(10, stats.Level); - Assert.AreEqual(80, stats.Health); -} -``` - ---- - -## 总结 - -遵循这些架构模式最佳实践,你将能够构建: - -- ✅ **清晰的代码结构** - 易于理解和维护 -- ✅ **松耦合的组件** - 便于测试和扩展 -- ✅ **可重用的模块** - 提高开发效率 -- ✅ **健壮的错误处理** - 提高系统稳定性 -- ✅ **完善的测试覆盖** - 保证代码质量 - -### 关键要点 - -1. **从简单开始**:不要过度设计,按需添加模式 -2. **理解每个模式的适用场景**:选择合适的模式解决问题 -3. **模式可以组合使用**:发挥各自优势 -4. **持续重构**:随着项目发展优化架构 -5. **注重可测试性**:好的架构应该易于测试 - -### 推荐学习路径 - -1. **入门**:MVC + 事件驱动 -2. **进阶**:命令模式 + 查询模式 + MVVM -3. **高级**:状态模式 + 对象池 + 依赖注入 -4. **专家**:模块化架构 + 所有模式组合 - -### 相关资源 - -- [架构核心文档](/zh-CN/core/architecture) -- [命令模式文档](/zh-CN/core/command) -- [查询模式文档](/zh-CN/core/query) -- [事件系统文档](/zh-CN/core/events) -- [状态机文档](/zh-CN/core/state-machine) -- [IoC 容器文档](/zh-CN/core/ioc) - -记住,好的架构不是一蹴而就的,需要持续的学习、实践和改进。 - ---- - -**文档版本**: 2.0.0 -**最后更新**: 2026-03-07 +# 架构设计模式指南 + +> 全面介绍 GFramework 中的架构设计模式,帮助你构建清晰、可维护、可扩展的游戏架构。 + +## 📋 目录 + +- [概述](#概述) +- [MVC 模式](#mvc-模式) +- [MVVM 模式](#mvvm-模式) +- [命令模式](#命令模式) +- [查询模式](#查询模式) +- [事件驱动模式](#事件驱动模式) +- [依赖注入模式](#依赖注入模式) +- [服务定位器模式](#服务定位器模式) +- [对象池模式](#对象池模式) +- [状态模式](#状态模式) +- [设计原则](#设计原则) +- [架构分层](#架构分层) +- [依赖管理](#依赖管理) +- [事件系统设计](#事件系统设计) +- [模块化架构](#模块化架构) +- [错误处理策略](#错误处理策略) +- [测试策略](#测试策略) +- [重构指南](#重构指南) +- [模式选择与组合](#模式选择与组合) +- [常见问题](#常见问题) + +## 概述 + +架构设计模式是经过验证的解决方案,用于解决软件开发中的常见问题。GFramework 内置了多种设计模式,帮助你构建高质量的游戏应用。 + +### 为什么需要架构设计模式? + +1. **提高代码质量**:遵循最佳实践,减少 bug +2. **增强可维护性**:清晰的结构,易于理解和修改 +3. **促进团队协作**:统一的代码风格和架构 +4. **提升可扩展性**:轻松添加新功能 +5. **简化测试**:解耦的组件更容易测试 + +### GFramework 支持的核心模式 + +| 模式 | 用途 | 核心组件 | +|-----------|--------------|-------------------------| +| **MVC** | 分离数据、视图和控制逻辑 | Model, Controller | +| **MVVM** | 数据绑定和响应式 UI | Model, BindableProperty | +| **命令模式** | 封装操作请求 | ICommand, CommandBus | +| **查询模式** | 分离读操作 | IQuery, QueryBus | +| **事件驱动** | 松耦合通信 | IEventBus, Event | +| **依赖注入** | 控制反转 | IIocContainer | +| **服务定位器** | 服务查找 | Architecture.GetSystem | +| **对象池** | 对象复用 | IObjectPoolSystem | +| **状态模式** | 状态管理 | IStateMachine | + +## MVC 模式 + +### 概念 + +MVC(Model-View-Controller)是一种将应用程序分为三个核心组件的架构模式: + +- **Model(模型)**:管理数据和业务逻辑 +- **View(视图)**:显示数据给用户 +- **Controller(控制器)**:处理用户输入,协调 Model 和 View + +### 在 GFramework 中的实现 + +```csharp +// Model - 数据层 +public class PlayerModel : AbstractModel +{ + public BindableProperty<int> Health { get; } = new(100); + public BindableProperty<int> Score { get; } = new(0); + public BindableProperty<string> Name { get; } = new("Player"); + + protected override void OnInit() + { + // 监听数据变化 + Health.Register(newHealth => + { + if (newHealth <= 0) + { + this.SendEvent(new PlayerDiedEvent()); + } + }); + } +} + +// Controller - 控制层 +[ContextAware] +public partial class PlayerController : Node, IController +{ + private PlayerModel _playerModel; + + public override void _Ready() + { + _playerModel = this.GetModel<PlayerModel>(); + + // 监听数据变化,更新视图 + _playerModel.Health.Register(UpdateHealthUI); + _playerModel.Score.Register(UpdateScoreUI); + } + + // 处理用户输入 + public override void _Input(InputEvent @event) + { + if (@event is InputEventKey keyEvent && keyEvent.Pressed) + { + if (keyEvent.Keycode == Key.Space) + { + // 发送命令修改 Model + this.SendCommand(new AttackCommand()); + } + } + } + + // 更新视图 + private void UpdateHealthUI(int health) + { + var healthBar = GetNode<ProgressBar>("HealthBar"); + healthBar.Value = health; + } + + private void UpdateScoreUI(int score) + { + var scoreLabel = GetNode<Label>("ScoreLabel"); + scoreLabel.Text = $"Score: {score}"; + } +} + +// System - 业务逻辑层(可选) +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent<AttackCommand>(OnAttack); + } + + private void OnAttack(AttackCommand cmd) + { + var playerModel = this.GetModel<PlayerModel>(); + var enemyModel = this.GetModel<EnemyModel>(); + + // 计算伤害 + int damage = CalculateDamage(playerModel, enemyModel); + enemyModel.Health.Value -= damage; + + // 增加分数 + if (enemyModel.Health.Value <= 0) + { + playerModel.Score.Value += 100; + } + } + + private int CalculateDamage(PlayerModel player, EnemyModel enemy) + { + return 10; // 简化示例 + } +} +``` + +### MVC 优势 + +- ✅ **职责分离**:Model、View、Controller 各司其职 +- ✅ **易于测试**:可以独立测试每个组件 +- ✅ **可维护性高**:修改一个组件不影响其他组件 +- ✅ **支持多视图**:同一个 Model 可以有多个 View + +### 最佳实践 + +1. **Model 只负责数据**:不包含 UI 逻辑 +2. **Controller 协调交互**:不直接操作 UI 细节 +3. **View 只负责显示**:不包含业务逻辑 +4. **使用事件通信**:Model 变化通过事件通知 Controller + +## MVVM 模式 + +### 概念 + +MVVM(Model-View-ViewModel)是 MVC 的变体,强调数据绑定和响应式编程: + +- **Model**:数据和业务逻辑 +- **View**:用户界面 +- **ViewModel**:View 的抽象,提供数据绑定 + +### 在 GFramework 中的实现 + +```csharp +// Model - 数据层 +public class GameModel : AbstractModel +{ + public BindableProperty<int> CurrentLevel { get; } = new(1); + public BindableProperty<float> Progress { get; } = new(0f); + public BindableProperty<bool> IsLoading { get; } = new(false); + + protected override void OnInit() + { + Progress.Register(progress => + { + if (progress >= 1.0f) + { + this.SendEvent(new LevelCompletedEvent()); + } + }); + } +} + +// ViewModel - 视图模型(在 GFramework 中,Model 本身就是 ViewModel) +public class PlayerViewModel : AbstractModel +{ + private PlayerModel _playerModel; + + // 计算属性 + public BindableProperty<string> HealthText { get; } = new(""); + public BindableProperty<float> HealthPercentage { get; } = new(1.0f); + public BindableProperty<bool> IsAlive { get; } = new(true); + + protected override void OnInit() + { + _playerModel = this.GetModel<PlayerModel>(); + + // 绑定数据转换 + _playerModel.Health.Register(health => + { + HealthText.Value = $"{health} / {_playerModel.MaxHealth.Value}"; + HealthPercentage.Value = (float)health / _playerModel.MaxHealth.Value; + IsAlive.Value = health > 0; + }); + } +} + +// View - 视图层 +[ContextAware] +public partial class PlayerView : Control, IController +{ + private PlayerViewModel _viewModel; + private Label _healthLabel; + private ProgressBar _healthBar; + private Panel _deathPanel; + + public override void _Ready() + { + _viewModel = this.GetModel<PlayerViewModel>(); + + _healthLabel = GetNode<Label>("HealthLabel"); + _healthBar = GetNode<ProgressBar>("HealthBar"); + _deathPanel = GetNode<Panel>("DeathPanel"); + + // 数据绑定 + _viewModel.HealthText.Register(text => _healthLabel.Text = text); + _viewModel.HealthPercentage.Register(pct => _healthBar.Value = pct * 100); + _viewModel.IsAlive.Register(alive => _deathPanel.Visible = !alive); + } +} +``` + +### MVVM 优势 + +- ✅ **自动更新 UI**:数据变化自动反映到界面 +- ✅ **减少样板代码**:不需要手动更新 UI +- ✅ **易于测试**:ViewModel 可以独立测试 +- ✅ **支持复杂 UI**:适合数据驱动的界面 + +### 最佳实践 + +1. **使用 BindableProperty**:实现响应式数据 +2. **ViewModel 不依赖 View**:保持单向依赖 +3. **计算属性放在 ViewModel**:如百分比、格式化文本 +4. **避免在 View 中写业务逻辑**:只负责数据绑定 + +## 命令模式 + +### 概念 + +命令模式将请求封装为对象,从而支持参数化、队列化、日志记录和撤销操作。 + +### 在 GFramework 中的实现 + +```csharp +// 定义命令输入 +public class BuyItemInput : ICommandInput +{ + public string ItemId { get; set; } + public int Quantity { get; set; } +} + +// 实现命令 +public class BuyItemCommand : AbstractCommand<BuyItemInput> +{ + protected override void OnExecute(BuyItemInput input) + { + var playerModel = this.GetModel<PlayerModel>(); + var inventoryModel = this.GetModel<InventoryModel>(); + var shopModel = this.GetModel<ShopModel>(); + + // 获取物品信息 + var item = shopModel.GetItem(input.ItemId); + var totalCost = item.Price * input.Quantity; + + // 检查金币 + if (playerModel.Gold.Value < totalCost) + { + this.SendEvent(new InsufficientGoldEvent()); + return; + } + + // 扣除金币 + playerModel.Gold.Value -= totalCost; + + // 添加物品 + inventoryModel.AddItem(input.ItemId, input.Quantity); + + // 发送事件 + this.SendEvent(new ItemPurchasedEvent + { + ItemId = input.ItemId, + Quantity = input.Quantity, + Cost = totalCost + }); + } +} + +// 使用命令 +[ContextAware] +public partial class ShopController : IController +{ + public void OnBuyButtonClicked(string itemId, int quantity) + { + // 创建并发送命令 + var input = new BuyItemInput + { + ItemId = itemId, + Quantity = quantity + }; + + this.SendCommand(new BuyItemCommand { Input = input }); + } +} +``` + +### 支持撤销的命令 + +```csharp +public interface IUndoableCommand : ICommand +{ + void Undo(); +} + +public class MoveCommand : AbstractCommand, IUndoableCommand +{ + private Vector2 _previousPosition; + private Vector2 _newPosition; + + public MoveCommand(Vector2 newPosition) + { + _newPosition = newPosition; + } + + protected override void OnExecute() + { + var playerModel = this.GetModel<PlayerModel>(); + _previousPosition = playerModel.Position.Value; + playerModel.Position.Value = _newPosition; + } + + public void Undo() + { + var playerModel = this.GetModel<PlayerModel>(); + playerModel.Position.Value = _previousPosition; + } +} + +// 命令历史管理器 +public class CommandHistory +{ + private readonly Stack<IUndoableCommand> _history = new(); + + public void Execute(IUndoableCommand command) + { + command.Execute(); + _history.Push(command); + } + + public void Undo() + { + if (_history.Count > 0) + { + var command = _history.Pop(); + command.Undo(); + } + } +} +``` + +### 命令模式优势 + +- ✅ **解耦发送者和接收者**:调用者不需要知道实现细节 +- ✅ **支持撤销/重做**:保存命令历史 +- ✅ **支持队列和日志**:可以记录所有操作 +- ✅ **易于扩展**:添加新命令不影响现有代码 + +### 最佳实践 + +1. **命令保持原子性**:一个命令完成一个完整操作 +2. **使用输入对象传参**:避免构造函数参数过多 +3. **命令无状态**:执行完即可丢弃 +4. **发送事件通知结果**:而不是返回值 + +## 查询模式 + +### 概念 + +查询模式(CQRS 的一部分)将读操作与写操作分离,查询只读取数据,不修改状态。 + +### 在 GFramework 中的实现 + +```csharp +// 定义查询输入 +public class GetPlayerStatsInput : IQueryInput +{ + public string PlayerId { get; set; } +} + +// 定义查询结果 +public class PlayerStats +{ + public int Level { get; set; } + public int Health { get; set; } + public int MaxHealth { get; set; } + public int Attack { get; set; } + public int Defense { get; set; } + public int TotalPower { get; set; } +} + +// 实现查询 +public class GetPlayerStatsQuery : AbstractQuery<GetPlayerStatsInput, PlayerStats> +{ + protected override PlayerStats OnDo(GetPlayerStatsInput input) + { + var playerModel = this.GetModel<PlayerModel>(); + var equipmentModel = this.GetModel<EquipmentModel>(); + + // 计算总战力 + int basePower = playerModel.Level.Value * 10; + int equipmentPower = equipmentModel.GetTotalPower(); + + return new PlayerStats + { + Level = playerModel.Level.Value, + Health = playerModel.Health.Value, + MaxHealth = playerModel.MaxHealth.Value, + Attack = playerModel.Attack.Value + equipmentModel.GetAttackBonus(), + Defense = playerModel.Defense.Value + equipmentModel.GetDefenseBonus(), + TotalPower = basePower + equipmentPower + }; + } +} + +// 使用查询 +[ContextAware] +public partial class CharacterPanelController : IController +{ + public void ShowCharacterStats() + { + var input = new GetPlayerStatsInput { PlayerId = "player1" }; + var query = new GetPlayerStatsQuery { Input = input }; + var stats = this.SendQuery(query); + + // 显示统计信息 + DisplayStats(stats); + } + + private void DisplayStats(PlayerStats stats) + { + Console.WriteLine($"Level: {stats.Level}"); + Console.WriteLine($"Health: {stats.Health}/{stats.MaxHealth}"); + Console.WriteLine($"Attack: {stats.Attack}"); + Console.WriteLine($"Defense: {stats.Defense}"); + Console.WriteLine($"Total Power: {stats.TotalPower}"); + } +} +``` + +### 复杂查询示例 + +```csharp +// 查询背包中可装备的物品 +public class GetEquippableItemsQuery : AbstractQuery<EmptyQueryInput, List<Item>> +{ + protected override List<Item> OnDo(EmptyQueryInput input) + { + var inventoryModel = this.GetModel<InventoryModel>(); + var playerModel = this.GetModel<PlayerModel>(); + + return inventoryModel.GetAllItems() + .Where(item => item.Type == ItemType.Equipment) + .Where(item => item.RequiredLevel <= playerModel.Level.Value) + .OrderByDescending(item => item.Power) + .ToList(); + } +} + +// 组合查询 +public class CanUseSkillQuery : AbstractQuery<CanUseSkillInput, bool> +{ + protected override bool OnDo(CanUseSkillInput input) + { + var playerModel = this.GetModel<PlayerModel>(); + + // 查询技能消耗 + var costQuery = new GetSkillCostQuery { Input = new GetSkillCostInput { SkillId = input.SkillId } }; + var cost = this.SendQuery(costQuery); + + // 查询冷却状态 + var cooldownQuery = new IsSkillOnCooldownQuery { Input = new IsSkillOnCooldownInput { SkillId = input.SkillId } }; + var onCooldown = this.SendQuery(cooldownQuery); + + // 综合判断 + return playerModel.Mana.Value >= cost.ManaCost + && !onCooldown + && playerModel.Health.Value > 0; + } +} +``` + +### 查询模式优势 + +- ✅ **职责分离**:读写操作明确分离 +- ✅ **易于优化**:可以针对查询进行缓存优化 +- ✅ **提高可读性**:查询意图清晰 +- ✅ **支持复杂查询**:可以组合多个简单查询 + +### 最佳实践 + +1. **查询只读取,不修改**:保持查询的纯粹性 +2. **使用清晰的命名**:Get、Is、Can、Has 等前缀 +3. **避免过度查询**:频繁查询考虑使用 BindableProperty +4. **合理使用缓存**:复杂计算结果可以缓存 + +## 事件驱动模式 + +### 概念 + +事件驱动模式通过事件实现组件间的松耦合通信。发送者不需要知道接收者,接收者通过订阅事件来响应变化。 + +### 在 GFramework 中的实现 + +```csharp +// 定义事件 +public struct PlayerDiedEvent +{ + public Vector3 Position { get; set; } + public string Cause { get; set; } + public int FinalScore { get; set; } +} + +public struct EnemyKilledEvent +{ + public string EnemyId { get; set; } + public int Reward { get; set; } +} + +// Model 发送事件 +public class PlayerModel : AbstractModel +{ + public BindableProperty<int> Health { get; } = new(100); + public BindableProperty<Vector3> Position { get; } = new(Vector3.Zero); + + protected override void OnInit() + { + Health.Register(newHealth => + { + if (newHealth <= 0) + { + // 发送玩家死亡事件 + this.SendEvent(new PlayerDiedEvent + { + Position = Position.Value, + Cause = "Health depleted", + FinalScore = this.GetModel<GameModel>().Score.Value + }); + } + }); + } +} + +// System 监听和发送事件 +public class AchievementSystem : AbstractSystem +{ + private int _enemyKillCount = 0; + + protected override void OnInit() + { + // 监听敌人被杀事件 + this.RegisterEvent<EnemyKilledEvent>(OnEnemyKilled); + + // 监听玩家死亡事件 + this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied); + } + + private void OnEnemyKilled(EnemyKilledEvent e) + { + _enemyKillCount++; + + // 检查成就条件 + if (_enemyKillCount == 10) + { + this.SendEvent(new AchievementUnlockedEvent + { + AchievementId = "first_blood_10", + Title = "新手猎人", + Description = "击败10个敌人" + }); + } + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + // 记录统计数据 + var statsModel = this.GetModel<StatisticsModel>(); + statsModel.RecordDeath(e.Position, e.Cause); + } +} + +// Controller 监听事件 +[ContextAware] +public partial class UIController : IController +{ + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public void Initialize() + { + // 监听成就解锁事件 + this.RegisterEvent(OnAchievementUnlocked) + .AddToUnregisterList(_unregisterList); + + // 监听玩家死亡事件 + this.RegisterEvent(OnPlayerDied) + .AddToUnregisterList(_unregisterList); + } + + private void OnAchievementUnlocked(AchievementUnlockedEvent e) + { + ShowAchievementNotification(e.Title, e.Description); + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + ShowGameOverScreen(e.FinalScore); + } + + public void Cleanup() + { + _unregisterList.UnRegisterAll(); + } +} +``` + +### 事件组合 + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +// 使用 OrEvent 组合多个事件 +[ContextAware] +public partial class InputController : IController +{ + public void Initialize() + { + var onAnyInput = new OrEvent() + .Or(keyboardEvent) + .Or(mouseEvent) + .Or(gamepadEvent); + + onAnyInput.Register(() => + { + ResetIdleTimer(); + }); + } +} +``` + +### 事件驱动模式优势 + +- ✅ **松耦合**:发送者和接收者互不依赖 +- ✅ **一对多通信**:一个事件可以有多个监听者 +- ✅ **易于扩展**:添加新监听者不影响现有代码 +- ✅ **支持异步**:事件可以异步处理 + +### 最佳实践 + +1. **事件命名使用过去式**:PlayerDiedEvent、LevelCompletedEvent +2. **事件使用结构体**:减少内存分配 +3. **及时注销事件**:使用 IUnRegisterList 管理 +4. **避免事件循环**:事件处理器中谨慎发送新事件 + +## 依赖注入模式 + +### 概念 + +依赖注入(DI)是一种实现控制反转(IoC)的技术,通过外部注入依赖而不是在类内部创建。 + +### 在 GFramework 中的实现 + +```csharp +// 定义接口 +public interface IStorageService +{ + Task SaveAsync<T>(string key, T data); + Task<T> LoadAsync<T>(string key); +} + +public interface IAudioService +{ + void PlaySound(string soundId); + void PlayMusic(string musicId); +} + +// 实现服务 +public class LocalStorageService : IStorageService +{ + public async Task SaveAsync<T>(string key, T data) + { + var json = JsonSerializer.Serialize(data); + await File.WriteAllTextAsync($"saves/{key}.json", json); + } + + public async Task<T> LoadAsync<T>(string key) + { + var json = await File.ReadAllTextAsync($"saves/{key}.json"); + return JsonSerializer.Deserialize<T>(json); + } +} + +public class GodotAudioService : IAudioService +{ + private AudioStreamPlayer _soundPlayer; + private AudioStreamPlayer _musicPlayer; + + public void PlaySound(string soundId) + { + var sound = GD.Load<AudioStream>($"res://sounds/{soundId}.ogg"); + _soundPlayer.Stream = sound; + _soundPlayer.Play(); + } + + public void PlayMusic(string musicId) + { + var music = GD.Load<AudioStream>($"res://music/{musicId}.ogg"); + _musicPlayer.Stream = music; + _musicPlayer.Play(); + } +} + +// 在架构中注册服务 +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 注册服务实现 + RegisterUtility<IStorageService>(new LocalStorageService()); + RegisterUtility<IAudioService>(new GodotAudioService()); + + // 注册 System(System 会自动获取依赖) + RegisterSystem(new SaveSystem()); + RegisterSystem(new AudioSystem()); + } +} + +// System 使用依赖注入 +public class SaveSystem : AbstractSystem +{ + private IStorageService _storageService; + + protected override void OnInit() + { + // 从容器获取依赖 + _storageService = this.GetUtility<IStorageService>(); + + this.RegisterEvent<SaveGameEvent>(OnSaveGame); + } + + private async void OnSaveGame(SaveGameEvent e) + { + var playerModel = this.GetModel<PlayerModel>(); + var saveData = new SaveData + { + PlayerName = playerModel.Name.Value, + Level = playerModel.Level.Value, + Health = playerModel.Health.Value + }; + + await _storageService.SaveAsync("current_save", saveData); + } +} +``` + +### 构造函数注入(推荐) + +```csharp +public class SaveSystem : AbstractSystem +{ + private readonly IStorageService _storageService; + private readonly IAudioService _audioService; + + // 通过构造函数注入依赖 + public SaveSystem(IStorageService storageService, IAudioService audioService) + { + _storageService = storageService; + _audioService = audioService; + } + + protected override void OnInit() + { + this.RegisterEvent<SaveGameEvent>(OnSaveGame); + } + + private async void OnSaveGame(SaveGameEvent e) + { + await _storageService.SaveAsync("save", e.Data); + _audioService.PlaySound("save_success"); + } +} + +// 注册时传入依赖 +public class GameArchitecture : Architecture +{ + protected override void Init() + { + var storageService = new LocalStorageService(); + var audioService = new GodotAudioService(); + + RegisterUtility<IStorageService>(storageService); + RegisterUtility<IAudioService>(audioService); + + // 构造函数注入 + RegisterSystem(new SaveSystem(storageService, audioService)); + } +} +``` + +### 依赖注入优势 + +- ✅ **易于测试**:可以注入模拟对象 +- ✅ **松耦合**:依赖接口而非实现 +- ✅ **灵活配置**:运行时选择实现 +- ✅ **提高可维护性**:依赖关系清晰 + +### 最佳实践 + +1. **依赖接口而非实现**:使用 IStorageService 而非 LocalStorageService +2. **优先使用构造函数注入**:依赖关系更明确 +3. **避免循环依赖**:System 不应相互依赖 +4. **使用 IoC 容器管理生命周期**:让框架管理对象创建 + +## 服务定位器模式 + +### 概念 + +服务定位器模式提供一个全局访问点来获取服务,是依赖注入的替代方案。 + +### 在 GFramework 中的实现 + +```csharp +// GFramework 的 Architecture 本身就是服务定位器 +public class GameplaySystem : AbstractSystem +{ + protected override void OnInit() + { + // 通过服务定位器获取服务 + var playerModel = this.GetModel<PlayerModel>(); + var audioSystem = this.GetSystem<AudioSystem>(); + var storageUtility = this.GetUtility<IStorageUtility>(); + + // 使用服务 + playerModel.Health.Value = 100; + audioSystem.PlayBGM("gameplay"); + } +} + +// 在 Controller 中使用 +[ContextAware] +public partial class MenuController : IController +{ + public void OnStartButtonClicked() + { + // 通过架构获取服务 + var gameModel = this.GetModel(); + gameModel.GameState.Value = GameState.Playing; + + // 发送命令 + this.SendCommand(new StartGameCommand()); + } +} +``` + +### 自定义服务定位器 + +```csharp +// 创建专门的服务定位器 +public static class ServiceLocator +{ + private static readonly Dictionary<Type, object> _services = new(); + + public static void Register<T>(T service) + { + _services[typeof(T)] = service; + } + + public static T Get<T>() + { + if (_services.TryGetValue(typeof(T), out var service)) + { + return (T)service; + } + throw new InvalidOperationException($"Service {typeof(T)} not registered"); + } + + public static void Clear() + { + _services.Clear(); + } +} + +// 使用自定义服务定位器 +public class GameInitializer +{ + public void Initialize() + { + // 注册服务 + ServiceLocator.Register<IAnalyticsService>(new AnalyticsService()); + ServiceLocator.Register<ILeaderboardService>(new LeaderboardService()); + } +} + +public class GameOverScreen +{ + public void SubmitScore(int score) + { + // 获取服务 + var leaderboard = ServiceLocator.Get<ILeaderboardService>(); + leaderboard.SubmitScore(score); + + var analytics = ServiceLocator.Get<IAnalyticsService>(); + analytics.TrackEvent("game_over", new { score }); + } +} +``` + +### 服务定位器 vs 依赖注入 + +| 特性 | 服务定位器 | 依赖注入 | +|-----------|--------------|------------| +| **依赖可见性** | 隐式(运行时获取) | 显式(构造函数参数) | +| **易用性** | 简单直接 | 需要配置 | +| **测试性** | 较难(需要模拟全局状态) | 容易(注入模拟对象) | +| **编译时检查** | 无 | 有 | +| **适用场景** | 快速原型、小项目 | 大型项目、团队协作 | + +### 最佳实践 + +1. **小项目使用服务定位器**:简单直接 +2. **大项目使用依赖注入**:更易维护 +3. **避免过度使用**:不要把所有东西都放入定位器 +4. **提供清晰的 API**:GetModel、GetSystem、GetUtility + +## 对象池模式 + +### 概念 + +对象池模式通过复用对象来减少内存分配和垃圾回收,提高性能。 + +### 在 GFramework 中的实现 + +```csharp +// 定义可池化对象 +public class Bullet : Node2D, IPoolableNode +{ + public bool IsInPool { get; set; } + + public void OnSpawn() + { + // 从池中取出时调用 + Visible = true; + IsInPool = false; + } + + public void OnRecycle() + { + // 回收到池中时调用 + Visible = false; + IsInPool = true; + Position = Vector2.Zero; + Rotation = 0; + } +} + +// 创建对象池系统 +public class BulletPoolSystem : AbstractNodePoolSystem<Bullet> +{ + protected override Bullet CreateInstance() + { + var bullet = new Bullet(); + // 初始化子弹 + return bullet; + } + + protected override void OnInit() + { + // 预创建对象 + PrewarmPool(50); + } +} + +// 使用对象池 +public class WeaponSystem : AbstractSystem +{ + private BulletPoolSystem _bulletPool; + + protected override void OnInit() + { + _bulletPool = this.GetSystem<BulletPoolSystem>(); + this.RegisterEvent<FireWeaponEvent>(OnFireWeapon); + } + + private void OnFireWeapon(FireWeaponEvent e) + { + // 从池中获取子弹 + var bullet = _bulletPool.Spawn(); + bullet.Position = e.Position; + bullet.Rotation = e.Direction; + + // 3秒后回收 + ScheduleRecycle(bullet, 3.0f); + } + + private async void ScheduleRecycle(Bullet bullet, float delay) + { + await Task.Delay((int)(delay * 1000)); + _bulletPool.Recycle(bullet); + } +} +``` + +### 通用对象池 + +```csharp +// 通用对象池实现 +public class ObjectPool<T> where T : class, new() +{ + private readonly Stack<T> _pool = new(); + private readonly Action<T> _onSpawn; + private readonly Action<T> _onRecycle; + private readonly int _maxSize; + + public ObjectPool(int initialSize = 10, int maxSize = 100, + Action<T> onSpawn = null, Action<T> onRecycle = null) + { + _maxSize = maxSize; + _onSpawn = onSpawn; + _onRecycle = onRecycle; + + // 预创建对象 + for (int i = 0; i < initialSize; i++) + { + _pool.Push(new T()); + } + } + + public T Spawn() + { + T obj = _pool.Count > 0 ? _pool.Pop() : new T(); + _onSpawn?.Invoke(obj); + return obj; + } + + public void Recycle(T obj) + { + if (_pool.Count < _maxSize) + { + _onRecycle?.Invoke(obj); + _pool.Push(obj); + } + } + + public void Clear() + { + _pool.Clear(); + } +} + +// 使用通用对象池 +public class ParticleSystem : AbstractSystem +{ + private ObjectPool<Particle> _particlePool; + + protected override void OnInit() + { + _particlePool = new ObjectPool<Particle>( + initialSize: 100, + maxSize: 500, + onSpawn: p => p.Reset(), + onRecycle: p => p.Clear() + ); + } + + public void EmitParticles(Vector3 position, int count) + { + for (int i = 0; i < count; i++) + { + var particle = _particlePool.Spawn(); + particle.Position = position; + particle.Velocity = GetRandomVelocity(); + } + } +} +``` + +### 对象池优势 + +- ✅ **减少 GC 压力**:复用对象,减少内存分配 +- ✅ **提高性能**:避免频繁创建销毁对象 +- ✅ **稳定帧率**:减少 GC 导致的卡顿 +- ✅ **适合高频对象**:子弹、粒子、特效等 + +### 最佳实践 + +1. **预热池**:提前创建对象避免运行时分配 +2. **设置最大容量**:防止池无限增长 +3. **重置对象状态**:OnRecycle 中清理状态 +4. **监控池使用情况**:记录 Spawn/Recycle 次数 + +## 状态模式 + +### 概念 + +状态模式允许对象在内部状态改变时改变其行为,将状态相关的行为封装到独立的状态类中。 + +### 在 GFramework 中的实现 + +```csharp +// 定义游戏状态 +public class MenuState : ContextAwareStateBase +{ + public override void OnEnter(IState from) + { + Console.WriteLine("进入菜单状态"); + + // 显示菜单 UI + var uiSystem = this.GetSystem<UISystem>(); + uiSystem.ShowMenu(); + + // 播放菜单音乐 + var audioSystem = this.GetSystem<AudioSystem>(); + audioSystem.PlayBGM("menu_theme"); + } + + public override void OnExit(IState to) + { + Console.WriteLine("退出菜单状态"); + + // 隐藏菜单 UI + var uiSystem = this.GetSystem<UISystem>(); + uiSystem.HideMenu(); + } + + public override bool CanTransitionTo(IState target) + { + // 菜单可以转换到游戏或设置状态 + return target is GameplayState or SettingsState; + } +} + +public class GameplayState : ContextAwareStateBase +{ + public override void OnEnter(IState from) + { + Console.WriteLine("进入游戏状态"); + + // 初始化游戏 + var gameModel = this.GetModel<GameModel>(); + gameModel.Reset(); + + // 加载关卡 + this.SendCommand(new LoadLevelCommand { LevelId = 1 }); + + // 播放游戏音乐 + var audioSystem = this.GetSystem<AudioSystem>(); + audioSystem.PlayBGM("gameplay_theme"); + } + + public override void OnExit(IState to) + { + Console.WriteLine("退出游戏状态"); + + // 保存游戏进度 + this.SendCommand(new SaveGameCommand()); + } + + public override bool CanTransitionTo(IState target) + { + // 游戏中可以暂停或结束 + return target is PauseState or GameOverState; + } +} + +public class PauseState : ContextAwareStateBase +{ + public override void OnEnter(IState from) + { + Console.WriteLine("进入暂停状态"); + + // 暂停游戏 + var timeSystem = this.GetSystem<TimeSystem>(); + timeSystem.Pause(); + + // 显示暂停菜单 + var uiSystem = this.GetSystem<UISystem>(); + uiSystem.ShowPauseMenu(); + } + + public override void OnExit(IState to) + { + Console.WriteLine("退出暂停状态"); + + // 恢复游戏 + var timeSystem = this.GetSystem<TimeSystem>(); + timeSystem.Resume(); + + // 隐藏暂停菜单 + var uiSystem = this.GetSystem<UISystem>(); + uiSystem.HidePauseMenu(); + } + + public override bool CanTransitionTo(IState target) + { + // 暂停只能返回游戏或退出到菜单 + return target is GameplayState or MenuState; + } +} + +// 注册状态机 +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 创建状态机系统 + var stateMachine = new StateMachineSystem(); + + // 注册所有状态 + stateMachine + .Register(new MenuState()) + .Register(new GameplayState()) + .Register(new PauseState()) + .Register(new GameOverState()) + .Register(new SettingsState()); + + RegisterSystem<IStateMachineSystem>(stateMachine); + } +} + +// 使用状态机 +[ContextAware] +public partial class GameController : IController +{ + public async Task StartGame() + { + var stateMachine = this.GetSystem(); + await stateMachine.ChangeToAsync(); + } + + public async Task PauseGame() + { + var stateMachine = this.GetSystem(); + await stateMachine.ChangeToAsync(); + } + + public async Task ResumeGame() + { + var stateMachine = this.GetSystem(); + await stateMachine.ChangeToAsync(); + } +} +``` + +### 异步状态 + +```csharp +public class LoadingState : AsyncContextAwareStateBase +{ + public override async Task OnEnterAsync(IState from) + { + Console.WriteLine("开始加载..."); + + // 显示加载界面 + var uiSystem = this.GetSystem<UISystem>(); + uiSystem.ShowLoadingScreen(); + + // 异步加载资源 + await LoadResourcesAsync(); + + Console.WriteLine("加载完成"); + + // 自动切换到游戏状态 + var stateMachine = this.GetSystem<IStateMachineSystem>(); + await stateMachine.ChangeToAsync<GameplayState>(); + } + + private async Task LoadResourcesAsync() + { + // 模拟异步加载 + await Task.Delay(2000); + } + + public override async Task OnExitAsync(IState to) + { + // 隐藏加载界面 + var uiSystem = this.GetSystem<UISystem>(); + uiSystem.HideLoadingScreen(); + + await Task.CompletedTask; + } +} +``` + +### 状态模式优势 + +- ✅ **清晰的状态管理**:每个状态独立封装 +- ✅ **易于扩展**:添加新状态不影响现有代码 +- ✅ **状态转换验证**:CanTransitionTo 控制合法转换 +- ✅ **支持异步操作**:异步状态处理加载等操作 + +### 最佳实践 + +1. **状态保持单一职责**:每个状态只负责一个场景 +2. **使用转换验证**:防止非法状态转换 +3. **在 OnEnter 初始化,OnExit 清理**:保持状态独立 +4. **异步操作使用异步状态**:避免阻塞主线程 + +## 设计原则 + +### 1. 单一职责原则 (SRP) + +确保每个类只负责一个功能领域: + +```csharp +// ✅ 好的做法:职责单一 +public class PlayerMovementController : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnPlayerInput); + } + + private void OnPlayerInput(PlayerInputEvent e) + { + // 只负责移动逻辑 + ProcessMovement(e.Direction); + } + + private void ProcessMovement(Vector2 direction) + { + // 移动相关的业务逻辑 + } +} + +public class PlayerCombatController : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnAttackInput); + } + + private void OnAttackInput(AttackInputEvent e) + { + // 只负责战斗逻辑 + ProcessAttack(e.Target); + } + + private void ProcessAttack(Entity target) + { + // 战斗相关的业务逻辑 + } +} + +// ❌ 避免:职责混乱 +public class PlayerController : AbstractSystem +{ + private void OnPlayerInput(PlayerInputEvent e) + { + // 移动逻辑 + ProcessMovement(e.Direction); + + // 战斗逻辑 + if (e.IsAttacking) + { + ProcessAttack(e.Target); + } + + // UI逻辑 + UpdateHealthBar(); + + // 音效逻辑 + PlaySoundEffect(); + + // 存档逻辑 + SaveGame(); + + // 职责太多,难以维护 + } +} +``` + +### 2. 开闭原则 (OCP) + +设计应该对扩展开放,对修改封闭: + +```csharp +// ✅ 好的做法:使用接口和策略模式 +public interface IWeaponStrategy +{ + void Attack(Entity attacker, Entity target); + int CalculateDamage(Entity attacker, Entity target); +} + +public class SwordWeaponStrategy : IWeaponStrategy +{ + public void Attack(Entity attacker, Entity target) + { + var damage = CalculateDamage(attacker, target); + target.TakeDamage(damage); + PlaySwingAnimation(); + } + + public int CalculateDamage(Entity attacker, Entity target) + { + return attacker.Strength + GetSwordBonus() - target.Armor; + } +} + +public class MagicWeaponStrategy : IWeaponStrategy +{ + public void Attack(Entity attacker, Entity target) + { + var damage = CalculateDamage(attacker, target); + target.TakeDamage(damage); + CastMagicEffect(); + } + + public int CalculateDamage(Entity attacker, Entity target) + { + return attacker.Intelligence * 2 + GetMagicBonus() - target.MagicResistance; + } +} + +public class CombatSystem : AbstractSystem +{ + private readonly Dictionary _weaponStrategies; + + public CombatSystem() + { + _weaponStrategies = new() + { + { WeaponType.Sword, new SwordWeaponStrategy() }, + { WeaponType.Magic, new MagicWeaponStrategy() } + }; + } + + public void Attack(Entity attacker, Entity target) + { + var weaponType = attacker.EquippedWeapon.Type; + + if (_weaponStrategies.TryGetValue(weaponType, out var strategy)) + { + strategy.Attack(attacker, target); + } + } + + // 添加新武器类型时,只需要添加新的策略,不需要修改现有代码 + public void RegisterWeaponStrategy(WeaponType type, IWeaponStrategy strategy) + { + _weaponStrategies[type] = strategy; + } +} + +// ❌ 避免:需要修改现有代码来扩展 +public class CombatSystem : AbstractSystem +{ + public void Attack(Entity attacker, Entity target) + { + var weaponType = attacker.EquippedWeapon.Type; + + switch (weaponType) + { + case WeaponType.Sword: + // 剑的攻击逻辑 + break; + case WeaponType.Bow: + // 弓的攻击逻辑 + break; + default: + throw new NotSupportedException($"Weapon type {weaponType} not supported"); + } + + // 添加新武器类型时需要修改这里的 switch 语句 + } +} +``` + +### 3. 依赖倒置原则 (DIP) + +高层模块不应该依赖低层模块,两者都应该依赖抽象: + +```csharp +// ✅ 好的做法:依赖抽象 +public interface IDataStorage +{ + Task SaveAsync<T>(string key, T data); + Task<T> LoadAsync<T>(string key, T defaultValue = default); + Task ExistsAsync(string key); +} + +public class FileStorage : IDataStorage +{ + public async Task SaveAsync<T>(string key, T data) + { + var json = JsonConvert.SerializeObject(data); + await File.WriteAllTextAsync(GetFilePath(key), json); + } + + public async Task<T> LoadAsync<T>(string key, T defaultValue = default) + { + var filePath = GetFilePath(key); + if (!File.Exists(filePath)) + return defaultValue; + + var json = await File.ReadAllTextAsync(filePath); + return JsonConvert.DeserializeObject<T>(json); + } + + public async Task ExistsAsync(string key) + { + return File.Exists(GetFilePath(key)); + } + + private string GetFilePath(string key) + { + return $"saves/{key}.json"; + } +} + +public class CloudStorage : IDataStorage +{ + public async Task SaveAsync<T>(string key, T data) + { + // 云存储实现 + await UploadToCloud(key, data); + } + + public async Task<T> LoadAsync<T>(string key, T defaultValue = default) + { + // 云存储实现 + return await DownloadFromCloud<T>(key, defaultValue); + } + + public async Task ExistsAsync(string key) + { + // 云存储实现 + return await CheckCloudExists(key); + } +} + +// 高层模块依赖抽象 +public class SaveSystem : AbstractSystem +{ + private readonly IDataStorage _storage; + + public SaveSystem(IDataStorage storage) + { + _storage = storage; + } + + public async Task SaveGameAsync(SaveData data) + { + await _storage.SaveAsync("current_save", data); + } + + public async Task LoadGameAsync() + { + return await _storage.LoadAsync("current_save"); + } +} + +// ❌ 避免:依赖具体实现 +public class SaveSystem : AbstractSystem +{ + private readonly FileStorage _storage; // 直接依赖具体实现 + + public SaveSystem() + { + _storage = new FileStorage(); // 硬编码依赖 + } + + // 无法轻松切换到其他存储方式 +} +``` + +## 架构分层 + +### 1. 清晰的层次结构 + +```csharp +// ✅ 好的做法:清晰的分层架构 +namespace Game.Models +{ + // 数据层:只负责存储状态 + public class PlayerModel : AbstractModel + { + public BindableProperty Health { get; } = new(100); + public BindableProperty MaxHealth { get; } = new(100); + public BindableProperty Position { get; } = new(Vector2.Zero); + public BindableProperty State { get; } = new(PlayerState.Idle); + + protected override void OnInit() + { + // 只处理数据相关的逻辑 + Health.Register(OnHealthChanged); + } + + private void OnHealthChanged(int newHealth) + { + if (newHealth <= 0) + { + State.Value = PlayerState.Dead; + SendEvent(new PlayerDeathEvent()); + } + } + } + + public enum PlayerState + { + Idle, + Moving, + Attacking, + Dead + } +} + +namespace Game.Systems +{ + // 业务逻辑层:处理游戏逻辑 + public class PlayerMovementSystem : AbstractSystem + { + private PlayerModel _playerModel; + private GameModel _gameModel; + + protected override void OnInit() + { + _playerModel = GetModel(); + _gameModel = GetModel(); + + this.RegisterEvent(OnPlayerInput); + } + + private void OnPlayerInput(PlayerInputEvent e) + { + if (_gameModel.State.Value != GameState.Playing) + return; + + if (_playerModel.State.Value == PlayerState.Dead) + return; + + // 处理移动逻辑 + ProcessMovement(e.Direction); + } + + private void ProcessMovement(Vector2 direction) + { + if (direction != Vector2.Zero) + { + _playerModel.Position.Value += direction.Normalized() * GetMovementSpeed(); + _playerModel.State.Value = PlayerState.Moving; + + SendEvent(new PlayerMovedEvent { + NewPosition = _playerModel.Position.Value, + Direction = direction + }); + } + else + { + _playerModel.State.Value = PlayerState.Idle; + } + } + + private float GetMovementSpeed() + { + // 从玩家属性或其他地方获取速度 + return 5.0f; + } + } +} + +namespace Game.Controllers +{ + // 控制层:连接用户输入和业务逻辑 + [ContextAware] + public partial class PlayerController : Node, IController + { + private PlayerModel _playerModel; + + public override void _Ready() + { + _playerModel = this.GetModel(); + + // 监听用户输入 + SetProcessInput(true); + + // 监听数据变化,更新UI + _playerModel.Health.Register(UpdateHealthUI); + _playerModel.Position.Register(UpdatePosition); + } + + public override void _Input(InputEvent @event) + { + if (@event is InputEventKey keyEvent && keyEvent.Pressed) + { + var direction = GetInputDirection(keyEvent); + this.SendEvent(new PlayerInputEvent { Direction = direction }); + } + } + + private void UpdateHealthUI(int health) + { + // 更新UI显示 + var healthBar = GetNode("UI/HealthBar"); + healthBar.Value = (float)health / _playerModel.MaxHealth.Value * 100; + } + + private void UpdatePosition(Vector2 position) + { + // 更新玩家位置 + Position = position; + } + + private Vector2 GetInputDirection(InputEventKey keyEvent) + { + return keyEvent.Keycode switch + { + Key.W => Vector2.Up, + Key.S => Vector2.Down, + Key.A => Vector2.Left, + Key.D => Vector2.Right, + _ => Vector2.Zero + }; + } + } +} +``` + +### 2. 避免层次混乱 + +```csharp +// ❌ 避免:层次混乱 +public class PlayerController : Node, IController +{ + // 混合了数据层、业务逻辑层和控制层的职责 + public BindableProperty Health { get; } = new(100); // 数据层职责 + + public override void _Input(InputEvent @event) // 控制层职责 + { + if (@event is InputEventKey keyEvent && keyEvent.Pressed) + { + if (keyEvent.Keycode == Key.W) + { + Position += Vector2.Up * MovementSpeed; // 业务逻辑层职责 + } + + if (keyEvent.Keycode == Key.Space) + { + Health -= 10; // 业务逻辑层职责 + PlaySoundEffect(); // 业务逻辑层职责 + } + } + } + + // 这样会导致代码难以测试和维护 +} +``` + +## 依赖管理 + +### 1. 构造函数注入 + +```csharp +// ✅ 好的做法:构造函数注入 +public class PlayerCombatSystem : AbstractSystem +{ + private readonly PlayerModel _playerModel; + private readonly IWeaponService _weaponService; + private readonly ISoundService _soundService; + private readonly IEffectService _effectService; + + // 通过构造函数注入依赖 + public PlayerCombatSystem( + PlayerModel playerModel, + IWeaponService weaponService, + ISoundService soundService, + IEffectService effectService) + { + _playerModel = playerModel; + _weaponService = weaponService; + _soundService = soundService; + _effectService = effectService; + } + + protected override void OnInit() + { + this.RegisterEvent(OnAttack); + } + + private void OnAttack(AttackEvent e) + { + var weapon = _weaponService.GetEquippedWeapon(_playerModel); + var damage = _weaponService.CalculateDamage(weapon, e.Target); + + e.Target.TakeDamage(damage); + _soundService.PlayAttackSound(weapon.Type); + _effectService.PlayAttackEffect(_playerModel.Position, weapon.Type); + } +} + +// ❌ 避免:依赖注入容器 +public class PlayerCombatSystem : AbstractSystem +{ + private PlayerModel _playerModel; + private IWeaponService _weaponService; + private ISoundService _soundService; + private IEffectService _effectService; + + protected override void OnInit() + { + // 在运行时获取依赖,难以测试 + _playerModel = GetModel(); + _weaponService = GetService(); + _soundService = GetService(); + _effectService = GetService(); + } + + // 测试时难以模拟依赖 +} +``` + +### 2. 接口隔离 + +```csharp +// ✅ 好的做法:小而专注的接口 +public interface IMovementController +{ + void Move(Vector2 direction); + void Stop(); + bool CanMove(); +} + +public interface ICombatController +{ + void Attack(Entity target); + void Defend(); + bool CanAttack(); +} + +public interface IUIController +{ + void ShowHealthBar(); + void HideHealthBar(); + void UpdateHealthDisplay(int currentHealth, int maxHealth); +} + +public class PlayerController : Node, IMovementController, ICombatController, IUIController +{ + // 实现各个接口,职责清晰 +} + +// ❌ 避免:大而全的接口 +public interface IPlayerController +{ + void Move(Vector2 direction); + void Stop(); + void Attack(Entity target); + void Defend(); + void ShowHealthBar(); + void HideHealthBar(); + void UpdateHealthDisplay(int currentHealth, int maxHealth); + void SaveGame(); + void LoadGame(); + void Respawn(); + void PlayAnimation(string animationName); + void StopAnimation(); + // ... 更多方法,接口过于庞大 +} +``` + +## 事件系统设计 + +### 1. 事件命名和结构 + +```csharp +// ✅ 好的做法:清晰的事件命名和结构 +public struct PlayerHealthChangedEvent +{ + public int PreviousHealth { get; } + public int NewHealth { get; } + public int MaxHealth { get; } + public Vector3 DamagePosition { get; } + public DamageType DamageType { get; } +} + +public struct PlayerDiedEvent +{ + public Vector3 DeathPosition { get; } + public string CauseOfDeath { get; } + public TimeSpan SurvivalTime { get; } +} + +public struct WeaponEquippedEvent +{ + public string PlayerId { get; } + public WeaponType WeaponType { get; } + public string WeaponId { get; } +} + +// ❌ 避免:模糊的事件命名和结构 +public struct PlayerEvent +{ + public EventType Type { get; } + public object Data { get; } // 类型不安全 + public Dictionary Properties { get; } // 难以理解 +} +``` + +### 2. 事件处理职责 + +```csharp +// ✅ 好的做法:单一职责的事件处理 +public class UIHealthBarController : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnPlayerHealthChanged); + this.RegisterEvent(OnPlayerDied); + } + + private void OnPlayerHealthChanged(PlayerHealthChangedEvent e) + { + UpdateHealthBar(e.NewHealth, e.MaxHealth); + + if (e.NewHealth < e.PreviousHealth) + { + ShowDamageEffect(e.DamagePosition, e.PreviousHealth - e.NewHealth); + } + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + HideHealthBar(); + ShowDeathScreen(e.CauseOfDeath); + } +} + +public class AudioController : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnPlayerHealthChanged); + this.RegisterEvent(OnPlayerDied); + } + + private void OnPlayerHealthChanged(PlayerHealthChangedEvent e) + { + if (e.NewHealth < e.PreviousHealth) + { + PlayHurtSound(e.DamageType); + } + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + PlayDeathSound(); + } +} + +// ❌ 避免:一个处理器处理多种不相关的事件 +public class PlayerEventHandler : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnPlayerHealthChanged); + this.RegisterEvent(OnPlayerDied); + this.RegisterEvent(OnWeaponEquipped); + this.RegisterEvent(OnLevelUp); + // 注册太多事件,职责混乱 + } + + private void OnPlayerHealthChanged(PlayerHealthChangedEvent e) + { + UpdateUI(); // UI职责 + PlayAudio(); // 音频职责 + SaveStatistics(); // 存档职责 + UpdateAchievements(); // 成就系统职责 + // 一个事件处理器承担太多职责 + } +} +``` + +## 模块化架构 + +### 1. 模块边界清晰 + +```csharp +// ✅ 好的做法:清晰的模块边界 +public class AudioModule : AbstractModule +{ + // 模块只负责音频相关的功能 + public override void Install(IArchitecture architecture) + { + architecture.RegisterSystem(new AudioSystem()); + architecture.RegisterSystem(new MusicSystem()); + architecture.RegisterUtility(new AudioUtility()); + } +} + +public class InputModule : AbstractModule +{ + // 模块只负责输入相关的功能 + public override void Install(IArchitecture architecture) + { + architecture.RegisterSystem(new InputSystem()); + architecture.RegisterSystem(new InputMappingSystem()); + architecture.RegisterUtility(new InputUtility()); + } +} + +public class UIModule : AbstractModule +{ + // 模块只负责UI相关的功能 + public override void Install(IArchitecture architecture) + { + architecture.RegisterSystem(new UISystem()); + architecture.RegisterSystem(new HUDSystem()); + architecture.RegisterSystem(new MenuSystem()); + architecture.RegisterUtility(new UIUtility()); + } +} + +// ❌ 避免:模块职责混乱 +public class GameModule : AbstractModule +{ + public override void Install(IArchitecture architecture) + { + // 一个模块包含所有功能 + architecture.RegisterSystem(new AudioSystem()); // 音频 + architecture.RegisterSystem(new InputSystem()); // 输入 + architecture.RegisterSystem(new UISystem()); // UI + architecture.RegisterSystem(new CombatSystem()); // 战斗 + architecture.RegisterSystem(new InventorySystem()); // 背包 + architecture.RegisterSystem(new QuestSystem()); // 任务 + // 模块过于庞大,难以维护 + } +} +``` + +### 2. 模块间通信 + +```csharp +// ✅ 好的做法:通过事件进行模块间通信 +public class AudioModule : AbstractModule +{ + public override void Install(IArchitecture architecture) + { + architecture.RegisterSystem(new AudioSystem()); + } +} + +public class AudioSystem : AbstractSystem +{ + protected override void OnInit() + { + // 监听其他模块发送的事件 + this.RegisterEvent(OnPlayerAttack); + this.RegisterEvent(OnPlayerDied); + this.RegisterEvent(OnWeaponEquipped); + } + + private void OnPlayerAttack(PlayerAttackEvent e) + { + PlayAttackSound(e.WeaponType); + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + PlayDeathSound(); + } + + private void OnWeaponEquipped(WeaponEquippedEvent e) + { + PlayEquipSound(e.WeaponType); + } +} + +public class CombatModule : AbstractModule +{ + public override void Install(IArchitecture architecture) + { + architecture.RegisterSystem(new CombatSystem()); + } +} + +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnAttackInput); + } + + private void OnAttackInput(AttackInputEvent e) + { + ProcessAttack(e); + + // 发送事件通知其他模块 + SendEvent(new PlayerAttackEvent { + PlayerId = e.PlayerId, + WeaponType = GetPlayerWeaponType(e.PlayerId) + }); + } +} + +// ❌ 避免:模块间直接依赖 +public class CombatSystem : AbstractSystem +{ + private AudioSystem _audioSystem; // 直接依赖其他模块 + + protected override void OnInit() + { + // 直接获取其他模块的系统 + _audioSystem = GetSystem(); + } + + private void OnAttackInput(AttackInputEvent e) + { + ProcessAttack(e); + + // 直接调用其他模块的方法 + _audioSystem.PlayAttackSound(weaponType); + } +} +``` + +## 错误处理策略 + +### 1. 异常处理层次 + +```csharp +// ✅ 好的做法:分层异常处理 +public class GameApplicationException : Exception +{ + public string ErrorCode { get; } + public Dictionary Context { get; } + + public GameApplicationException(string message, string errorCode, + Dictionary context = null, Exception innerException = null) + : base(message, innerException) + { + ErrorCode = errorCode; + Context = context ?? new Dictionary(); + } +} + +public class PlayerException : GameApplicationException +{ + public PlayerException(string message, string errorCode, + Dictionary context = null, Exception innerException = null) + : base(message, errorCode, context, innerException) + { + } +} + +public class InventoryException : GameApplicationException +{ + public InventoryException(string message, string errorCode, + Dictionary context = null, Exception innerException = null) + : base(message, errorCode, context, innerException) + { + } +} + +// 在系统中的使用 +public class PlayerInventorySystem : AbstractSystem +{ + public void AddItem(string playerId, Item item) + { + try + { + ValidateItem(item); + CheckInventorySpace(playerId, item); + + AddItemToInventory(playerId, item); + + SendEvent(new ItemAddedEvent { PlayerId = playerId, Item = item }); + } + catch (ItemValidationException ex) + { + throw new InventoryException( + $"Failed to add item {item.Id} to player {playerId}", + "ITEM_VALIDATION_FAILED", + new Dictionary + { + ["playerId"] = playerId, + ["itemId"] = item.Id, + ["validationError"] = ex.Message + }, + ex + ); + } + catch (InventoryFullException ex) + { + throw new InventoryException( + $"Player {playerId} inventory is full", + "INVENTORY_FULL", + new Dictionary + { + ["playerId"] = playerId, + ["itemId"] = item.Id, + ["maxSlots"] = ex.MaxSlots, + ["currentSlots"] = ex.CurrentSlots + }, + ex + ); + } + catch (Exception ex) + { + // 捕获未知异常并包装 + throw new InventoryException( + $"Unexpected error adding item {item.Id} to player {playerId}", + "UNKNOWN_ERROR", + new Dictionary + { + ["playerId"] = playerId, + ["itemId"] = item.Id, + ["originalError"] = ex.Message + }, + ex + ); + } + } + + private void ValidateItem(Item item) + { + if (item == null) + throw new ItemValidationException("Item cannot be null"); + + if (string.IsNullOrEmpty(item.Id)) + throw new ItemValidationException("Item ID cannot be empty"); + + if (item.StackSize <= 0) + throw new ItemValidationException("Item stack size must be positive"); + } + + private void CheckInventorySpace(string playerId, Item item) + { + var inventory = GetPlayerInventory(playerId); + var requiredSpace = CalculateRequiredSpace(item); + + if (inventory.FreeSpace < requiredSpace) + { + throw new InventoryFullException( + inventory.FreeSpace, + inventory.MaxSlots + ); + } + } +} +``` + +### 2. 错误恢复策略 + +```csharp +// ✅ 好的做法:优雅的错误恢复 +public class SaveSystem : AbstractSystem +{ + private readonly IStorage _primaryStorage; + private readonly IStorage _backupStorage; + + public SaveSystem(IStorage primaryStorage, IStorage backupStorage = null) + { + _primaryStorage = primaryStorage; + _backupStorage = backupStorage ?? new LocalStorage("backup"); + } + + public async Task LoadSaveDataAsync(string saveId) + { + try + { + // 尝试从主存储加载 + return await _primaryStorage.ReadAsync(saveId); + } + catch (StorageException ex) + { + Logger.Warning($"Failed to load from primary storage: {ex.Message}"); + + try + { + // 尝试从备份存储加载 + var backupData = await _backupStorage.ReadAsync(saveId); + Logger.Info($"Successfully loaded from backup storage: {saveId}"); + + // 恢复到主存储 + await _primaryStorage.WriteAsync(saveId, backupData); + + return backupData; + } + catch (Exception backupEx) + { + Logger.Error($"Failed to load from backup storage: {backupEx.Message}"); + + // 返回默认存档数据 + return GetDefaultSaveData(); + } + } + } + + private SaveData GetDefaultSaveData() + { + Logger.Warning("Returning default save data due to loading failures"); + return new SaveData + { + PlayerId = "default", + Level = 1, + Health = 100, + Position = Vector3.Zero, + CreatedAt = DateTime.UtcNow + }; + } +} + +// ❌ 避免:粗暴的错误处理 +public class SaveSystem : AbstractSystem +{ + public async Task LoadSaveDataAsync(string saveId) + { + try + { + return await _storage.ReadAsync(saveId); + } + catch (Exception ex) + { + // 直接抛出异常,不提供恢复机制 + throw new Exception($"Failed to load save: {ex.Message}", ex); + } + } +} +``` + +## 测试策略 + +### 1. 可测试的架构设计 + +```csharp +// ✅ 好的做法:可测试的架构 +public interface IPlayerMovementService +{ + void MovePlayer(string playerId, Vector2 direction); + bool CanPlayerMove(string playerId); +} + +public class PlayerMovementService : IPlayerMovementService +{ + private readonly IPlayerRepository _playerRepository; + private readonly ICollisionService _collisionService; + private readonly IMapService _mapService; + + public PlayerMovementService( + IPlayerRepository playerRepository, + ICollisionService collisionService, + IMapService mapService) + { + _playerRepository = playerRepository; + _collisionService = collisionService; + _mapService = mapService; + } + + public void MovePlayer(string playerId, Vector2 direction) + { + if (!CanPlayerMove(playerId)) + return; + + var player = _playerRepository.GetById(playerId); + var newPosition = player.Position + direction * player.Speed; + + if (_collisionService.CanMoveTo(newPosition)) + { + player.Position = newPosition; + _playerRepository.Update(player); + } + } + + public bool CanPlayerMove(string playerId) + { + var player = _playerRepository.GetById(playerId); + return player != null && player.IsAlive && !player.IsStunned; + } +} + +// 测试代码 +[TestFixture] +public class PlayerMovementServiceTests +{ + private Mock _mockPlayerRepository; + private Mock _mockCollisionService; + private Mock _mockMapService; + private PlayerMovementService _movementService; + + [SetUp] + public void Setup() + { + _mockPlayerRepository = new Mock(); + _mockCollisionService = new Mock(); + _mockMapService = new Mock(); + + _movementService = new PlayerMovementService( + _mockPlayerRepository.Object, + _mockCollisionService.Object, + _mockMapService.Object + ); + } + + [Test] + public void MovePlayer_ValidMovement_ShouldUpdatePlayerPosition() + { + // Arrange + var playerId = "player1"; + var player = new Player { Id = playerId, Position = Vector2.Zero, Speed = 5.0f }; + var direction = Vector2.Right; + + _mockPlayerRepository.Setup(r => r.GetById(playerId)).Returns(player); + _mockCollisionService.Setup(c => c.CanMoveTo(It.IsAny())).Returns(true); + + // Act + _movementService.MovePlayer(playerId, direction); + + // Assert + _mockPlayerRepository.Verify(r => r.Update(It.Is(p => p.Position == Vector2.Right * 5.0f)), Times.Once); + } + + [Test] + public void MovePlayer_CollisionBlocked_ShouldNotUpdatePlayerPosition() + { + // Arrange + var playerId = "player1"; + var player = new Player { Id = playerId, Position = Vector2.Zero, Speed = 5.0f }; + var direction = Vector2.Right; + + _mockPlayerRepository.Setup(r => r.GetById(playerId)).Returns(player); + _mockCollisionService.Setup(c => c.CanMoveTo(It.IsAny())).Returns(false); + + // Act + _movementService.MovePlayer(playerId, direction); + + // Assert + _mockPlayerRepository.Verify(r => r.Update(It.IsAny()), Times.Never); + } +} + +// ❌ 避免:难以测试的设计 +public class PlayerMovementSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnMovementInput); + } + + private void OnMovementInput(MovementInputEvent e) + { + var player = GetModel(); // 依赖架构,难以测试 + var newPosition = player.Position + e.Direction * player.Speed; + + if (CanMoveTo(newPosition)) // 私有方法,难以直接测试 + { + player.Position = newPosition; + } + } + + private bool CanMoveTo(Vector2 position) + { + // 复杂的碰撞检测逻辑,难以测试 + return true; + } +} +``` + +## 重构指南 + +### 1. 识别代码异味 + +```csharp +// ❌ 代码异味:长方法、重复代码、上帝类 +public class GameManager : Node +{ + public void ProcessPlayerInput(InputEvent @event) + { + // 长方法 - 做太多事情 + if (@event is InputEventKey keyEvent && keyEvent.Pressed) + { + switch (keyEvent.Keycode) + { + case Key.W: + MovePlayer(Vector2.Up); + PlayFootstepSound(); + UpdatePlayerAnimation("walk_up"); + CheckPlayerCollisions(); + UpdateCameraPosition(); + SavePlayerPosition(); + break; + case Key.S: + MovePlayer(Vector2.Down); + PlayFootstepSound(); + UpdatePlayerAnimation("walk_down"); + CheckPlayerCollisions(); + UpdateCameraPosition(); + SavePlayerPosition(); + break; + // 重复代码 + } + } + } + + private void MovePlayer(Vector2 direction) + { + Player.Position += direction * Player.Speed; + } + + private void PlayFootstepSound() + { + AudioPlayer.Play("footstep.wav"); + } + + // ... 更多方法,类过于庞大 +} + +// ✅ 重构后:职责分离 +public class PlayerInputController : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnInput); + } + + private void OnInput(InputEvent e) + { + if (e is InputEventKey keyEvent && keyEvent.Pressed) + { + var direction = GetDirectionFromKey(keyEvent.Keycode); + if (direction != Vector2.Zero) + { + SendEvent(new PlayerMoveEvent { Direction = direction }); + } + } + } + + private Vector2 GetDirectionFromKey(Key keycode) + { + return keycode switch + { + Key.W => Vector2.Up, + Key.S => Vector2.Down, + Key.A => Vector2.Left, + Key.D => Vector2.Right, + _ => Vector2.Zero + }; + } +} + +public class PlayerMovementSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnPlayerMove); + } + + private void OnPlayerMove(PlayerMoveEvent e) + { + var playerModel = GetModel(); + var newPosition = playerModel.Position + e.Direction * playerModel.Speed; + + if (CanMoveTo(newPosition)) + { + playerModel.Position = newPosition; + SendEvent(new PlayerMovedEvent { NewPosition = newPosition }); + } + } + + private bool CanMoveTo(Vector2 position) + { + var collisionService = GetUtility(); + return collisionService.CanMoveTo(position); + } +} + +public class AudioSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnPlayerMoved); + } + + private void OnPlayerMoved(PlayerMovedEvent e) + { + PlayFootstepSound(); + } + + private void PlayFootstepSound() + { + var audioUtility = GetUtility(); + audioUtility.PlaySound("footstep.wav"); + } +} + +public class AnimationSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnPlayerMoved); + } + + private void OnPlayerMoved(PlayerMovedEvent e) + { + var animationName = GetAnimationNameFromDirection(e.Direction); + SendEvent(new PlayAnimationEvent { AnimationName = animationName }); + } + + private string GetAnimationNameFromDirection(Vector2 direction) + { + if (direction == Vector2.Up) return "walk_up"; + if (direction == Vector2.Down) return "walk_down"; + if (direction == Vector2.Left) return "walk_left"; + if (direction == Vector2.Right) return "walk_right"; + return "idle"; + } +} +``` + +### 2. 渐进式重构 + +```csharp +// 第一步:提取重复代码 +public class PlayerController : Node +{ + public void ProcessInput(InputEvent @event) + { + if (@event is InputEventKey keyEvent && keyEvent.Pressed) + { + Vector2 direction; + switch (keyEvent.Keycode) + { + case Key.W: + direction = Vector2.Up; + break; + case Key.S: + direction = Vector2.Down; + break; + case Key.A: + direction = Vector2.Left; + break; + case Key.D: + direction = Vector2.Right; + break; + default: + return; + } + + MovePlayer(direction); + } + } +} + +// 第二步:提取方法 +public class PlayerController : Node +{ + public void ProcessInput(InputEvent @event) + { + if (@event is InputEventKey keyEvent && keyEvent.Pressed) + { + var direction = GetDirectionFromKey(keyEvent.Keycode); + if (direction != Vector2.Zero) + { + MovePlayer(direction); + } + } + } + + private Vector2 GetDirectionFromKey(Key keycode) + { + return keycode switch + { + Key.W => Vector2.Up, + Key.S => Vector2.Down, + Key.A => Vector2.Left, + Key.D => Vector2.Right, + _ => Vector2.Zero + }; + } +} + +// 第三步:引入系统和事件 +public class PlayerController : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnInput); + } + + private void OnInput(InputEvent e) + { + if (e is InputEventKey keyEvent && keyEvent.Pressed) + { + var direction = GetDirectionFromKey(keyEvent.Keycode); + if (direction != Vector2.Zero) + { + SendEvent(new PlayerMoveEvent { Direction = direction }); + } + } + } + + private Vector2 GetDirectionFromKey(Key keycode) + { + return keycode switch + { + Key.W => Vector2.Up, + Key.S => Vector2.Down, + Key.A => Vector2.Left, + Key.D => Vector2.Right, + _ => Vector2.Zero + }; + } +} +``` + +--- + +## 模式选择与组合 + +### 何时使用哪种模式? + +#### 小型项目(原型、Demo) + +```csharp +// 推荐组合:MVC + 事件驱动 + 服务定位器 +public class SimpleGameArchitecture : Architecture +{ + protected override void Init() + { + // Model + RegisterModel(new PlayerModel()); + RegisterModel(new GameModel()); + + // System + RegisterSystem(new GameplaySystem()); + RegisterSystem(new AudioSystem()); + + // 使用服务定位器模式 + // Controller 通过 this.GetModel/GetSystem 获取服务 + } +} +``` + +**优势**: + +- 快速开发 +- 代码简洁 +- 易于理解 + +#### 中型项目(独立游戏) + +```csharp +// 推荐组合:MVC + MVVM + 命令/查询 + 事件驱动 + 依赖注入 +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 注册 Utility(依赖注入) + RegisterUtility<IStorageService>(new LocalStorageService()); + RegisterUtility<IAudioService>(new GodotAudioService()); + + // 注册 Model(MVVM) + RegisterModel(new PlayerModel()); + RegisterModel(new PlayerViewModel()); + + // 注册 System(命令/查询处理) + RegisterSystem(new CombatSystem()); + RegisterSystem(new InventorySystem()); + + // 状态机 + var stateMachine = new StateMachineSystem(); + stateMachine + .Register(new MenuState()) + .Register(new GameplayState()); + RegisterSystem<IStateMachineSystem>(stateMachine); + } +} +``` + +**优势**: + +- 职责清晰 +- 易于测试 +- 支持团队协作 + +#### 大型项目(商业游戏) + +```csharp +// 推荐组合:所有模式 + 模块化架构 +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 模块化安装 + InstallModule(new CoreModule()); + InstallModule(new AudioModule()); + InstallModule(new NetworkModule()); + InstallModule(new UIModule()); + InstallModule(new GameplayModule()); + } +} + +// 核心模块 +public class CoreModule : IArchitectureModule +{ + public void Install(IArchitecture architecture) + { + // 依赖注入 + architecture.RegisterUtility<IStorageService>(new CloudStorageService()); + architecture.RegisterUtility<IAnalyticsService>(new AnalyticsService()); + + // 对象池 + architecture.RegisterSystem(new BulletPoolSystem()); + architecture.RegisterSystem(new ParticlePoolSystem()); + } +} + +// 游戏玩法模块 +public class GameplayModule : IArchitectureModule +{ + public void Install(IArchitecture architecture) + { + // Model + architecture.RegisterModel(new PlayerModel()); + architecture.RegisterModel(new EnemyModel()); + + // System(使用命令/查询模式) + architecture.RegisterSystem(new CombatSystem()); + architecture.RegisterSystem(new MovementSystem()); + + // 状态机 + var stateMachine = new StateMachineSystem(); + stateMachine + .Register(new MenuState()) + .Register(new LoadingState()) + .Register(new GameplayState()) + .Register(new PauseState()) + .Register(new GameOverState()); + architecture.RegisterSystem<IStateMachineSystem>(stateMachine); + } +} +``` + +**优势**: + +- 高度模块化 +- 易于维护和扩展 +- 支持大型团队 +- 完善的测试覆盖 + +### 模式组合示例 + +#### 组合 1:MVVM + 命令模式 + +```csharp +// ViewModel +public class ShopViewModel : AbstractModel +{ + public BindableProperty<int> PlayerGold { get; } = new(1000); + public BindableProperty<bool> CanBuy { get; } = new(true); + public BindableProperty<string> StatusMessage { get; } = new(""); + + protected override void OnInit() + { + // 监听购买事件 + this.RegisterEvent<ItemPurchasedEvent>(OnItemPurchased); + this.RegisterEvent<InsufficientGoldEvent>(OnInsufficientGold); + + // 监听金币变化 + PlayerGold.Register(gold => + { + CanBuy.Value = gold >= 100; + }); + } + + private void OnItemPurchased(ItemPurchasedEvent e) + { + PlayerGold.Value -= e.Cost; + StatusMessage.Value = $"购买成功:{e.ItemName}"; + } + + private void OnInsufficientGold(InsufficientGoldEvent e) + { + StatusMessage.Value = "金币不足!"; + } +} + +// View +public class ShopView : Control +{ + private ShopViewModel _viewModel; + + public override void _Ready() + { + _viewModel = GetModel<ShopViewModel>(); + + // 数据绑定 + _viewModel.PlayerGold.Register(gold => + { + GetNode<Label>("GoldLabel").Text = $"金币:{gold}"; + }); + + _viewModel.CanBuy.Register(canBuy => + { + GetNode<Button>("BuyButton").Disabled = !canBuy; + }); + + _viewModel.StatusMessage.Register(msg => + { + GetNode<Label>("StatusLabel").Text = msg; + }); + } + + private void OnBuyButtonPressed() + { + // 发送命令 + this.SendCommand(new BuyItemCommand + { + Input = new BuyItemInput { ItemId = "sword_01" } + }); + } +} +``` + +#### 组合 2:状态模式 + 对象池 + +```csharp +// 游戏状态使用对象池 +public class GameplayState : ContextAwareStateBase +{ + private BulletPoolSystem _bulletPool; + private ParticlePoolSystem _particlePool; + + public override void OnEnter(IState from) + { + // 获取对象池 + _bulletPool = this.GetSystem<BulletPoolSystem>(); + _particlePool = this.GetSystem<ParticlePoolSystem>(); + + // 预热对象池 + _bulletPool.PrewarmPool(100); + _particlePool.PrewarmPool(200); + + // 注册事件 + this.RegisterEvent<FireWeaponEvent>(OnFireWeapon); + } + + private void OnFireWeapon(FireWeaponEvent e) + { + // 从池中获取子弹 + var bullet = _bulletPool.Spawn(); + bullet.Position = e.Position; + bullet.Direction = e.Direction; + + // 生成粒子效果 + var particle = _particlePool.Spawn(); + particle.Position = e.Position; + } + + public override void OnExit(IState to) + { + // 回收所有对象 + _bulletPool.RecycleAll(); + _particlePool.RecycleAll(); + } +} +``` + +#### 组合 3:事件驱动 + 查询模式 + +```csharp +// 成就系统:监听事件,使用查询验证条件 +public class AchievementSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent<EnemyKilledEvent>(OnEnemyKilled); + this.RegisterEvent<LevelCompletedEvent>(OnLevelCompleted); + } + + private void OnEnemyKilled(EnemyKilledEvent e) + { + // 查询击杀总数 + var query = new GetTotalKillsQuery { Input = new EmptyQueryInput() }; + var totalKills = this.SendQuery(query); + + // 检查成就 + if (totalKills == 100) + { + UnlockAchievement("kill_100_enemies"); + } + } + + private void OnLevelCompleted(LevelCompletedEvent e) + { + // 查询是否满足完美通关条件 + var query = new IsPerfectClearQuery + { + Input = new IsPerfectClearInput { LevelId = e.LevelId } + }; + var isPerfect = this.SendQuery(query); + + if (isPerfect) + { + UnlockAchievement($"perfect_clear_level_{e.LevelId}"); + } + } +} +``` + +### 模式选择决策树 + +``` +需要管理游戏状态? +├─ 是 → 使用状态模式 +└─ 否 → 继续 + +需要频繁创建/销毁对象? +├─ 是 → 使用对象池模式 +└─ 否 → 继续 + +需要解耦组件通信? +├─ 是 → 使用事件驱动模式 +└─ 否 → 继续 + +需要封装操作? +├─ 是 → 使用命令模式 +└─ 否 → 继续 + +需要分离读写操作? +├─ 是 → 使用查询模式(CQRS) +└─ 否 → 继续 + +需要数据绑定和响应式 UI? +├─ 是 → 使用 MVVM 模式 +└─ 否 → 使用 MVC 模式 + +需要管理依赖? +├─ 大型项目 → 使用依赖注入 +└─ 小型项目 → 使用服务定位器 +``` + +## 常见问题 + +### Q1: 应该使用 MVC 还是 MVVM? + +**A**: 取决于项目需求: + +- **使用 MVC**: + - 简单的 UI 更新 + - 不需要复杂的数据绑定 + - 快速原型开发 + +- **使用 MVVM**: + - 复杂的数据驱动 UI + - 需要自动更新界面 + - 大量计算属性 + +**推荐**:可以混合使用,简单界面用 MVC,复杂界面用 MVVM。 + +### Q2: 命令模式和查询模式有什么区别? + +**A**: + +| 特性 | 命令模式 | 查询模式 | +|---------|----------------|---------------------| +| **目的** | 修改状态 | 读取数据 | +| **返回值** | 可选 | 必须有 | +| **副作用** | 有 | 无 | +| **示例** | BuyItemCommand | GetPlayerStatsQuery | + +**原则**:命令改变状态,查询读取状态,两者不混用。 + +### Q3: 何时使用事件,何时使用命令? + +**A**: + +- **使用事件**: + - 通知状态变化 + - 一对多通信 + - 跨模块通信 + - 不关心处理结果 + +- **使用命令**: + - 执行具体操作 + - 需要封装逻辑 + - 需要撤销/重做 + - 需要返回结果 + +**示例**: + +```csharp +// 使用命令执行操作 +this.SendCommand(new BuyItemCommand()); + +// 使用事件通知结果 +this.SendEvent(new ItemPurchasedEvent()); +``` + +### Q4: 依赖注入和服务定位器哪个更好? + +**A**: + +- **依赖注入**: + - ✅ 依赖关系明确 + - ✅ 易于测试 + - ✅ 编译时检查 + - ❌ 配置复杂 + +- **服务定位器**: + - ✅ 简单直接 + - ✅ 易于使用 + - ❌ 依赖隐式 + - ❌ 难以测试 + +**推荐**: + +- 小项目:服务定位器 +- 大项目:依赖注入 +- 混合使用:核心服务用依赖注入,辅助服务用服务定位器 + +### Q5: 对象池适合哪些场景? + +**A**: + +**适合**: + +- 频繁创建/销毁的对象(子弹、粒子) +- 创建成本高的对象(网络连接) +- 需要稳定帧率的场景 + +**不适合**: + +- 创建频率低的对象 +- 对象状态复杂难以重置 +- 内存受限的场景 + +**示例**: + +```csharp +// ✅ 适合使用对象池 +- 子弹、导弹 +- 粒子效果 +- UI 元素(列表项) +- 音效播放器 + +// ❌ 不适合使用对象池 +- 玩家角色 +- 关卡数据 +- 配置对象 +``` + +### Q6: 状态机和简单的 if-else 有什么区别? + +**A**: + +**简单 if-else**: + +```csharp +// ❌ 难以维护 +public void Update() +{ + if (gameState == GameState.Menu) + { + UpdateMenu(); + } + else if (gameState == GameState.Playing) + { + UpdateGameplay(); + } + else if (gameState == GameState.Paused) + { + UpdatePause(); + } + // 状态逻辑分散,难以管理 +} +``` + +**状态机**: + +```csharp +// ✅ 清晰易维护 +public class MenuState : ContextAwareStateBase +{ + public override void OnEnter(IState from) + { + // 进入菜单的所有逻辑集中在这里 + } + + public override void OnExit(IState to) + { + // 退出菜单的所有逻辑集中在这里 + } +} +``` + +**优势**: + +- 状态逻辑封装 +- 易于添加新状态 +- 支持状态转换验证 +- 支持状态历史 + +### Q7: 如何避免过度设计? + +**A**: + +**原则**: + +1. **从简单开始**:先用最简单的方案 +2. **按需重构**:遇到问题再优化 +3. **YAGNI 原则**:You Aren't Gonna Need It + +**示例**: + +```csharp +// 第一版:简单直接 +public class Player +{ + public int Health = 100; +} + +// 第二版:需要通知时添加事件 +public class Player +{ + private int _health = 100; + public int Health + { + get => _health; + set + { + _health = value; + OnHealthChanged?.Invoke(value); + } + } + public event Action<int> OnHealthChanged; +} + +// 第三版:需要更多功能时使用 BindableProperty +public class PlayerModel : AbstractModel +{ + public BindableProperty<int> Health { get; } = new(100); +} +``` + +### Q8: 如何在现有项目中引入这些模式? + +**A**: + +**渐进式重构**: + +1. **第一步:引入事件系统** + ```csharp + // 替换直接调用为事件 + // 之前:uiManager.UpdateHealth(health); + // 之后:SendEvent(new HealthChangedEvent { Health = health }); + ``` + +2. **第二步:提取 Model** + ```csharp + // 将数据从各处集中到 Model + public class PlayerModel : AbstractModel + { + public BindableProperty<int> Health { get; } = new(100); + } + ``` + +3. **第三步:引入命令模式** + ```csharp + // 封装操作为命令 + public class HealPlayerCommand : AbstractCommand + { + protected override void OnExecute() + { + var player = this.GetModel<PlayerModel>(); + player.Health.Value = player.MaxHealth.Value; + } + } + ``` + +4. **第四步:添加查询模式** + ```csharp + // 分离读操作 + public class GetPlayerStatsQuery : AbstractQuery<PlayerStats> + { + protected override PlayerStats OnDo() + { + // 查询逻辑 + } + } + ``` + +### Q9: 性能会受到影响吗? + +**A**: + +**影响很小**: + +- 事件系统:微秒级开销 +- 命令/查询:几乎无开销 +- IoC 容器:字典查找,O(1) + +**优化建议**: + +1. **避免频繁事件**:不要每帧发送事件 +2. **缓存查询结果**:复杂查询结果可以缓存 +3. **使用对象池**:减少 GC 压力 +4. **批量操作**:合并多个小操作 + +**性能对比**: + +```csharp +// 直接调用:~1ns +player.Health = 100; + +// 通过命令:~100ns +SendCommand(new SetHealthCommand { Health = 100 }); + +// 差异可以忽略不计,但带来了更好的架构 +``` + +### Q10: 如何测试使用这些模式的代码? + +**A**: + +**单元测试示例**: + +```csharp +[Test] +public void BuyItemCommand_InsufficientGold_ShouldNotBuyItem() +{ + // Arrange + var architecture = new TestArchitecture(); + var playerModel = new PlayerModel(); + playerModel.Gold.Value = 50; // 金币不足 + architecture.RegisterModel(playerModel); + + var command = new BuyItemCommand + { + Input = new BuyItemInput { ItemId = "sword", Price = 100 } + }; + command.SetArchitecture(architecture); + + // Act + command.Execute(); + + // Assert + Assert.AreEqual(50, playerModel.Gold.Value); // 金币未变化 +} + +[Test] +public void GetPlayerStatsQuery_ShouldReturnCorrectStats() +{ + // Arrange + var architecture = new TestArchitecture(); + var playerModel = new PlayerModel(); + playerModel.Level.Value = 10; + playerModel.Health.Value = 80; + architecture.RegisterModel(playerModel); + + var query = new GetPlayerStatsQuery(); + query.SetArchitecture(architecture); + + // Act + var stats = query.Do(); + + // Assert + Assert.AreEqual(10, stats.Level); + Assert.AreEqual(80, stats.Health); +} +``` + +--- + +## 总结 + +遵循这些架构模式最佳实践,你将能够构建: + +- ✅ **清晰的代码结构** - 易于理解和维护 +- ✅ **松耦合的组件** - 便于测试和扩展 +- ✅ **可重用的模块** - 提高开发效率 +- ✅ **健壮的错误处理** - 提高系统稳定性 +- ✅ **完善的测试覆盖** - 保证代码质量 + +### 关键要点 + +1. **从简单开始**:不要过度设计,按需添加模式 +2. **理解每个模式的适用场景**:选择合适的模式解决问题 +3. **模式可以组合使用**:发挥各自优势 +4. **持续重构**:随着项目发展优化架构 +5. **注重可测试性**:好的架构应该易于测试 + +### 推荐学习路径 + +1. **入门**:MVC + 事件驱动 +2. **进阶**:命令模式 + 查询模式 + MVVM +3. **高级**:状态模式 + 对象池 + 依赖注入 +4. **专家**:模块化架构 + 所有模式组合 + +### 相关资源 + +- [架构核心文档](/zh-CN/core/architecture) +- [命令模式文档](/zh-CN/core/command) +- [查询模式文档](/zh-CN/core/query) +- [事件系统文档](/zh-CN/core/events) +- [状态机文档](/zh-CN/core/state-machine) +- [IoC 容器文档](/zh-CN/core/ioc) + +记住,好的架构不是一蹴而就的,需要持续的学习、实践和改进。 + +--- + +**文档版本**: 2.0.0 +**最后更新**: 2026-03-07 **作者**: GFramework Team \ No newline at end of file diff --git a/docs/zh-CN/best-practices/index.md b/docs/zh-CN/best-practices/index.md index 4b76286..6eb1b22 100644 --- a/docs/zh-CN/best-practices/index.md +++ b/docs/zh-CN/best-practices/index.md @@ -1,483 +1,491 @@ -# 最佳实践 - -本文档总结了使用 GFramework 的最佳实践和设计模式。 - -## 架构设计 - -### 1. 清晰的职责分离 - -**原则**:每一层都有明确的职责,不要混淆。 - -```csharp -// ✅ 正确的职责分离 -public class PlayerModel : AbstractModel -{ - // Model:只存储数据 - public BindableProperty Health { get; } = new(100); - public BindableProperty Score { get; } = new(0); -} - -public class CombatSystem : AbstractSystem -{ - // System:处理业务逻辑 - protected override void OnInit() - { - this.RegisterEvent(OnAttack); - } - - private void OnAttack(AttackEvent e) - { - var player = this.GetModel(); - player.Health.Value -= e.Damage; - } -} - -public class PlayerController : IController -{ - // Controller:连接 UI 和逻辑 - public void Initialize() - { - var player = _architecture.GetModel(); - player.Health.RegisterWithInitValue(OnHealthChanged); - } - - private void OnHealthChanged(int health) - { - UpdateHealthDisplay(health); - } -} -``` - -### 2. 事件驱动设计 - -**原则**:使用事件解耦组件,避免直接调用。 - -```csharp -// ❌ 紧耦合 -public class SystemA : AbstractSystem -{ - private void OnEvent(EventA e) - { - var systemB = this.GetSystem(); - systemB.DoSomething(); // 直接调用 - } -} - -// ✅ 松耦合 -public class SystemA : AbstractSystem -{ - private void OnEvent(EventA e) - { - this.SendEvent(new EventB()); // 发送事件 - } -} - -public class SystemB : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnEventB); - } -} -``` - -### 3. 命令查询分离 - -**原则**:明确区分修改状态(Command)和查询状态(Query)。 - -```csharp -// ✅ 正确的 CQRS -public class MovePlayerCommand : AbstractCommand -{ - public Vector2 Direction { get; set; } - - protected override void OnDo() - { - // 修改状态 - this.SendEvent(new PlayerMovedEvent { Direction = Direction }); - } -} - -public class GetPlayerPositionQuery : AbstractQuery -{ - protected override Vector2 OnDo() - { - // 只查询,不修改 - return this.GetModel().Position.Value; - } -} -``` - -## 代码组织 - -### 1. 项目结构 - -``` -GameProject/ -├── Models/ -│ ├── PlayerModel.cs -│ ├── GameStateModel.cs -│ └── InventoryModel.cs -├── Systems/ -│ ├── CombatSystem.cs -│ ├── InventorySystem.cs -│ └── GameLogicSystem.cs -├── Commands/ -│ ├── AttackCommand.cs -│ ├── MoveCommand.cs -│ └── UseItemCommand.cs -├── Queries/ -│ ├── GetPlayerHealthQuery.cs -│ └── GetInventoryItemsQuery.cs -├── Events/ -│ ├── PlayerDiedEvent.cs -│ ├── ItemUsedEvent.cs -│ └── EnemyDamagedEvent.cs -├── Controllers/ -│ ├── PlayerController.cs -│ └── UIController.cs -├── Utilities/ -│ ├── StorageUtility.cs -│ └── MathUtility.cs -└── GameArchitecture.cs -``` - -### 2. 命名规范 - -```csharp -// Models:使用 Model 后缀 -public class PlayerModel : AbstractModel { } -public class GameStateModel : AbstractModel { } - -// Systems:使用 System 后缀 -public class CombatSystem : AbstractSystem { } -public class InventorySystem : AbstractSystem { } - -// Commands:使用 Command 后缀 -public class AttackCommand : AbstractCommand { } -public class MoveCommand : AbstractCommand { } - -// Queries:使用 Query 后缀 -public class GetPlayerHealthQuery : AbstractQuery { } -public class GetInventoryItemsQuery : AbstractQuery> { } - -// Events:使用 Event 后缀 -public class PlayerDiedEvent : IEvent { } -public class ItemUsedEvent : IEvent { } - -// Controllers:使用 Controller 后缀 -public class PlayerController : IController { } - -// Utilities:使用 Utility 后缀 -public class StorageUtility : IUtility { } -``` - -## 内存管理 - -### 1. 正确的注销管理 - -```csharp -public class MyController : IController -{ - private IUnRegisterList _unregisterList = new UnRegisterList(); - - public void Initialize() - { - var model = _architecture.GetModel(); - - // 注册事件并添加到注销列表 - this.RegisterEvent(OnPlayerDied) - .AddToUnregisterList(_unregisterList); - - // 注册属性监听并添加到注销列表 - model.Health.Register(OnHealthChanged) - .AddToUnregisterList(_unregisterList); - } - - public void Cleanup() - { - // 统一注销所有监听器 - _unregisterList.UnRegisterAll(); - } - - private void OnPlayerDied(PlayerDiedEvent e) { } - private void OnHealthChanged(int health) { } -} -``` - -### 2. 生命周期管理 - -```csharp -public class GameManager -{ - private GameArchitecture _architecture; - - public void StartGame() - { - _architecture = new GameArchitecture(); - _architecture.Initialize(); - } - - public void EndGame() - { - // 销毁架构,自动清理所有组件 - _architecture.Destroy(); - _architecture = null; - } -} -``` - -## 性能优化 - -### 1. 缓存组件引用 - -```csharp -// ❌ 低效:每次都查询 -public void Update() -{ - var model = _architecture.GetModel(); - model.Health.Value -= 1; -} - -// ✅ 高效:缓存引用 -private PlayerModel _playerModel; - -public void Initialize() -{ - _playerModel = _architecture.GetModel(); -} - -public void Update() -{ - _playerModel.Health.Value -= 1; -} -``` - -### 2. 避免频繁的事件创建 - -```csharp -// ❌ 低效:每帧创建新事件 -public void Update() -{ - this.SendEvent(new UpdateEvent()); // 频繁分配内存 -} - -// ✅ 高效:复用事件或使用对象池 -private UpdateEvent _updateEvent = new UpdateEvent(); - -public void Update() -{ - this.SendEvent(_updateEvent); -} -``` - -### 3. 异步处理重操作 - -```csharp -public class LoadDataCommand : AbstractCommand -{ - protected override async void OnDo() - { - // 异步加载数据,不阻塞主线程 - var data = await LoadDataAsync(); - this.SendEvent(new DataLoadedEvent { Data = data }); - } - - private async Task LoadDataAsync() - { - return await Task.Run(() => - { - // 耗时操作 - return new Data(); - }); - } -} -``` - -## 测试 - -### 1. 单元测试 - -```csharp -[TestFixture] -public class CombatSystemTests -{ - private GameArchitecture _architecture; - private PlayerModel _playerModel; - - [SetUp] - public void Setup() - { - _architecture = new TestArchitecture(); - _architecture.Initialize(); - _playerModel = _architecture.GetModel(); - } - - [TearDown] - public void Teardown() - { - _architecture.Destroy(); - } - - [Test] - public void PlayerTakeDamage_ReducesHealth() - { - _playerModel.Health.Value = 100; - _architecture.SendEvent(new DamageEvent { Amount = 10 }); - Assert.AreEqual(90, _playerModel.Health.Value); - } - - [Test] - public void PlayerDies_WhenHealthReachesZero() - { - _playerModel.Health.Value = 10; - _architecture.SendEvent(new DamageEvent { Amount = 10 }); - Assert.AreEqual(0, _playerModel.Health.Value); - } -} -``` - -### 2. 集成测试 - -```csharp -[TestFixture] -public class GameFlowTests -{ - private GameArchitecture _architecture; - - [SetUp] - public void Setup() - { - _architecture = new GameArchitecture(); - _architecture.Initialize(); - } - - [Test] - public void CompleteGameFlow() - { - // 初始化 - var player = _architecture.GetModel(); - Assert.AreEqual(100, player.Health.Value); - - // 执行操作 - _architecture.SendCommand(new AttackCommand { Damage = 20 }); - - // 验证结果 - Assert.AreEqual(80, player.Health.Value); - } -} -``` - -## 文档 - -### 1. 代码注释 - -```csharp -/// -/// 玩家模型,存储玩家的所有状态数据 -/// -public class PlayerModel : AbstractModel -{ - /// - /// 玩家的生命值,使用 BindableProperty 实现响应式更新 - /// - public BindableProperty Health { get; } = new(100); - - protected override void OnInit() - { - // 监听生命值变化,当生命值为 0 时发送死亡事件 - Health.Register(hp => - { - if (hp <= 0) - this.SendEvent(new PlayerDiedEvent()); - }); - } -} -``` - -### 2. 架构文档 - -为你的项目编写架构文档,说明: - -- 主要的 Model、System、Command、Query -- 关键事件流 -- 组件间的通信方式 -- 扩展点和插件机制 - -## 常见陷阱 - -### 1. 在 Model 中包含业务逻辑 - -```csharp -// ❌ 错误 -public class PlayerModel : AbstractModel -{ - public void TakeDamage(int damage) - { - Health.Value -= damage; - if (Health.Value <= 0) - Die(); - } -} - -// ✅ 正确 -public class CombatSystem : AbstractSystem -{ - private void OnDamage(DamageEvent e) - { - var player = this.GetModel(); - player.Health.Value -= e.Amount; - } -} -``` - -### 2. 忘记注销监听器 - -```csharp -// ❌ 错误:可能导致内存泄漏 -public void Initialize() -{ - this.RegisterEvent(OnEvent1); // 未注销 -} - -// ✅ 正确 -private IUnRegisterList _unregisterList = new UnRegisterList(); - -public void Initialize() -{ - this.RegisterEvent(OnEvent1) - .AddToUnregisterList(_unregisterList); -} - -public void Cleanup() -{ - _unregisterList.UnRegisterAll(); -} -``` - -### 3. 直接调用其他系统 - -```csharp -// ❌ 错误:紧耦合 -public class SystemA : AbstractSystem -{ - private void OnEvent(EventA e) - { - var systemB = this.GetSystem(); - systemB.DoSomething(); - } -} - -// ✅ 正确:使用事件解耦 -public class SystemA : AbstractSystem -{ - private void OnEvent(EventA e) - { - this.SendEvent(new EventB()); - } -} -``` - ---- - -遵循这些最佳实践将帮助你构建可维护、高效、可扩展的应用程序。 +# 最佳实践 + +本文档总结了使用 GFramework 的最佳实践和设计模式。 + +## 架构设计 + +### 1. 清晰的职责分离 + +**原则**:每一层都有明确的职责,不要混淆。 + +```csharp +// ✅ 正确的职责分离 +public class PlayerModel : AbstractModel +{ + // Model:只存储数据 + public BindableProperty Health { get; } = new(100); + public BindableProperty Score { get; } = new(0); +} + +public class CombatSystem : AbstractSystem +{ + // System:处理业务逻辑 + protected override void OnInit() + { + this.RegisterEvent(OnAttack); + } + + private void OnAttack(AttackEvent e) + { + var player = this.GetModel(); + player.Health.Value -= e.Damage; + } +} + +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class PlayerController : IController +{ + // Controller:连接 UI 和逻辑 + public void Initialize() + { + var player = this.GetModel(); + player.Health.RegisterWithInitValue(OnHealthChanged); + } + + private void OnHealthChanged(int health) + { + UpdateHealthDisplay(health); + } +} +``` + +### 2. 事件驱动设计 + +**原则**:使用事件解耦组件,避免直接调用。 + +```csharp +// ❌ 紧耦合 +public class SystemA : AbstractSystem +{ + private void OnEvent(EventA e) + { + var systemB = this.GetSystem(); + systemB.DoSomething(); // 直接调用 + } +} + +// ✅ 松耦合 +public class SystemA : AbstractSystem +{ + private void OnEvent(EventA e) + { + this.SendEvent(new EventB()); // 发送事件 + } +} + +public class SystemB : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnEventB); + } +} +``` + +### 3. 命令查询分离 + +**原则**:明确区分修改状态(Command)和查询状态(Query)。 + +```csharp +// ✅ 正确的 CQRS +public class MovePlayerCommand : AbstractCommand +{ + public Vector2 Direction { get; set; } + + protected override void OnDo() + { + // 修改状态 + this.SendEvent(new PlayerMovedEvent { Direction = Direction }); + } +} + +public class GetPlayerPositionQuery : AbstractQuery +{ + protected override Vector2 OnDo() + { + // 只查询,不修改 + return this.GetModel().Position.Value; + } +} +``` + +## 代码组织 + +### 1. 项目结构 + +``` +GameProject/ +├── Models/ +│ ├── PlayerModel.cs +│ ├── GameStateModel.cs +│ └── InventoryModel.cs +├── Systems/ +│ ├── CombatSystem.cs +│ ├── InventorySystem.cs +│ └── GameLogicSystem.cs +├── Commands/ +│ ├── AttackCommand.cs +│ ├── MoveCommand.cs +│ └── UseItemCommand.cs +├── Queries/ +│ ├── GetPlayerHealthQuery.cs +│ └── GetInventoryItemsQuery.cs +├── Events/ +│ ├── PlayerDiedEvent.cs +│ ├── ItemUsedEvent.cs +│ └── EnemyDamagedEvent.cs +├── Controllers/ +│ ├── PlayerController.cs +│ └── UIController.cs +├── Utilities/ +│ ├── StorageUtility.cs +│ └── MathUtility.cs +└── GameArchitecture.cs +``` + +### 2. 命名规范 + +```csharp +// Models:使用 Model 后缀 +public class PlayerModel : AbstractModel { } +public class GameStateModel : AbstractModel { } + +// Systems:使用 System 后缀 +public class CombatSystem : AbstractSystem { } +public class InventorySystem : AbstractSystem { } + +// Commands:使用 Command 后缀 +public class AttackCommand : AbstractCommand { } +public class MoveCommand : AbstractCommand { } + +// Queries:使用 Query 后缀 +public class GetPlayerHealthQuery : AbstractQuery { } +public class GetInventoryItemsQuery : AbstractQuery> { } + +// Events:使用 Event 后缀 +public class PlayerDiedEvent : IEvent { } +public class ItemUsedEvent : IEvent { } + +// Controllers:使用 Controller 后缀 +public class PlayerController : IController { } + +// Utilities:使用 Utility 后缀 +public class StorageUtility : IUtility { } +``` + +## 内存管理 + +### 1. 正确的注销管理 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class MyController : IController +{ + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public void Initialize() + { + var model = this.GetModel(); + + // 注册事件并添加到注销列表 + this.RegisterEvent(OnPlayerDied) + .AddToUnregisterList(_unregisterList); + + // 注册属性监听并添加到注销列表 + model.Health.Register(OnHealthChanged) + .AddToUnregisterList(_unregisterList); + } + + public void Cleanup() + { + // 统一注销所有监听器 + _unregisterList.UnRegisterAll(); + } + + private void OnPlayerDied(PlayerDiedEvent e) { } + private void OnHealthChanged(int health) { } +} +``` + +### 2. 生命周期管理 + +```csharp +public class GameManager +{ + private GameArchitecture _architecture; + + public void StartGame() + { + _architecture = new GameArchitecture(); + _architecture.Initialize(); + } + + public void EndGame() + { + // 销毁架构,自动清理所有组件 + _architecture.Destroy(); + _architecture = null; + } +} +``` + +## 性能优化 + +### 1. 缓存组件引用 + +```csharp +// ❌ 低效:每次都查询 +public void Update() +{ + var model = this.GetModel(); + model.Health.Value -= 1; +} + +// ✅ 高效:缓存引用 +private PlayerModel _playerModel; + +public void Initialize() +{ + _playerModel = this.GetModel(); +} + +public void Update() +{ + _playerModel.Health.Value -= 1; +} +``` + +### 2. 避免频繁的事件创建 + +```csharp +// ❌ 低效:每帧创建新事件 +public void Update() +{ + this.SendEvent(new UpdateEvent()); // 频繁分配内存 +} + +// ✅ 高效:复用事件或使用对象池 +private UpdateEvent _updateEvent = new UpdateEvent(); + +public void Update() +{ + this.SendEvent(_updateEvent); +} +``` + +### 3. 异步处理重操作 + +```csharp +public class LoadDataCommand : AbstractCommand +{ + protected override async void OnDo() + { + // 异步加载数据,不阻塞主线程 + var data = await LoadDataAsync(); + this.SendEvent(new DataLoadedEvent { Data = data }); + } + + private async Task LoadDataAsync() + { + return await Task.Run(() => + { + // 耗时操作 + return new Data(); + }); + } +} +``` + +## 测试 + +### 1. 单元测试 + +```csharp +[TestFixture] +public class CombatSystemTests +{ + private GameArchitecture _architecture; + private PlayerModel _playerModel; + + [SetUp] + public void Setup() + { + _architecture = new TestArchitecture(); + _architecture.Initialize(); + _playerModel = _architecture.GetModel(); + } + + [TearDown] + public void Teardown() + { + _architecture.Destroy(); + } + + [Test] + public void PlayerTakeDamage_ReducesHealth() + { + _playerModel.Health.Value = 100; + _architecture.SendEvent(new DamageEvent { Amount = 10 }); + Assert.AreEqual(90, _playerModel.Health.Value); + } + + [Test] + public void PlayerDies_WhenHealthReachesZero() + { + _playerModel.Health.Value = 10; + _architecture.SendEvent(new DamageEvent { Amount = 10 }); + Assert.AreEqual(0, _playerModel.Health.Value); + } +} +``` + +### 2. 集成测试 + +```csharp +[TestFixture] +public class GameFlowTests +{ + private GameArchitecture _architecture; + + [SetUp] + public void Setup() + { + _architecture = new GameArchitecture(); + _architecture.Initialize(); + } + + [Test] + public void CompleteGameFlow() + { + // 初始化 + var player = _architecture.GetModel(); + Assert.AreEqual(100, player.Health.Value); + + // 执行操作 + _architecture.SendCommand(new AttackCommand { Damage = 20 }); + + // 验证结果 + Assert.AreEqual(80, player.Health.Value); + } +} +``` + +## 文档 + +### 1. 代码注释 + +```csharp +/// +/// 玩家模型,存储玩家的所有状态数据 +/// +public class PlayerModel : AbstractModel +{ + /// + /// 玩家的生命值,使用 BindableProperty 实现响应式更新 + /// + public BindableProperty Health { get; } = new(100); + + protected override void OnInit() + { + // 监听生命值变化,当生命值为 0 时发送死亡事件 + Health.Register(hp => + { + if (hp <= 0) + this.SendEvent(new PlayerDiedEvent()); + }); + } +} +``` + +### 2. 架构文档 + +为你的项目编写架构文档,说明: + +- 主要的 Model、System、Command、Query +- 关键事件流 +- 组件间的通信方式 +- 扩展点和插件机制 + +## 常见陷阱 + +### 1. 在 Model 中包含业务逻辑 + +```csharp +// ❌ 错误 +public class PlayerModel : AbstractModel +{ + public void TakeDamage(int damage) + { + Health.Value -= damage; + if (Health.Value <= 0) + Die(); + } +} + +// ✅ 正确 +public class CombatSystem : AbstractSystem +{ + private void OnDamage(DamageEvent e) + { + var player = this.GetModel(); + player.Health.Value -= e.Amount; + } +} +``` + +### 2. 忘记注销监听器 + +```csharp +// ❌ 错误:可能导致内存泄漏 +public void Initialize() +{ + this.RegisterEvent(OnEvent1); // 未注销 +} + +// ✅ 正确 +private IUnRegisterList _unregisterList = new UnRegisterList(); + +public void Initialize() +{ + this.RegisterEvent(OnEvent1) + .AddToUnregisterList(_unregisterList); +} + +public void Cleanup() +{ + _unregisterList.UnRegisterAll(); +} +``` + +### 3. 直接调用其他系统 + +```csharp +// ❌ 错误:紧耦合 +public class SystemA : AbstractSystem +{ + private void OnEvent(EventA e) + { + var systemB = this.GetSystem(); + systemB.DoSomething(); + } +} + +// ✅ 正确:使用事件解耦 +public class SystemA : AbstractSystem +{ + private void OnEvent(EventA e) + { + this.SendEvent(new EventB()); + } +} +``` + +--- + +遵循这些最佳实践将帮助你构建可维护、高效、可扩展的应用程序。 diff --git a/docs/zh-CN/best-practices/performance.md b/docs/zh-CN/best-practices/performance.md index 65e9c57..375e23c 100644 --- a/docs/zh-CN/best-practices/performance.md +++ b/docs/zh-CN/best-practices/performance.md @@ -1,1359 +1,1364 @@ -# 性能优化指南 - -> 全面的性能优化策略和最佳实践,帮助你构建高性能的游戏应用。 - -## 📋 目录 - -- [概述](#概述) -- [核心概念](#核心概念) -- [对象池优化](#对象池优化) -- [事件系统优化](#事件系统优化) -- [协程优化](#协程优化) -- [资源管理优化](#资源管理优化) -- [ECS 性能优化](#ecs-性能优化) -- [内存优化](#内存优化) -- [最佳实践](#最佳实践) -- [常见问题](#常见问题) - -## 概述 - -性能优化是游戏开发中的关键环节。良好的性能不仅能提供流畅的用户体验,还能降低设备功耗,延长电池寿命。本指南将介绍 GFramework -中的性能优化策略和最佳实践。 - -### 性能优化的重要性 - -- **用户体验** - 流畅的帧率和快速的响应时间 -- **设备兼容性** - 在低端设备上也能良好运行 -- **资源效率** - 降低内存占用和 CPU 使用率 -- **电池寿命** - 减少不必要的计算和内存分配 - -## 核心概念 - -### 1. 性能瓶颈 - -性能瓶颈是指限制系统整体性能的关键因素: - -- **CPU 瓶颈** - 过多的计算、复杂的逻辑 -- **内存瓶颈** - 频繁的 GC、内存泄漏 -- **GPU 瓶颈** - 过多的绘制调用、复杂的着色器 -- **I/O 瓶颈** - 频繁的文件读写、网络请求 - -### 2. 性能指标 - -关键的性能指标: - -- **帧率 (FPS)** - 每秒渲染的帧数,目标 60 FPS -- **帧时间** - 每帧的处理时间,目标 <16.67ms -- **内存占用** - 应用程序使用的内存量 -- **GC 频率** - 垃圾回收的频率和耗时 -- **加载时间** - 场景和资源的加载时间 - -### 3. 优化策略 - -性能优化的基本策略: - -- **测量优先** - 先测量,再优化 -- **找到瓶颈** - 使用性能分析工具定位问题 -- **渐进优化** - 逐步优化,避免过早优化 -- **权衡取舍** - 在性能和可维护性之间找到平衡 - -## 对象池优化 - -对象池是减少 GC 压力的有效手段,通过复用对象避免频繁的内存分配和释放。 - -### 1. 使用对象池系统 - -```csharp -// ✅ 好的做法:使用对象池 -public class BulletPoolSystem : AbstractObjectPoolSystem<string, Bullet> -{ - protected override Bullet Create(string key) - { - // 创建新的子弹对象 - var bullet = new Bullet(); - bullet.Initialize(key); - return bullet; - } -} - -public class Bullet : IPoolableObject -{ - public string Type { get; private set; } - public Vector2 Position { get; set; } - public Vector2 Velocity { get; set; } - public bool IsActive { get; private set; } - - public void Initialize(string type) - { - Type = type; - } - - public void OnAcquire() - { - // 从池中获取时重置状态 - IsActive = true; - Position = Vector2.Zero; - Velocity = Vector2.Zero; - } - - public void OnRelease() - { - // 归还到池中时清理状态 - IsActive = false; - } - - public void OnPoolDestroy() - { - // 对象被销毁时的清理 - } -} - -// 使用对象池 -public class CombatSystem : AbstractSystem -{ - private BulletPoolSystem _bulletPool; - - protected override void OnInit() - { - _bulletPool = GetSystem<BulletPoolSystem>(); - - // 预热对象池 - _bulletPool.Prewarm("normal_bullet", 50); - _bulletPool.Prewarm("fire_bullet", 20); - } - - private void FireBullet(string bulletType, Vector2 position, Vector2 direction) - { - // 从池中获取子弹 - var bullet = _bulletPool.Acquire(bulletType); - bullet.Position = position; - bullet.Velocity = direction * 10f; - - // 使用完毕后归还 - // bullet 会在生命周期结束时自动归还 - } -} - -// ❌ 避免:频繁创建和销毁对象 -public class CombatSystem : AbstractSystem -{ - private void FireBullet(string bulletType, Vector2 position, Vector2 direction) - { - // 每次都创建新对象,产生大量 GC - var bullet = new Bullet(); - bullet.Type = bulletType; - bullet.Position = position; - bullet.Velocity = direction * 10f; - - // 使用完毕后直接丢弃,等待 GC 回收 - } -} -``` - -### 2. StringBuilder 池 - -GFramework 提供了 `StringBuilderPool` 用于高效的字符串构建: - -```csharp -// ✅ 好的做法:使用 StringBuilderPool -public string FormatPlayerInfo(Player player) -{ - using var sb = StringBuilderPool.GetScoped(); - sb.Value.Append("Player: "); - sb.Value.Append(player.Name); - sb.Value.Append(", Level: "); - sb.Value.Append(player.Level); - sb.Value.Append(", HP: "); - sb.Value.Append(player.Health); - sb.Value.Append("/"); - sb.Value.Append(player.MaxHealth); - return sb.Value.ToString(); -} - -// 或者手动管理 -public string FormatPlayerInfo(Player player) -{ - var sb = StringBuilderPool.Rent(); - try - { - sb.Append("Player: ").Append(player.Name); - sb.Append(", Level: ").Append(player.Level); - sb.Append(", HP: ").Append(player.Health).Append("/").Append(player.MaxHealth); - return sb.ToString(); - } - finally - { - StringBuilderPool.Return(sb); - } -} - -// ❌ 避免:频繁的字符串拼接 -public string FormatPlayerInfo(Player player) -{ - // 每次拼接都会创建新的字符串对象 - return "Player: " + player.Name + - ", Level: " + player.Level + - ", HP: " + player.Health + "/" + player.MaxHealth; -} -``` - -### 3. ArrayPool 优化 - -使用 `ArrayPool` 避免频繁的数组分配: - -```csharp -// ✅ 好的做法:使用 ArrayPool -public void ProcessEntities(List<Entity> entities) -{ - using var scopedArray = ArrayPool<Entity>.Shared.GetScoped(entities.Count); - var array = scopedArray.Array; - - // 复制到数组进行处理 - entities.CopyTo(array, 0); - - // 处理数组 - for (int i = 0; i < entities.Count; i++) - { - ProcessEntity(array[i]); - } - - // 自动归还到池中 -} - -// ❌ 避免:频繁创建数组 -public void ProcessEntities(List<Entity> entities) -{ - // 每次都创建新数组 - var array = entities.ToArray(); - - foreach (var entity in array) - { - ProcessEntity(entity); - } - - // 数组等待 GC 回收 -} -``` - -### 4. 对象池统计 - -监控对象池的使用情况: - -```csharp -public class PoolMonitorSystem : AbstractSystem -{ - private BulletPoolSystem _bulletPool; - - protected override void OnInit() - { - _bulletPool = GetSystem<BulletPoolSystem>(); - } - - public void LogPoolStatistics(string poolKey) - { - var stats = _bulletPool.GetStatistics(poolKey); - - Logger.Info($"Pool Statistics for '{poolKey}':"); - Logger.Info($" Available: {stats.AvailableCount}"); - Logger.Info($" Active: {stats.ActiveCount}"); - Logger.Info($" Total Created: {stats.TotalCreated}"); - Logger.Info($" Total Acquired: {stats.TotalAcquired}"); - Logger.Info($" Total Released: {stats.TotalReleased}"); - Logger.Info($" Total Destroyed: {stats.TotalDestroyed}"); - - // 检查是否需要调整池大小 - if (stats.TotalDestroyed > stats.TotalCreated * 0.5) - { - Logger.Warning($"Pool '{poolKey}' has high destruction rate, consider increasing max capacity"); - } - } -} -``` - -## 事件系统优化 - -事件系统是游戏架构的核心,优化事件处理可以显著提升性能。 - -### 1. 避免事件订阅泄漏 - -```csharp -// ✅ 好的做法:正确管理事件订阅 -public class PlayerController : Node, IController -{ - private IUnRegisterList _unRegisterList = new UnRegisterList(); - private PlayerModel _playerModel; - - public void Initialize(IArchitectureContext context) - { - _playerModel = context.GetModel<PlayerModel>(); - - // 使用 UnRegisterList 管理订阅 - context.RegisterEvent<PlayerDamagedEvent>(OnPlayerDamaged) - .AddTo(_unRegisterList); - - _playerModel.Health.Register(OnHealthChanged) - .AddTo(_unRegisterList); - } - - public void Cleanup() - { - // 统一取消所有订阅 - _unRegisterList.UnRegisterAll(); - } - - private void OnPlayerDamaged(PlayerDamagedEvent e) { } - private void OnHealthChanged(int health) { } -} - -// ❌ 避免:忘记取消订阅 -public class PlayerController : Node, IController -{ - public void Initialize(IArchitectureContext context) - { - // 订阅事件但从不取消订阅 - context.RegisterEvent<PlayerDamagedEvent>(OnPlayerDamaged); - - var playerModel = context.GetModel<PlayerModel>(); - playerModel.Health.Register(OnHealthChanged); - - // 当对象被销毁时,这些订阅仍然存在,导致内存泄漏 - } -} -``` - -### 2. 使用结构体事件 - -使用值类型事件避免堆分配: - -```csharp -// ✅ 好的做法:使用结构体事件 -public struct PlayerMovedEvent -{ - public string PlayerId { get; init; } - public Vector2 OldPosition { get; init; } - public Vector2 NewPosition { get; init; } - public float DeltaTime { get; init; } -} - -public class MovementSystem : AbstractSystem -{ - private void NotifyPlayerMoved(string playerId, Vector2 oldPos, Vector2 newPos, float deltaTime) - { - // 结构体在栈上分配,无 GC 压力 - SendEvent(new PlayerMovedEvent - { - PlayerId = playerId, - OldPosition = oldPos, - NewPosition = newPos, - DeltaTime = deltaTime - }); - } -} - -// ❌ 避免:使用类事件 -public class PlayerMovedEvent -{ - public string PlayerId { get; set; } - public Vector2 OldPosition { get; set; } - public Vector2 NewPosition { get; set; } - public float DeltaTime { get; set; } -} - -// 每次发送事件都会在堆上分配对象 -``` - -### 3. 批量事件处理 - -对于高频事件,考虑批量处理: - -```csharp -// ✅ 好的做法:批量处理事件 -public class DamageSystem : AbstractSystem -{ - private readonly List<DamageInfo> _pendingDamages = new(); - private float _batchInterval = 0.1f; - private float _timeSinceLastBatch = 0f; - - public void Update(float deltaTime) - { - _timeSinceLastBatch += deltaTime; - - if (_timeSinceLastBatch >= _batchInterval) - { - ProcessDamageBatch(); - _timeSinceLastBatch = 0f; - } - } - - public void QueueDamage(string entityId, int damage, DamageType type) - { - _pendingDamages.Add(new DamageInfo - { - EntityId = entityId, - Damage = damage, - Type = type - }); - } - - private void ProcessDamageBatch() - { - if (_pendingDamages.Count == 0) - return; - - // 批量处理所有伤害 - foreach (var damageInfo in _pendingDamages) - { - ApplyDamage(damageInfo); - } - - // 发送单个批量事件 - SendEvent(new DamageBatchProcessedEvent - { - DamageCount = _pendingDamages.Count, - TotalDamage = _pendingDamages.Sum(d => d.Damage) - }); - - _pendingDamages.Clear(); - } -} - -// ❌ 避免:每次都立即处理 -public class DamageSystem : AbstractSystem -{ - public void ApplyDamage(string entityId, int damage, DamageType type) - { - // 每次伤害都立即处理并发送事件 - ProcessDamage(entityId, damage, type); - SendEvent(new DamageAppliedEvent { EntityId = entityId, Damage = damage }); - } -} -``` - -### 4. 事件优先级优化 - -合理使用事件优先级避免不必要的处理: - -```csharp -// ✅ 好的做法:使用优先级控制事件传播 -public class InputSystem : AbstractSystem -{ - protected override void OnInit() - { - // UI 输入处理器优先级最高 - this.RegisterEvent<InputEvent>(OnUIInput, priority: 100); - } - - private void OnUIInput(InputEvent e) - { - if (IsUIHandlingInput()) - { - // 停止事件传播,避免游戏逻辑处理 - e.StopPropagation(); - } - } -} - -public class GameplayInputSystem : AbstractSystem -{ - protected override void OnInit() - { - // 游戏逻辑输入处理器优先级较低 - this.RegisterEvent<InputEvent>(OnGameplayInput, priority: 50); - } - - private void OnGameplayInput(InputEvent e) - { - // 如果 UI 已经处理了输入,这里不会执行 - if (!e.IsPropagationStopped) - { - ProcessGameplayInput(e); - } - } -} -``` - -## 协程优化 - -协程是处理异步逻辑的强大工具,但不当使用会影响性能。 - -### 1. 避免过度嵌套 - -```csharp -// ✅ 好的做法:扁平化协程结构 -public IEnumerator<IYieldInstruction> LoadGameSequence() -{ - // 显示加载界面 - yield return ShowLoadingScreen(); - - // 加载配置 - yield return LoadConfiguration(); - - // 加载资源 - yield return LoadResources(); - - // 初始化系统 - yield return InitializeSystems(); - - // 隐藏加载界面 - yield return HideLoadingScreen(); -} - -private IEnumerator<IYieldInstruction> LoadConfiguration() -{ - var config = await ConfigManager.LoadAsync(); - ApplyConfiguration(config); - yield break; -} - -// ❌ 避免:深度嵌套 -public IEnumerator<IYieldInstruction> LoadGameSequence() -{ - yield return ShowLoadingScreen().Then(() => - { - return LoadConfiguration().Then(() => - { - return LoadResources().Then(() => - { - return InitializeSystems().Then(() => - { - return HideLoadingScreen(); - }); - }); - }); - }); -} -``` - -### 2. 协程池化 - -对于频繁启动的协程,考虑复用: - -```csharp -// ✅ 好的做法:复用协程逻辑 -public class EffectSystem : AbstractSystem -{ - private readonly Dictionary<string, IEnumerator<IYieldInstruction>> _effectCoroutines = new(); - - public CoroutineHandle PlayEffect(string effectId, Vector2 position) - { - // 复用协程逻辑 - return this.StartCoroutine(EffectCoroutine(effectId, position)); - } - - private IEnumerator<IYieldInstruction> EffectCoroutine(string effectId, Vector2 position) - { - var effect = CreateEffect(effectId, position); - - // 播放效果 - yield return new Delay(effect.Duration); - - // 清理效果 - DestroyEffect(effect); - } -} -``` - -### 3. 合理使用 WaitForFrames - -```csharp -// ✅ 好的做法:使用 WaitForFrames 分帧处理 -public IEnumerator<IYieldInstruction> ProcessLargeDataSet(List<Data> dataSet) -{ - const int batchSize = 100; - - for (int i = 0; i < dataSet.Count; i += batchSize) - { - int end = Math.Min(i + batchSize, dataSet.Count); - - // 处理一批数据 - for (int j = i; j < end; j++) - { - ProcessData(dataSet[j]); - } - - // 等待一帧,避免卡顿 - yield return new WaitOneFrame(); - } -} - -// ❌ 避免:一次性处理大量数据 -public void ProcessLargeDataSet(List<Data> dataSet) -{ - // 一次性处理所有数据,可能导致帧率下降 - foreach (var data in dataSet) - { - ProcessData(data); - } -} -``` - -### 4. 协程取消优化 - -```csharp -// ✅ 好的做法:及时取消不需要的协程 -public class AnimationController : AbstractSystem -{ - private CoroutineHandle _currentAnimation; - - public void PlayAnimation(string animationName) - { - // 取消当前动画 - if (_currentAnimation.IsValid) - { - this.StopCoroutine(_currentAnimation); - } - - // 播放新动画 - _currentAnimation = this.StartCoroutine(AnimationCoroutine(animationName)); - } - - private IEnumerator<IYieldInstruction> AnimationCoroutine(string animationName) - { - var animation = GetAnimation(animationName); - - while (!animation.IsComplete) - { - animation.Update(Time.DeltaTime); - yield return new WaitOneFrame(); - } - } -} -``` - -## 资源管理优化 - -高效的资源管理可以显著减少加载时间和内存占用。 - -### 1. 资源预加载 - -```csharp -// ✅ 好的做法:预加载常用资源 -public class ResourcePreloader : AbstractSystem -{ - private IResourceManager _resourceManager; - - protected override void OnInit() - { - _resourceManager = GetUtility<IResourceManager>(); - } - - public async Task PreloadCommonResources() - { - // 预加载 UI 资源 - await _resourceManager.PreloadAsync<Texture>("ui/button_normal"); - await _resourceManager.PreloadAsync<Texture>("ui/button_pressed"); - await _resourceManager.PreloadAsync<Texture>("ui/button_hover"); - - // 预加载音效 - await _resourceManager.PreloadAsync<AudioClip>("sfx/button_click"); - await _resourceManager.PreloadAsync<AudioClip>("sfx/button_hover"); - - // 预加载特效 - await _resourceManager.PreloadAsync<ParticleSystem>("effects/hit_effect"); - await _resourceManager.PreloadAsync<ParticleSystem>("effects/explosion"); - } -} -``` - -### 2. 异步加载 - -```csharp -// ✅ 好的做法:使用异步加载避免阻塞 -public class SceneLoader : AbstractSystem -{ - private IResourceManager _resourceManager; - - public async Task LoadSceneAsync(string sceneName) - { - // 显示加载进度 - var progress = 0f; - UpdateLoadingProgress(progress); - - // 异步加载场景资源 - var sceneData = await _resourceManager.LoadAsync<SceneData>($"scenes/{sceneName}"); - progress += 0.3f; - UpdateLoadingProgress(progress); - - // 异步加载场景依赖的资源 - await LoadSceneDependencies(sceneData); - progress += 0.5f; - UpdateLoadingProgress(progress); - - // 初始化场景 - await InitializeScene(sceneData); - progress = 1f; - UpdateLoadingProgress(progress); - } - - private async Task LoadSceneDependencies(SceneData sceneData) - { - var tasks = new List<Task>(); - - foreach (var dependency in sceneData.Dependencies) - { - tasks.Add(_resourceManager.LoadAsync<object>(dependency)); - } - - await Task.WhenAll(tasks); - } -} - -// ❌ 避免:同步加载阻塞主线程 -public class SceneLoader : AbstractSystem -{ - public void LoadScene(string sceneName) - { - // 同步加载会阻塞主线程,导致卡顿 - var sceneData = _resourceManager.Load<SceneData>($"scenes/{sceneName}"); - LoadSceneDependencies(sceneData); - InitializeScene(sceneData); - } -} -``` - -### 3. 资源引用计数 - -```csharp -// ✅ 好的做法:使用资源句柄管理引用 -public class EntityRenderer : AbstractSystem -{ - private readonly Dictionary<string, IResourceHandle<Texture>> _textureHandles = new(); - - public void LoadTexture(string entityId, string texturePath) - { - // 获取资源句柄 - var handle = _resourceManager.GetHandle<Texture>(texturePath); - if (handle != null) - { - _textureHandles[entityId] = handle; - } - } - - public void UnloadTexture(string entityId) - { - if (_textureHandles.TryGetValue(entityId, out var handle)) - { - // 释放句柄,自动管理引用计数 - handle.Dispose(); - _textureHandles.Remove(entityId); - } - } - - protected override void OnDestroy() - { - // 清理所有句柄 - foreach (var handle in _textureHandles.Values) - { - handle.Dispose(); - } - _textureHandles.Clear(); - } -} -``` - -### 4. 资源缓存策略 - -```csharp -// ✅ 好的做法:使用合适的释放策略 -public class GameResourceManager : AbstractSystem -{ - private IResourceManager _resourceManager; - - protected override void OnInit() - { - _resourceManager = GetUtility<IResourceManager>(); - - // 设置自动释放策略 - _resourceManager.SetReleaseStrategy(new AutoReleaseStrategy()); - } - - public void OnSceneChanged() - { - // 场景切换时卸载未使用的资源 - UnloadUnusedResources(); - } - - private void UnloadUnusedResources() - { - var loadedPaths = _resourceManager.GetLoadedResourcePaths().ToList(); - - foreach (var path in loadedPaths) - { - // 检查资源是否仍在使用 - if (!IsResourceInUse(path)) - { - _resourceManager.Unload(path); - } - } - } - - private bool IsResourceInUse(string path) - { - // 检查资源引用计数 - return false; // 实现具体逻辑 - } -} -``` - -## ECS 性能优化 - -Entity Component System (ECS) 是高性能游戏架构的关键。 - -### 1. 组件设计优化 - -```csharp -// ✅ 好的做法:使用值类型组件 -public struct Position -{ - public float X; - public float Y; - public float Z; -} - -public struct Velocity -{ - public float X; - public float Y; - public float Z; -} - -public struct Health -{ - public int Current; - public int Max; -} - -// ❌ 避免:使用引用类型组件 -public class Position -{ - public float X { get; set; } - public float Y { get; set; } - public float Z { get; set; } -} -``` - -### 2. 查询优化 - -```csharp -// ✅ 好的做法:使用高效的查询 -public class MovementSystem : ArchSystemAdapter -{ - private QueryDescription _movementQuery; - - public override void Initialize() - { - // 预先构建查询 - _movementQuery = new QueryDescription() - .WithAll<Position, Velocity>() - .WithNone<Frozen>(); - } - - public override void Update(float deltaTime) - { - // 使用预构建的查询 - World.Query(in _movementQuery, (ref Position pos, ref Velocity vel) => - { - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - pos.Z += vel.Z * deltaTime; - }); - } -} - -// ❌ 避免:每帧构建查询 -public class MovementSystem : ArchSystemAdapter -{ - public override void Update(float deltaTime) - { - // 每帧都构建新查询,性能差 - var query = new QueryDescription() - .WithAll<Position, Velocity>() - .WithNone<Frozen>(); - - World.Query(in query, (ref Position pos, ref Velocity vel) => - { - pos.X += vel.X * deltaTime; - pos.Y += vel.Y * deltaTime; - pos.Z += vel.Z * deltaTime; - }); - } -} -``` - -### 3. 批量处理 - -```csharp -// ✅ 好的做法:批量处理实体 -public class DamageSystem : ArchSystemAdapter -{ - private readonly List<(EntityReference entity, int damage)> _pendingDamages = new(); - - public void QueueDamage(EntityReference entity, int damage) - { - _pendingDamages.Add((entity, damage)); - } - - public override void Update(float deltaTime) - { - if (_pendingDamages.Count == 0) - return; - - // 批量应用伤害 - foreach (var (entity, damage) in _pendingDamages) - { - if (World.IsAlive(entity)) - { - ref var health = ref World.Get<Health>(entity); - health.Current = Math.Max(0, health.Current - damage); - - if (health.Current == 0) - { - World.Destroy(entity); - } - } - } - - _pendingDamages.Clear(); - } -} -``` - -### 4. 避免装箱 - -```csharp -// ✅ 好的做法:避免装箱操作 -public struct EntityId : IEquatable<EntityId> -{ - public int Value; - - public bool Equals(EntityId other) => Value == other.Value; - public override bool Equals(object obj) => obj is EntityId other && Equals(other); - public override int GetHashCode() => Value; -} - -// 使用泛型避免装箱 -public class EntityCache<T> where T : struct -{ - private readonly Dictionary<EntityId, T> _cache = new(); - - public void Add(EntityId id, T data) - { - _cache[id] = data; // 无装箱 - } - - public bool TryGet(EntityId id, out T data) - { - return _cache.TryGetValue(id, out data); // 无装箱 - } -} - -// ❌ 避免:导致装箱的操作 -public class EntityCache -{ - private readonly Dictionary<int, object> _cache = new(); - - public void Add(int id, object data) - { - _cache[id] = data; // 值类型会装箱 - } -} -``` - -## 内存优化 - -减少内存分配和 GC 压力是性能优化的重要方面。 - -### 1. 使用 Span<T> - -```csharp -// ✅ 好的做法:使用 Span 避免分配 -public void ProcessData(ReadOnlySpan<byte> data) -{ - // 直接在栈上处理,无堆分配 - Span<int> results = stackalloc int[data.Length]; - - for (int i = 0; i < data.Length; i++) - { - results[i] = ProcessByte(data[i]); - } - - // 使用结果 - UseResults(results); -} - -// 解析字符串避免分配 -public bool TryParseValue(ReadOnlySpan<char> input, out int result) -{ - return int.TryParse(input, out result); -} - -// ❌ 避免:不必要的数组分配 -public void ProcessData(byte[] data) -{ - // 创建新数组,产生 GC - var results = new int[data.Length]; - - for (int i = 0; i < data.Length; i++) - { - results[i] = ProcessByte(data[i]); - } - - UseResults(results); -} -``` - -### 2. 结构体优化 - -```csharp -// ✅ 好的做法:使用 readonly struct -public readonly struct Vector2D -{ - public readonly float X; - public readonly float Y; - - public Vector2D(float x, float y) - { - X = x; - Y = y; - } - - public float Length() => MathF.Sqrt(X * X + Y * Y); - - public Vector2D Normalized() - { - var length = Length(); - return length > 0 ? new Vector2D(X / length, Y / length) : this; - } -} - -// ❌ 避免:可变结构体 -public struct Vector2D -{ - public float X { get; set; } - public float Y { get; set; } - - // 可变结构体可能导致意外的复制 -} -``` - -### 3. 避免闭包分配 - -```csharp -// ✅ 好的做法:避免闭包捕获 -public class EventProcessor : AbstractSystem -{ - private readonly Action<PlayerEvent> _cachedHandler; - - public EventProcessor() - { - // 缓存委托,避免每次分配 - _cachedHandler = HandlePlayerEvent; - } - - protected override void OnInit() - { - this.RegisterEvent(_cachedHandler); - } - - private void HandlePlayerEvent(PlayerEvent e) - { - ProcessEvent(e); - } -} - -// ❌ 避免:每次都创建新的闭包 -public class EventProcessor : AbstractSystem -{ - protected override void OnInit() - { - // 每次都创建新的委托和闭包 - this.RegisterEvent<PlayerEvent>(e => - { - ProcessEvent(e); - }); - } -} -``` - -### 4. 字符串优化 - -```csharp -// ✅ 好的做法:减少字符串分配 -public class Logger -{ - private readonly StringBuilder _sb = new(); - - public void LogFormat(string format, params object[] args) - { - _sb.Clear(); - _sb.AppendFormat(format, args); - Log(_sb.ToString()); - } - - // 使用字符串插值的优化版本 - public void LogInterpolated(ref DefaultInterpolatedStringHandler handler) - { - Log(handler.ToStringAndClear()); - } -} - -// 使用 string.Create 避免中间分配 -public string CreateEntityName(int id, string type) -{ - return string.Create(type.Length + 10, (id, type), (span, state) => - { - state.type.AsSpan().CopyTo(span); - span = span.Slice(state.type.Length); - span[0] = '_'; - state.id.TryFormat(span.Slice(1), out _); - }); -} - -// ❌ 避免:频繁的字符串拼接 -public string CreateEntityName(int id, string type) -{ - return type + "_" + id.ToString(); // 创建多个临时字符串 -} -``` - -## 最佳实践 - -### 1. 性能测试 - -```csharp -// ✅ 使用性能测试验证优化效果 -[TestFixture] -public class PerformanceTests -{ - [Test] - [Performance] - public void ObjectPool_Performance_Test() - { - var pool = new BulletPoolSystem(); - pool.Prewarm("bullet", 1000); - - Measure.Method(() => - { - var bullet = pool.Acquire("bullet"); - pool.Release("bullet", bullet); - }) - .WarmupCount(10) - .MeasurementCount(100) - .Run(); - } - - [Test] - public void CompareAllocationMethods() - { - // 测试对象池 - var poolTime = MeasureTime(() => - { - var pool = ArrayPool<int>.Shared; - var array = pool.Rent(1000); - pool.Return(array); - }); - - // 测试直接分配 - var allocTime = MeasureTime(() => - { - var array = new int[1000]; - }); - - Assert.Less(poolTime, allocTime, "Object pool should be faster"); - } - - private long MeasureTime(Action action) - { - var sw = Stopwatch.StartNew(); - for (int i = 0; i < 10000; i++) - { - action(); - } - sw.Stop(); - return sw.ElapsedMilliseconds; - } -} -``` - -### 2. 性能监控 - -```csharp -// ✅ 实现性能监控系统 -public class PerformanceMonitor : AbstractSystem -{ - private readonly Dictionary<string, PerformanceMetric> _metrics = new(); - private float _updateInterval = 1.0f; - private float _timeSinceUpdate = 0f; - - public void Update(float deltaTime) - { - _timeSinceUpdate += deltaTime; - - if (_timeSinceUpdate >= _updateInterval) - { - UpdateMetrics(); - _timeSinceUpdate = 0f; - } - } - - public void RecordMetric(string name, float value) - { - if (!_metrics.TryGetValue(name, out var metric)) - { - metric = new PerformanceMetric(name); - _metrics[name] = metric; - } - - metric.AddSample(value); - } - - private void UpdateMetrics() - { - foreach (var metric in _metrics.Values) - { - Logger.Info($"{metric.Name}: Avg={metric.Average:F2}ms, " + - $"Min={metric.Min:F2}ms, Max={metric.Max:F2}ms"); - metric.Reset(); - } - } -} - -public class PerformanceMetric -{ - public string Name { get; } - public float Average => _count > 0 ? _sum / _count : 0; - public float Min { get; private set; } = float.MaxValue; - public float Max { get; private set; } = float.MinValue; - - private float _sum; - private int _count; - - public PerformanceMetric(string name) - { - Name = name; - } - - public void AddSample(float value) - { - _sum += value; - _count++; - Min = Math.Min(Min, value); - Max = Math.Max(Max, value); - } - - public void Reset() - { - _sum = 0; - _count = 0; - Min = float.MaxValue; - Max = float.MinValue; - } -} -``` - -### 3. 性能分析工具 - -使用性能分析工具定位瓶颈: - -- **Unity Profiler** - Unity 内置的性能分析工具 -- **Godot Profiler** - Godot 的性能监控工具 -- **dotTrace** - JetBrains 的 .NET 性能分析器 -- **PerfView** - Microsoft 的性能分析工具 - -### 4. 性能优化清单 - -在优化性能时,遵循以下清单: - -- [ ] 使用对象池减少 GC 压力 -- [ ] 正确管理事件订阅,避免内存泄漏 -- [ ] 使用结构体事件避免堆分配 -- [ ] 合理使用协程,避免过度嵌套 -- [ ] 异步加载资源,避免阻塞主线程 -- [ ] 使用 ECS 架构提高数据局部性 -- [ ] 使用 Span<T> 和 stackalloc 减少分配 -- [ ] 避免装箱操作 -- [ ] 缓存常用的委托和对象 -- [ ] 批量处理高频操作 -- [ ] 定期进行性能测试和监控 - -## 常见问题 - -### Q1: 什么时候应该使用对象池? - -**A**: 当满足以下条件时考虑使用对象池: - -- 对象创建成本高(如包含复杂初始化逻辑) -- 对象频繁创建和销毁(如子弹、特效粒子) -- 对象生命周期短暂但使用频繁 -- 需要减少 GC 压力 - -### Q2: 如何判断是否存在内存泄漏? - -**A**: 观察以下指标: - -- 内存使用持续增长,不会下降 -- GC 频率增加但内存不释放 -- 事件订阅数量持续增加 -- 对象池中的活跃对象数量异常 - -使用内存分析工具(如 dotMemory)定位泄漏源。 - -### Q3: 协程和 async/await 如何选择? - -**A**: 选择建议: - -- **协程** - 游戏逻辑、动画序列、分帧处理 -- **async/await** - I/O 操作、网络请求、文件读写 - -协程更适合游戏帧循环,async/await 更适合异步 I/O。 - -### Q4: 如何优化大量实体的性能? - -**A**: 使用 ECS 架构: - -- 使用值类型组件减少堆分配 -- 预构建查询避免每帧创建 -- 批量处理实体操作 -- 使用并行处理(如果支持) - -### Q5: 什么时候应该进行性能优化? - -**A**: 遵循以下原则: - -- **先测量,再优化** - 使用性能分析工具找到真正的瓶颈 -- **优先优化热点** - 集中优化占用时间最多的代码 -- **避免过早优化** - 在功能完成后再进行优化 -- **保持可读性** - 不要为了微小的性能提升牺牲代码可读性 - -### Q6: 如何减少 GC 压力? - -**A**: 采取以下措施: - -- 使用对象池复用对象 -- 使用值类型(struct)而非引用类型(class) -- 使用 Span<T> 和 stackalloc 进行栈分配 -- 避免闭包捕获和装箱操作 -- 缓存常用对象和委托 -- 使用 StringBuilder 而非字符串拼接 - ---- - -## 总结 - -性能优化是一个持续的过程,需要: - -- ✅ **测量优先** - 使用工具定位真正的瓶颈 -- ✅ **合理使用对象池** - 减少 GC 压力 -- ✅ **优化事件系统** - 避免内存泄漏和不必要的处理 -- ✅ **高效使用协程** - 避免过度嵌套和不必要的等待 -- ✅ **智能资源管理** - 异步加载和合理缓存 -- ✅ **ECS 架构优化** - 提高数据局部性和处理效率 -- ✅ **内存优化** - 减少分配,使用栈内存 -- ✅ **持续监控** - 建立性能监控系统 - -记住,性能优化要在可维护性和性能之间找到平衡,不要为了微小的性能提升而牺牲代码质量。 - ---- - -**文档版本**: 1.0.0 -**更新日期**: 2026-03-07 +# 性能优化指南 + +> 全面的性能优化策略和最佳实践,帮助你构建高性能的游戏应用。 + +## 📋 目录 + +- [概述](#概述) +- [核心概念](#核心概念) +- [对象池优化](#对象池优化) +- [事件系统优化](#事件系统优化) +- [协程优化](#协程优化) +- [资源管理优化](#资源管理优化) +- [ECS 性能优化](#ecs-性能优化) +- [内存优化](#内存优化) +- [最佳实践](#最佳实践) +- [常见问题](#常见问题) + +## 概述 + +性能优化是游戏开发中的关键环节。良好的性能不仅能提供流畅的用户体验,还能降低设备功耗,延长电池寿命。本指南将介绍 GFramework +中的性能优化策略和最佳实践。 + +### 性能优化的重要性 + +- **用户体验** - 流畅的帧率和快速的响应时间 +- **设备兼容性** - 在低端设备上也能良好运行 +- **资源效率** - 降低内存占用和 CPU 使用率 +- **电池寿命** - 减少不必要的计算和内存分配 + +## 核心概念 + +### 1. 性能瓶颈 + +性能瓶颈是指限制系统整体性能的关键因素: + +- **CPU 瓶颈** - 过多的计算、复杂的逻辑 +- **内存瓶颈** - 频繁的 GC、内存泄漏 +- **GPU 瓶颈** - 过多的绘制调用、复杂的着色器 +- **I/O 瓶颈** - 频繁的文件读写、网络请求 + +### 2. 性能指标 + +关键的性能指标: + +- **帧率 (FPS)** - 每秒渲染的帧数,目标 60 FPS +- **帧时间** - 每帧的处理时间,目标 <16.67ms +- **内存占用** - 应用程序使用的内存量 +- **GC 频率** - 垃圾回收的频率和耗时 +- **加载时间** - 场景和资源的加载时间 + +### 3. 优化策略 + +性能优化的基本策略: + +- **测量优先** - 先测量,再优化 +- **找到瓶颈** - 使用性能分析工具定位问题 +- **渐进优化** - 逐步优化,避免过早优化 +- **权衡取舍** - 在性能和可维护性之间找到平衡 + +## 对象池优化 + +对象池是减少 GC 压力的有效手段,通过复用对象避免频繁的内存分配和释放。 + +### 1. 使用对象池系统 + +```csharp +// ✅ 好的做法:使用对象池 +public class BulletPoolSystem : AbstractObjectPoolSystem<string, Bullet> +{ + protected override Bullet Create(string key) + { + // 创建新的子弹对象 + var bullet = new Bullet(); + bullet.Initialize(key); + return bullet; + } +} + +public class Bullet : IPoolableObject +{ + public string Type { get; private set; } + public Vector2 Position { get; set; } + public Vector2 Velocity { get; set; } + public bool IsActive { get; private set; } + + public void Initialize(string type) + { + Type = type; + } + + public void OnAcquire() + { + // 从池中获取时重置状态 + IsActive = true; + Position = Vector2.Zero; + Velocity = Vector2.Zero; + } + + public void OnRelease() + { + // 归还到池中时清理状态 + IsActive = false; + } + + public void OnPoolDestroy() + { + // 对象被销毁时的清理 + } +} + +// 使用对象池 +public class CombatSystem : AbstractSystem +{ + private BulletPoolSystem _bulletPool; + + protected override void OnInit() + { + _bulletPool = GetSystem<BulletPoolSystem>(); + + // 预热对象池 + _bulletPool.Prewarm("normal_bullet", 50); + _bulletPool.Prewarm("fire_bullet", 20); + } + + private void FireBullet(string bulletType, Vector2 position, Vector2 direction) + { + // 从池中获取子弹 + var bullet = _bulletPool.Acquire(bulletType); + bullet.Position = position; + bullet.Velocity = direction * 10f; + + // 使用完毕后归还 + // bullet 会在生命周期结束时自动归还 + } +} + +// ❌ 避免:频繁创建和销毁对象 +public class CombatSystem : AbstractSystem +{ + private void FireBullet(string bulletType, Vector2 position, Vector2 direction) + { + // 每次都创建新对象,产生大量 GC + var bullet = new Bullet(); + bullet.Type = bulletType; + bullet.Position = position; + bullet.Velocity = direction * 10f; + + // 使用完毕后直接丢弃,等待 GC 回收 + } +} +``` + +### 2. StringBuilder 池 + +GFramework 提供了 `StringBuilderPool` 用于高效的字符串构建: + +```csharp +// ✅ 好的做法:使用 StringBuilderPool +public string FormatPlayerInfo(Player player) +{ + using var sb = StringBuilderPool.GetScoped(); + sb.Value.Append("Player: "); + sb.Value.Append(player.Name); + sb.Value.Append(", Level: "); + sb.Value.Append(player.Level); + sb.Value.Append(", HP: "); + sb.Value.Append(player.Health); + sb.Value.Append("/"); + sb.Value.Append(player.MaxHealth); + return sb.Value.ToString(); +} + +// 或者手动管理 +public string FormatPlayerInfo(Player player) +{ + var sb = StringBuilderPool.Rent(); + try + { + sb.Append("Player: ").Append(player.Name); + sb.Append(", Level: ").Append(player.Level); + sb.Append(", HP: ").Append(player.Health).Append("/").Append(player.MaxHealth); + return sb.ToString(); + } + finally + { + StringBuilderPool.Return(sb); + } +} + +// ❌ 避免:频繁的字符串拼接 +public string FormatPlayerInfo(Player player) +{ + // 每次拼接都会创建新的字符串对象 + return "Player: " + player.Name + + ", Level: " + player.Level + + ", HP: " + player.Health + "/" + player.MaxHealth; +} +``` + +### 3. ArrayPool 优化 + +使用 `ArrayPool` 避免频繁的数组分配: + +```csharp +// ✅ 好的做法:使用 ArrayPool +public void ProcessEntities(List<Entity> entities) +{ + using var scopedArray = ArrayPool<Entity>.Shared.GetScoped(entities.Count); + var array = scopedArray.Array; + + // 复制到数组进行处理 + entities.CopyTo(array, 0); + + // 处理数组 + for (int i = 0; i < entities.Count; i++) + { + ProcessEntity(array[i]); + } + + // 自动归还到池中 +} + +// ❌ 避免:频繁创建数组 +public void ProcessEntities(List<Entity> entities) +{ + // 每次都创建新数组 + var array = entities.ToArray(); + + foreach (var entity in array) + { + ProcessEntity(entity); + } + + // 数组等待 GC 回收 +} +``` + +### 4. 对象池统计 + +监控对象池的使用情况: + +```csharp +public class PoolMonitorSystem : AbstractSystem +{ + private BulletPoolSystem _bulletPool; + + protected override void OnInit() + { + _bulletPool = GetSystem<BulletPoolSystem>(); + } + + public void LogPoolStatistics(string poolKey) + { + var stats = _bulletPool.GetStatistics(poolKey); + + Logger.Info($"Pool Statistics for '{poolKey}':"); + Logger.Info($" Available: {stats.AvailableCount}"); + Logger.Info($" Active: {stats.ActiveCount}"); + Logger.Info($" Total Created: {stats.TotalCreated}"); + Logger.Info($" Total Acquired: {stats.TotalAcquired}"); + Logger.Info($" Total Released: {stats.TotalReleased}"); + Logger.Info($" Total Destroyed: {stats.TotalDestroyed}"); + + // 检查是否需要调整池大小 + if (stats.TotalDestroyed > stats.TotalCreated * 0.5) + { + Logger.Warning($"Pool '{poolKey}' has high destruction rate, consider increasing max capacity"); + } + } +} +``` + +## 事件系统优化 + +事件系统是游戏架构的核心,优化事件处理可以显著提升性能。 + +### 1. 避免事件订阅泄漏 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +// ✅ 好的做法:正确管理事件订阅 +[ContextAware] +public partial class PlayerController : Node, IController +{ + private IUnRegisterList _unRegisterList = new UnRegisterList(); + private PlayerModel _playerModel; + + public void Initialize() + { + _playerModel = this.GetModel<PlayerModel>(); + + // 使用 UnRegisterList 管理订阅 + this.RegisterEvent<PlayerDamagedEvent>(OnPlayerDamaged) + .AddTo(_unRegisterList); + + _playerModel.Health.Register(OnHealthChanged) + .AddTo(_unRegisterList); + } + + public void Cleanup() + { + // 统一取消所有订阅 + _unRegisterList.UnRegisterAll(); + } + + private void OnPlayerDamaged(PlayerDamagedEvent e) { } + private void OnHealthChanged(int health) { } +} + +// ❌ 避免:忘记取消订阅 +[ContextAware] +public partial class PlayerController : Node, IController +{ + public void Initialize() + { + // 订阅事件但从不取消订阅 + this.RegisterEvent<PlayerDamagedEvent>(OnPlayerDamaged); + + var playerModel = this.GetModel<PlayerModel>(); + playerModel.Health.Register(OnHealthChanged); + + // 当对象被销毁时,这些订阅仍然存在,导致内存泄漏 + } +} +``` + +### 2. 使用结构体事件 + +使用值类型事件避免堆分配: + +```csharp +// ✅ 好的做法:使用结构体事件 +public struct PlayerMovedEvent +{ + public string PlayerId { get; init; } + public Vector2 OldPosition { get; init; } + public Vector2 NewPosition { get; init; } + public float DeltaTime { get; init; } +} + +public class MovementSystem : AbstractSystem +{ + private void NotifyPlayerMoved(string playerId, Vector2 oldPos, Vector2 newPos, float deltaTime) + { + // 结构体在栈上分配,无 GC 压力 + SendEvent(new PlayerMovedEvent + { + PlayerId = playerId, + OldPosition = oldPos, + NewPosition = newPos, + DeltaTime = deltaTime + }); + } +} + +// ❌ 避免:使用类事件 +public class PlayerMovedEvent +{ + public string PlayerId { get; set; } + public Vector2 OldPosition { get; set; } + public Vector2 NewPosition { get; set; } + public float DeltaTime { get; set; } +} + +// 每次发送事件都会在堆上分配对象 +``` + +### 3. 批量事件处理 + +对于高频事件,考虑批量处理: + +```csharp +// ✅ 好的做法:批量处理事件 +public class DamageSystem : AbstractSystem +{ + private readonly List<DamageInfo> _pendingDamages = new(); + private float _batchInterval = 0.1f; + private float _timeSinceLastBatch = 0f; + + public void Update(float deltaTime) + { + _timeSinceLastBatch += deltaTime; + + if (_timeSinceLastBatch >= _batchInterval) + { + ProcessDamageBatch(); + _timeSinceLastBatch = 0f; + } + } + + public void QueueDamage(string entityId, int damage, DamageType type) + { + _pendingDamages.Add(new DamageInfo + { + EntityId = entityId, + Damage = damage, + Type = type + }); + } + + private void ProcessDamageBatch() + { + if (_pendingDamages.Count == 0) + return; + + // 批量处理所有伤害 + foreach (var damageInfo in _pendingDamages) + { + ApplyDamage(damageInfo); + } + + // 发送单个批量事件 + SendEvent(new DamageBatchProcessedEvent + { + DamageCount = _pendingDamages.Count, + TotalDamage = _pendingDamages.Sum(d => d.Damage) + }); + + _pendingDamages.Clear(); + } +} + +// ❌ 避免:每次都立即处理 +public class DamageSystem : AbstractSystem +{ + public void ApplyDamage(string entityId, int damage, DamageType type) + { + // 每次伤害都立即处理并发送事件 + ProcessDamage(entityId, damage, type); + SendEvent(new DamageAppliedEvent { EntityId = entityId, Damage = damage }); + } +} +``` + +### 4. 事件优先级优化 + +合理使用事件优先级避免不必要的处理: + +```csharp +// ✅ 好的做法:使用优先级控制事件传播 +public class InputSystem : AbstractSystem +{ + protected override void OnInit() + { + // UI 输入处理器优先级最高 + this.RegisterEvent<InputEvent>(OnUIInput, priority: 100); + } + + private void OnUIInput(InputEvent e) + { + if (IsUIHandlingInput()) + { + // 停止事件传播,避免游戏逻辑处理 + e.StopPropagation(); + } + } +} + +public class GameplayInputSystem : AbstractSystem +{ + protected override void OnInit() + { + // 游戏逻辑输入处理器优先级较低 + this.RegisterEvent<InputEvent>(OnGameplayInput, priority: 50); + } + + private void OnGameplayInput(InputEvent e) + { + // 如果 UI 已经处理了输入,这里不会执行 + if (!e.IsPropagationStopped) + { + ProcessGameplayInput(e); + } + } +} +``` + +## 协程优化 + +协程是处理异步逻辑的强大工具,但不当使用会影响性能。 + +### 1. 避免过度嵌套 + +```csharp +// ✅ 好的做法:扁平化协程结构 +public IEnumerator<IYieldInstruction> LoadGameSequence() +{ + // 显示加载界面 + yield return ShowLoadingScreen(); + + // 加载配置 + yield return LoadConfiguration(); + + // 加载资源 + yield return LoadResources(); + + // 初始化系统 + yield return InitializeSystems(); + + // 隐藏加载界面 + yield return HideLoadingScreen(); +} + +private IEnumerator<IYieldInstruction> LoadConfiguration() +{ + var config = await ConfigManager.LoadAsync(); + ApplyConfiguration(config); + yield break; +} + +// ❌ 避免:深度嵌套 +public IEnumerator<IYieldInstruction> LoadGameSequence() +{ + yield return ShowLoadingScreen().Then(() => + { + return LoadConfiguration().Then(() => + { + return LoadResources().Then(() => + { + return InitializeSystems().Then(() => + { + return HideLoadingScreen(); + }); + }); + }); + }); +} +``` + +### 2. 协程池化 + +对于频繁启动的协程,考虑复用: + +```csharp +// ✅ 好的做法:复用协程逻辑 +public class EffectSystem : AbstractSystem +{ + private readonly Dictionary<string, IEnumerator<IYieldInstruction>> _effectCoroutines = new(); + + public CoroutineHandle PlayEffect(string effectId, Vector2 position) + { + // 复用协程逻辑 + return this.StartCoroutine(EffectCoroutine(effectId, position)); + } + + private IEnumerator<IYieldInstruction> EffectCoroutine(string effectId, Vector2 position) + { + var effect = CreateEffect(effectId, position); + + // 播放效果 + yield return new Delay(effect.Duration); + + // 清理效果 + DestroyEffect(effect); + } +} +``` + +### 3. 合理使用 WaitForFrames + +```csharp +// ✅ 好的做法:使用 WaitForFrames 分帧处理 +public IEnumerator<IYieldInstruction> ProcessLargeDataSet(List<Data> dataSet) +{ + const int batchSize = 100; + + for (int i = 0; i < dataSet.Count; i += batchSize) + { + int end = Math.Min(i + batchSize, dataSet.Count); + + // 处理一批数据 + for (int j = i; j < end; j++) + { + ProcessData(dataSet[j]); + } + + // 等待一帧,避免卡顿 + yield return new WaitOneFrame(); + } +} + +// ❌ 避免:一次性处理大量数据 +public void ProcessLargeDataSet(List<Data> dataSet) +{ + // 一次性处理所有数据,可能导致帧率下降 + foreach (var data in dataSet) + { + ProcessData(data); + } +} +``` + +### 4. 协程取消优化 + +```csharp +// ✅ 好的做法:及时取消不需要的协程 +public class AnimationController : AbstractSystem +{ + private CoroutineHandle _currentAnimation; + + public void PlayAnimation(string animationName) + { + // 取消当前动画 + if (_currentAnimation.IsValid) + { + this.StopCoroutine(_currentAnimation); + } + + // 播放新动画 + _currentAnimation = this.StartCoroutine(AnimationCoroutine(animationName)); + } + + private IEnumerator<IYieldInstruction> AnimationCoroutine(string animationName) + { + var animation = GetAnimation(animationName); + + while (!animation.IsComplete) + { + animation.Update(Time.DeltaTime); + yield return new WaitOneFrame(); + } + } +} +``` + +## 资源管理优化 + +高效的资源管理可以显著减少加载时间和内存占用。 + +### 1. 资源预加载 + +```csharp +// ✅ 好的做法:预加载常用资源 +public class ResourcePreloader : AbstractSystem +{ + private IResourceManager _resourceManager; + + protected override void OnInit() + { + _resourceManager = GetUtility<IResourceManager>(); + } + + public async Task PreloadCommonResources() + { + // 预加载 UI 资源 + await _resourceManager.PreloadAsync<Texture>("ui/button_normal"); + await _resourceManager.PreloadAsync<Texture>("ui/button_pressed"); + await _resourceManager.PreloadAsync<Texture>("ui/button_hover"); + + // 预加载音效 + await _resourceManager.PreloadAsync<AudioClip>("sfx/button_click"); + await _resourceManager.PreloadAsync<AudioClip>("sfx/button_hover"); + + // 预加载特效 + await _resourceManager.PreloadAsync<ParticleSystem>("effects/hit_effect"); + await _resourceManager.PreloadAsync<ParticleSystem>("effects/explosion"); + } +} +``` + +### 2. 异步加载 + +```csharp +// ✅ 好的做法:使用异步加载避免阻塞 +public class SceneLoader : AbstractSystem +{ + private IResourceManager _resourceManager; + + public async Task LoadSceneAsync(string sceneName) + { + // 显示加载进度 + var progress = 0f; + UpdateLoadingProgress(progress); + + // 异步加载场景资源 + var sceneData = await _resourceManager.LoadAsync<SceneData>($"scenes/{sceneName}"); + progress += 0.3f; + UpdateLoadingProgress(progress); + + // 异步加载场景依赖的资源 + await LoadSceneDependencies(sceneData); + progress += 0.5f; + UpdateLoadingProgress(progress); + + // 初始化场景 + await InitializeScene(sceneData); + progress = 1f; + UpdateLoadingProgress(progress); + } + + private async Task LoadSceneDependencies(SceneData sceneData) + { + var tasks = new List<Task>(); + + foreach (var dependency in sceneData.Dependencies) + { + tasks.Add(_resourceManager.LoadAsync<object>(dependency)); + } + + await Task.WhenAll(tasks); + } +} + +// ❌ 避免:同步加载阻塞主线程 +public class SceneLoader : AbstractSystem +{ + public void LoadScene(string sceneName) + { + // 同步加载会阻塞主线程,导致卡顿 + var sceneData = _resourceManager.Load<SceneData>($"scenes/{sceneName}"); + LoadSceneDependencies(sceneData); + InitializeScene(sceneData); + } +} +``` + +### 3. 资源引用计数 + +```csharp +// ✅ 好的做法:使用资源句柄管理引用 +public class EntityRenderer : AbstractSystem +{ + private readonly Dictionary<string, IResourceHandle<Texture>> _textureHandles = new(); + + public void LoadTexture(string entityId, string texturePath) + { + // 获取资源句柄 + var handle = _resourceManager.GetHandle<Texture>(texturePath); + if (handle != null) + { + _textureHandles[entityId] = handle; + } + } + + public void UnloadTexture(string entityId) + { + if (_textureHandles.TryGetValue(entityId, out var handle)) + { + // 释放句柄,自动管理引用计数 + handle.Dispose(); + _textureHandles.Remove(entityId); + } + } + + protected override void OnDestroy() + { + // 清理所有句柄 + foreach (var handle in _textureHandles.Values) + { + handle.Dispose(); + } + _textureHandles.Clear(); + } +} +``` + +### 4. 资源缓存策略 + +```csharp +// ✅ 好的做法:使用合适的释放策略 +public class GameResourceManager : AbstractSystem +{ + private IResourceManager _resourceManager; + + protected override void OnInit() + { + _resourceManager = GetUtility<IResourceManager>(); + + // 设置自动释放策略 + _resourceManager.SetReleaseStrategy(new AutoReleaseStrategy()); + } + + public void OnSceneChanged() + { + // 场景切换时卸载未使用的资源 + UnloadUnusedResources(); + } + + private void UnloadUnusedResources() + { + var loadedPaths = _resourceManager.GetLoadedResourcePaths().ToList(); + + foreach (var path in loadedPaths) + { + // 检查资源是否仍在使用 + if (!IsResourceInUse(path)) + { + _resourceManager.Unload(path); + } + } + } + + private bool IsResourceInUse(string path) + { + // 检查资源引用计数 + return false; // 实现具体逻辑 + } +} +``` + +## ECS 性能优化 + +Entity Component System (ECS) 是高性能游戏架构的关键。 + +### 1. 组件设计优化 + +```csharp +// ✅ 好的做法:使用值类型组件 +public struct Position +{ + public float X; + public float Y; + public float Z; +} + +public struct Velocity +{ + public float X; + public float Y; + public float Z; +} + +public struct Health +{ + public int Current; + public int Max; +} + +// ❌ 避免:使用引用类型组件 +public class Position +{ + public float X { get; set; } + public float Y { get; set; } + public float Z { get; set; } +} +``` + +### 2. 查询优化 + +```csharp +// ✅ 好的做法:使用高效的查询 +public class MovementSystem : ArchSystemAdapter +{ + private QueryDescription _movementQuery; + + public override void Initialize() + { + // 预先构建查询 + _movementQuery = new QueryDescription() + .WithAll<Position, Velocity>() + .WithNone<Frozen>(); + } + + public override void Update(float deltaTime) + { + // 使用预构建的查询 + World.Query(in _movementQuery, (ref Position pos, ref Velocity vel) => + { + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; + pos.Z += vel.Z * deltaTime; + }); + } +} + +// ❌ 避免:每帧构建查询 +public class MovementSystem : ArchSystemAdapter +{ + public override void Update(float deltaTime) + { + // 每帧都构建新查询,性能差 + var query = new QueryDescription() + .WithAll<Position, Velocity>() + .WithNone<Frozen>(); + + World.Query(in query, (ref Position pos, ref Velocity vel) => + { + pos.X += vel.X * deltaTime; + pos.Y += vel.Y * deltaTime; + pos.Z += vel.Z * deltaTime; + }); + } +} +``` + +### 3. 批量处理 + +```csharp +// ✅ 好的做法:批量处理实体 +public class DamageSystem : ArchSystemAdapter +{ + private readonly List<(EntityReference entity, int damage)> _pendingDamages = new(); + + public void QueueDamage(EntityReference entity, int damage) + { + _pendingDamages.Add((entity, damage)); + } + + public override void Update(float deltaTime) + { + if (_pendingDamages.Count == 0) + return; + + // 批量应用伤害 + foreach (var (entity, damage) in _pendingDamages) + { + if (World.IsAlive(entity)) + { + ref var health = ref World.Get<Health>(entity); + health.Current = Math.Max(0, health.Current - damage); + + if (health.Current == 0) + { + World.Destroy(entity); + } + } + } + + _pendingDamages.Clear(); + } +} +``` + +### 4. 避免装箱 + +```csharp +// ✅ 好的做法:避免装箱操作 +public struct EntityId : IEquatable<EntityId> +{ + public int Value; + + public bool Equals(EntityId other) => Value == other.Value; + public override bool Equals(object obj) => obj is EntityId other && Equals(other); + public override int GetHashCode() => Value; +} + +// 使用泛型避免装箱 +public class EntityCache<T> where T : struct +{ + private readonly Dictionary<EntityId, T> _cache = new(); + + public void Add(EntityId id, T data) + { + _cache[id] = data; // 无装箱 + } + + public bool TryGet(EntityId id, out T data) + { + return _cache.TryGetValue(id, out data); // 无装箱 + } +} + +// ❌ 避免:导致装箱的操作 +public class EntityCache +{ + private readonly Dictionary<int, object> _cache = new(); + + public void Add(int id, object data) + { + _cache[id] = data; // 值类型会装箱 + } +} +``` + +## 内存优化 + +减少内存分配和 GC 压力是性能优化的重要方面。 + +### 1. 使用 Span<T> + +```csharp +// ✅ 好的做法:使用 Span 避免分配 +public void ProcessData(ReadOnlySpan<byte> data) +{ + // 直接在栈上处理,无堆分配 + Span<int> results = stackalloc int[data.Length]; + + for (int i = 0; i < data.Length; i++) + { + results[i] = ProcessByte(data[i]); + } + + // 使用结果 + UseResults(results); +} + +// 解析字符串避免分配 +public bool TryParseValue(ReadOnlySpan<char> input, out int result) +{ + return int.TryParse(input, out result); +} + +// ❌ 避免:不必要的数组分配 +public void ProcessData(byte[] data) +{ + // 创建新数组,产生 GC + var results = new int[data.Length]; + + for (int i = 0; i < data.Length; i++) + { + results[i] = ProcessByte(data[i]); + } + + UseResults(results); +} +``` + +### 2. 结构体优化 + +```csharp +// ✅ 好的做法:使用 readonly struct +public readonly struct Vector2D +{ + public readonly float X; + public readonly float Y; + + public Vector2D(float x, float y) + { + X = x; + Y = y; + } + + public float Length() => MathF.Sqrt(X * X + Y * Y); + + public Vector2D Normalized() + { + var length = Length(); + return length > 0 ? new Vector2D(X / length, Y / length) : this; + } +} + +// ❌ 避免:可变结构体 +public struct Vector2D +{ + public float X { get; set; } + public float Y { get; set; } + + // 可变结构体可能导致意外的复制 +} +``` + +### 3. 避免闭包分配 + +```csharp +// ✅ 好的做法:避免闭包捕获 +public class EventProcessor : AbstractSystem +{ + private readonly Action<PlayerEvent> _cachedHandler; + + public EventProcessor() + { + // 缓存委托,避免每次分配 + _cachedHandler = HandlePlayerEvent; + } + + protected override void OnInit() + { + this.RegisterEvent(_cachedHandler); + } + + private void HandlePlayerEvent(PlayerEvent e) + { + ProcessEvent(e); + } +} + +// ❌ 避免:每次都创建新的闭包 +public class EventProcessor : AbstractSystem +{ + protected override void OnInit() + { + // 每次都创建新的委托和闭包 + this.RegisterEvent<PlayerEvent>(e => + { + ProcessEvent(e); + }); + } +} +``` + +### 4. 字符串优化 + +```csharp +// ✅ 好的做法:减少字符串分配 +public class Logger +{ + private readonly StringBuilder _sb = new(); + + public void LogFormat(string format, params object[] args) + { + _sb.Clear(); + _sb.AppendFormat(format, args); + Log(_sb.ToString()); + } + + // 使用字符串插值的优化版本 + public void LogInterpolated(ref DefaultInterpolatedStringHandler handler) + { + Log(handler.ToStringAndClear()); + } +} + +// 使用 string.Create 避免中间分配 +public string CreateEntityName(int id, string type) +{ + return string.Create(type.Length + 10, (id, type), (span, state) => + { + state.type.AsSpan().CopyTo(span); + span = span.Slice(state.type.Length); + span[0] = '_'; + state.id.TryFormat(span.Slice(1), out _); + }); +} + +// ❌ 避免:频繁的字符串拼接 +public string CreateEntityName(int id, string type) +{ + return type + "_" + id.ToString(); // 创建多个临时字符串 +} +``` + +## 最佳实践 + +### 1. 性能测试 + +```csharp +// ✅ 使用性能测试验证优化效果 +[TestFixture] +public class PerformanceTests +{ + [Test] + [Performance] + public void ObjectPool_Performance_Test() + { + var pool = new BulletPoolSystem(); + pool.Prewarm("bullet", 1000); + + Measure.Method(() => + { + var bullet = pool.Acquire("bullet"); + pool.Release("bullet", bullet); + }) + .WarmupCount(10) + .MeasurementCount(100) + .Run(); + } + + [Test] + public void CompareAllocationMethods() + { + // 测试对象池 + var poolTime = MeasureTime(() => + { + var pool = ArrayPool<int>.Shared; + var array = pool.Rent(1000); + pool.Return(array); + }); + + // 测试直接分配 + var allocTime = MeasureTime(() => + { + var array = new int[1000]; + }); + + Assert.Less(poolTime, allocTime, "Object pool should be faster"); + } + + private long MeasureTime(Action action) + { + var sw = Stopwatch.StartNew(); + for (int i = 0; i < 10000; i++) + { + action(); + } + sw.Stop(); + return sw.ElapsedMilliseconds; + } +} +``` + +### 2. 性能监控 + +```csharp +// ✅ 实现性能监控系统 +public class PerformanceMonitor : AbstractSystem +{ + private readonly Dictionary<string, PerformanceMetric> _metrics = new(); + private float _updateInterval = 1.0f; + private float _timeSinceUpdate = 0f; + + public void Update(float deltaTime) + { + _timeSinceUpdate += deltaTime; + + if (_timeSinceUpdate >= _updateInterval) + { + UpdateMetrics(); + _timeSinceUpdate = 0f; + } + } + + public void RecordMetric(string name, float value) + { + if (!_metrics.TryGetValue(name, out var metric)) + { + metric = new PerformanceMetric(name); + _metrics[name] = metric; + } + + metric.AddSample(value); + } + + private void UpdateMetrics() + { + foreach (var metric in _metrics.Values) + { + Logger.Info($"{metric.Name}: Avg={metric.Average:F2}ms, " + + $"Min={metric.Min:F2}ms, Max={metric.Max:F2}ms"); + metric.Reset(); + } + } +} + +public class PerformanceMetric +{ + public string Name { get; } + public float Average => _count > 0 ? _sum / _count : 0; + public float Min { get; private set; } = float.MaxValue; + public float Max { get; private set; } = float.MinValue; + + private float _sum; + private int _count; + + public PerformanceMetric(string name) + { + Name = name; + } + + public void AddSample(float value) + { + _sum += value; + _count++; + Min = Math.Min(Min, value); + Max = Math.Max(Max, value); + } + + public void Reset() + { + _sum = 0; + _count = 0; + Min = float.MaxValue; + Max = float.MinValue; + } +} +``` + +### 3. 性能分析工具 + +使用性能分析工具定位瓶颈: + +- **Unity Profiler** - Unity 内置的性能分析工具 +- **Godot Profiler** - Godot 的性能监控工具 +- **dotTrace** - JetBrains 的 .NET 性能分析器 +- **PerfView** - Microsoft 的性能分析工具 + +### 4. 性能优化清单 + +在优化性能时,遵循以下清单: + +- [ ] 使用对象池减少 GC 压力 +- [ ] 正确管理事件订阅,避免内存泄漏 +- [ ] 使用结构体事件避免堆分配 +- [ ] 合理使用协程,避免过度嵌套 +- [ ] 异步加载资源,避免阻塞主线程 +- [ ] 使用 ECS 架构提高数据局部性 +- [ ] 使用 Span<T> 和 stackalloc 减少分配 +- [ ] 避免装箱操作 +- [ ] 缓存常用的委托和对象 +- [ ] 批量处理高频操作 +- [ ] 定期进行性能测试和监控 + +## 常见问题 + +### Q1: 什么时候应该使用对象池? + +**A**: 当满足以下条件时考虑使用对象池: + +- 对象创建成本高(如包含复杂初始化逻辑) +- 对象频繁创建和销毁(如子弹、特效粒子) +- 对象生命周期短暂但使用频繁 +- 需要减少 GC 压力 + +### Q2: 如何判断是否存在内存泄漏? + +**A**: 观察以下指标: + +- 内存使用持续增长,不会下降 +- GC 频率增加但内存不释放 +- 事件订阅数量持续增加 +- 对象池中的活跃对象数量异常 + +使用内存分析工具(如 dotMemory)定位泄漏源。 + +### Q3: 协程和 async/await 如何选择? + +**A**: 选择建议: + +- **协程** - 游戏逻辑、动画序列、分帧处理 +- **async/await** - I/O 操作、网络请求、文件读写 + +协程更适合游戏帧循环,async/await 更适合异步 I/O。 + +### Q4: 如何优化大量实体的性能? + +**A**: 使用 ECS 架构: + +- 使用值类型组件减少堆分配 +- 预构建查询避免每帧创建 +- 批量处理实体操作 +- 使用并行处理(如果支持) + +### Q5: 什么时候应该进行性能优化? + +**A**: 遵循以下原则: + +- **先测量,再优化** - 使用性能分析工具找到真正的瓶颈 +- **优先优化热点** - 集中优化占用时间最多的代码 +- **避免过早优化** - 在功能完成后再进行优化 +- **保持可读性** - 不要为了微小的性能提升牺牲代码可读性 + +### Q6: 如何减少 GC 压力? + +**A**: 采取以下措施: + +- 使用对象池复用对象 +- 使用值类型(struct)而非引用类型(class) +- 使用 Span<T> 和 stackalloc 进行栈分配 +- 避免闭包捕获和装箱操作 +- 缓存常用对象和委托 +- 使用 StringBuilder 而非字符串拼接 + +--- + +## 总结 + +性能优化是一个持续的过程,需要: + +- ✅ **测量优先** - 使用工具定位真正的瓶颈 +- ✅ **合理使用对象池** - 减少 GC 压力 +- ✅ **优化事件系统** - 避免内存泄漏和不必要的处理 +- ✅ **高效使用协程** - 避免过度嵌套和不必要的等待 +- ✅ **智能资源管理** - 异步加载和合理缓存 +- ✅ **ECS 架构优化** - 提高数据局部性和处理效率 +- ✅ **内存优化** - 减少分配,使用栈内存 +- ✅ **持续监控** - 建立性能监控系统 + +记住,性能优化要在可维护性和性能之间找到平衡,不要为了微小的性能提升而牺牲代码质量。 + +--- + +**文档版本**: 1.0.0 +**更新日期**: 2026-03-07 diff --git a/docs/zh-CN/core/architecture.md b/docs/zh-CN/core/architecture.md index 76592bf..ea4e6e4 100644 --- a/docs/zh-CN/core/architecture.md +++ b/docs/zh-CN/core/architecture.md @@ -1,606 +1,608 @@ -# Architecture 包使用说明 - -## 概述 - -Architecture 包是整个框架的核心,提供了基于分层架构模式的应用程序架构基础。它实现了依赖注入(IoC) -容器、组件生命周期管理,以及命令、查询、事件的统一调度机制。 - -**注意**:本框架的 Core 模块与 Godot 解耦,Godot 相关集成在 GFramework.Godot 包中实现。 - -## 核心接口 - -### IArchitecture - -架构接口,定义了框架的核心功能契约。 - -**主要职责:** - -- **组件注册**:注册 System、Model、Utility -- **组件获取**:从容器中获取已注册的组件 -- **命令处理**:发送并执行命令 -- **查询处理**:发送并执行查询 -- **事件管理**:发送、注册、注销事件 -- **模块管理**:安装和管理架构模块 -- **生命周期管理**:管理架构的初始化、运行和销毁阶段 - -**核心方法:** -```csharp -// 组件注册 -void RegisterSystem(TSystem system) where TSystem : ISystem; -void RegisterModel(TModel model) where TModel : IModel; -void RegisterUtility(TUtility utility) where TUtility : IUtility; - -// 组件获取(通过容器) -T GetModel() where T : class, IModel; -T GetSystem() where T : class, ISystem; -T GetUtility() where T : class, IUtility; - -// 命令处理 -void SendCommand(ICommand command); -TResult SendCommand(ICommand command); - -// 查询处理 -TResult SendQuery(IQuery query); - -// 事件管理 -void SendEvent() where T : new(); -void SendEvent(T e); -IUnRegister RegisterEvent(Action onEvent); -void UnRegisterEvent(Action onEvent); -``` - -### IArchitecturePhaseAware - -架构阶段感知接口,允许组件监听架构阶段变化。 - -**核心方法:** -```csharp -void OnArchitecturePhase(ArchitecturePhase phase); -``` - -### IArchitectureModule - -架构模块接口,支持模块化架构扩展。 - -**核心方法:** -```csharp -void Install(IArchitecture architecture); -``` - -### IAsyncInitializable - -异步初始化接口,支持组件异步初始化。 - -**核心方法:** -```csharp -Task InitializeAsync(); -``` - -## 核心类 - -### Architecture 架构基类 - -架构基类,实现了 `IArchitecture` 接口,提供完整的架构功能实现。 - -**构造函数参数:** -```csharp -public abstract class Architecture( - IArchitectureConfiguration? configuration = null, - IEnvironment? environment = null, - IArchitectureServices? services = null, - IArchitectureContext? context = null -) -``` - -**特性:** - -- **阶段式生命周期管理** - :支持多个架构阶段(BeforeUtilityInit、AfterUtilityInit、BeforeModelInit、AfterModelInit、BeforeSystemInit、AfterSystemInit、Ready、FailedInitialization、Destroying、Destroyed) -- **模块安装系统**:支持通过 `InstallModule` 扩展架构功能 -- **异步初始化**:支持同步和异步两种初始化方式 -- **IoC 容器集成**:内置依赖注入容器 -- **事件系统集成**:集成类型化事件系统 -- **与平台无关**:Core 模块不依赖 Godot,可以在任何 .NET 环境中使用 -- **严格的阶段验证**:可配置的阶段转换验证机制 -- **组件生命周期管理**:自动管理组件的初始化和销毁 - -**架构阶段:** -```csharp -public enum ArchitecturePhase -{ - None = 0, // 初始阶段 - BeforeUtilityInit = 1, // 工具初始化前 - AfterUtilityInit = 2, // 工具初始化后 - BeforeModelInit = 3, // 模型初始化前 - AfterModelInit = 4, // 模型初始化后 - BeforeSystemInit = 5, // 系统初始化前 - AfterSystemInit = 6, // 系统初始化后 - Ready = 7, // 就绪状态 - FailedInitialization = 8, // 初始化失败 - Destroying = 9, // 正在销毁 - Destroyed = 10 // 已销毁 -} -``` - -**初始化流程:** - -1. 创建架构实例(传入配置或使用默认配置) -2. 初始化基础上下文和日志系统 -3. 调用用户自定义的 `Init()` 方法 -4. 按顺序初始化组件: - - 工具初始化(BeforeUtilityInit → AfterUtilityInit) - - 模型初始化(BeforeModelInit → AfterModelInit) - - 系统初始化(BeforeSystemInit → AfterSystemInit) -5. 冻结 IOC 容器 -6. 进入 Ready 阶段 - -**销毁流程:** - -1. 进入 Destroying 阶段 -2. 按注册逆序销毁所有实现了 `IDisposable` 的组件 -3. 进入 Destroyed 阶段 -4. 清理所有资源 - -**使用示例:** - -```csharp -// 1. 定义你的架构(继承 Architecture 基类) -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册 Model - RegisterModel(new PlayerModel()); - RegisterModel(new InventoryModel()); - - // 注册 System - RegisterSystem(new GameplaySystem()); - RegisterSystem(new SaveSystem()); - - // 注册 Utility - RegisterUtility(new StorageUtility()); - RegisterUtility(new TimeUtility()); - } -} - -// 2. 创建并初始化架构 -var architecture = new GameArchitecture(); -architecture.Initialize(); - -// 或者异步初始化 -// var architecture = new GameArchitecture(); -// await architecture.InitializeAsync(); - -// 3. 等待架构就绪 -await architecture.WaitUntilReadyAsync(); - -// 4. 通过依赖注入使用架构 -// 在 Controller 或其他组件中获取架构实例 -public class GameController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void Start() - { - // 获取 Model - var playerModel = this.GetModel(); - - // 发送命令 - this.SendCommand(new StartGameCommand()); - - // 发送查询 - var score = this.SendQuery(new GetScoreQuery()); - - // 注册事件 - this.RegisterEvent(OnPlayerDied); - } - - private void OnPlayerDied(PlayerDiedEvent e) - { - // 处理玩家死亡事件 - } -} -``` - -**核心方法与属性:** - -#### 初始化方法 - -**Initialize()** - -同步初始化方法,阻塞当前线程直到初始化完成。 -```csharp -public void Initialize() -``` - -**特点:** - -- 阻塞式初始化 -- 适用于简单场景或控制台应用 -- 初始化失败时抛出异常并进入 `FailedInitialization` 阶段 - -使用示例: -```csharp -var architecture = new GameArchitecture(); -architecture.Initialize(); // 阻塞等待初始化完成 -``` - -异常处理:如果初始化过程中发生异常,架构会进入 `FailedInitialization` 阶段。 - -**InitializeAsync()** - -异步初始化方法,返回 Task 以便调用者可以等待初始化完成。 -```csharp -public async Task InitializeAsync() -``` - -**特点:** - -- 非阻塞式初始化 -- 支持异步组件初始化 -- 适用于需要异步加载资源的场景 -- 初始化失败时抛出异常并进入 `FailedInitialization` 阶段 - -使用示例: -```csharp -var architecture = new GameArchitecture(); -await architecture.InitializeAsync(); // 异步等待初始化完成 -``` - -优势: -- 支持异步初始化 Model 和 System -- 可以利用异步 I/O 操作(如异步加载数据) -- 提高初始化性能 -- 不会阻塞主线程 - -#### 模块管理 - -**InstallModule(IArchitectureModule module)** - -安装架构模块,用于扩展架构功能。 -```csharp -public IArchitectureModule InstallModule(IArchitectureModule module) -``` - -**返回值:** 返回安装的模块实例 - -**特点:** - -- 模块安装时会自动注册生命周期钩子 -- 模块可以注册额外的组件到架构中 -- 只能在架构进入 Ready 阶段之前安装模块 - -参数: - -- `module`:要安装的模块实例 - -使用示例: - -```csharp -// 定义模块 -public class NetworkModule : IArchitectureModule -{ - public void Install(IArchitecture architecture) - { - // 注册网络相关的 System 和 Utility - architecture.RegisterSystem(new NetworkSystem()); - architecture.RegisterUtility(new HttpClientUtility()); - } -} - -// 安装模块 -var architecture = new GameArchitecture(); -var installedModule = architecture.InstallModule(new NetworkModule()); -``` - -#### 生命周期钩子 - -**RegisterLifecycleHook(IArchitectureLifecycle hook)** - -注册生命周期钩子,用于在架构阶段变化时执行自定义逻辑。 -```csharp -public IArchitectureLifecycle RegisterLifecycleHook(IArchitectureLifecycle hook) -``` - -**返回值:** 返回注册的生命周期钩子实例 - -**特点:** - -- 生命周期钩子可以监听所有架构阶段变化 -- 只能在架构进入 Ready 阶段之前注册 -- 架构会按注册顺序通知所有钩子 - -参数: - -- `hook`:生命周期钩子实例 - -使用示例: - -```csharp -// 定义生命周期钩子 -public class PerformanceMonitorHook : IArchitectureLifecycle -{ - public void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - switch (phase) - { - case ArchitecturePhase.BeforeModelInit: - Console.WriteLine("开始监控 Model 初始化性能"); - break; - case ArchitecturePhase.AfterModelInit: - Console.WriteLine("Model 初始化完成,停止监控"); - break; - case ArchitecturePhase.Ready: - Console.WriteLine("架构就绪,开始性能统计"); - break; - } - } -} - -// 注册生命周期钩子 -var architecture = new GameArchitecture(); -var registeredHook = architecture.RegisterLifecycleHook(new PerformanceMonitorHook()); -architecture.Initialize(); -``` - -**注意:** 生命周期钩子只能在架构进入 Ready 阶段之前注册。 - -#### 属性 - -**CurrentPhase** - -获取当前架构的阶段。 -```csharp -public ArchitecturePhase CurrentPhase { get; private set; } -``` - -**特点:** - -- 只读属性,外部无法直接修改 -- 实时反映架构的当前状态 -- 可用于条件判断和状态检查 - -使用示例: - -```csharp -var architecture = new GameArchitecture(); - -// 检查架构是否已就绪 -if (architecture.CurrentPhase == ArchitecturePhase.Ready) -{ - Console.WriteLine("架构已就绪,可以开始游戏"); -} - -// 在异步操作中检查阶段变化 -await Task.Run(async () => -{ - while (architecture.CurrentPhase != ArchitecturePhase.Ready) - { - Console.WriteLine($"当前阶段: {architecture.CurrentPhase}"); - await Task.Delay(100); - } -}); -``` - -**Context** - -获取架构上下文,提供对架构服务的访问。 -```csharp -public IArchitectureContext Context { get; } -``` - -**特点:** - -- 提供对架构核心服务的访问 -- 包含事件总线、命令总线、查询总线等 -- 是架构内部服务的统一入口 - -使用示例: - -```csharp -// 通过 Context 访问服务 -var context = architecture.Context; -var eventBus = context.EventBus; -var commandBus = context.CommandBus; -var queryBus = context.QueryBus; -var environment = context.Environment; -``` - -#### 高级特性 - -```csharp -// 1. 使用自定义配置 -var config = new ArchitectureConfiguration -{ - ArchitectureProperties = new ArchitectureProperties - { - StrictPhaseValidation = true, // 启用严格阶段验证 - AllowLateRegistration = false // 禁止就绪后注册组件 - } -}; -var architecture = new GameArchitecture(configuration: config); - -// 2. 模块安装 -var module = new GameModule(); -architecture.InstallModule(module); - -// 3. 监听架构阶段变化 -public class GamePhaseListener : IArchitecturePhaseAware -{ - public void OnArchitecturePhase(ArchitecturePhase phase) - { - switch (phase) - { - case ArchitecturePhase.Ready: - Console.WriteLine("架构已就绪,可以开始游戏了"); - break; - case ArchitecturePhase.Destroying: - Console.WriteLine("架构正在销毁"); - break; - } - } -} - -// 4. 生命周期钩子 -public class LifecycleHook : IArchitectureLifecycle -{ - public void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - Console.WriteLine($"架构阶段变化: {phase}"); - } -} - -// 5. 等待架构就绪 -public async Task WaitForArchitectureReady() -{ - var architecture = new GameArchitecture(); - var initTask = architecture.InitializeAsync(); - - // 可以在其他地方等待架构就绪 - await architecture.WaitUntilReadyAsync(); - Console.WriteLine("架构已就绪"); -} -``` - -### ArchitectureConfiguration 架构配置类 - -架构配置类,用于配置架构的行为。 - -**主要配置项:** - -```csharp -public class ArchitectureConfiguration : IArchitectureConfiguration -{ - public IArchitectureProperties ArchitectureProperties { get; set; } = new ArchitectureProperties(); - public IEnvironmentProperties EnvironmentProperties { get; set; } = new EnvironmentProperties(); - public ILoggerProperties LoggerProperties { get; set; } = new LoggerProperties(); -} - -public class ArchitectureProperties -{ - public bool StrictPhaseValidation { get; set; } = false; // 严格阶段验证 - public bool AllowLateRegistration { get; set; } = true; // 允许延迟注册 -} -``` - -**使用示例:** - -```csharp -var config = new ArchitectureConfiguration -{ - ArchitectureProperties = new ArchitectureProperties - { - StrictPhaseValidation = true, // 启用严格阶段验证 - AllowLateRegistration = false // 禁止就绪后注册组件 - }, - LoggerProperties = new LoggerProperties - { - LoggerFactoryProvider = new ConsoleLoggerFactoryProvider() // 自定义日志工厂 - } -}; - -var architecture = new GameArchitecture(configuration: config); -``` - -### ArchitectureServices 架构服务类 - -架构服务类,管理命令总线、查询总线、IOC容器和类型事件系统。 - -**核心服务:** - -- `IIocContainer Container`:依赖注入容器 -- `IEventBus EventBus`:事件总线 -- `ICommandBus CommandBus`:命令总线 -- `IQueryBus QueryBus`:查询总线 - -### ArchitectureContext 架构上下文类 - -架构上下文类,提供对架构服务的访问。 - -**功能:** - -- 统一访问架构核心服务 -- 管理服务实例的生命周期 -- 提供服务解析功能 - -### GameContext 游戏上下文类 - -游戏上下文类,管理架构上下文与类型的绑定关系。 - -**功能:** - -- 维护架构类型与上下文实例的映射 -- 提供全局上下文访问 -- 支持多架构实例管理 - -## 设计模式 - -### 1. 依赖注入 - -通过构造函数注入或容器解析获取架构实例。 - -### 2. 控制反转 (IoC) - -使用内置 IoC 容器管理组件生命周期和依赖关系。 - -### 3. 命令模式 - -通过 `ICommand` 封装所有用户操作。 - -### 4. 查询模式 (CQRS) - -通过 `IQuery` 分离查询和命令操作。 - -### 5. 观察者模式 - -通过事件系统实现组件间的松耦合通信。 - -### 6. 阶段式生命周期管理 - -通过 `ArchitecturePhase` 枚举和生命周期钩子管理架构状态。 - -### 7. 组合优于继承 - -通过接口组合获得不同能力,而不是深层继承链。 - -### 8. 模块化设计 - -通过 `IArchitectureModule` 实现架构的可扩展性。 - -### 9. 工厂模式 - -通过配置对象和工厂方法创建架构实例。 - -## 最佳实践 - -1. **保持架构类简洁**:只在 `Init()` 中注册组件,不要包含业务逻辑 -2. **合理划分职责**: - - Model:数据和状态 - - System:业务逻辑和规则 - - Utility:无状态的工具方法 -3. **使用依赖注入**:通过构造函数注入架构实例,便于测试 -4. **事件命名规范**:使用过去式命名事件类,如 `PlayerDiedEvent` -5. **避免循环依赖**:System 不应直接引用 System,应通过事件通信 -6. **使用模块扩展**:通过 `IArchitectureModule` 实现架构的可扩展性 -7. **Core 模块与平台解耦**:GFramework.Core 不包含 Godot 相关代码,Godot 集成在单独模块中 -8. **合理配置阶段验证**:根据项目需求配置 `StrictPhaseValidation` 和 `AllowLateRegistration` -9. **及时处理初始化异常**:捕获并处理架构初始化过程中的异常 -10. **使用异步初始化**:对于需要加载大量资源的场景,优先使用 `InitializeAsync()` - -## 相关包 - -- **command** - 命令模式实现 -- **query** - 查询模式实现 -- **events** - 事件系统 -- **ioc** - IoC 容器 -- **model** - 数据模型 -- **system** - 业务系统 -- **utility** - 工具类 -- **GFramework.Godot** - Godot 特定集成(GodotNode 扩展、GodotLogger 等) -- **extensions** - 扩展方法 -- **logging** - 日志系统 -- **environment** - 环境管理 - ---- - +# Architecture 包使用说明 + +## 概述 + +Architecture 包是整个框架的核心,提供了基于分层架构模式的应用程序架构基础。它实现了依赖注入(IoC) +容器、组件生命周期管理,以及命令、查询、事件的统一调度机制。 + +**注意**:本框架的 Core 模块与 Godot 解耦,Godot 相关集成在 GFramework.Godot 包中实现。 + +## 核心接口 + +### IArchitecture + +架构接口,定义了框架的核心功能契约。 + +**主要职责:** + +- **组件注册**:注册 System、Model、Utility +- **组件获取**:从容器中获取已注册的组件 +- **命令处理**:发送并执行命令 +- **查询处理**:发送并执行查询 +- **事件管理**:发送、注册、注销事件 +- **模块管理**:安装和管理架构模块 +- **生命周期管理**:管理架构的初始化、运行和销毁阶段 + +**核心方法:** +```csharp +// 组件注册 +void RegisterSystem(TSystem system) where TSystem : ISystem; +void RegisterModel(TModel model) where TModel : IModel; +void RegisterUtility(TUtility utility) where TUtility : IUtility; + +// 组件获取(通过容器) +T GetModel() where T : class, IModel; +T GetSystem() where T : class, ISystem; +T GetUtility() where T : class, IUtility; + +// 命令处理 +void SendCommand(ICommand command); +TResult SendCommand(ICommand command); + +// 查询处理 +TResult SendQuery(IQuery query); + +// 事件管理 +void SendEvent() where T : new(); +void SendEvent(T e); +IUnRegister RegisterEvent(Action onEvent); +void UnRegisterEvent(Action onEvent); +``` + +### IArchitecturePhaseAware + +架构阶段感知接口,允许组件监听架构阶段变化。 + +**核心方法:** +```csharp +void OnArchitecturePhase(ArchitecturePhase phase); +``` + +### IArchitectureModule + +架构模块接口,支持模块化架构扩展。 + +**核心方法:** +```csharp +void Install(IArchitecture architecture); +``` + +### IAsyncInitializable + +异步初始化接口,支持组件异步初始化。 + +**核心方法:** +```csharp +Task InitializeAsync(); +``` + +## 核心类 + +### Architecture 架构基类 + +架构基类,实现了 `IArchitecture` 接口,提供完整的架构功能实现。 + +**构造函数参数:** +```csharp +public abstract class Architecture( + IArchitectureConfiguration? configuration = null, + IEnvironment? environment = null, + IArchitectureServices? services = null, + IArchitectureContext? context = null +) +``` + +**特性:** + +- **阶段式生命周期管理** + :支持多个架构阶段(BeforeUtilityInit、AfterUtilityInit、BeforeModelInit、AfterModelInit、BeforeSystemInit、AfterSystemInit、Ready、FailedInitialization、Destroying、Destroyed) +- **模块安装系统**:支持通过 `InstallModule` 扩展架构功能 +- **异步初始化**:支持同步和异步两种初始化方式 +- **IoC 容器集成**:内置依赖注入容器 +- **事件系统集成**:集成类型化事件系统 +- **与平台无关**:Core 模块不依赖 Godot,可以在任何 .NET 环境中使用 +- **严格的阶段验证**:可配置的阶段转换验证机制 +- **组件生命周期管理**:自动管理组件的初始化和销毁 + +**架构阶段:** +```csharp +public enum ArchitecturePhase +{ + None = 0, // 初始阶段 + BeforeUtilityInit = 1, // 工具初始化前 + AfterUtilityInit = 2, // 工具初始化后 + BeforeModelInit = 3, // 模型初始化前 + AfterModelInit = 4, // 模型初始化后 + BeforeSystemInit = 5, // 系统初始化前 + AfterSystemInit = 6, // 系统初始化后 + Ready = 7, // 就绪状态 + FailedInitialization = 8, // 初始化失败 + Destroying = 9, // 正在销毁 + Destroyed = 10 // 已销毁 +} +``` + +**初始化流程:** + +1. 创建架构实例(传入配置或使用默认配置) +2. 初始化基础上下文和日志系统 +3. 调用用户自定义的 `Init()` 方法 +4. 按顺序初始化组件: + - 工具初始化(BeforeUtilityInit → AfterUtilityInit) + - 模型初始化(BeforeModelInit → AfterModelInit) + - 系统初始化(BeforeSystemInit → AfterSystemInit) +5. 冻结 IOC 容器 +6. 进入 Ready 阶段 + +**销毁流程:** + +1. 进入 Destroying 阶段 +2. 按注册逆序销毁所有实现了 `IDisposable` 的组件 +3. 进入 Destroyed 阶段 +4. 清理所有资源 + +**使用示例:** + +```csharp +// 1. 定义你的架构(继承 Architecture 基类) +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 注册 Model + RegisterModel(new PlayerModel()); + RegisterModel(new InventoryModel()); + + // 注册 System + RegisterSystem(new GameplaySystem()); + RegisterSystem(new SaveSystem()); + + // 注册 Utility + RegisterUtility(new StorageUtility()); + RegisterUtility(new TimeUtility()); + } +} + +// 2. 创建并初始化架构 +var architecture = new GameArchitecture(); +architecture.Initialize(); + +// 或者异步初始化 +// var architecture = new GameArchitecture(); +// await architecture.InitializeAsync(); + +// 3. 等待架构就绪 +await architecture.WaitUntilReadyAsync(); + +// 4. 通过依赖注入使用架构 +// 在 Controller 或其他组件中获取架构实例 +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameController : IController +{ + public void Start() + { + // 获取 Model(使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口)) + var playerModel = this.GetModel(); + + // 发送命令 + this.SendCommand(new StartGameCommand()); + + // 发送查询 + var score = this.SendQuery(new GetScoreQuery()); + + // 注册事件 + this.RegisterEvent(OnPlayerDied); + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + // 处理玩家死亡事件 + } +} +``` + +**核心方法与属性:** + +#### 初始化方法 + +**Initialize()** + +同步初始化方法,阻塞当前线程直到初始化完成。 +```csharp +public void Initialize() +``` + +**特点:** + +- 阻塞式初始化 +- 适用于简单场景或控制台应用 +- 初始化失败时抛出异常并进入 `FailedInitialization` 阶段 + +使用示例: +```csharp +var architecture = new GameArchitecture(); +architecture.Initialize(); // 阻塞等待初始化完成 +``` + +异常处理:如果初始化过程中发生异常,架构会进入 `FailedInitialization` 阶段。 + +**InitializeAsync()** + +异步初始化方法,返回 Task 以便调用者可以等待初始化完成。 +```csharp +public async Task InitializeAsync() +``` + +**特点:** + +- 非阻塞式初始化 +- 支持异步组件初始化 +- 适用于需要异步加载资源的场景 +- 初始化失败时抛出异常并进入 `FailedInitialization` 阶段 + +使用示例: +```csharp +var architecture = new GameArchitecture(); +await architecture.InitializeAsync(); // 异步等待初始化完成 +``` + +优势: +- 支持异步初始化 Model 和 System +- 可以利用异步 I/O 操作(如异步加载数据) +- 提高初始化性能 +- 不会阻塞主线程 + +#### 模块管理 + +**InstallModule(IArchitectureModule module)** + +安装架构模块,用于扩展架构功能。 +```csharp +public IArchitectureModule InstallModule(IArchitectureModule module) +``` + +**返回值:** 返回安装的模块实例 + +**特点:** + +- 模块安装时会自动注册生命周期钩子 +- 模块可以注册额外的组件到架构中 +- 只能在架构进入 Ready 阶段之前安装模块 + +参数: + +- `module`:要安装的模块实例 + +使用示例: + +```csharp +// 定义模块 +public class NetworkModule : IArchitectureModule +{ + public void Install(IArchitecture architecture) + { + // 注册网络相关的 System 和 Utility + architecture.RegisterSystem(new NetworkSystem()); + architecture.RegisterUtility(new HttpClientUtility()); + } +} + +// 安装模块 +var architecture = new GameArchitecture(); +var installedModule = architecture.InstallModule(new NetworkModule()); +``` + +#### 生命周期钩子 + +**RegisterLifecycleHook(IArchitectureLifecycle hook)** + +注册生命周期钩子,用于在架构阶段变化时执行自定义逻辑。 +```csharp +public IArchitectureLifecycle RegisterLifecycleHook(IArchitectureLifecycle hook) +``` + +**返回值:** 返回注册的生命周期钩子实例 + +**特点:** + +- 生命周期钩子可以监听所有架构阶段变化 +- 只能在架构进入 Ready 阶段之前注册 +- 架构会按注册顺序通知所有钩子 + +参数: + +- `hook`:生命周期钩子实例 + +使用示例: + +```csharp +// 定义生命周期钩子 +public class PerformanceMonitorHook : IArchitectureLifecycle +{ + public void OnPhase(ArchitecturePhase phase, IArchitecture architecture) + { + switch (phase) + { + case ArchitecturePhase.BeforeModelInit: + Console.WriteLine("开始监控 Model 初始化性能"); + break; + case ArchitecturePhase.AfterModelInit: + Console.WriteLine("Model 初始化完成,停止监控"); + break; + case ArchitecturePhase.Ready: + Console.WriteLine("架构就绪,开始性能统计"); + break; + } + } +} + +// 注册生命周期钩子 +var architecture = new GameArchitecture(); +var registeredHook = architecture.RegisterLifecycleHook(new PerformanceMonitorHook()); +architecture.Initialize(); +``` + +**注意:** 生命周期钩子只能在架构进入 Ready 阶段之前注册。 + +#### 属性 + +**CurrentPhase** + +获取当前架构的阶段。 +```csharp +public ArchitecturePhase CurrentPhase { get; private set; } +``` + +**特点:** + +- 只读属性,外部无法直接修改 +- 实时反映架构的当前状态 +- 可用于条件判断和状态检查 + +使用示例: + +```csharp +var architecture = new GameArchitecture(); + +// 检查架构是否已就绪 +if (architecture.CurrentPhase == ArchitecturePhase.Ready) +{ + Console.WriteLine("架构已就绪,可以开始游戏"); +} + +// 在异步操作中检查阶段变化 +await Task.Run(async () => +{ + while (architecture.CurrentPhase != ArchitecturePhase.Ready) + { + Console.WriteLine($"当前阶段: {architecture.CurrentPhase}"); + await Task.Delay(100); + } +}); +``` + +**Context** + +获取架构上下文,提供对架构服务的访问。 +```csharp +public IArchitectureContext Context { get; } +``` + +**特点:** + +- 提供对架构核心服务的访问 +- 包含事件总线、命令总线、查询总线等 +- 是架构内部服务的统一入口 + +使用示例: + +```csharp +// 通过 Context 访问服务 +var context = architecture.Context; +var eventBus = context.EventBus; +var commandBus = context.CommandBus; +var queryBus = context.QueryBus; +var environment = context.Environment; +``` + +#### 高级特性 + +```csharp +// 1. 使用自定义配置 +var config = new ArchitectureConfiguration +{ + ArchitectureProperties = new ArchitectureProperties + { + StrictPhaseValidation = true, // 启用严格阶段验证 + AllowLateRegistration = false // 禁止就绪后注册组件 + } +}; +var architecture = new GameArchitecture(configuration: config); + +// 2. 模块安装 +var module = new GameModule(); +architecture.InstallModule(module); + +// 3. 监听架构阶段变化 +public class GamePhaseListener : IArchitecturePhaseAware +{ + public void OnArchitecturePhase(ArchitecturePhase phase) + { + switch (phase) + { + case ArchitecturePhase.Ready: + Console.WriteLine("架构已就绪,可以开始游戏了"); + break; + case ArchitecturePhase.Destroying: + Console.WriteLine("架构正在销毁"); + break; + } + } +} + +// 4. 生命周期钩子 +public class LifecycleHook : IArchitectureLifecycle +{ + public void OnPhase(ArchitecturePhase phase, IArchitecture architecture) + { + Console.WriteLine($"架构阶段变化: {phase}"); + } +} + +// 5. 等待架构就绪 +public async Task WaitForArchitectureReady() +{ + var architecture = new GameArchitecture(); + var initTask = architecture.InitializeAsync(); + + // 可以在其他地方等待架构就绪 + await architecture.WaitUntilReadyAsync(); + Console.WriteLine("架构已就绪"); +} +``` + +### ArchitectureConfiguration 架构配置类 + +架构配置类,用于配置架构的行为。 + +**主要配置项:** + +```csharp +public class ArchitectureConfiguration : IArchitectureConfiguration +{ + public IArchitectureProperties ArchitectureProperties { get; set; } = new ArchitectureProperties(); + public IEnvironmentProperties EnvironmentProperties { get; set; } = new EnvironmentProperties(); + public ILoggerProperties LoggerProperties { get; set; } = new LoggerProperties(); +} + +public class ArchitectureProperties +{ + public bool StrictPhaseValidation { get; set; } = false; // 严格阶段验证 + public bool AllowLateRegistration { get; set; } = true; // 允许延迟注册 +} +``` + +**使用示例:** + +```csharp +var config = new ArchitectureConfiguration +{ + ArchitectureProperties = new ArchitectureProperties + { + StrictPhaseValidation = true, // 启用严格阶段验证 + AllowLateRegistration = false // 禁止就绪后注册组件 + }, + LoggerProperties = new LoggerProperties + { + LoggerFactoryProvider = new ConsoleLoggerFactoryProvider() // 自定义日志工厂 + } +}; + +var architecture = new GameArchitecture(configuration: config); +``` + +### ArchitectureServices 架构服务类 + +架构服务类,管理命令总线、查询总线、IOC容器和类型事件系统。 + +**核心服务:** + +- `IIocContainer Container`:依赖注入容器 +- `IEventBus EventBus`:事件总线 +- `ICommandBus CommandBus`:命令总线 +- `IQueryBus QueryBus`:查询总线 + +### ArchitectureContext 架构上下文类 + +架构上下文类,提供对架构服务的访问。 + +**功能:** + +- 统一访问架构核心服务 +- 管理服务实例的生命周期 +- 提供服务解析功能 + +### GameContext 游戏上下文类 + +游戏上下文类,管理架构上下文与类型的绑定关系。 + +**功能:** + +- 维护架构类型与上下文实例的映射 +- 提供全局上下文访问 +- 支持多架构实例管理 + +## 设计模式 + +### 1. 依赖注入 + +通过构造函数注入或容器解析获取架构实例。 + +### 2. 控制反转 (IoC) + +使用内置 IoC 容器管理组件生命周期和依赖关系。 + +### 3. 命令模式 + +通过 `ICommand` 封装所有用户操作。 + +### 4. 查询模式 (CQRS) + +通过 `IQuery` 分离查询和命令操作。 + +### 5. 观察者模式 + +通过事件系统实现组件间的松耦合通信。 + +### 6. 阶段式生命周期管理 + +通过 `ArchitecturePhase` 枚举和生命周期钩子管理架构状态。 + +### 7. 组合优于继承 + +通过接口组合获得不同能力,而不是深层继承链。 + +### 8. 模块化设计 + +通过 `IArchitectureModule` 实现架构的可扩展性。 + +### 9. 工厂模式 + +通过配置对象和工厂方法创建架构实例。 + +## 最佳实践 + +1. **保持架构类简洁**:只在 `Init()` 中注册组件,不要包含业务逻辑 +2. **合理划分职责**: + - Model:数据和状态 + - System:业务逻辑和规则 + - Utility:无状态的工具方法 +3. **使用依赖注入**:通过构造函数注入架构实例,便于测试 +4. **事件命名规范**:使用过去式命名事件类,如 `PlayerDiedEvent` +5. **避免循环依赖**:System 不应直接引用 System,应通过事件通信 +6. **使用模块扩展**:通过 `IArchitectureModule` 实现架构的可扩展性 +7. **Core 模块与平台解耦**:GFramework.Core 不包含 Godot 相关代码,Godot 集成在单独模块中 +8. **合理配置阶段验证**:根据项目需求配置 `StrictPhaseValidation` 和 `AllowLateRegistration` +9. **及时处理初始化异常**:捕获并处理架构初始化过程中的异常 +10. **使用异步初始化**:对于需要加载大量资源的场景,优先使用 `InitializeAsync()` + +## 相关包 + +- **command** - 命令模式实现 +- **query** - 查询模式实现 +- **events** - 事件系统 +- **ioc** - IoC 容器 +- **model** - 数据模型 +- **system** - 业务系统 +- **utility** - 工具类 +- **GFramework.Godot** - Godot 特定集成(GodotNode 扩展、GodotLogger 等) +- **extensions** - 扩展方法 +- **logging** - 日志系统 +- **environment** - 环境管理 + +--- + **许可证**:Apache 2.0 \ No newline at end of file diff --git a/docs/zh-CN/core/command.md b/docs/zh-CN/core/command.md index 3f4252c..cd0d132 100644 --- a/docs/zh-CN/core/command.md +++ b/docs/zh-CN/core/command.md @@ -1,447 +1,451 @@ -# Command 包使用说明 - -## 概述 - -Command 包实现了命令模式(Command Pattern),用于封装用户操作和业务逻辑。通过命令模式,可以将请求封装为对象,实现操作的参数化、队列化、日志记录、撤销等功能。 - -命令系统是 GFramework CQRS 架构的重要组成部分,与事件系统和查询系统协同工作,实现完整的业务逻辑处理流程。 - -## 核心接口 - -### ICommand - -无返回值命令接口,定义了命令的基本契约。 - -**核心方法:** - -```csharp -void Execute(); // 执行命令 -``` - -### ICommand`` - -带返回值的命令接口,用于需要返回执行结果的命令。 - -**核心方法:** - -```csharp -TResult Execute(); // 执行命令并返回结果 -``` - -## 核心类 - -### AbstractCommand - -无返回值命令的抽象基类,提供了命令的基础实现。它继承自 ContextAwareBase,具有上下文感知能力。 - -**核心方法:** - -```csharp -void ICommand.Execute(); // 实现 ICommand 接口 -protected abstract void OnExecute(); // 抽象执行方法,由子类实现 -``` - -**使用示例:** - -```csharp -// 定义一个无返回值的基础命令 -public class SimpleCommand : AbstractCommand -{ - protected override void OnExecute() - { - var playerModel = this.GetModel(); - playerModel.Health.Value = playerModel.MaxHealth.Value; - this.SendEvent(new PlayerHealthRestoredEvent()); - } -} - -// 使用命令 -public class GameController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void OnRestoreHealthButtonClicked() - { - this.SendCommand(new SimpleCommand()); - } -} -``` - -### AbstractCommand`` - -无输入参数但带返回值的命令基类。 - -**核心方法:** - -```csharp -TResult ICommand.Execute(); // 实现 ICommand 接口 -protected abstract TResult OnExecute(); // 抽象执行方法,由子类实现 -``` - -**使用示例:** - -```csharp -// 定义一个无输入但有返回值的命令 -public class GetPlayerHealthQuery : AbstractCommand -{ - protected override int OnExecute() - { - var playerModel = this.GetModel(); - return playerModel.Health.Value; - } -} - -// 使用命令 -public class UISystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnUpdateUI); - } - - private void OnUpdateUI(UpdateUIEvent e) - { - var health = this.SendCommand(new GetPlayerHealthQuery()); - Console.WriteLine($"Player health: {health}"); - } -} -``` - -## 命令的生命周期 - -1. **创建命令**:实例化命令对象,传入必要的参数 -2. **执行命令**:调用 `Execute()` 方法,内部委托给 `OnExecute()` -3. **返回结果**:对于带返回值的命令,返回执行结果 -4. **命令销毁**:命令执行完毕后可以被垃圾回收 - -**注意事项:** - -- 命令应该是无状态的,执行完即可丢弃 -- 避免在命令中保存长期引用 -- 命令执行应该是原子操作 - -## CommandBus - 命令总线 - -### 功能说明 - -`CommandBus` 是命令执行的核心组件,负责发送和执行命令。 - -**主要方法:** - -```csharp -void Send(ICommand command); // 发送无返回值命令 -TResult Send(ICommand command); // 发送带返回值命令 -``` - -**特点:** - -- 统一的命令执行入口 -- 支持同步命令执行 -- 与架构上下文集成 - -### 使用示例 - -```csharp -// 通过架构获取命令总线 -var commandBus = architecture.Context.CommandBus; - -// 发送无返回值命令 -commandBus.Send(new StartGameCommand(1, "Player1")); - -// 发送带返回值命令 -var damage = commandBus.Send(new CalculateDamageCommand(100, 50)); -``` - -## 命令基类变体 - -框架提供了多种命令基类以满足不同需求: - -### AbstractCommand`` - -带输入参数的无返回值命令类。通过 `ICommandInput` 接口传递参数。 - -**核心方法:** - -```csharp -void ICommand.Execute(); // 实现 ICommand 接口 -protected abstract void OnExecute(TInput input); // 抽象执行方法,接收输入参数 -``` - -**使用示例:** - -```csharp -// 定义输入对象 -public class StartGameInput : ICommandInput -{ - public int LevelId { get; set; } - public string PlayerName { get; set; } -} - -// 定义命令 -public class StartGameCommand : AbstractCommand -{ - protected override void OnExecute(StartGameInput input) - { - var playerModel = this.GetModel(); - var gameModel = this.GetModel(); - - playerModel.PlayerName.Value = input.PlayerName; - gameModel.CurrentLevel.Value = input.LevelId; - gameModel.GameState.Value = GameState.Playing; - - this.SendEvent(new GameStartedEvent()); - } -} - -// 使用命令 -public class GameController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void OnStartButtonClicked() - { - var input = new StartGameInput { LevelId = 1, PlayerName = "Player1" }; - this.SendCommand(new StartGameCommand { Input = input }); - } -} -``` - -### AbstractCommand`` - -既带输入参数又带返回值的命令类。 - -**核心方法:** - -```csharp -TResult ICommand.Execute(); // 实现 ICommand 接口 -protected abstract TResult OnExecute(TInput input); // 抽象执行方法,接收输入参数 -``` - -**使用示例:** - -```csharp -// 定义输入对象 -public class CalculateDamageInput : ICommandInput -{ - public int AttackerAttackPower { get; set; } - public int DefenderDefense { get; set; } -} - -// 定义命令 -public class CalculateDamageCommand : AbstractCommand -{ - protected override int OnExecute(CalculateDamageInput input) - { - var config = this.GetModel(); - var baseDamage = input.AttackerAttackPower - input.DefenderDefense; - var finalDamage = Math.Max(1, baseDamage * config.DamageMultiplier); - return (int)finalDamage; - } -} - -// 使用命令 -public class CombatSystem : AbstractSystem -{ - protected override void OnInit() { } - - public void Attack(Character attacker, Character defender) - { - var input = new CalculateDamageInput - { - AttackerAttackPower = attacker.AttackPower, - DefenderDefense = defender.Defense - }; - - var damage = this.SendCommand(new CalculateDamageCommand { Input = input }); - defender.Health -= damage; - this.SendEvent(new DamageDealtEvent(attacker, defender, damage)); - } -} -``` - -### AbstractAsyncCommand`` - -支持异步执行的带输入参数的无返回值命令基类。 - -**核心方法:** - -```csharp -Task IAsyncCommand.ExecuteAsync(); // 实现异步命令接口 -protected abstract Task OnExecuteAsync(TInput input); // 抽象异步执行方法 -``` - -### AbstractAsyncCommand`` - -支持异步执行的既带输入参数又带返回值的命令基类。 - -**核心方法:** - -```csharp -Task IAsyncCommand.ExecuteAsync(); // 实现异步命令接口 -protected abstract Task OnExecuteAsync(TInput input); // 抽象异步执行方法 -``` - -**使用示例:** - -```csharp -// 定义输入对象 -public class LoadSaveDataInput : ICommandInput -{ - public string SaveSlot { get; set; } -} - -// 定义异步命令 -public class LoadSaveDataCommand : AbstractAsyncCommand -{ - protected override async Task OnExecuteAsync(LoadSaveDataInput input) - { - var storage = this.GetUtility(); - return await storage.LoadSaveDataAsync(input.SaveSlot); - } -} - -// 使用异步命令 -public class SaveSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnLoadGameRequest); - } - - private async void OnLoadGameRequest(LoadGameRequestEvent e) - { - var input = new LoadSaveDataInput { SaveSlot = e.SaveSlot }; - var saveData = await this.SendCommandAsync(new LoadSaveDataCommand { Input = input }); - - if (saveData != null) - { - this.SendEvent(new GameLoadedEvent { SaveData = saveData }); - } - } -} -``` - -## 命令处理器执行 - -所有发送给命令总线的命令最终都会通过 `CommandExecutor` 来执行: - -```csharp -public class CommandExecutor -{ - public static void Execute(ICommand command) - { - command.Execute(); - } - - public static TResult Execute(ICommand command) - { - return command.Execute(); - } -} -``` - -**特点:** - -- 提供统一的命令执行机制 -- 支持同步和异步命令执行 -- 可以扩展添加中间件逻辑 - -## 使用场景 - -### 1. 用户交互操作 - -```csharp -public class SaveGameCommand : AbstractCommand -{ - private readonly string _saveSlot; - - public SaveGameCommand(string saveSlot) - { - _saveSlot = saveSlot; - } - - protected override void OnExecute() - { - var saveSystem = this.GetSystem(); - var playerModel = this.GetModel(); - - saveSystem.SavePlayerData(playerModel, _saveSlot); - this.SendEvent(new GameSavedEvent(_saveSlot)); - } -} -``` - -### 2. 业务流程控制 - -```csharp -public class LoadLevelCommand : AbstractCommand -{ - private readonly int _levelId; - - public LoadLevelCommand(int levelId) - { - _levelId = levelId; - } - - protected override void OnExecute() - { - var levelSystem = this.GetSystem(); - var uiSystem = this.GetSystem(); - - // 显示加载界面 - uiSystem.ShowLoadingScreen(); - - // 加载关卡 - levelSystem.LoadLevel(_levelId); - - // 发送事件 - this.SendEvent(new LevelLoadedEvent(_levelId)); - } -} -``` - -## 最佳实践 - -1. **保持命令原子性**:一个命令应该完成一个完整的业务操作 -2. **命令无状态**:命令不应该保存长期状态,执行完即可丢弃 -3. **参数通过构造函数传递**:命令需要的参数应在创建时传入 -4. **避免命令嵌套**:命令内部尽量不要发送其他命令,使用事件通信 -5. **合理使用返回值**:只在确实需要返回结果时使用带返回值的命令 -6. **命令命名规范**:使用动词+名词形式,如 `StartGameCommand`、`SavePlayerCommand` -7. **单一职责原则**:每个命令只负责一个特定的业务操作 -8. **使用异步命令**:对于需要长时间执行的操作,使用异步命令避免阻塞 -9. **命令验证**:在命令执行前验证输入参数的有效性 -10. **错误处理**:在命令中适当处理异常情况 - -## 命令模式优势 - -### 1. 可扩展性 - -- 命令可以被序列化和存储 -- 支持命令队列和批处理 -- 便于实现撤销/重做功能 - -### 2. 可测试性 - -- 命令逻辑独立,易于单元测试 -- 可以模拟命令执行结果 -- 支持行为驱动开发 - -### 3. 可维护性 - -- 业务逻辑集中管理 -- 降低组件间耦合度 -- 便于重构和扩展 - -## 相关包 - -- [`architecture`](./architecture.md) - 架构核心,负责命令的分发和执行 -- [`extensions`](./extensions.md) - 提供 `SendCommand()` 扩展方法 -- [`query`](./query.md) - 查询模式,用于数据查询 -- [`events`](./events.md) - 事件系统,命令执行后的通知机制 -- [`system`](./system.md) - 业务系统,命令的主要执行者 -- [`model`](./model.md) - 数据模型,命令操作的数据 - ---- - +# Command 包使用说明 + +## 概述 + +Command 包实现了命令模式(Command Pattern),用于封装用户操作和业务逻辑。通过命令模式,可以将请求封装为对象,实现操作的参数化、队列化、日志记录、撤销等功能。 + +命令系统是 GFramework CQRS 架构的重要组成部分,与事件系统和查询系统协同工作,实现完整的业务逻辑处理流程。 + +## 核心接口 + +### ICommand + +无返回值命令接口,定义了命令的基本契约。 + +**核心方法:** + +```csharp +void Execute(); // 执行命令 +``` + +### ICommand`` + +带返回值的命令接口,用于需要返回执行结果的命令。 + +**核心方法:** + +```csharp +TResult Execute(); // 执行命令并返回结果 +``` + +## 核心类 + +### AbstractCommand + +无返回值命令的抽象基类,提供了命令的基础实现。它继承自 ContextAwareBase,具有上下文感知能力。 + +**核心方法:** + +```csharp +void ICommand.Execute(); // 实现 ICommand 接口 +protected abstract void OnExecute(); // 抽象执行方法,由子类实现 +``` + +**使用示例:** + +```csharp +// 定义一个无返回值的基础命令 +public class SimpleCommand : AbstractCommand +{ + protected override void OnExecute() + { + var playerModel = this.GetModel(); + playerModel.Health.Value = playerModel.MaxHealth.Value; + this.SendEvent(new PlayerHealthRestoredEvent()); + } +} + +// 使用命令 +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameController : IController +{ + public void OnRestoreHealthButtonClicked() + { + this.SendCommand(new SimpleCommand()); + } +} +``` + +### AbstractCommand`` + +无输入参数但带返回值的命令基类。 + +**核心方法:** + +```csharp +TResult ICommand.Execute(); // 实现 ICommand 接口 +protected abstract TResult OnExecute(); // 抽象执行方法,由子类实现 +``` + +**使用示例:** + +```csharp +// 定义一个无输入但有返回值的命令 +public class GetPlayerHealthQuery : AbstractCommand +{ + protected override int OnExecute() + { + var playerModel = this.GetModel(); + return playerModel.Health.Value; + } +} + +// 使用命令 +public class UISystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnUpdateUI); + } + + private void OnUpdateUI(UpdateUIEvent e) + { + var health = this.SendCommand(new GetPlayerHealthQuery()); + Console.WriteLine($"Player health: {health}"); + } +} +``` + +## 命令的生命周期 + +1. **创建命令**:实例化命令对象,传入必要的参数 +2. **执行命令**:调用 `Execute()` 方法,内部委托给 `OnExecute()` +3. **返回结果**:对于带返回值的命令,返回执行结果 +4. **命令销毁**:命令执行完毕后可以被垃圾回收 + +**注意事项:** + +- 命令应该是无状态的,执行完即可丢弃 +- 避免在命令中保存长期引用 +- 命令执行应该是原子操作 + +## CommandBus - 命令总线 + +### 功能说明 + +`CommandBus` 是命令执行的核心组件,负责发送和执行命令。 + +**主要方法:** + +```csharp +void Send(ICommand command); // 发送无返回值命令 +TResult Send(ICommand command); // 发送带返回值命令 +``` + +**特点:** + +- 统一的命令执行入口 +- 支持同步命令执行 +- 与架构上下文集成 + +### 使用示例 + +```csharp +// 通过架构获取命令总线 +var commandBus = architecture.Context.CommandBus; + +// 发送无返回值命令 +commandBus.Send(new StartGameCommand(1, "Player1")); + +// 发送带返回值命令 +var damage = commandBus.Send(new CalculateDamageCommand(100, 50)); +``` + +## 命令基类变体 + +框架提供了多种命令基类以满足不同需求: + +### AbstractCommand`` + +带输入参数的无返回值命令类。通过 `ICommandInput` 接口传递参数。 + +**核心方法:** + +```csharp +void ICommand.Execute(); // 实现 ICommand 接口 +protected abstract void OnExecute(TInput input); // 抽象执行方法,接收输入参数 +``` + +**使用示例:** + +```csharp +// 定义输入对象 +public class StartGameInput : ICommandInput +{ + public int LevelId { get; set; } + public string PlayerName { get; set; } +} + +// 定义命令 +public class StartGameCommand : AbstractCommand +{ + protected override void OnExecute(StartGameInput input) + { + var playerModel = this.GetModel(); + var gameModel = this.GetModel(); + + playerModel.PlayerName.Value = input.PlayerName; + gameModel.CurrentLevel.Value = input.LevelId; + gameModel.GameState.Value = GameState.Playing; + + this.SendEvent(new GameStartedEvent()); + } +} + +// 使用命令 +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameController : IController +{ + public void OnStartButtonClicked() + { + var input = new StartGameInput { LevelId = 1, PlayerName = "Player1" }; + this.SendCommand(new StartGameCommand { Input = input }); + } +} +``` + +### AbstractCommand`` + +既带输入参数又带返回值的命令类。 + +**核心方法:** + +```csharp +TResult ICommand.Execute(); // 实现 ICommand 接口 +protected abstract TResult OnExecute(TInput input); // 抽象执行方法,接收输入参数 +``` + +**使用示例:** + +```csharp +// 定义输入对象 +public class CalculateDamageInput : ICommandInput +{ + public int AttackerAttackPower { get; set; } + public int DefenderDefense { get; set; } +} + +// 定义命令 +public class CalculateDamageCommand : AbstractCommand +{ + protected override int OnExecute(CalculateDamageInput input) + { + var config = this.GetModel(); + var baseDamage = input.AttackerAttackPower - input.DefenderDefense; + var finalDamage = Math.Max(1, baseDamage * config.DamageMultiplier); + return (int)finalDamage; + } +} + +// 使用命令 +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() { } + + public void Attack(Character attacker, Character defender) + { + var input = new CalculateDamageInput + { + AttackerAttackPower = attacker.AttackPower, + DefenderDefense = defender.Defense + }; + + var damage = this.SendCommand(new CalculateDamageCommand { Input = input }); + defender.Health -= damage; + this.SendEvent(new DamageDealtEvent(attacker, defender, damage)); + } +} +``` + +### AbstractAsyncCommand`` + +支持异步执行的带输入参数的无返回值命令基类。 + +**核心方法:** + +```csharp +Task IAsyncCommand.ExecuteAsync(); // 实现异步命令接口 +protected abstract Task OnExecuteAsync(TInput input); // 抽象异步执行方法 +``` + +### AbstractAsyncCommand`` + +支持异步执行的既带输入参数又带返回值的命令基类。 + +**核心方法:** + +```csharp +Task IAsyncCommand.ExecuteAsync(); // 实现异步命令接口 +protected abstract Task OnExecuteAsync(TInput input); // 抽象异步执行方法 +``` + +**使用示例:** + +```csharp +// 定义输入对象 +public class LoadSaveDataInput : ICommandInput +{ + public string SaveSlot { get; set; } +} + +// 定义异步命令 +public class LoadSaveDataCommand : AbstractAsyncCommand +{ + protected override async Task OnExecuteAsync(LoadSaveDataInput input) + { + var storage = this.GetUtility(); + return await storage.LoadSaveDataAsync(input.SaveSlot); + } +} + +// 使用异步命令 +public class SaveSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnLoadGameRequest); + } + + private async void OnLoadGameRequest(LoadGameRequestEvent e) + { + var input = new LoadSaveDataInput { SaveSlot = e.SaveSlot }; + var saveData = await this.SendCommandAsync(new LoadSaveDataCommand { Input = input }); + + if (saveData != null) + { + this.SendEvent(new GameLoadedEvent { SaveData = saveData }); + } + } +} +``` + +## 命令处理器执行 + +所有发送给命令总线的命令最终都会通过 `CommandExecutor` 来执行: + +```csharp +public class CommandExecutor +{ + public static void Execute(ICommand command) + { + command.Execute(); + } + + public static TResult Execute(ICommand command) + { + return command.Execute(); + } +} +``` + +**特点:** + +- 提供统一的命令执行机制 +- 支持同步和异步命令执行 +- 可以扩展添加中间件逻辑 + +## 使用场景 + +### 1. 用户交互操作 + +```csharp +public class SaveGameCommand : AbstractCommand +{ + private readonly string _saveSlot; + + public SaveGameCommand(string saveSlot) + { + _saveSlot = saveSlot; + } + + protected override void OnExecute() + { + var saveSystem = this.GetSystem(); + var playerModel = this.GetModel(); + + saveSystem.SavePlayerData(playerModel, _saveSlot); + this.SendEvent(new GameSavedEvent(_saveSlot)); + } +} +``` + +### 2. 业务流程控制 + +```csharp +public class LoadLevelCommand : AbstractCommand +{ + private readonly int _levelId; + + public LoadLevelCommand(int levelId) + { + _levelId = levelId; + } + + protected override void OnExecute() + { + var levelSystem = this.GetSystem(); + var uiSystem = this.GetSystem(); + + // 显示加载界面 + uiSystem.ShowLoadingScreen(); + + // 加载关卡 + levelSystem.LoadLevel(_levelId); + + // 发送事件 + this.SendEvent(new LevelLoadedEvent(_levelId)); + } +} +``` + +## 最佳实践 + +1. **保持命令原子性**:一个命令应该完成一个完整的业务操作 +2. **命令无状态**:命令不应该保存长期状态,执行完即可丢弃 +3. **参数通过构造函数传递**:命令需要的参数应在创建时传入 +4. **避免命令嵌套**:命令内部尽量不要发送其他命令,使用事件通信 +5. **合理使用返回值**:只在确实需要返回结果时使用带返回值的命令 +6. **命令命名规范**:使用动词+名词形式,如 `StartGameCommand`、`SavePlayerCommand` +7. **单一职责原则**:每个命令只负责一个特定的业务操作 +8. **使用异步命令**:对于需要长时间执行的操作,使用异步命令避免阻塞 +9. **命令验证**:在命令执行前验证输入参数的有效性 +10. **错误处理**:在命令中适当处理异常情况 + +## 命令模式优势 + +### 1. 可扩展性 + +- 命令可以被序列化和存储 +- 支持命令队列和批处理 +- 便于实现撤销/重做功能 + +### 2. 可测试性 + +- 命令逻辑独立,易于单元测试 +- 可以模拟命令执行结果 +- 支持行为驱动开发 + +### 3. 可维护性 + +- 业务逻辑集中管理 +- 降低组件间耦合度 +- 便于重构和扩展 + +## 相关包 + +- [`architecture`](./architecture.md) - 架构核心,负责命令的分发和执行 +- [`extensions`](./extensions.md) - 提供 `SendCommand()` 扩展方法 +- [`query`](./query.md) - 查询模式,用于数据查询 +- [`events`](./events.md) - 事件系统,命令执行后的通知机制 +- [`system`](./system.md) - 业务系统,命令的主要执行者 +- [`model`](./model.md) - 数据模型,命令操作的数据 + +--- + **许可证**:Apache 2.0 \ No newline at end of file diff --git a/docs/zh-CN/core/configuration.md b/docs/zh-CN/core/configuration.md index 4d06026..5f26fb8 100644 --- a/docs/zh-CN/core/configuration.md +++ b/docs/zh-CN/core/configuration.md @@ -1,938 +1,939 @@ -# Configuration 包使用说明 - -## 概述 - -Configuration 包提供了线程安全的配置管理系统,支持类型安全的配置存储、访问、监听和持久化。配置管理器可以用于管理游戏设置、运行时参数、开发配置等各种键值对数据。 - -配置系统是 GFramework 架构中的实用工具(Utility),可以在架构的任何层级中使用,提供统一的配置管理能力。 - -## 核心接口 - -### IConfigurationManager - -配置管理器接口,提供类型安全的配置存储和访问。所有方法都是线程安全的。 - -**核心方法:** - -```csharp -// 配置访问 -T? GetConfig<T>(string key); // 获取配置值 -T GetConfig<T>(string key, T defaultValue); // 获取配置值(带默认值) -void SetConfig<T>(string key, T value); // 设置配置值 -bool HasConfig(string key); // 检查配置是否存在 -bool RemoveConfig(string key); // 移除配置 -void Clear(); // 清空所有配置 - -// 配置监听 -IUnRegister WatchConfig<T>(string key, Action<T> onChange); // 监听配置变化 - -// 持久化 -void LoadFromJson(string json); // 从 JSON 加载 -string SaveToJson(); // 保存为 JSON -void LoadFromFile(string path); // 从文件加载 -void SaveToFile(string path); // 保存到文件 - -// 工具方法 -int Count { get; } // 获取配置数量 -IEnumerable GetAllKeys(); // 获取所有配置键 -``` - -## 核心类 - -### ConfigurationManager - -配置管理器实现,提供线程安全的配置存储和访问。 - -**特性:** - -- 线程安全:所有公共方法都是线程安全的 -- 类型安全:支持泛型类型的配置值 -- 自动类型转换:支持基本类型的自动转换 -- 配置监听:支持监听配置变化并触发回调 -- JSON 持久化:支持 JSON 格式的配置加载和保存 - -**使用示例:** - -```csharp -// 创建配置管理器 -var configManager = new ConfigurationManager(); - -// 设置配置 -configManager.SetConfig("game.difficulty", "Normal"); -configManager.SetConfig("audio.volume", 0.8f); -configManager.SetConfig("graphics.quality", 2); - -// 获取配置 -var difficulty = configManager.GetConfig("game.difficulty"); -var volume = configManager.GetConfig("audio.volume"); -var quality = configManager.GetConfig("graphics.quality"); - -// 使用默认值 -var fov = configManager.GetConfig("graphics.fov", 90); -``` - -## 基本用法 - -### 1. 设置和获取配置 - -```csharp -public class GameSettings : IUtility -{ - private readonly IConfigurationManager _config = new ConfigurationManager(); - - public void Initialize() - { - // 设置游戏配置 - _config.SetConfig("player.name", "Player1"); - _config.SetConfig("player.level", 1); - _config.SetConfig("player.experience", 0); - - // 设置游戏选项 - _config.SetConfig("options.showTutorial", true); - _config.SetConfig("options.language", "zh-CN"); - } - - public string GetPlayerName() - { - return _config.GetConfig("player.name") ?? "Unknown"; - } - - public int GetPlayerLevel() - { - return _config.GetConfig("player.level", 1); - } -} -``` - -### 2. 检查和移除配置 - -```csharp -public class ConfigurationService -{ - private readonly IConfigurationManager _config; - - public ConfigurationService(IConfigurationManager config) - { - _config = config; - } - - public void ResetPlayerData() - { - // 检查配置是否存在 - if (_config.HasConfig("player.name")) - { - _config.RemoveConfig("player.name"); - } - - if (_config.HasConfig("player.level")) - { - _config.RemoveConfig("player.level"); - } - - // 或者清空所有配置 - // _config.Clear(); - } - - public void PrintAllConfigs() - { - Console.WriteLine($"Total configs: {_config.Count}"); - - foreach (var key in _config.GetAllKeys()) - { - Console.WriteLine($"Key: {key}"); - } - } -} -``` - -### 3. 支持的数据类型 - -```csharp -public class TypeExamples -{ - private readonly IConfigurationManager _config = new ConfigurationManager(); - - public void SetupConfigs() - { - // 基本类型 - _config.SetConfig("int.value", 42); - _config.SetConfig("float.value", 3.14f); - _config.SetConfig("double.value", 2.718); - _config.SetConfig("bool.value", true); - _config.SetConfig("string.value", "Hello"); - - // 复杂类型 - _config.SetConfig("vector.position", new Vector3(1, 2, 3)); - _config.SetConfig("list.items", new List { "A", "B", "C" }); - _config.SetConfig("dict.data", new Dictionary - { - ["key1"] = 1, - ["key2"] = 2 - }); - } - - public void GetConfigs() - { - var intValue = _config.GetConfig("int.value"); - var floatValue = _config.GetConfig("float.value"); - var boolValue = _config.GetConfig("bool.value"); - var stringValue = _config.GetConfig("string.value"); - - var position = _config.GetConfig("vector.position"); - var items = _config.GetConfig>("list.items"); - } -} -``` - -## 高级用法 - -### 1. 配置监听(热更新) - -配置监听允许在配置值变化时自动触发回调,实现配置的热更新。 - -```csharp -public class AudioManager : AbstractSystem -{ - private IUnRegister _volumeWatcher; - - protected override void OnInit() - { - var config = this.GetUtility(); - - // 监听音量配置变化 - _volumeWatcher = config.WatchConfig("audio.masterVolume", newVolume => - { - UpdateMasterVolume(newVolume); - this.GetUtility()?.Info($"Master volume changed to: {newVolume}"); - }); - - // 监听音效开关 - config.WatchConfig("audio.sfxEnabled", enabled => - { - if (enabled) - EnableSoundEffects(); - else - DisableSoundEffects(); - }); - } - - private void UpdateMasterVolume(float volume) - { - // 更新音频引擎的主音量 - AudioEngine.SetMasterVolume(volume); - } - - protected override void OnDestroy() - { - // 取消监听 - _volumeWatcher?.UnRegister(); - } -} -``` - -### 2. 多个监听器 - -```csharp -public class GraphicsManager : AbstractSystem -{ - protected override void OnInit() - { - var config = this.GetUtility(); - - // 多个组件监听同一个配置 - config.WatchConfig("graphics.quality", quality => - { - UpdateTextureQuality(quality); - }); - - config.WatchConfig("graphics.quality", quality => - { - UpdateShadowQuality(quality); - }); - - config.WatchConfig("graphics.quality", quality => - { - UpdatePostProcessing(quality); - }); - } - - private void UpdateTextureQuality(int quality) { } - private void UpdateShadowQuality(int quality) { } - private void UpdatePostProcessing(int quality) { } -} -``` - -### 3. 配置持久化 - -#### 保存和加载 JSON - -```csharp -public class ConfigurationPersistence -{ - private readonly IConfigurationManager _config; - private readonly string _configPath = "config/game_settings.json"; - - public ConfigurationPersistence(IConfigurationManager config) - { - _config = config; - } - - public void SaveConfiguration() - { - try - { - // 保存到文件 - _config.SaveToFile(_configPath); - Console.WriteLine($"Configuration saved to {_configPath}"); - } - catch (Exception ex) - { - Console.WriteLine($"Failed to save configuration: {ex.Message}"); - } - } - - public void LoadConfiguration() - { - try - { - // 从文件加载 - if (File.Exists(_configPath)) - { - _config.LoadFromFile(_configPath); - Console.WriteLine($"Configuration loaded from {_configPath}"); - } - else - { - Console.WriteLine("Configuration file not found, using defaults"); - SetDefaultConfiguration(); - } - } - catch (Exception ex) - { - Console.WriteLine($"Failed to load configuration: {ex.Message}"); - SetDefaultConfiguration(); - } - } - - private void SetDefaultConfiguration() - { - _config.SetConfig("audio.masterVolume", 1.0f); - _config.SetConfig("audio.musicVolume", 0.8f); - _config.SetConfig("audio.sfxVolume", 1.0f); - _config.SetConfig("graphics.quality", 2); - _config.SetConfig("graphics.fullscreen", true); - } -} -``` - -#### JSON 字符串操作 - -```csharp -public class ConfigurationExport -{ - private readonly IConfigurationManager _config; - - public ConfigurationExport(IConfigurationManager config) - { - _config = config; - } - - public string ExportToJson() - { - // 导出为 JSON 字符串 - return _config.SaveToJson(); - } - - public void ImportFromJson(string json) - { - // 从 JSON 字符串导入 - _config.LoadFromJson(json); - } - - public void ShareConfiguration() - { - // 导出配置用于分享 - var json = _config.SaveToJson(); - - // 可以通过网络发送、保存到剪贴板等 - Clipboard.SetText(json); - Console.WriteLine("Configuration copied to clipboard"); - } -} -``` - -### 4. 多环境配置 - -```csharp -public class EnvironmentConfiguration -{ - private readonly IConfigurationManager _config; - private readonly string _environment; - - public EnvironmentConfiguration(IConfigurationManager config, string environment) - { - _config = config; - _environment = environment; - } - - public void LoadEnvironmentConfig() - { - // 根据环境加载不同的配置文件 - var configPath = _environment switch - { - "development" => "config/dev.json", - "staging" => "config/staging.json", - "production" => "config/prod.json", - _ => "config/default.json" - }; - - if (File.Exists(configPath)) - { - _config.LoadFromFile(configPath); - Console.WriteLine($"Loaded {_environment} configuration"); - } - - // 设置环境特定的配置 - ApplyEnvironmentOverrides(); - } - - private void ApplyEnvironmentOverrides() - { - switch (_environment) - { - case "development": - _config.SetConfig("debug.enabled", true); - _config.SetConfig("logging.level", "Debug"); - _config.SetConfig("api.endpoint", "http://localhost:3000"); - break; - - case "production": - _config.SetConfig("debug.enabled", false); - _config.SetConfig("logging.level", "Warning"); - _config.SetConfig("api.endpoint", "https://api.production.com"); - break; - } - } -} -``` - -### 5. 配置验证 - -```csharp -public class ConfigurationValidator -{ - private readonly IConfigurationManager _config; - - public ConfigurationValidator(IConfigurationManager config) - { - _config = config; - } - - public bool ValidateConfiguration() - { - var isValid = true; - - // 验证必需的配置项 - if (!_config.HasConfig("game.version")) - { - Console.WriteLine("Error: game.version is required"); - isValid = false; - } - - // 验证配置值范围 - var volume = _config.GetConfig("audio.masterVolume", -1f); - if (volume < 0f || volume > 1f) - { - Console.WriteLine("Error: audio.masterVolume must be between 0 and 1"); - isValid = false; - } - - // 验证配置类型 - try - { - var quality = _config.GetConfig("graphics.quality"); - if (quality < 0 || quality > 3) - { - Console.WriteLine("Error: graphics.quality must be between 0 and 3"); - isValid = false; - } - } - catch - { - Console.WriteLine("Error: graphics.quality must be an integer"); - isValid = false; - } - - return isValid; - } - - public void ApplyConstraints() - { - // 自动修正超出范围的值 - var volume = _config.GetConfig("audio.masterVolume", 1f); - if (volume < 0f) _config.SetConfig("audio.masterVolume", 0f); - if (volume > 1f) _config.SetConfig("audio.masterVolume", 1f); - - var quality = _config.GetConfig("graphics.quality", 2); - if (quality < 0) _config.SetConfig("graphics.quality", 0); - if (quality > 3) _config.SetConfig("graphics.quality", 3); - } -} -``` - -### 6. 配置分组管理 - -```csharp -public class ConfigurationGroups -{ - private readonly IConfigurationManager _config; - - public ConfigurationGroups(IConfigurationManager config) - { - _config = config; - } - - // 音频配置组 - public class AudioConfig - { - public float MasterVolume { get; set; } = 1.0f; - public float MusicVolume { get; set; } = 0.8f; - public float SfxVolume { get; set; } = 1.0f; - public bool Muted { get; set; } = false; - } - - // 图形配置组 - public class GraphicsConfig - { - public int Quality { get; set; } = 2; - public bool Fullscreen { get; set; } = true; - public int ResolutionWidth { get; set; } = 1920; - public int ResolutionHeight { get; set; } = 1080; - public bool VSync { get; set; } = true; - } - - public void SaveAudioConfig(AudioConfig audio) - { - _config.SetConfig("audio.masterVolume", audio.MasterVolume); - _config.SetConfig("audio.musicVolume", audio.MusicVolume); - _config.SetConfig("audio.sfxVolume", audio.SfxVolume); - _config.SetConfig("audio.muted", audio.Muted); - } - - public AudioConfig LoadAudioConfig() - { - return new AudioConfig - { - MasterVolume = _config.GetConfig("audio.masterVolume", 1.0f), - MusicVolume = _config.GetConfig("audio.musicVolume", 0.8f), - SfxVolume = _config.GetConfig("audio.sfxVolume", 1.0f), - Muted = _config.GetConfig("audio.muted", false) - }; - } - - public void SaveGraphicsConfig(GraphicsConfig graphics) - { - _config.SetConfig("graphics.quality", graphics.Quality); - _config.SetConfig("graphics.fullscreen", graphics.Fullscreen); - _config.SetConfig("graphics.resolutionWidth", graphics.ResolutionWidth); - _config.SetConfig("graphics.resolutionHeight", graphics.ResolutionHeight); - _config.SetConfig("graphics.vsync", graphics.VSync); - } - - public GraphicsConfig LoadGraphicsConfig() - { - return new GraphicsConfig - { - Quality = _config.GetConfig("graphics.quality", 2), - Fullscreen = _config.GetConfig("graphics.fullscreen", true), - ResolutionWidth = _config.GetConfig("graphics.resolutionWidth", 1920), - ResolutionHeight = _config.GetConfig("graphics.resolutionHeight", 1080), - VSync = _config.GetConfig("graphics.vsync", true) - }; - } -} -``` - -## 在架构中使用 - -### 注册为 Utility - -```csharp -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册配置管理器 - this.RegisterUtility(new ConfigurationManager()); - } -} -``` - -### 在 System 中使用 - -```csharp -public class SettingsSystem : AbstractSystem -{ - private IConfigurationManager _config; - - protected override void OnInit() - { - _config = this.GetUtility(); - - // 加载配置 - LoadSettings(); - - // 监听配置变化 - _config.WatchConfig("game.language", OnLanguageChanged); - } - - private void LoadSettings() - { - try - { - _config.LoadFromFile("settings.json"); - } - catch - { - // 使用默认设置 - SetDefaultSettings(); - } - } - - private void SetDefaultSettings() - { - _config.SetConfig("game.language", "en-US"); - _config.SetConfig("game.difficulty", "Normal"); - _config.SetConfig("audio.masterVolume", 1.0f); - } - - private void OnLanguageChanged(string newLanguage) - { - // 切换游戏语言 - LocalizationManager.SetLanguage(newLanguage); - } - - public void SaveSettings() - { - _config.SaveToFile("settings.json"); - } -} -``` - -### 在 Controller 中使用 - -```csharp -public class SettingsController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void ApplyGraphicsSettings(int quality, bool fullscreen) - { - var config = this.GetUtility(); - - // 更新配置(会自动触发监听器) - config.SetConfig("graphics.quality", quality); - config.SetConfig("graphics.fullscreen", fullscreen); - - // 保存配置 - SaveSettings(); - } - - public void ResetToDefaults() - { - var config = this.GetUtility(); - - // 清空所有配置 - config.Clear(); - - // 重新设置默认值 - config.SetConfig("audio.masterVolume", 1.0f); - config.SetConfig("graphics.quality", 2); - config.SetConfig("game.language", "en-US"); - - SaveSettings(); - } - - private void SaveSettings() - { - var config = this.GetUtility(); - config.SaveToFile("settings.json"); - } -} -``` - -## 最佳实践 - -### 1. 配置键命名规范 - -使用分层的点号命名法,便于组织和管理: - -```csharp -// 推荐的命名方式 -_config.SetConfig("audio.master.volume", 1.0f); -_config.SetConfig("audio.music.volume", 0.8f); -_config.SetConfig("graphics.quality.level", 2); -_config.SetConfig("graphics.resolution.width", 1920); -_config.SetConfig("player.stats.health", 100); -_config.SetConfig("player.stats.mana", 50); - -// 避免的命名方式 -_config.SetConfig("AudioMasterVolume", 1.0f); // 不使用驼峰命名 -_config.SetConfig("vol", 1.0f); // 不使用缩写 -_config.SetConfig("config_1", 1.0f); // 不使用无意义的名称 -``` - -### 2. 使用默认值 - -始终为 `GetConfig` 提供合理的默认值,避免空引用: - -```csharp -// 推荐 -var volume = _config.GetConfig("audio.volume", 1.0f); -var quality = _config.GetConfig("graphics.quality", 2); - -// 不推荐 -var volume = _config.GetConfig("audio.volume"); // 可能返回 0 -if (volume == 0) volume = 1.0f; // 需要额外的检查 -``` - -### 3. 配置文件组织 - -将配置文件按环境和用途分类: - -``` -config/ -├── default.json # 默认配置 -├── dev.json # 开发环境配置 -├── staging.json # 测试环境配置 -├── prod.json # 生产环境配置 -└── user/ - ├── settings.json # 用户设置 - └── keybindings.json # 键位绑定 -``` - -### 4. 配置安全 - -不要在配置中存储敏感信息: - -```csharp -// 不要这样做 -_config.SetConfig("api.key", "secret_key_12345"); -_config.SetConfig("user.password", "password123"); - -// 应该使用专门的安全存储 -SecureStorage.SetSecret("api.key", "secret_key_12345"); -``` - -### 5. 监听器管理 - -及时注销不再需要的监听器,避免内存泄漏: - -```csharp -public class MySystem : AbstractSystem -{ - private readonly List _watchers = new(); - - protected override void OnInit() - { - var config = this.GetUtility(); - - // 保存监听器引用 - _watchers.Add(config.WatchConfig("audio.volume", OnVolumeChanged)); - _watchers.Add(config.WatchConfig("graphics.quality", OnQualityChanged)); - } - - protected override void OnDestroy() - { - // 注销所有监听器 - foreach (var watcher in _watchers) - { - watcher.UnRegister(); - } - _watchers.Clear(); - } -} -``` - -### 6. 线程安全使用 - -虽然 `ConfigurationManager` 是线程安全的,但在多线程环境中仍需注意: - -```csharp -public class ThreadSafeConfigAccess -{ - private readonly IConfigurationManager _config; - - public void UpdateFromMultipleThreads() - { - // 可以安全地从多个线程访问 - Parallel.For(0, 10, i => - { - _config.SetConfig($"thread.{i}.value", i); - var value = _config.GetConfig($"thread.{i}.value", 0); - }); - } - - public void WatchFromMultipleThreads() - { - // 监听器回调可能在不同线程执行 - _config.WatchConfig("shared.value", newValue => - { - // 确保线程安全的操作 - lock (_lockObject) - { - UpdateSharedResource(newValue); - } - }); - } - - private readonly object _lockObject = new(); - private void UpdateSharedResource(int value) { } -} -``` - -### 7. 配置变更通知 - -避免在配置监听器中触发大量的配置变更,可能导致循环调用: - -```csharp -// 不推荐:可能导致无限循环 -_config.WatchConfig("value.a", a => -{ - _config.SetConfig("value.b", a + 1); // 触发 b 的监听器 -}); - -_config.WatchConfig("value.b", b => -{ - _config.SetConfig("value.a", b + 1); // 触发 a 的监听器 -}); - -// 推荐:使用标志位避免循环 -private bool _isUpdating = false; - -_config.WatchConfig("value.a", a => -{ - if (_isUpdating) return; - _isUpdating = true; - _config.SetConfig("value.b", a + 1); - _isUpdating = false; -}); -``` - -## 常见问题 - -### Q1: 配置值类型转换失败怎么办? - -A: `ConfigurationManager` 会尝试自动转换类型,如果失败会返回默认值。建议使用带默认值的 `GetConfig` 方法: - -```csharp -// 如果转换失败,返回默认值 1.0f -var volume = _config.GetConfig("audio.volume", 1.0f); -``` - -### Q2: 如何处理配置文件不存在的情况? - -A: 使用 try-catch 捕获异常,并提供默认配置: - -```csharp -try -{ - _config.LoadFromFile("settings.json"); -} -catch (FileNotFoundException) -{ - // 使用默认配置 - SetDefaultConfiguration(); - // 保存默认配置 - _config.SaveToFile("settings.json"); -} -``` - -### Q3: 配置监听器何时被触发? - -A: 只有当配置值真正发生变化时才会触发监听器。如果设置相同的值,监听器不会被触发: - -```csharp -_config.SetConfig("key", 42); - -_config.WatchConfig("key", value => -{ - Console.WriteLine($"Changed to: {value}"); -}); - -_config.SetConfig("key", 42); // 不会触发(值未变化) -_config.SetConfig("key", 100); // 会触发 -``` - -### Q4: 如何实现配置的版本控制? - -A: 可以在配置中添加版本号,并在加载时进行迁移: - -```csharp -public class ConfigurationMigration -{ - private readonly IConfigurationManager _config; - - public void LoadAndMigrate(string path) - { - _config.LoadFromFile(path); - - var version = _config.GetConfig("config.version", 1); - - if (version < 2) - { - MigrateToV2(); - } - - if (version < 3) - { - MigrateToV3(); - } - - _config.SetConfig("config.version", 3); - _config.SaveToFile(path); - } - - private void MigrateToV2() - { - // 迁移逻辑 - if (_config.HasConfig("old.key")) - { - var value = _config.GetConfig("old.key"); - _config.SetConfig("new.key", value); - _config.RemoveConfig("old.key"); - } - } - - private void MigrateToV3() { } -} -``` - -### Q5: 配置管理器的性能如何? - -A: `ConfigurationManager` 使用 `ConcurrentDictionary` 实现,具有良好的并发性能。但要注意: - -- 避免频繁的文件 I/O 操作 -- 监听器回调应保持轻量 -- 大量配置项时考虑分组管理 - -```csharp -// 推荐:批量更新后一次性保存 -_config.SetConfig("key1", value1); -_config.SetConfig("key2", value2); -_config.SetConfig("key3", value3); -_config.SaveToFile("settings.json"); // 一次性保存 - -// 不推荐:每次更新都保存 -_config.SetConfig("key1", value1); -_config.SaveToFile("settings.json"); -_config.SetConfig("key2", value2); -_config.SaveToFile("settings.json"); -``` - -## 相关包 - -- [`architecture`](./architecture.md) - 配置管理器作为 Utility 注册到架构 -- [`utility`](./utility.md) - 配置管理器实现 IUtility 接口 -- [`events`](./events.md) - 配置变化可以触发事件 -- [`logging`](./logging.md) - 配置管理器内部使用日志记录 +# Configuration 包使用说明 + +## 概述 + +Configuration 包提供了线程安全的配置管理系统,支持类型安全的配置存储、访问、监听和持久化。配置管理器可以用于管理游戏设置、运行时参数、开发配置等各种键值对数据。 + +配置系统是 GFramework 架构中的实用工具(Utility),可以在架构的任何层级中使用,提供统一的配置管理能力。 + +## 核心接口 + +### IConfigurationManager + +配置管理器接口,提供类型安全的配置存储和访问。所有方法都是线程安全的。 + +**核心方法:** + +```csharp +// 配置访问 +T? GetConfig<T>(string key); // 获取配置值 +T GetConfig<T>(string key, T defaultValue); // 获取配置值(带默认值) +void SetConfig<T>(string key, T value); // 设置配置值 +bool HasConfig(string key); // 检查配置是否存在 +bool RemoveConfig(string key); // 移除配置 +void Clear(); // 清空所有配置 + +// 配置监听 +IUnRegister WatchConfig<T>(string key, Action<T> onChange); // 监听配置变化 + +// 持久化 +void LoadFromJson(string json); // 从 JSON 加载 +string SaveToJson(); // 保存为 JSON +void LoadFromFile(string path); // 从文件加载 +void SaveToFile(string path); // 保存到文件 + +// 工具方法 +int Count { get; } // 获取配置数量 +IEnumerable GetAllKeys(); // 获取所有配置键 +``` + +## 核心类 + +### ConfigurationManager + +配置管理器实现,提供线程安全的配置存储和访问。 + +**特性:** + +- 线程安全:所有公共方法都是线程安全的 +- 类型安全:支持泛型类型的配置值 +- 自动类型转换:支持基本类型的自动转换 +- 配置监听:支持监听配置变化并触发回调 +- JSON 持久化:支持 JSON 格式的配置加载和保存 + +**使用示例:** + +```csharp +// 创建配置管理器 +var configManager = new ConfigurationManager(); + +// 设置配置 +configManager.SetConfig("game.difficulty", "Normal"); +configManager.SetConfig("audio.volume", 0.8f); +configManager.SetConfig("graphics.quality", 2); + +// 获取配置 +var difficulty = configManager.GetConfig("game.difficulty"); +var volume = configManager.GetConfig("audio.volume"); +var quality = configManager.GetConfig("graphics.quality"); + +// 使用默认值 +var fov = configManager.GetConfig("graphics.fov", 90); +``` + +## 基本用法 + +### 1. 设置和获取配置 + +```csharp +public class GameSettings : IUtility +{ + private readonly IConfigurationManager _config = new ConfigurationManager(); + + public void Initialize() + { + // 设置游戏配置 + _config.SetConfig("player.name", "Player1"); + _config.SetConfig("player.level", 1); + _config.SetConfig("player.experience", 0); + + // 设置游戏选项 + _config.SetConfig("options.showTutorial", true); + _config.SetConfig("options.language", "zh-CN"); + } + + public string GetPlayerName() + { + return _config.GetConfig("player.name") ?? "Unknown"; + } + + public int GetPlayerLevel() + { + return _config.GetConfig("player.level", 1); + } +} +``` + +### 2. 检查和移除配置 + +```csharp +public class ConfigurationService +{ + private readonly IConfigurationManager _config; + + public ConfigurationService(IConfigurationManager config) + { + _config = config; + } + + public void ResetPlayerData() + { + // 检查配置是否存在 + if (_config.HasConfig("player.name")) + { + _config.RemoveConfig("player.name"); + } + + if (_config.HasConfig("player.level")) + { + _config.RemoveConfig("player.level"); + } + + // 或者清空所有配置 + // _config.Clear(); + } + + public void PrintAllConfigs() + { + Console.WriteLine($"Total configs: {_config.Count}"); + + foreach (var key in _config.GetAllKeys()) + { + Console.WriteLine($"Key: {key}"); + } + } +} +``` + +### 3. 支持的数据类型 + +```csharp +public class TypeExamples +{ + private readonly IConfigurationManager _config = new ConfigurationManager(); + + public void SetupConfigs() + { + // 基本类型 + _config.SetConfig("int.value", 42); + _config.SetConfig("float.value", 3.14f); + _config.SetConfig("double.value", 2.718); + _config.SetConfig("bool.value", true); + _config.SetConfig("string.value", "Hello"); + + // 复杂类型 + _config.SetConfig("vector.position", new Vector3(1, 2, 3)); + _config.SetConfig("list.items", new List { "A", "B", "C" }); + _config.SetConfig("dict.data", new Dictionary + { + ["key1"] = 1, + ["key2"] = 2 + }); + } + + public void GetConfigs() + { + var intValue = _config.GetConfig("int.value"); + var floatValue = _config.GetConfig("float.value"); + var boolValue = _config.GetConfig("bool.value"); + var stringValue = _config.GetConfig("string.value"); + + var position = _config.GetConfig("vector.position"); + var items = _config.GetConfig>("list.items"); + } +} +``` + +## 高级用法 + +### 1. 配置监听(热更新) + +配置监听允许在配置值变化时自动触发回调,实现配置的热更新。 + +```csharp +public class AudioManager : AbstractSystem +{ + private IUnRegister _volumeWatcher; + + protected override void OnInit() + { + var config = this.GetUtility(); + + // 监听音量配置变化 + _volumeWatcher = config.WatchConfig("audio.masterVolume", newVolume => + { + UpdateMasterVolume(newVolume); + this.GetUtility()?.Info($"Master volume changed to: {newVolume}"); + }); + + // 监听音效开关 + config.WatchConfig("audio.sfxEnabled", enabled => + { + if (enabled) + EnableSoundEffects(); + else + DisableSoundEffects(); + }); + } + + private void UpdateMasterVolume(float volume) + { + // 更新音频引擎的主音量 + AudioEngine.SetMasterVolume(volume); + } + + protected override void OnDestroy() + { + // 取消监听 + _volumeWatcher?.UnRegister(); + } +} +``` + +### 2. 多个监听器 + +```csharp +public class GraphicsManager : AbstractSystem +{ + protected override void OnInit() + { + var config = this.GetUtility(); + + // 多个组件监听同一个配置 + config.WatchConfig("graphics.quality", quality => + { + UpdateTextureQuality(quality); + }); + + config.WatchConfig("graphics.quality", quality => + { + UpdateShadowQuality(quality); + }); + + config.WatchConfig("graphics.quality", quality => + { + UpdatePostProcessing(quality); + }); + } + + private void UpdateTextureQuality(int quality) { } + private void UpdateShadowQuality(int quality) { } + private void UpdatePostProcessing(int quality) { } +} +``` + +### 3. 配置持久化 + +#### 保存和加载 JSON + +```csharp +public class ConfigurationPersistence +{ + private readonly IConfigurationManager _config; + private readonly string _configPath = "config/game_settings.json"; + + public ConfigurationPersistence(IConfigurationManager config) + { + _config = config; + } + + public void SaveConfiguration() + { + try + { + // 保存到文件 + _config.SaveToFile(_configPath); + Console.WriteLine($"Configuration saved to {_configPath}"); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to save configuration: {ex.Message}"); + } + } + + public void LoadConfiguration() + { + try + { + // 从文件加载 + if (File.Exists(_configPath)) + { + _config.LoadFromFile(_configPath); + Console.WriteLine($"Configuration loaded from {_configPath}"); + } + else + { + Console.WriteLine("Configuration file not found, using defaults"); + SetDefaultConfiguration(); + } + } + catch (Exception ex) + { + Console.WriteLine($"Failed to load configuration: {ex.Message}"); + SetDefaultConfiguration(); + } + } + + private void SetDefaultConfiguration() + { + _config.SetConfig("audio.masterVolume", 1.0f); + _config.SetConfig("audio.musicVolume", 0.8f); + _config.SetConfig("audio.sfxVolume", 1.0f); + _config.SetConfig("graphics.quality", 2); + _config.SetConfig("graphics.fullscreen", true); + } +} +``` + +#### JSON 字符串操作 + +```csharp +public class ConfigurationExport +{ + private readonly IConfigurationManager _config; + + public ConfigurationExport(IConfigurationManager config) + { + _config = config; + } + + public string ExportToJson() + { + // 导出为 JSON 字符串 + return _config.SaveToJson(); + } + + public void ImportFromJson(string json) + { + // 从 JSON 字符串导入 + _config.LoadFromJson(json); + } + + public void ShareConfiguration() + { + // 导出配置用于分享 + var json = _config.SaveToJson(); + + // 可以通过网络发送、保存到剪贴板等 + Clipboard.SetText(json); + Console.WriteLine("Configuration copied to clipboard"); + } +} +``` + +### 4. 多环境配置 + +```csharp +public class EnvironmentConfiguration +{ + private readonly IConfigurationManager _config; + private readonly string _environment; + + public EnvironmentConfiguration(IConfigurationManager config, string environment) + { + _config = config; + _environment = environment; + } + + public void LoadEnvironmentConfig() + { + // 根据环境加载不同的配置文件 + var configPath = _environment switch + { + "development" => "config/dev.json", + "staging" => "config/staging.json", + "production" => "config/prod.json", + _ => "config/default.json" + }; + + if (File.Exists(configPath)) + { + _config.LoadFromFile(configPath); + Console.WriteLine($"Loaded {_environment} configuration"); + } + + // 设置环境特定的配置 + ApplyEnvironmentOverrides(); + } + + private void ApplyEnvironmentOverrides() + { + switch (_environment) + { + case "development": + _config.SetConfig("debug.enabled", true); + _config.SetConfig("logging.level", "Debug"); + _config.SetConfig("api.endpoint", "http://localhost:3000"); + break; + + case "production": + _config.SetConfig("debug.enabled", false); + _config.SetConfig("logging.level", "Warning"); + _config.SetConfig("api.endpoint", "https://api.production.com"); + break; + } + } +} +``` + +### 5. 配置验证 + +```csharp +public class ConfigurationValidator +{ + private readonly IConfigurationManager _config; + + public ConfigurationValidator(IConfigurationManager config) + { + _config = config; + } + + public bool ValidateConfiguration() + { + var isValid = true; + + // 验证必需的配置项 + if (!_config.HasConfig("game.version")) + { + Console.WriteLine("Error: game.version is required"); + isValid = false; + } + + // 验证配置值范围 + var volume = _config.GetConfig("audio.masterVolume", -1f); + if (volume < 0f || volume > 1f) + { + Console.WriteLine("Error: audio.masterVolume must be between 0 and 1"); + isValid = false; + } + + // 验证配置类型 + try + { + var quality = _config.GetConfig("graphics.quality"); + if (quality < 0 || quality > 3) + { + Console.WriteLine("Error: graphics.quality must be between 0 and 3"); + isValid = false; + } + } + catch + { + Console.WriteLine("Error: graphics.quality must be an integer"); + isValid = false; + } + + return isValid; + } + + public void ApplyConstraints() + { + // 自动修正超出范围的值 + var volume = _config.GetConfig("audio.masterVolume", 1f); + if (volume < 0f) _config.SetConfig("audio.masterVolume", 0f); + if (volume > 1f) _config.SetConfig("audio.masterVolume", 1f); + + var quality = _config.GetConfig("graphics.quality", 2); + if (quality < 0) _config.SetConfig("graphics.quality", 0); + if (quality > 3) _config.SetConfig("graphics.quality", 3); + } +} +``` + +### 6. 配置分组管理 + +```csharp +public class ConfigurationGroups +{ + private readonly IConfigurationManager _config; + + public ConfigurationGroups(IConfigurationManager config) + { + _config = config; + } + + // 音频配置组 + public class AudioConfig + { + public float MasterVolume { get; set; } = 1.0f; + public float MusicVolume { get; set; } = 0.8f; + public float SfxVolume { get; set; } = 1.0f; + public bool Muted { get; set; } = false; + } + + // 图形配置组 + public class GraphicsConfig + { + public int Quality { get; set; } = 2; + public bool Fullscreen { get; set; } = true; + public int ResolutionWidth { get; set; } = 1920; + public int ResolutionHeight { get; set; } = 1080; + public bool VSync { get; set; } = true; + } + + public void SaveAudioConfig(AudioConfig audio) + { + _config.SetConfig("audio.masterVolume", audio.MasterVolume); + _config.SetConfig("audio.musicVolume", audio.MusicVolume); + _config.SetConfig("audio.sfxVolume", audio.SfxVolume); + _config.SetConfig("audio.muted", audio.Muted); + } + + public AudioConfig LoadAudioConfig() + { + return new AudioConfig + { + MasterVolume = _config.GetConfig("audio.masterVolume", 1.0f), + MusicVolume = _config.GetConfig("audio.musicVolume", 0.8f), + SfxVolume = _config.GetConfig("audio.sfxVolume", 1.0f), + Muted = _config.GetConfig("audio.muted", false) + }; + } + + public void SaveGraphicsConfig(GraphicsConfig graphics) + { + _config.SetConfig("graphics.quality", graphics.Quality); + _config.SetConfig("graphics.fullscreen", graphics.Fullscreen); + _config.SetConfig("graphics.resolutionWidth", graphics.ResolutionWidth); + _config.SetConfig("graphics.resolutionHeight", graphics.ResolutionHeight); + _config.SetConfig("graphics.vsync", graphics.VSync); + } + + public GraphicsConfig LoadGraphicsConfig() + { + return new GraphicsConfig + { + Quality = _config.GetConfig("graphics.quality", 2), + Fullscreen = _config.GetConfig("graphics.fullscreen", true), + ResolutionWidth = _config.GetConfig("graphics.resolutionWidth", 1920), + ResolutionHeight = _config.GetConfig("graphics.resolutionHeight", 1080), + VSync = _config.GetConfig("graphics.vsync", true) + }; + } +} +``` + +## 在架构中使用 + +### 注册为 Utility + +```csharp +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 注册配置管理器 + this.RegisterUtility(new ConfigurationManager()); + } +} +``` + +### 在 System 中使用 + +```csharp +public class SettingsSystem : AbstractSystem +{ + private IConfigurationManager _config; + + protected override void OnInit() + { + _config = this.GetUtility(); + + // 加载配置 + LoadSettings(); + + // 监听配置变化 + _config.WatchConfig("game.language", OnLanguageChanged); + } + + private void LoadSettings() + { + try + { + _config.LoadFromFile("settings.json"); + } + catch + { + // 使用默认设置 + SetDefaultSettings(); + } + } + + private void SetDefaultSettings() + { + _config.SetConfig("game.language", "en-US"); + _config.SetConfig("game.difficulty", "Normal"); + _config.SetConfig("audio.masterVolume", 1.0f); + } + + private void OnLanguageChanged(string newLanguage) + { + // 切换游戏语言 + LocalizationManager.SetLanguage(newLanguage); + } + + public void SaveSettings() + { + _config.SaveToFile("settings.json"); + } +} +``` + +### 在 Controller 中使用 + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class SettingsController : IController +{ + public void ApplyGraphicsSettings(int quality, bool fullscreen) + { + var config = this.GetUtility(); + + // 更新配置(会自动触发监听器) + config.SetConfig("graphics.quality", quality); + config.SetConfig("graphics.fullscreen", fullscreen); + + // 保存配置 + SaveSettings(); + } + + public void ResetToDefaults() + { + var config = this.GetUtility(); + + // 清空所有配置 + config.Clear(); + + // 重新设置默认值 + config.SetConfig("audio.masterVolume", 1.0f); + config.SetConfig("graphics.quality", 2); + config.SetConfig("game.language", "en-US"); + + SaveSettings(); + } + + private void SaveSettings() + { + var config = this.GetUtility(); + config.SaveToFile("settings.json"); + } +} +``` + +## 最佳实践 + +### 1. 配置键命名规范 + +使用分层的点号命名法,便于组织和管理: + +```csharp +// 推荐的命名方式 +_config.SetConfig("audio.master.volume", 1.0f); +_config.SetConfig("audio.music.volume", 0.8f); +_config.SetConfig("graphics.quality.level", 2); +_config.SetConfig("graphics.resolution.width", 1920); +_config.SetConfig("player.stats.health", 100); +_config.SetConfig("player.stats.mana", 50); + +// 避免的命名方式 +_config.SetConfig("AudioMasterVolume", 1.0f); // 不使用驼峰命名 +_config.SetConfig("vol", 1.0f); // 不使用缩写 +_config.SetConfig("config_1", 1.0f); // 不使用无意义的名称 +``` + +### 2. 使用默认值 + +始终为 `GetConfig` 提供合理的默认值,避免空引用: + +```csharp +// 推荐 +var volume = _config.GetConfig("audio.volume", 1.0f); +var quality = _config.GetConfig("graphics.quality", 2); + +// 不推荐 +var volume = _config.GetConfig("audio.volume"); // 可能返回 0 +if (volume == 0) volume = 1.0f; // 需要额外的检查 +``` + +### 3. 配置文件组织 + +将配置文件按环境和用途分类: + +``` +config/ +├── default.json # 默认配置 +├── dev.json # 开发环境配置 +├── staging.json # 测试环境配置 +├── prod.json # 生产环境配置 +└── user/ + ├── settings.json # 用户设置 + └── keybindings.json # 键位绑定 +``` + +### 4. 配置安全 + +不要在配置中存储敏感信息: + +```csharp +// 不要这样做 +_config.SetConfig("api.key", "secret_key_12345"); +_config.SetConfig("user.password", "password123"); + +// 应该使用专门的安全存储 +SecureStorage.SetSecret("api.key", "secret_key_12345"); +``` + +### 5. 监听器管理 + +及时注销不再需要的监听器,避免内存泄漏: + +```csharp +public class MySystem : AbstractSystem +{ + private readonly List _watchers = new(); + + protected override void OnInit() + { + var config = this.GetUtility(); + + // 保存监听器引用 + _watchers.Add(config.WatchConfig("audio.volume", OnVolumeChanged)); + _watchers.Add(config.WatchConfig("graphics.quality", OnQualityChanged)); + } + + protected override void OnDestroy() + { + // 注销所有监听器 + foreach (var watcher in _watchers) + { + watcher.UnRegister(); + } + _watchers.Clear(); + } +} +``` + +### 6. 线程安全使用 + +虽然 `ConfigurationManager` 是线程安全的,但在多线程环境中仍需注意: + +```csharp +public class ThreadSafeConfigAccess +{ + private readonly IConfigurationManager _config; + + public void UpdateFromMultipleThreads() + { + // 可以安全地从多个线程访问 + Parallel.For(0, 10, i => + { + _config.SetConfig($"thread.{i}.value", i); + var value = _config.GetConfig($"thread.{i}.value", 0); + }); + } + + public void WatchFromMultipleThreads() + { + // 监听器回调可能在不同线程执行 + _config.WatchConfig("shared.value", newValue => + { + // 确保线程安全的操作 + lock (_lockObject) + { + UpdateSharedResource(newValue); + } + }); + } + + private readonly object _lockObject = new(); + private void UpdateSharedResource(int value) { } +} +``` + +### 7. 配置变更通知 + +避免在配置监听器中触发大量的配置变更,可能导致循环调用: + +```csharp +// 不推荐:可能导致无限循环 +_config.WatchConfig("value.a", a => +{ + _config.SetConfig("value.b", a + 1); // 触发 b 的监听器 +}); + +_config.WatchConfig("value.b", b => +{ + _config.SetConfig("value.a", b + 1); // 触发 a 的监听器 +}); + +// 推荐:使用标志位避免循环 +private bool _isUpdating = false; + +_config.WatchConfig("value.a", a => +{ + if (_isUpdating) return; + _isUpdating = true; + _config.SetConfig("value.b", a + 1); + _isUpdating = false; +}); +``` + +## 常见问题 + +### Q1: 配置值类型转换失败怎么办? + +A: `ConfigurationManager` 会尝试自动转换类型,如果失败会返回默认值。建议使用带默认值的 `GetConfig` 方法: + +```csharp +// 如果转换失败,返回默认值 1.0f +var volume = _config.GetConfig("audio.volume", 1.0f); +``` + +### Q2: 如何处理配置文件不存在的情况? + +A: 使用 try-catch 捕获异常,并提供默认配置: + +```csharp +try +{ + _config.LoadFromFile("settings.json"); +} +catch (FileNotFoundException) +{ + // 使用默认配置 + SetDefaultConfiguration(); + // 保存默认配置 + _config.SaveToFile("settings.json"); +} +``` + +### Q3: 配置监听器何时被触发? + +A: 只有当配置值真正发生变化时才会触发监听器。如果设置相同的值,监听器不会被触发: + +```csharp +_config.SetConfig("key", 42); + +_config.WatchConfig("key", value => +{ + Console.WriteLine($"Changed to: {value}"); +}); + +_config.SetConfig("key", 42); // 不会触发(值未变化) +_config.SetConfig("key", 100); // 会触发 +``` + +### Q4: 如何实现配置的版本控制? + +A: 可以在配置中添加版本号,并在加载时进行迁移: + +```csharp +public class ConfigurationMigration +{ + private readonly IConfigurationManager _config; + + public void LoadAndMigrate(string path) + { + _config.LoadFromFile(path); + + var version = _config.GetConfig("config.version", 1); + + if (version < 2) + { + MigrateToV2(); + } + + if (version < 3) + { + MigrateToV3(); + } + + _config.SetConfig("config.version", 3); + _config.SaveToFile(path); + } + + private void MigrateToV2() + { + // 迁移逻辑 + if (_config.HasConfig("old.key")) + { + var value = _config.GetConfig("old.key"); + _config.SetConfig("new.key", value); + _config.RemoveConfig("old.key"); + } + } + + private void MigrateToV3() { } +} +``` + +### Q5: 配置管理器的性能如何? + +A: `ConfigurationManager` 使用 `ConcurrentDictionary` 实现,具有良好的并发性能。但要注意: + +- 避免频繁的文件 I/O 操作 +- 监听器回调应保持轻量 +- 大量配置项时考虑分组管理 + +```csharp +// 推荐:批量更新后一次性保存 +_config.SetConfig("key1", value1); +_config.SetConfig("key2", value2); +_config.SetConfig("key3", value3); +_config.SaveToFile("settings.json"); // 一次性保存 + +// 不推荐:每次更新都保存 +_config.SetConfig("key1", value1); +_config.SaveToFile("settings.json"); +_config.SetConfig("key2", value2); +_config.SaveToFile("settings.json"); +``` + +## 相关包 + +- [`architecture`](./architecture.md) - 配置管理器作为 Utility 注册到架构 +- [`utility`](./utility.md) - 配置管理器实现 IUtility 接口 +- [`events`](./events.md) - 配置变化可以触发事件 +- [`logging`](./logging.md) - 配置管理器内部使用日志记录 diff --git a/docs/zh-CN/core/ecs.md b/docs/zh-CN/core/ecs.md index c7567d1..527fa6a 100644 --- a/docs/zh-CN/core/ecs.md +++ b/docs/zh-CN/core/ecs.md @@ -232,13 +232,14 @@ public class GameArchitecture : Architecture using Arch.Core; using GFramework.Core.Abstractions.controller; using MyGame.Components; +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; -public class GameController : IController +[ContextAware] +public partial class GameController : IController { private World _world; - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - public void Start() { // 获取 World diff --git a/docs/zh-CN/core/events.md b/docs/zh-CN/core/events.md index 3509f31..82b43e7 100644 --- a/docs/zh-CN/core/events.md +++ b/docs/zh-CN/core/events.md @@ -1,594 +1,602 @@ -# Events 包使用说明 - -## 概述 - -Events 包提供了一套完整的事件系统,实现了观察者模式(Observer Pattern)。通过事件系统,可以实现组件间的松耦合通信,支持无参和带参事件、事件注册/注销、以及灵活的事件组合。 - -事件系统是 GFramework 架构中组件间通信的核心机制,与命令模式和查询模式共同构成了完整的 CQRS 架构。 - -## 核心接口 - -### IEvent - -基础事件接口,定义了事件注册的基本功能。 - -**核心方法:** - -```csharp -IUnRegister Register(Action onEvent); // 注册事件处理函数 -``` - -### IUnRegister - -注销接口,用于取消事件注册。 - -**核心方法:** - -```csharp -void UnRegister(); // 执行注销操作 -``` - -### IUnRegisterList - -注销列表接口,用于批量管理注销对象。 - -**属性:** - -```csharp -IList UnregisterList { get; } // 获取注销列表 -``` - -### IEventBus - -事件总线接口,提供基于类型的事件发送和注册。 - -**核心方法:** - -```csharp -IUnRegister Register(Action onEvent); // 注册类型化事件 -void Send(T e); // 发送事件实例 -void Send() where T : new(); // 发送事件(自动创建实例) -void UnRegister(Action onEvent); // 注销事件监听器 -``` - -## 核心类 - -### EasyEvent - -无参事件类,支持注册、注销和触发无参事件。 - -**核心方法:** - -```csharp -IUnRegister Register(Action onEvent); // 注册事件监听器 -void Trigger(); // 触发事件 -``` - -**使用示例:** - -```csharp -// 创建事件 -var onClicked = new EasyEvent(); - -// 注册监听 -var unregister = onClicked.Register(() => -{ - Console.WriteLine("Button clicked!"); -}); - -// 触发事件 -onClicked.Trigger(); - -// 取消注册 -unregister.UnRegister(); -``` - -### Event`` - -单参数泛型事件类,支持一个参数的事件。 - -**核心方法:** - -```csharp -IUnRegister Register(Action onEvent); // 注册事件监听器 -void Trigger(T eventData); // 触发事件并传递参数 -``` - -**使用示例:** - -```csharp -// 创建带参数的事件 -var onScoreChanged = new Event(); - -// 注册监听 -onScoreChanged.Register(newScore => -{ - Console.WriteLine($"Score changed to: {newScore}"); -}); - -// 触发事件并传递参数 -onScoreChanged.Trigger(100); -``` - -### Event - -双参数泛型事件类。 - -**核心方法:** - -```csharp -IUnRegister Register(Action onEvent); // 注册事件监听器 -void Trigger(T param1, TK param2); // 触发事件并传递两个参数 -``` - -**使用示例:** - -```csharp -// 伤害事件:攻击者、伤害值 -var onDamageDealt = new Event(); - -onDamageDealt.Register((attacker, damage) => -{ - Console.WriteLine($"{attacker} dealt {damage} damage!"); -}); - -onDamageDealt.Trigger("Player", 50); -``` - -### EasyEvents - -全局事件管理器,提供类型安全的事件注册和获取。 - -**核心方法:** - -```csharp -static void Register() where T : IEvent, new(); // 注册事件类型 -static T Get() where T : IEvent, new(); // 获取事件实例 -static T GetOrAddEvent() where T : IEvent, new(); // 获取或创建事件实例 -``` - -**使用示例:** - -```csharp -// 注册全局事件类型 -EasyEvents.Register(); - -// 获取事件实例 -var gameStartEvent = EasyEvents.Get(); - -// 注册监听 -gameStartEvent.Register(() => -{ - Console.WriteLine("Game started!"); -}); - -// 触发事件 -gameStartEvent.Trigger(); -``` - -### EventBus - -类型化事件系统,支持基于类型的事件发送和注册。这是架构中默认的事件总线实现。 - -**核心方法:** - -```csharp -IUnRegister Register(Action onEvent); // 注册类型化事件 -void Send(T e); // 发送事件实例 -void Send() where T : new(); // 发送事件(自动创建实例) -void UnRegister(Action onEvent); // 注销事件监听器 -``` - -**使用示例:** - -```csharp -// 使用全局事件系统 -var eventBus = new EventBus(); - -// 注册类型化事件 -eventBus.Register(e => -{ - Console.WriteLine($"Player died at position: {e.Position}"); -}); - -// 发送事件(传递实例) -eventBus.Send(new PlayerDiedEvent -{ - Position = new Vector3(10, 0, 5) -}); - -// 发送事件(自动创建实例) -eventBus.Send(); - -// 注销事件监听器 -eventBus.UnRegister(OnPlayerDied); -``` - -### DefaultUnRegister - -默认注销器实现,封装注销回调。 - -**使用示例:** - -```csharp -Action onUnregister = () => Console.WriteLine("Unregistered"); -var unregister = new DefaultUnRegister(onUnregister); - -// 执行注销 -unregister.UnRegister(); -``` - -### OrEvent - -事件或运算组合器,当任意一个事件触发时触发。 - -**核心方法:** - -```csharp -OrEvent Or(IEvent @event); // 添加要组合的事件 -``` - -**使用示例:** - -```csharp -var onAnyInput = new OrEvent() - .Or(onKeyPressed) - .Or(onMouseClicked) - .Or(onTouchDetected); - -// 当上述任意事件触发时,执行回调 -onAnyInput.Register(() => -{ - Console.WriteLine("Input detected!"); -}); -``` - -### UnRegisterList - -批量管理注销对象的列表。 - -**核心方法:** - -```csharp -void Add(IUnRegister unRegister); // 添加注销器到列表 -void UnRegisterAll(); // 批量注销所有事件 -``` - -**使用示例:** - -```csharp -var unregisterList = new UnRegisterList(); - -// 添加到列表 -someEvent.Register(OnEvent).AddToUnregisterList(unregisterList); - -// 批量注销 -unregisterList.UnRegisterAll(); -``` - -### ArchitectureEvents - -定义了架构生命周期相关的事件。 - -**包含事件:** - -- `ArchitectureLifecycleReadyEvent` - 架构生命周期准备就绪 -- `ArchitectureDestroyingEvent` - 架构销毁中 -- `ArchitectureDestroyedEvent` - 架构已销毁 -- `ArchitectureFailedInitializationEvent` - 架构初始化失败 - -## 在架构中使用事件 - -### 定义事件类 - -```csharp -// 简单事件 -public struct GameStartedEvent { } - -// 带数据的事件 -public struct PlayerDiedEvent -{ - public Vector3 Position; - public string Cause; -} - -// 复杂事件 -public struct LevelCompletedEvent -{ - public int LevelId; - public float CompletionTime; - public int Score; - public List Achievements; -} -``` - -### Model 中发送事件 - -```csharp -public class PlayerModel : AbstractModel -{ - public BindableProperty Health { get; } = new(100); - - protected override void OnInit() - { - // 监听生命值变化 - Health.Register(newHealth => - { - if (newHealth <= 0) - { - // 发送玩家死亡事件 - this.SendEvent(new PlayerDiedEvent - { - Position = Position, - Cause = "Health depleted" - }); - } - }); - } -} -``` - -### System 中发送事件 - -```csharp -public class CombatSystem : AbstractSystem -{ - protected override void OnInit() { } - - public void DealDamage(Character attacker, Character target, int damage) - { - target.Health -= damage; - - // 发送伤害事件 - this.SendEvent(new DamageDealtEvent - { - Attacker = attacker.Name, - Target = target.Name, - Damage = damage - }); - } -} -``` - -### Controller 中注册事件 - -```csharp -public class GameController : IController -{ - private IUnRegisterList _unregisterList = new UnRegisterList(); - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void Initialize() - { - // 注册多个事件 - this.RegisterEvent(OnGameStarted) - .AddToUnregisterList(_unregisterList); - - this.RegisterEvent(OnPlayerDied) - .AddToUnregisterList(_unregisterList); - - this.RegisterEvent(OnLevelCompleted) - .AddToUnregisterList(_unregisterList); - } - - private void OnGameStarted(GameStartedEvent e) - { - Console.WriteLine("Game started!"); - } - - private void OnPlayerDied(PlayerDiedEvent e) - { - Console.WriteLine($"Player died at {e.Position}: {e.Cause}"); - ShowGameOverScreen(); - } - - private void OnLevelCompleted(LevelCompletedEvent e) - { - Console.WriteLine($"Level {e.LevelId} completed! Score: {e.Score}"); - ShowVictoryScreen(e); - } - - public void Cleanup() - { - _unregisterList.UnRegisterAll(); - } -} -``` - -## 高级用法 - -### 1. 事件链式组合 - -```csharp -// 使用 Or 组合多个事件 -var onAnyDamage = new OrEvent() - .Or(onPhysicalDamage) - .Or(onMagicDamage) - .Or(onPoisonDamage); - -onAnyDamage.Register(() => -{ - PlayDamageSound(); -}); -``` - -### 2. 事件过滤 - -```csharp -// 只处理高伤害事件 -this.RegisterEvent(e => -{ - if (e.Damage >= 50) - { - ShowCriticalHitEffect(); - } -}); -``` - -### 3. 事件转发 - -```csharp -public class EventBridge : AbstractSystem -{ - protected override void OnInit() - { - // 将内部事件转发为公共事件 - this.RegisterEvent(e => - { - this.SendEvent(new PublicPlayerDiedEvent - { - PlayerId = e.Id, - Timestamp = DateTime.UtcNow - }); - }); - } -} -``` - -### 4. 临时事件监听 - -```csharp -public class TutorialController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void Initialize() - { - // 只监听一次 - IUnRegister unregister = null; - unregister = this.RegisterEvent(e => - { - ShowTutorialComplete(); - unregister?.UnRegister(); // 立即注销 - }); - } -} -``` - -### 5. 条件事件 - -```csharp -public class AchievementSystem : AbstractSystem -{ - private int _killCount = 0; - - protected override void OnInit() - { - this.RegisterEvent(e => - { - _killCount++; - - // 条件满足时发送成就事件 - if (_killCount >= 100) - { - this.SendEvent(new AchievementUnlockedEvent - { - AchievementId = "kill_100_enemies" - }); - } - }); - } -} -``` - -## 生命周期管理 - -### 使用 UnRegisterList - -```csharp -public class MyController : IController -{ - // 统一管理所有注销对象 - private IUnRegisterList _unregisterList = new UnRegisterList(); - - public void Initialize() - { - // 所有注册都添加到列表 - this.RegisterEvent(OnEvent1) - .AddToUnregisterList(_unregisterList); - - this.RegisterEvent(OnEvent2) - .AddToUnregisterList(_unregisterList); - } - - public void Cleanup() - { - // 一次性注销所有 - _unregisterList.UnRegisterAll(); - } -} -``` - -## 最佳实践 - -1. **事件命名规范** - - 使用过去式:`PlayerDiedEvent`、`LevelCompletedEvent` - - 使用 `Event` 后缀:便于识别 - - 使用结构体:减少内存分配 - -2. **事件数据设计** - - 只包含必要信息 - - 使用值类型(struct)提高性能 - - 避免传递可变引用 - -3. **避免事件循环** - - 事件处理器中谨慎发送新事件 - - 使用命令打破循环依赖 - -4. **合理使用事件** - - 用于通知状态变化 - - 用于跨模块通信 - - 不用于返回数据(使用 Query) - -5. **注销管理** - - 始终注销事件监听 - - 使用 `IUnRegisterList` 批量管理 - - 在适当的生命周期点调用 `Cleanup()` - -6. **性能考虑** - - 避免频繁触发的事件(如每帧) - - 事件处理器保持轻量 - - 使用结构体事件减少 GC - -7. **事件设计原则** - - 高内聚:事件应该代表一个完整的业务概念 - - 低耦合:事件发送者不需要知道接收者 - - 可测试:事件应该易于模拟和测试 - -## 事件 vs 其他通信方式 - -| 方式 | 适用场景 | 优点 | 缺点 | -|----------------------|--------------|-----------|---------| -| **Event** | 状态变化通知、跨模块通信 | 松耦合、一对多 | 难以追踪调用链 | -| **Command** | 执行操作、修改状态 | 封装逻辑、可撤销 | 单向通信 | -| **Query** | 查询数据 | 职责清晰、有返回值 | 同步调用 | -| **BindableProperty** | UI 数据绑定 | 自动更新、响应式 | 仅限单一属性 | - -## 事件系统架构 - -事件系统在 GFramework 中的架构位置: - -``` -Architecture (架构核心) -├── EventBus (事件总线) -├── CommandBus (命令总线) -├── QueryBus (查询总线) -└── IocContainer (IoC容器) - -Components (组件) -├── Model (发送事件) -├── System (发送/接收事件) -└── Controller (接收事件) -``` - -## 相关包 - -- [`architecture`](./architecture.md) - 提供全局事件系统 -- [`extensions`](./extensions.md) - 提供事件扩展方法 -- [`property`](./property.md) - 可绑定属性基于事件实现 -- **Controller** - 控制器监听事件(接口定义在 Core.Abstractions 中) -- [`model`](./model.md) - 模型发送事件 -- [`system`](./system.md) - 系统发送和监听事件 -- [`command`](./command.md) - 与事件配合实现 CQRS +# Events 包使用说明 + +## 概述 + +Events 包提供了一套完整的事件系统,实现了观察者模式(Observer Pattern)。通过事件系统,可以实现组件间的松耦合通信,支持无参和带参事件、事件注册/注销、以及灵活的事件组合。 + +事件系统是 GFramework 架构中组件间通信的核心机制,与命令模式和查询模式共同构成了完整的 CQRS 架构。 + +## 核心接口 + +### IEvent + +基础事件接口,定义了事件注册的基本功能。 + +**核心方法:** + +```csharp +IUnRegister Register(Action onEvent); // 注册事件处理函数 +``` + +### IUnRegister + +注销接口,用于取消事件注册。 + +**核心方法:** + +```csharp +void UnRegister(); // 执行注销操作 +``` + +### IUnRegisterList + +注销列表接口,用于批量管理注销对象。 + +**属性:** + +```csharp +IList UnregisterList { get; } // 获取注销列表 +``` + +### IEventBus + +事件总线接口,提供基于类型的事件发送和注册。 + +**核心方法:** + +```csharp +IUnRegister Register(Action onEvent); // 注册类型化事件 +void Send(T e); // 发送事件实例 +void Send() where T : new(); // 发送事件(自动创建实例) +void UnRegister(Action onEvent); // 注销事件监听器 +``` + +## 核心类 + +### EasyEvent + +无参事件类,支持注册、注销和触发无参事件。 + +**核心方法:** + +```csharp +IUnRegister Register(Action onEvent); // 注册事件监听器 +void Trigger(); // 触发事件 +``` + +**使用示例:** + +```csharp +// 创建事件 +var onClicked = new EasyEvent(); + +// 注册监听 +var unregister = onClicked.Register(() => +{ + Console.WriteLine("Button clicked!"); +}); + +// 触发事件 +onClicked.Trigger(); + +// 取消注册 +unregister.UnRegister(); +``` + +### Event`` + +单参数泛型事件类,支持一个参数的事件。 + +**核心方法:** + +```csharp +IUnRegister Register(Action onEvent); // 注册事件监听器 +void Trigger(T eventData); // 触发事件并传递参数 +``` + +**使用示例:** + +```csharp +// 创建带参数的事件 +var onScoreChanged = new Event(); + +// 注册监听 +onScoreChanged.Register(newScore => +{ + Console.WriteLine($"Score changed to: {newScore}"); +}); + +// 触发事件并传递参数 +onScoreChanged.Trigger(100); +``` + +### Event + +双参数泛型事件类。 + +**核心方法:** + +```csharp +IUnRegister Register(Action onEvent); // 注册事件监听器 +void Trigger(T param1, TK param2); // 触发事件并传递两个参数 +``` + +**使用示例:** + +```csharp +// 伤害事件:攻击者、伤害值 +var onDamageDealt = new Event(); + +onDamageDealt.Register((attacker, damage) => +{ + Console.WriteLine($"{attacker} dealt {damage} damage!"); +}); + +onDamageDealt.Trigger("Player", 50); +``` + +### EasyEvents + +全局事件管理器,提供类型安全的事件注册和获取。 + +**核心方法:** + +```csharp +static void Register() where T : IEvent, new(); // 注册事件类型 +static T Get() where T : IEvent, new(); // 获取事件实例 +static T GetOrAddEvent() where T : IEvent, new(); // 获取或创建事件实例 +``` + +**使用示例:** + +```csharp +// 注册全局事件类型 +EasyEvents.Register(); + +// 获取事件实例 +var gameStartEvent = EasyEvents.Get(); + +// 注册监听 +gameStartEvent.Register(() => +{ + Console.WriteLine("Game started!"); +}); + +// 触发事件 +gameStartEvent.Trigger(); +``` + +### EventBus + +类型化事件系统,支持基于类型的事件发送和注册。这是架构中默认的事件总线实现。 + +**核心方法:** + +```csharp +IUnRegister Register(Action onEvent); // 注册类型化事件 +void Send(T e); // 发送事件实例 +void Send() where T : new(); // 发送事件(自动创建实例) +void UnRegister(Action onEvent); // 注销事件监听器 +``` + +**使用示例:** + +```csharp +// 使用全局事件系统 +var eventBus = new EventBus(); + +// 注册类型化事件 +eventBus.Register(e => +{ + Console.WriteLine($"Player died at position: {e.Position}"); +}); + +// 发送事件(传递实例) +eventBus.Send(new PlayerDiedEvent +{ + Position = new Vector3(10, 0, 5) +}); + +// 发送事件(自动创建实例) +eventBus.Send(); + +// 注销事件监听器 +eventBus.UnRegister(OnPlayerDied); +``` + +### DefaultUnRegister + +默认注销器实现,封装注销回调。 + +**使用示例:** + +```csharp +Action onUnregister = () => Console.WriteLine("Unregistered"); +var unregister = new DefaultUnRegister(onUnregister); + +// 执行注销 +unregister.UnRegister(); +``` + +### OrEvent + +事件或运算组合器,当任意一个事件触发时触发。 + +**核心方法:** + +```csharp +OrEvent Or(IEvent @event); // 添加要组合的事件 +``` + +**使用示例:** + +```csharp +var onAnyInput = new OrEvent() + .Or(onKeyPressed) + .Or(onMouseClicked) + .Or(onTouchDetected); + +// 当上述任意事件触发时,执行回调 +onAnyInput.Register(() => +{ + Console.WriteLine("Input detected!"); +}); +``` + +### UnRegisterList + +批量管理注销对象的列表。 + +**核心方法:** + +```csharp +void Add(IUnRegister unRegister); // 添加注销器到列表 +void UnRegisterAll(); // 批量注销所有事件 +``` + +**使用示例:** + +```csharp +var unregisterList = new UnRegisterList(); + +// 添加到列表 +someEvent.Register(OnEvent).AddToUnregisterList(unregisterList); + +// 批量注销 +unregisterList.UnRegisterAll(); +``` + +### ArchitectureEvents + +定义了架构生命周期相关的事件。 + +**包含事件:** + +- `ArchitectureLifecycleReadyEvent` - 架构生命周期准备就绪 +- `ArchitectureDestroyingEvent` - 架构销毁中 +- `ArchitectureDestroyedEvent` - 架构已销毁 +- `ArchitectureFailedInitializationEvent` - 架构初始化失败 + +## 在架构中使用事件 + +### 定义事件类 + +```csharp +// 简单事件 +public struct GameStartedEvent { } + +// 带数据的事件 +public struct PlayerDiedEvent +{ + public Vector3 Position; + public string Cause; +} + +// 复杂事件 +public struct LevelCompletedEvent +{ + public int LevelId; + public float CompletionTime; + public int Score; + public List Achievements; +} +``` + +### Model 中发送事件 + +```csharp +public class PlayerModel : AbstractModel +{ + public BindableProperty Health { get; } = new(100); + + protected override void OnInit() + { + // 监听生命值变化 + Health.Register(newHealth => + { + if (newHealth <= 0) + { + // 发送玩家死亡事件 + this.SendEvent(new PlayerDiedEvent + { + Position = Position, + Cause = "Health depleted" + }); + } + }); + } +} +``` + +### System 中发送事件 + +```csharp +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() { } + + public void DealDamage(Character attacker, Character target, int damage) + { + target.Health -= damage; + + // 发送伤害事件 + this.SendEvent(new DamageDealtEvent + { + Attacker = attacker.Name, + Target = target.Name, + Damage = damage + }); + } +} +``` + +### Controller 中注册事件 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameController : IController +{ + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public void Initialize() + { + // 注册多个事件 + this.RegisterEvent(OnGameStarted) + .AddToUnregisterList(_unregisterList); + + this.RegisterEvent(OnPlayerDied) + .AddToUnregisterList(_unregisterList); + + this.RegisterEvent(OnLevelCompleted) + .AddToUnregisterList(_unregisterList); + } + + private void OnGameStarted(GameStartedEvent e) + { + Console.WriteLine("Game started!"); + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + Console.WriteLine($"Player died at {e.Position}: {e.Cause}"); + ShowGameOverScreen(); + } + + private void OnLevelCompleted(LevelCompletedEvent e) + { + Console.WriteLine($"Level {e.LevelId} completed! Score: {e.Score}"); + ShowVictoryScreen(e); + } + + public void Cleanup() + { + _unregisterList.UnRegisterAll(); + } +} +``` + +## 高级用法 + +### 1. 事件链式组合 + +```csharp +// 使用 Or 组合多个事件 +var onAnyDamage = new OrEvent() + .Or(onPhysicalDamage) + .Or(onMagicDamage) + .Or(onPoisonDamage); + +onAnyDamage.Register(() => +{ + PlayDamageSound(); +}); +``` + +### 2. 事件过滤 + +```csharp +// 只处理高伤害事件 +this.RegisterEvent(e => +{ + if (e.Damage >= 50) + { + ShowCriticalHitEffect(); + } +}); +``` + +### 3. 事件转发 + +```csharp +public class EventBridge : AbstractSystem +{ + protected override void OnInit() + { + // 将内部事件转发为公共事件 + this.RegisterEvent(e => + { + this.SendEvent(new PublicPlayerDiedEvent + { + PlayerId = e.Id, + Timestamp = DateTime.UtcNow + }); + }); + } +} +``` + +### 4. 临时事件监听 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class TutorialController : IController +{ + public void Initialize() + { + // 只监听一次 + IUnRegister unregister = null; + unregister = this.RegisterEvent(e => + { + ShowTutorialComplete(); + unregister?.UnRegister(); // 立即注销 + }); + } +} +``` + +### 5. 条件事件 + +```csharp +public class AchievementSystem : AbstractSystem +{ + private int _killCount = 0; + + protected override void OnInit() + { + this.RegisterEvent(e => + { + _killCount++; + + // 条件满足时发送成就事件 + if (_killCount >= 100) + { + this.SendEvent(new AchievementUnlockedEvent + { + AchievementId = "kill_100_enemies" + }); + } + }); + } +} +``` + +## 生命周期管理 + +### 使用 UnRegisterList + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class MyController : IController +{ + // 统一管理所有注销对象 + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public void Initialize() + { + // 所有注册都添加到列表 + this.RegisterEvent(OnEvent1) + .AddToUnregisterList(_unregisterList); + + this.RegisterEvent(OnEvent2) + .AddToUnregisterList(_unregisterList); + } + + public void Cleanup() + { + // 一次性注销所有 + _unregisterList.UnRegisterAll(); + } +} +``` + +## 最佳实践 + +1. **事件命名规范** + - 使用过去式:`PlayerDiedEvent`、`LevelCompletedEvent` + - 使用 `Event` 后缀:便于识别 + - 使用结构体:减少内存分配 + +2. **事件数据设计** + - 只包含必要信息 + - 使用值类型(struct)提高性能 + - 避免传递可变引用 + +3. **避免事件循环** + - 事件处理器中谨慎发送新事件 + - 使用命令打破循环依赖 + +4. **合理使用事件** + - 用于通知状态变化 + - 用于跨模块通信 + - 不用于返回数据(使用 Query) + +5. **注销管理** + - 始终注销事件监听 + - 使用 `IUnRegisterList` 批量管理 + - 在适当的生命周期点调用 `Cleanup()` + +6. **性能考虑** + - 避免频繁触发的事件(如每帧) + - 事件处理器保持轻量 + - 使用结构体事件减少 GC + +7. **事件设计原则** + - 高内聚:事件应该代表一个完整的业务概念 + - 低耦合:事件发送者不需要知道接收者 + - 可测试:事件应该易于模拟和测试 + +## 事件 vs 其他通信方式 + +| 方式 | 适用场景 | 优点 | 缺点 | +|----------------------|--------------|-----------|---------| +| **Event** | 状态变化通知、跨模块通信 | 松耦合、一对多 | 难以追踪调用链 | +| **Command** | 执行操作、修改状态 | 封装逻辑、可撤销 | 单向通信 | +| **Query** | 查询数据 | 职责清晰、有返回值 | 同步调用 | +| **BindableProperty** | UI 数据绑定 | 自动更新、响应式 | 仅限单一属性 | + +## 事件系统架构 + +事件系统在 GFramework 中的架构位置: + +``` +Architecture (架构核心) +├── EventBus (事件总线) +├── CommandBus (命令总线) +├── QueryBus (查询总线) +└── IocContainer (IoC容器) + +Components (组件) +├── Model (发送事件) +├── System (发送/接收事件) +└── Controller (接收事件) +``` + +## 相关包 + +- [`architecture`](./architecture.md) - 提供全局事件系统 +- [`extensions`](./extensions.md) - 提供事件扩展方法 +- [`property`](./property.md) - 可绑定属性基于事件实现 +- **Controller** - 控制器监听事件(接口定义在 Core.Abstractions 中) +- [`model`](./model.md) - 模型发送事件 +- [`system`](./system.md) - 系统发送和监听事件 +- [`command`](./command.md) - 与事件配合实现 CQRS - [`query`](./query.md) - 与事件配合实现 CQRS \ No newline at end of file diff --git a/docs/zh-CN/core/pause.md b/docs/zh-CN/core/pause.md index 3b64014..9e43304 100644 --- a/docs/zh-CN/core/pause.md +++ b/docs/zh-CN/core/pause.md @@ -1,918 +1,929 @@ -# 暂停管理系统使用说明 - -## 概述 - -暂停管理系统(Pause System)提供了一套完整的游戏暂停控制机制,支持多层嵌套暂停、分组暂停、以及灵活的暂停处理器扩展。该系统基于栈结构实现,能够优雅地处理复杂的暂停场景,如菜单叠加、对话框弹出等。 - -暂停系统是 GFramework 架构中的核心工具(Utility),与其他系统协同工作,为游戏提供统一的暂停管理能力。 - -**主要特性:** - -- **嵌套暂停**:支持多层暂停请求,只有所有请求都解除后才恢复 -- **分组管理**:不同系统可以独立暂停(游戏逻辑、动画、音频等) -- **线程安全**:使用读写锁保证并发安全 -- **作用域管理**:支持 `using` 语法自动管理暂停生命周期 -- **事件通知**:状态变化时通知所有注册的处理器 -- **优先级控制**:处理器按优先级顺序执行 - -## 核心概念 - -### 暂停栈(Pause Stack) - -暂停系统使用栈结构管理暂停请求。每次调用 `Push` 会将暂停请求压入栈中,调用 `Pop` 会从栈中移除对应的请求。只有当栈为空时,游戏才会恢复运行。 - -``` -栈深度 3: [暂停原因: "库存界面"] -栈深度 2: [暂停原因: "对话框"] -栈深度 1: [暂停原因: "暂停菜单"] -``` - -### 暂停组(Pause Group) - -暂停组允许不同系统独立控制暂停状态。例如,打开菜单时可以暂停游戏逻辑但保持 UI 动画运行。 - -**预定义组:** - -- `Global` - 全局暂停(影响所有系统) -- `Gameplay` - 游戏逻辑暂停(不影响 UI) -- `Animation` - 动画暂停 -- `Audio` - 音频暂停 -- `Custom1/2/3` - 自定义组 - -### 暂停令牌(Pause Token) - -每次暂停请求都会返回一个唯一的令牌,用于后续恢复操作。令牌基于 GUID 实现,确保唯一性。 - -```csharp -public readonly struct PauseToken -{ - public Guid Id { get; } - public bool IsValid => Id != Guid.Empty; -} -``` - -### 暂停处理器(Pause Handler) - -处理器实现具体的暂停/恢复逻辑,如控制物理引擎、音频系统等。处理器按优先级顺序执行。 - -```csharp -public interface IPauseHandler -{ - int Priority { get; } // 优先级(数值越小越高) - void OnPauseStateChanged(PauseGroup group, bool isPaused); -} -``` - -## 核心接口 - -### IPauseStackManager - -暂停栈管理器接口,提供暂停控制的所有功能。 - -**核心方法:** - -```csharp -// 推入暂停请求 -PauseToken Push(string reason, PauseGroup group = PauseGroup.Global); - -// 弹出暂停请求 -bool Pop(PauseToken token); - -// 查询暂停状态 -bool IsPaused(PauseGroup group = PauseGroup.Global); - -// 获取暂停深度 -int GetPauseDepth(PauseGroup group = PauseGroup.Global); - -// 获取暂停原因列表 -IReadOnlyList GetPauseReasons(PauseGroup group = PauseGroup.Global); - -// 创建暂停作用域 -IDisposable PauseScope(string reason, PauseGroup group = PauseGroup.Global); - -// 清空指定组 -void ClearGroup(PauseGroup group); - -// 清空所有组 -void ClearAll(); - -// 注册/注销处理器 -void RegisterHandler(IPauseHandler handler); -void UnregisterHandler(IPauseHandler handler); - -// 状态变化事件 -event Action? OnPauseStateChanged; -``` - -## 基本用法 - -### 1. 获取暂停管理器 - -```csharp -public class GameController : IController -{ - private IPauseStackManager _pauseManager; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void Initialize() - { - // 从架构中获取暂停管理器 - _pauseManager = this.GetUtility(); - } -} -``` - -### 2. 简单的暂停/恢复 - -```csharp -public class PauseMenuController : IController -{ - private IPauseStackManager _pauseManager; - private PauseToken _pauseToken; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void Initialize() - { - _pauseManager = this.GetUtility(); - } - - public void OpenPauseMenu() - { - // 暂停游戏 - _pauseToken = _pauseManager.Push("暂停菜单"); - - Console.WriteLine($"游戏已暂停,深度: {_pauseManager.GetPauseDepth()}"); - } - - public void ClosePauseMenu() - { - // 恢复游戏 - if (_pauseToken.IsValid) - { - _pauseManager.Pop(_pauseToken); - Console.WriteLine("游戏已恢复"); - } - } -} -``` - -### 3. 使用作用域自动管理 - -```csharp -public class DialogController : IController -{ - private IPauseStackManager _pauseManager; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void ShowDialog(string message) - { - // 使用 using 语法,自动管理暂停生命周期 - using (_pauseManager.PauseScope("对话框")) - { - Console.WriteLine($"显示对话框: {message}"); - // 对话框显示期间游戏暂停 - WaitForUserInput(); - } - // 离开作用域后自动恢复 - } -} -``` - -### 4. 查询暂停状态 - -```csharp -public class GameplaySystem : AbstractSystem -{ - private IPauseStackManager _pauseManager; - - protected override void OnInit() - { - _pauseManager = this.GetUtility(); - } - - public void Update(float deltaTime) - { - // 检查是否暂停 - if (_pauseManager.IsPaused(PauseGroup.Gameplay)) - { - return; // 暂停时跳过更新 - } - - // 正常游戏逻辑 - UpdateGameLogic(deltaTime); - } -} -``` - -## 高级用法 - -### 1. 嵌套暂停 - -```csharp -public class UIManager : IController -{ - private IPauseStackManager _pauseManager; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void ShowNestedMenus() - { - // 第一层:主菜单 - var token1 = _pauseManager.Push("主菜单"); - Console.WriteLine($"深度: {_pauseManager.GetPauseDepth()}"); // 输出: 1 - - // 第二层:设置菜单 - var token2 = _pauseManager.Push("设置菜单"); - Console.WriteLine($"深度: {_pauseManager.GetPauseDepth()}"); // 输出: 2 - - // 第三层:确认对话框 - var token3 = _pauseManager.Push("确认对话框"); - Console.WriteLine($"深度: {_pauseManager.GetPauseDepth()}"); // 输出: 3 - - // 关闭对话框 - _pauseManager.Pop(token3); - Console.WriteLine($"仍然暂停: {_pauseManager.IsPaused()}"); // 输出: True - - // 关闭设置菜单 - _pauseManager.Pop(token2); - Console.WriteLine($"仍然暂停: {_pauseManager.IsPaused()}"); // 输出: True - - // 关闭主菜单 - _pauseManager.Pop(token1); - Console.WriteLine($"已恢复: {!_pauseManager.IsPaused()}"); // 输出: True - } -} -``` - -### 2. 分组暂停 - -```csharp -public class GameManager : IController -{ - private IPauseStackManager _pauseManager; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void OpenInventory() - { - // 只暂停游戏逻辑,UI 和音频继续运行 - var token = _pauseManager.Push("库存界面", PauseGroup.Gameplay); - - Console.WriteLine($"游戏逻辑暂停: {_pauseManager.IsPaused(PauseGroup.Gameplay)}"); - Console.WriteLine($"音频暂停: {_pauseManager.IsPaused(PauseGroup.Audio)}"); - Console.WriteLine($"全局暂停: {_pauseManager.IsPaused(PauseGroup.Global)}"); - } - - public void OpenPauseMenu() - { - // 全局暂停,影响所有系统 - var token = _pauseManager.Push("暂停菜单", PauseGroup.Global); - - Console.WriteLine($"所有系统已暂停"); - } - - public void MuteAudio() - { - // 只暂停音频 - var token = _pauseManager.Push("静音", PauseGroup.Audio); - } -} -``` - -### 3. 自定义暂停处理器 - -```csharp -// 物理引擎暂停处理器 -public class PhysicsPauseHandler : IPauseHandler -{ - private readonly PhysicsWorld _physicsWorld; - - public PhysicsPauseHandler(PhysicsWorld physicsWorld) - { - _physicsWorld = physicsWorld; - } - - // 高优先级,确保物理引擎最先暂停 - public int Priority => 10; - - public void OnPauseStateChanged(PauseGroup group, bool isPaused) - { - // 只响应游戏逻辑和全局暂停 - if (group == PauseGroup.Gameplay || group == PauseGroup.Global) - { - _physicsWorld.Enabled = !isPaused; - Console.WriteLine($"物理引擎 {(isPaused ? "已暂停" : "已恢复")}"); - } - } -} - -// 音频系统暂停处理器 -public class AudioPauseHandler : IPauseHandler -{ - private readonly AudioSystem _audioSystem; - - public AudioPauseHandler(AudioSystem audioSystem) - { - _audioSystem = audioSystem; - } - - public int Priority => 20; - - public void OnPauseStateChanged(PauseGroup group, bool isPaused) - { - // 响应音频和全局暂停 - if (group == PauseGroup.Audio || group == PauseGroup.Global) - { - if (isPaused) - { - _audioSystem.PauseAll(); - } - else - { - _audioSystem.ResumeAll(); - } - } - } -} - -// 注册处理器 -public class GameInitializer -{ - public void Initialize() - { - var pauseManager = architecture.GetUtility(); - var physicsWorld = GetPhysicsWorld(); - var audioSystem = GetAudioSystem(); - - // 注册处理器 - pauseManager.RegisterHandler(new PhysicsPauseHandler(physicsWorld)); - pauseManager.RegisterHandler(new AudioPauseHandler(audioSystem)); - } -} -``` - -### 4. 监听暂停状态变化 - -```csharp -public class PauseIndicator : IController -{ - private IPauseStackManager _pauseManager; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void Initialize() - { - _pauseManager = this.GetUtility(); - - // 订阅状态变化事件 - _pauseManager.OnPauseStateChanged += OnPauseStateChanged; - } - - private void OnPauseStateChanged(PauseGroup group, bool isPaused) - { - Console.WriteLine($"暂停状态变化: 组={group}, 暂停={isPaused}"); - - if (group == PauseGroup.Global) - { - if (isPaused) - { - ShowPauseIndicator(); - } - else - { - HidePauseIndicator(); - } - } - } - - public void Cleanup() - { - _pauseManager.OnPauseStateChanged -= OnPauseStateChanged; - } -} -``` - -### 5. 调试暂停状态 - -```csharp -public class PauseDebugger : IController -{ - private IPauseStackManager _pauseManager; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void PrintPauseStatus() - { - Console.WriteLine("=== 暂停状态 ==="); - - foreach (PauseGroup group in Enum.GetValues(typeof(PauseGroup))) - { - var isPaused = _pauseManager.IsPaused(group); - var depth = _pauseManager.GetPauseDepth(group); - var reasons = _pauseManager.GetPauseReasons(group); - - Console.WriteLine($"\n组: {group}"); - Console.WriteLine($" 状态: {(isPaused ? "暂停" : "运行")}"); - Console.WriteLine($" 深度: {depth}"); - - if (reasons.Count > 0) - { - Console.WriteLine(" 原因:"); - foreach (var reason in reasons) - { - Console.WriteLine($" - {reason}"); - } - } - } - } -} -``` - -### 6. 紧急恢复 - -```csharp -public class EmergencyController : IController -{ - private IPauseStackManager _pauseManager; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void ForceResumeAll() - { - // 清空所有暂停请求(谨慎使用) - _pauseManager.ClearAll(); - Console.WriteLine("已强制恢复所有系统"); - } - - public void ForceResumeGameplay() - { - // 只清空游戏逻辑组 - _pauseManager.ClearGroup(PauseGroup.Gameplay); - Console.WriteLine("已强制恢复游戏逻辑"); - } -} -``` - -## Godot 集成 - -### GodotPauseHandler - -GFramework.Godot 提供了 Godot 引擎的暂停处理器实现: - -```csharp -public class GodotPauseHandler : IPauseHandler -{ - private readonly SceneTree _tree; - - public GodotPauseHandler(SceneTree tree) - { - _tree = tree; - } - - public int Priority => 0; - - public void OnPauseStateChanged(PauseGroup group, bool isPaused) - { - // 只有 Global 组影响 Godot 的全局暂停 - if (group == PauseGroup.Global) - { - _tree.Paused = isPaused; - } - } -} -``` - -### 在 Godot 中使用 - -```csharp -public partial class GameRoot : Node -{ - private IPauseStackManager _pauseManager; - - public override void _Ready() - { - // 获取暂停管理器 - _pauseManager = architecture.GetUtility(); - - // 注册 Godot 处理器 - var godotHandler = new GodotPauseHandler(GetTree()); - _pauseManager.RegisterHandler(godotHandler); - } - - public void OnPauseButtonPressed() - { - // 暂停游戏 - _pauseManager.Push("玩家暂停", PauseGroup.Global); - } -} -``` - -### 配合 ProcessMode - -```csharp -public partial class PauseMenu : Control -{ - public override void _Ready() - { - // 设置为 Always 模式,暂停时仍然处理输入 - ProcessMode = ProcessModeEnum.Always; - } - - public override void _Input(InputEvent @event) - { - if (@event.IsActionPressed("ui_cancel")) - { - var pauseManager = this.GetUtility(); - - if (pauseManager.IsPaused()) - { - // 恢复游戏 - ResumeGame(); - } - else - { - // 暂停游戏 - PauseGame(); - } - } - } -} -``` - -## 最佳实践 - -### 1. 使用作用域管理 - -优先使用 `PauseScope` 而不是手动 `Push/Pop`,避免忘记恢复: - -```csharp -// ✅ 推荐 -public void ShowDialog() -{ - using (_pauseManager.PauseScope("对话框")) - { - // 对话框逻辑 - } - // 自动恢复 -} - -// ❌ 不推荐 -public void ShowDialog() -{ - var token = _pauseManager.Push("对话框"); - // 对话框逻辑 - _pauseManager.Pop(token); // 容易忘记 -} -``` - -### 2. 提供清晰的暂停原因 - -暂停原因用于调试,应该清晰描述暂停来源: - -```csharp -// ✅ 推荐 -_pauseManager.Push("主菜单 - 设置页面"); -_pauseManager.Push("过场动画 - 关卡加载"); -_pauseManager.Push("教程对话框 - 第一关"); - -// ❌ 不推荐 -_pauseManager.Push("pause"); -_pauseManager.Push("menu"); -``` - -### 3. 合理选择暂停组 - -根据实际需求选择合适的暂停组: - -```csharp -// 打开库存:只暂停游戏逻辑 -_pauseManager.Push("库存界面", PauseGroup.Gameplay); - -// 打开暂停菜单:全局暂停 -_pauseManager.Push("暂停菜单", PauseGroup.Global); - -// 播放过场动画:暂停游戏逻辑和输入 -_pauseManager.Push("过场动画", PauseGroup.Gameplay); -``` - -### 4. 处理器优先级设计 - -合理设置处理器优先级,确保正确的执行顺序: - -```csharp -// 物理引擎:高优先级(10),最先暂停 -public class PhysicsPauseHandler : IPauseHandler -{ - public int Priority => 10; -} - -// 音频系统:中优先级(20) -public class AudioPauseHandler : IPauseHandler -{ - public int Priority => 20; -} - -// UI 动画:低优先级(30),最后暂停 -public class UiAnimationPauseHandler : IPauseHandler -{ - public int Priority => 30; -} -``` - -### 5. 避免在处理器中抛出异常 - -处理器异常会被捕获并记录,但不会中断其他处理器: - -```csharp -public class SafePauseHandler : IPauseHandler -{ - public int Priority => 0; - - public void OnPauseStateChanged(PauseGroup group, bool isPaused) - { - try - { - // 可能失败的操作 - RiskyOperation(); - } - catch (Exception ex) - { - // 记录错误但不抛出 - Console.WriteLine($"暂停处理失败: {ex.Message}"); - } - } -} -``` - -### 6. 线程安全考虑 - -暂停管理器是线程安全的,但处理器回调在主线程执行: - -```csharp -public class ThreadSafeUsage -{ - private IPauseStackManager _pauseManager; - - public void WorkerThread() - { - // ✅ 可以从任何线程调用 - Task.Run(() => - { - var token = _pauseManager.Push("后台任务"); - // 执行任务 - _pauseManager.Pop(token); - }); - } -} -``` - -### 7. 清理资源 - -在组件销毁时注销处理器和事件: - -```csharp -public class ProperCleanup : IController -{ - private IPauseStackManager _pauseManager; - private IPauseHandler _customHandler; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void Initialize() - { - _pauseManager = this.GetUtility(); - _customHandler = new CustomPauseHandler(); - - _pauseManager.RegisterHandler(_customHandler); - _pauseManager.OnPauseStateChanged += OnPauseChanged; - } - - public void Cleanup() - { - _pauseManager.UnregisterHandler(_customHandler); - _pauseManager.OnPauseStateChanged -= OnPauseChanged; - } - - private void OnPauseChanged(PauseGroup group, bool isPaused) { } -} -``` - -## 常见问题 - -### Q1: 为什么调用 Pop 后游戏还是暂停? - -A: 暂停系统使用栈结构,只有当栈为空时才会恢复。检查是否有其他暂停请求: - -```csharp -// 调试暂停状态 -var depth = _pauseManager.GetPauseDepth(); -var reasons = _pauseManager.GetPauseReasons(); - -Console.WriteLine($"当前暂停深度: {depth}"); -Console.WriteLine("暂停原因:"); -foreach (var reason in reasons) -{ - Console.WriteLine($" - {reason}"); -} -``` - -### Q2: 如何实现"暂停时显示菜单"? - -A: 使用 Godot 的 `ProcessMode` 或监听暂停事件: - -```csharp -public partial class PauseMenu : Control -{ - public override void _Ready() - { - // 方案 1: 设置为 Always 模式 - ProcessMode = ProcessModeEnum.Always; - Visible = false; - - // 方案 2: 监听暂停事件 - var pauseManager = this.GetUtility(); - pauseManager.OnPauseStateChanged += (group, isPaused) => - { - if (group == PauseGroup.Global) - { - Visible = isPaused; - } - }; - } -} -``` - -### Q3: 可以在暂停期间执行某些逻辑吗? - -A: 可以,通过检查暂停状态或使用不同的暂停组: - -```csharp -public class SelectiveSystem : AbstractSystem -{ - protected override void OnInit() { } - - public void Update(float deltaTime) - { - var pauseManager = this.GetUtility(); - - // 方案 1: 检查特定组 - if (!pauseManager.IsPaused(PauseGroup.Gameplay)) - { - UpdateGameplay(deltaTime); - } - - // UI 始终更新(不检查暂停) - UpdateUI(deltaTime); - } -} -``` - -### Q4: 如何实现"慢动作"效果? - -A: 暂停系统控制是否执行,时间缩放需要使用时间系统: - -```csharp -public class SlowMotionController : IController -{ - private ITimeProvider _timeProvider; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void EnableSlowMotion() - { - // 使用时间缩放而不是暂停 - _timeProvider.TimeScale = 0.3f; - } - - public void DisableSlowMotion() - { - _timeProvider.TimeScale = 1.0f; - } -} -``` - -### Q5: 暂停管理器的性能如何? - -A: 暂停管理器使用读写锁优化并发性能: - -- 查询操作(`IsPaused`)使用读锁,支持并发 -- 修改操作(`Push/Pop`)使用写锁,互斥执行 -- 事件通知在锁外执行,避免死锁 -- 适合频繁查询、偶尔修改的场景 - -### Q6: 可以动态添加/移除暂停组吗? - -A: 暂停组是枚举类型,不支持动态添加。可以使用自定义组: - -```csharp -// 使用预定义的自定义组 -_pauseManager.Push("特殊效果", PauseGroup.Custom1); -_pauseManager.Push("天气系统", PauseGroup.Custom2); -_pauseManager.Push("AI 系统", PauseGroup.Custom3); -``` - -### Q7: 如何处理异步操作中的暂停? - -A: 使用 `PauseScope` 配合 `async/await`: - -```csharp -public class AsyncPauseExample : IController -{ - private IPauseStackManager _pauseManager; - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public async Task ShowAsyncDialog() - { - using (_pauseManager.PauseScope("异步对话框")) - { - await Task.Delay(1000); - Console.WriteLine("对话框显示中..."); - await WaitForUserInput(); - } - // 自动恢复 - } -} -``` - -## 架构集成 - -### 在架构中注册 - -```csharp -public class GameArchitecture : Architecture -{ - protected override void OnRegisterUtility() - { - // 注册暂停管理器 - RegisterUtility(new PauseStackManager()); - } - - protected override void OnInit() - { - // 注册默认处理器 - var pauseManager = GetUtility(); - - // Godot 处理器 - if (Engine.IsEditorHint() == false) - { - var tree = (GetTree() as SceneTree)!; - pauseManager.RegisterHandler(new GodotPauseHandler(tree)); - } - } -} -``` - -### 与其他系统协同 - -```csharp -// 与事件系统配合 -public class PauseEventBridge : AbstractSystem -{ - protected override void OnInit() - { - var pauseManager = this.GetUtility(); - - pauseManager.OnPauseStateChanged += (group, isPaused) => - { - // 发送暂停事件 - this.SendEvent(new GamePausedEvent - { - Group = group, - IsPaused = isPaused - }); - }; - } -} - -// 与命令系统配合 -public class PauseCommand : AbstractCommand -{ - private readonly string _reason; - private readonly PauseGroup _group; - - public PauseCommand(string reason, PauseGroup group = PauseGroup.Global) - { - _reason = reason; - _group = group; - } - - protected override void OnExecute() - { - var pauseManager = this.GetUtility(); - pauseManager.Push(_reason, _group); - } -} -``` - -## 相关包 - -- [`architecture`](./architecture.md) - 架构核心,提供工具注册 -- [`utility`](./utility.md) - 工具基类 -- [`events`](./events.md) - 事件系统,用于状态通知 -- [`lifecycle`](./lifecycle.md) - 生命周期管理 -- [`logging`](./logging.md) - 日志系统,用于调试 -- [Godot 集成](../godot/index.md) - Godot 引擎集成 +# 暂停管理系统使用说明 + +## 概述 + +暂停管理系统(Pause System)提供了一套完整的游戏暂停控制机制,支持多层嵌套暂停、分组暂停、以及灵活的暂停处理器扩展。该系统基于栈结构实现,能够优雅地处理复杂的暂停场景,如菜单叠加、对话框弹出等。 + +暂停系统是 GFramework 架构中的核心工具(Utility),与其他系统协同工作,为游戏提供统一的暂停管理能力。 + +**主要特性:** + +- **嵌套暂停**:支持多层暂停请求,只有所有请求都解除后才恢复 +- **分组管理**:不同系统可以独立暂停(游戏逻辑、动画、音频等) +- **线程安全**:使用读写锁保证并发安全 +- **作用域管理**:支持 `using` 语法自动管理暂停生命周期 +- **事件通知**:状态变化时通知所有注册的处理器 +- **优先级控制**:处理器按优先级顺序执行 + +## 核心概念 + +### 暂停栈(Pause Stack) + +暂停系统使用栈结构管理暂停请求。每次调用 `Push` 会将暂停请求压入栈中,调用 `Pop` 会从栈中移除对应的请求。只有当栈为空时,游戏才会恢复运行。 + +``` +栈深度 3: [暂停原因: "库存界面"] +栈深度 2: [暂停原因: "对话框"] +栈深度 1: [暂停原因: "暂停菜单"] +``` + +### 暂停组(Pause Group) + +暂停组允许不同系统独立控制暂停状态。例如,打开菜单时可以暂停游戏逻辑但保持 UI 动画运行。 + +**预定义组:** + +- `Global` - 全局暂停(影响所有系统) +- `Gameplay` - 游戏逻辑暂停(不影响 UI) +- `Animation` - 动画暂停 +- `Audio` - 音频暂停 +- `Custom1/2/3` - 自定义组 + +### 暂停令牌(Pause Token) + +每次暂停请求都会返回一个唯一的令牌,用于后续恢复操作。令牌基于 GUID 实现,确保唯一性。 + +```csharp +public readonly struct PauseToken +{ + public Guid Id { get; } + public bool IsValid => Id != Guid.Empty; +} +``` + +### 暂停处理器(Pause Handler) + +处理器实现具体的暂停/恢复逻辑,如控制物理引擎、音频系统等。处理器按优先级顺序执行。 + +```csharp +public interface IPauseHandler +{ + int Priority { get; } // 优先级(数值越小越高) + void OnPauseStateChanged(PauseGroup group, bool isPaused); +} +``` + +## 核心接口 + +### IPauseStackManager + +暂停栈管理器接口,提供暂停控制的所有功能。 + +**核心方法:** + +```csharp +// 推入暂停请求 +PauseToken Push(string reason, PauseGroup group = PauseGroup.Global); + +// 弹出暂停请求 +bool Pop(PauseToken token); + +// 查询暂停状态 +bool IsPaused(PauseGroup group = PauseGroup.Global); + +// 获取暂停深度 +int GetPauseDepth(PauseGroup group = PauseGroup.Global); + +// 获取暂停原因列表 +IReadOnlyList GetPauseReasons(PauseGroup group = PauseGroup.Global); + +// 创建暂停作用域 +IDisposable PauseScope(string reason, PauseGroup group = PauseGroup.Global); + +// 清空指定组 +void ClearGroup(PauseGroup group); + +// 清空所有组 +void ClearAll(); + +// 注册/注销处理器 +void RegisterHandler(IPauseHandler handler); +void UnregisterHandler(IPauseHandler handler); + +// 状态变化事件 +event Action? OnPauseStateChanged; +``` + +## 基本用法 + +### 1. 获取暂停管理器 + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameController : IController +{ + private IPauseStackManager _pauseManager; + + public void Initialize() + { + // 从架构中获取暂停管理器 + _pauseManager = this.GetUtility(); + } +} +``` + +### 2. 简单的暂停/恢复 + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class PauseMenuController : IController +{ + private IPauseStackManager _pauseManager; + private PauseToken _pauseToken; + + public void Initialize() + { + _pauseManager = this.GetUtility(); + } + + public void OpenPauseMenu() + { + // 暂停游戏 + _pauseToken = _pauseManager.Push("暂停菜单"); + + Console.WriteLine($"游戏已暂停,深度: {_pauseManager.GetPauseDepth()}"); + } + + public void ClosePauseMenu() + { + // 恢复游戏 + if (_pauseToken.IsValid) + { + _pauseManager.Pop(_pauseToken); + Console.WriteLine("游戏已恢复"); + } + } +} +``` + +### 3. 使用作用域自动管理 + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class DialogController : IController +{ + private IPauseStackManager _pauseManager; + + public void ShowDialog(string message) + { + // 使用 using 语法,自动管理暂停生命周期 + using (_pauseManager.PauseScope("对话框")) + { + Console.WriteLine($"显示对话框: {message}"); + // 对话框显示期间游戏暂停 + WaitForUserInput(); + } + // 离开作用域后自动恢复 + } +} +``` + +### 4. 查询暂停状态 + +```csharp +public class GameplaySystem : AbstractSystem +{ + private IPauseStackManager _pauseManager; + + protected override void OnInit() + { + _pauseManager = this.GetUtility(); + } + + public void Update(float deltaTime) + { + // 检查是否暂停 + if (_pauseManager.IsPaused(PauseGroup.Gameplay)) + { + return; // 暂停时跳过更新 + } + + // 正常游戏逻辑 + UpdateGameLogic(deltaTime); + } +} +``` + +## 高级用法 + +### 1. 嵌套暂停 + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class UIManager : IController +{ + private IPauseStackManager _pauseManager; + + public void ShowNestedMenus() + { + // 第一层:主菜单 + var token1 = _pauseManager.Push("主菜单"); + Console.WriteLine($"深度: {_pauseManager.GetPauseDepth()}"); // 输出: 1 + + // 第二层:设置菜单 + var token2 = _pauseManager.Push("设置菜单"); + Console.WriteLine($"深度: {_pauseManager.GetPauseDepth()}"); // 输出: 2 + + // 第三层:确认对话框 + var token3 = _pauseManager.Push("确认对话框"); + Console.WriteLine($"深度: {_pauseManager.GetPauseDepth()}"); // 输出: 3 + + // 关闭对话框 + _pauseManager.Pop(token3); + Console.WriteLine($"仍然暂停: {_pauseManager.IsPaused()}"); // 输出: True + + // 关闭设置菜单 + _pauseManager.Pop(token2); + Console.WriteLine($"仍然暂停: {_pauseManager.IsPaused()}"); // 输出: True + + // 关闭主菜单 + _pauseManager.Pop(token1); + Console.WriteLine($"已恢复: {!_pauseManager.IsPaused()}"); // 输出: True + } +} +``` + +### 2. 分组暂停 + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameManager : IController +{ + private IPauseStackManager _pauseManager; + + public void OpenInventory() + { + // 只暂停游戏逻辑,UI 和音频继续运行 + var token = _pauseManager.Push("库存界面", PauseGroup.Gameplay); + + Console.WriteLine($"游戏逻辑暂停: {_pauseManager.IsPaused(PauseGroup.Gameplay)}"); + Console.WriteLine($"音频暂停: {_pauseManager.IsPaused(PauseGroup.Audio)}"); + Console.WriteLine($"全局暂停: {_pauseManager.IsPaused(PauseGroup.Global)}"); + } + + public void OpenPauseMenu() + { + // 全局暂停,影响所有系统 + var token = _pauseManager.Push("暂停菜单", PauseGroup.Global); + + Console.WriteLine($"所有系统已暂停"); + } + + public void MuteAudio() + { + // 只暂停音频 + var token = _pauseManager.Push("静音", PauseGroup.Audio); + } +} +``` + +### 3. 自定义暂停处理器 + +```csharp +// 物理引擎暂停处理器 +public class PhysicsPauseHandler : IPauseHandler +{ + private readonly PhysicsWorld _physicsWorld; + + public PhysicsPauseHandler(PhysicsWorld physicsWorld) + { + _physicsWorld = physicsWorld; + } + + // 高优先级,确保物理引擎最先暂停 + public int Priority => 10; + + public void OnPauseStateChanged(PauseGroup group, bool isPaused) + { + // 只响应游戏逻辑和全局暂停 + if (group == PauseGroup.Gameplay || group == PauseGroup.Global) + { + _physicsWorld.Enabled = !isPaused; + Console.WriteLine($"物理引擎 {(isPaused ? "已暂停" : "已恢复")}"); + } + } +} + +// 音频系统暂停处理器 +public class AudioPauseHandler : IPauseHandler +{ + private readonly AudioSystem _audioSystem; + + public AudioPauseHandler(AudioSystem audioSystem) + { + _audioSystem = audioSystem; + } + + public int Priority => 20; + + public void OnPauseStateChanged(PauseGroup group, bool isPaused) + { + // 响应音频和全局暂停 + if (group == PauseGroup.Audio || group == PauseGroup.Global) + { + if (isPaused) + { + _audioSystem.PauseAll(); + } + else + { + _audioSystem.ResumeAll(); + } + } + } +} + +// 注册处理器 +public class GameInitializer +{ + public void Initialize() + { + var pauseManager = architecture.GetUtility(); + var physicsWorld = GetPhysicsWorld(); + var audioSystem = GetAudioSystem(); + + // 注册处理器 + pauseManager.RegisterHandler(new PhysicsPauseHandler(physicsWorld)); + pauseManager.RegisterHandler(new AudioPauseHandler(audioSystem)); + } +} +``` + +### 4. 监听暂停状态变化 + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class PauseIndicator : IController +{ + private IPauseStackManager _pauseManager; + + public void Initialize() + { + _pauseManager = this.GetUtility(); + + // 订阅状态变化事件 + _pauseManager.OnPauseStateChanged += OnPauseStateChanged; + } + + private void OnPauseStateChanged(PauseGroup group, bool isPaused) + { + Console.WriteLine($"暂停状态变化: 组={group}, 暂停={isPaused}"); + + if (group == PauseGroup.Global) + { + if (isPaused) + { + ShowPauseIndicator(); + } + else + { + HidePauseIndicator(); + } + } + } + + public void Cleanup() + { + _pauseManager.OnPauseStateChanged -= OnPauseStateChanged; + } +} +``` + +### 5. 调试暂停状态 + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class PauseDebugger : IController +{ + private IPauseStackManager _pauseManager; + + public void PrintPauseStatus() + { + Console.WriteLine("=== 暂停状态 ==="); + + foreach (PauseGroup group in Enum.GetValues(typeof(PauseGroup))) + { + var isPaused = _pauseManager.IsPaused(group); + var depth = _pauseManager.GetPauseDepth(group); + var reasons = _pauseManager.GetPauseReasons(group); + + Console.WriteLine($"\n组: {group}"); + Console.WriteLine($" 状态: {(isPaused ? "暂停" : "运行")}"); + Console.WriteLine($" 深度: {depth}"); + + if (reasons.Count > 0) + { + Console.WriteLine(" 原因:"); + foreach (var reason in reasons) + { + Console.WriteLine($" - {reason}"); + } + } + } + } +} +``` + +### 6. 紧急恢复 + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class EmergencyController : IController +{ + private IPauseStackManager _pauseManager; + + public void ForceResumeAll() + { + // 清空所有暂停请求(谨慎使用) + _pauseManager.ClearAll(); + Console.WriteLine("已强制恢复所有系统"); + } + + public void ForceResumeGameplay() + { + // 只清空游戏逻辑组 + _pauseManager.ClearGroup(PauseGroup.Gameplay); + Console.WriteLine("已强制恢复游戏逻辑"); + } +} +``` + +## Godot 集成 + +### GodotPauseHandler + +GFramework.Godot 提供了 Godot 引擎的暂停处理器实现: + +```csharp +public class GodotPauseHandler : IPauseHandler +{ + private readonly SceneTree _tree; + + public GodotPauseHandler(SceneTree tree) + { + _tree = tree; + } + + public int Priority => 0; + + public void OnPauseStateChanged(PauseGroup group, bool isPaused) + { + // 只有 Global 组影响 Godot 的全局暂停 + if (group == PauseGroup.Global) + { + _tree.Paused = isPaused; + } + } +} +``` + +### 在 Godot 中使用 + +```csharp +public partial class GameRoot : Node +{ + private IPauseStackManager _pauseManager; + + public override void _Ready() + { + // 获取暂停管理器 + _pauseManager = architecture.GetUtility(); + + // 注册 Godot 处理器 + var godotHandler = new GodotPauseHandler(GetTree()); + _pauseManager.RegisterHandler(godotHandler); + } + + public void OnPauseButtonPressed() + { + // 暂停游戏 + _pauseManager.Push("玩家暂停", PauseGroup.Global); + } +} +``` + +### 配合 ProcessMode + +```csharp +public partial class PauseMenu : Control +{ + public override void _Ready() + { + // 设置为 Always 模式,暂停时仍然处理输入 + ProcessMode = ProcessModeEnum.Always; + } + + public override void _Input(InputEvent @event) + { + if (@event.IsActionPressed("ui_cancel")) + { + var pauseManager = this.GetUtility(); + + if (pauseManager.IsPaused()) + { + // 恢复游戏 + ResumeGame(); + } + else + { + // 暂停游戏 + PauseGame(); + } + } + } +} +``` + +## 最佳实践 + +### 1. 使用作用域管理 + +优先使用 `PauseScope` 而不是手动 `Push/Pop`,避免忘记恢复: + +```csharp +// ✅ 推荐 +public void ShowDialog() +{ + using (_pauseManager.PauseScope("对话框")) + { + // 对话框逻辑 + } + // 自动恢复 +} + +// ❌ 不推荐 +public void ShowDialog() +{ + var token = _pauseManager.Push("对话框"); + // 对话框逻辑 + _pauseManager.Pop(token); // 容易忘记 +} +``` + +### 2. 提供清晰的暂停原因 + +暂停原因用于调试,应该清晰描述暂停来源: + +```csharp +// ✅ 推荐 +_pauseManager.Push("主菜单 - 设置页面"); +_pauseManager.Push("过场动画 - 关卡加载"); +_pauseManager.Push("教程对话框 - 第一关"); + +// ❌ 不推荐 +_pauseManager.Push("pause"); +_pauseManager.Push("menu"); +``` + +### 3. 合理选择暂停组 + +根据实际需求选择合适的暂停组: + +```csharp +// 打开库存:只暂停游戏逻辑 +_pauseManager.Push("库存界面", PauseGroup.Gameplay); + +// 打开暂停菜单:全局暂停 +_pauseManager.Push("暂停菜单", PauseGroup.Global); + +// 播放过场动画:暂停游戏逻辑和输入 +_pauseManager.Push("过场动画", PauseGroup.Gameplay); +``` + +### 4. 处理器优先级设计 + +合理设置处理器优先级,确保正确的执行顺序: + +```csharp +// 物理引擎:高优先级(10),最先暂停 +public class PhysicsPauseHandler : IPauseHandler +{ + public int Priority => 10; +} + +// 音频系统:中优先级(20) +public class AudioPauseHandler : IPauseHandler +{ + public int Priority => 20; +} + +// UI 动画:低优先级(30),最后暂停 +public class UiAnimationPauseHandler : IPauseHandler +{ + public int Priority => 30; +} +``` + +### 5. 避免在处理器中抛出异常 + +处理器异常会被捕获并记录,但不会中断其他处理器: + +```csharp +public class SafePauseHandler : IPauseHandler +{ + public int Priority => 0; + + public void OnPauseStateChanged(PauseGroup group, bool isPaused) + { + try + { + // 可能失败的操作 + RiskyOperation(); + } + catch (Exception ex) + { + // 记录错误但不抛出 + Console.WriteLine($"暂停处理失败: {ex.Message}"); + } + } +} +``` + +### 6. 线程安全考虑 + +暂停管理器是线程安全的,但处理器回调在主线程执行: + +```csharp +public class ThreadSafeUsage +{ + private IPauseStackManager _pauseManager; + + public void WorkerThread() + { + // ✅ 可以从任何线程调用 + Task.Run(() => + { + var token = _pauseManager.Push("后台任务"); + // 执行任务 + _pauseManager.Pop(token); + }); + } +} +``` + +### 7. 清理资源 + +在组件销毁时注销处理器和事件: + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class ProperCleanup : IController +{ + private IPauseStackManager _pauseManager; + private IPauseHandler _customHandler; + + public void Initialize() + { + _pauseManager = this.GetUtility(); + _customHandler = new CustomPauseHandler(); + + _pauseManager.RegisterHandler(_customHandler); + _pauseManager.OnPauseStateChanged += OnPauseChanged; + } + + public void Cleanup() + { + _pauseManager.UnregisterHandler(_customHandler); + _pauseManager.OnPauseStateChanged -= OnPauseChanged; + } + + private void OnPauseChanged(PauseGroup group, bool isPaused) { } +} +``` + +## 常见问题 + +### Q1: 为什么调用 Pop 后游戏还是暂停? + +A: 暂停系统使用栈结构,只有当栈为空时才会恢复。检查是否有其他暂停请求: + +```csharp +// 调试暂停状态 +var depth = _pauseManager.GetPauseDepth(); +var reasons = _pauseManager.GetPauseReasons(); + +Console.WriteLine($"当前暂停深度: {depth}"); +Console.WriteLine("暂停原因:"); +foreach (var reason in reasons) +{ + Console.WriteLine($" - {reason}"); +} +``` + +### Q2: 如何实现"暂停时显示菜单"? + +A: 使用 Godot 的 `ProcessMode` 或监听暂停事件: + +```csharp +public partial class PauseMenu : Control +{ + public override void _Ready() + { + // 方案 1: 设置为 Always 模式 + ProcessMode = ProcessModeEnum.Always; + Visible = false; + + // 方案 2: 监听暂停事件 + var pauseManager = this.GetUtility(); + pauseManager.OnPauseStateChanged += (group, isPaused) => + { + if (group == PauseGroup.Global) + { + Visible = isPaused; + } + }; + } +} +``` + +### Q3: 可以在暂停期间执行某些逻辑吗? + +A: 可以,通过检查暂停状态或使用不同的暂停组: + +```csharp +public class SelectiveSystem : AbstractSystem +{ + protected override void OnInit() { } + + public void Update(float deltaTime) + { + var pauseManager = this.GetUtility(); + + // 方案 1: 检查特定组 + if (!pauseManager.IsPaused(PauseGroup.Gameplay)) + { + UpdateGameplay(deltaTime); + } + + // UI 始终更新(不检查暂停) + UpdateUI(deltaTime); + } +} +``` + +### Q4: 如何实现"慢动作"效果? + +A: 暂停系统控制是否执行,时间缩放需要使用时间系统: + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class SlowMotionController : IController +{ + private ITimeProvider _timeProvider; + + public void EnableSlowMotion() + { + // 使用时间缩放而不是暂停 + _timeProvider.TimeScale = 0.3f; + } + + public void DisableSlowMotion() + { + _timeProvider.TimeScale = 1.0f; + } +} +``` + +### Q5: 暂停管理器的性能如何? + +A: 暂停管理器使用读写锁优化并发性能: + +- 查询操作(`IsPaused`)使用读锁,支持并发 +- 修改操作(`Push/Pop`)使用写锁,互斥执行 +- 事件通知在锁外执行,避免死锁 +- 适合频繁查询、偶尔修改的场景 + +### Q6: 可以动态添加/移除暂停组吗? + +A: 暂停组是枚举类型,不支持动态添加。可以使用自定义组: + +```csharp +// 使用预定义的自定义组 +_pauseManager.Push("特殊效果", PauseGroup.Custom1); +_pauseManager.Push("天气系统", PauseGroup.Custom2); +_pauseManager.Push("AI 系统", PauseGroup.Custom3); +``` + +### Q7: 如何处理异步操作中的暂停? + +A: 使用 `PauseScope` 配合 `async/await`: + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class AsyncPauseExample : IController +{ + private IPauseStackManager _pauseManager; + + public async Task ShowAsyncDialog() + { + using (_pauseManager.PauseScope("异步对话框")) + { + await Task.Delay(1000); + Console.WriteLine("对话框显示中..."); + await WaitForUserInput(); + } + // 自动恢复 + } +} +``` + +## 架构集成 + +### 在架构中注册 + +```csharp +public class GameArchitecture : Architecture +{ + protected override void OnRegisterUtility() + { + // 注册暂停管理器 + RegisterUtility(new PauseStackManager()); + } + + protected override void OnInit() + { + // 注册默认处理器 + var pauseManager = GetUtility(); + + // Godot 处理器 + if (Engine.IsEditorHint() == false) + { + var tree = (GetTree() as SceneTree)!; + pauseManager.RegisterHandler(new GodotPauseHandler(tree)); + } + } +} +``` + +### 与其他系统协同 + +```csharp +// 与事件系统配合 +public class PauseEventBridge : AbstractSystem +{ + protected override void OnInit() + { + var pauseManager = this.GetUtility(); + + pauseManager.OnPauseStateChanged += (group, isPaused) => + { + // 发送暂停事件 + this.SendEvent(new GamePausedEvent + { + Group = group, + IsPaused = isPaused + }); + }; + } +} + +// 与命令系统配合 +public class PauseCommand : AbstractCommand +{ + private readonly string _reason; + private readonly PauseGroup _group; + + public PauseCommand(string reason, PauseGroup group = PauseGroup.Global) + { + _reason = reason; + _group = group; + } + + protected override void OnExecute() + { + var pauseManager = this.GetUtility(); + pauseManager.Push(_reason, _group); + } +} +``` + +## 相关包 + +- [`architecture`](./architecture.md) - 架构核心,提供工具注册 +- [`utility`](./utility.md) - 工具基类 +- [`events`](./events.md) - 事件系统,用于状态通知 +- [`lifecycle`](./lifecycle.md) - 生命周期管理 +- [`logging`](./logging.md) - 日志系统,用于调试 +- [Godot 集成](../godot/index.md) - Godot 引擎集成 diff --git a/docs/zh-CN/core/property.md b/docs/zh-CN/core/property.md index 0e35b48..8c3cd97 100644 --- a/docs/zh-CN/core/property.md +++ b/docs/zh-CN/core/property.md @@ -1,427 +1,434 @@ -# Property 包使用说明 - -## 概述 - -Property 包提供了可绑定属性(BindableProperty)的实现,支持属性值的监听和响应式编程。这是实现数据绑定和响应式编程的核心组件。 - -BindableProperty 是 GFramework 中 Model 层数据管理的基础,通过事件机制实现属性变化的通知。 - -## 核心接口 - -### IReadonlyBindableProperty`` - -只读可绑定属性接口,提供属性值的读取和变更监听功能。 - -**核心成员:** - -```csharp -// 获取属性值 -T Value { get; } - -// 注册监听(不立即触发回调) -IUnRegister Register(Action onValueChanged); - -// 注册监听并立即触发回调传递当前值 -IUnRegister RegisterWithInitValue(Action action); - -// 取消监听 -void UnRegister(Action onValueChanged); -``` - -### IBindableProperty`` - -可绑定属性接口,继承自只读接口,增加了修改能力。 - -**核心成员:** - -```csharp -// 可读写的属性值 -new T Value { get; set; } - -// 设置值但不触发事件 -void SetValueWithoutEvent(T newValue); -``` - -## 核心类 - -### BindableProperty`` - -可绑定属性的完整实现。 - -**核心方法:** - -```csharp -// 构造函数 -BindableProperty(T defaultValue = default!); - -// 属性值 -T Value { get; set; } - -// 注册监听 -IUnRegister Register(Action onValueChanged); -IUnRegister RegisterWithInitValue(Action action); - -// 取消监听 -void UnRegister(Action onValueChanged); - -// 设置值但不触发事件 -void SetValueWithoutEvent(T newValue); - -// 设置自定义比较器 -BindableProperty WithComparer(Func comparer); -``` - -**使用示例:** - -```csharp -// 创建可绑定属性 -var health = new BindableProperty(100); - -// 监听值变化(不会立即触发) -var unregister = health.Register(newValue => -{ - Console.WriteLine($"Health changed to: {newValue}"); -}); - -// 设置值(会触发监听器) -health.Value = 50; // 输出: Health changed to: 50 - -// 取消监听 -unregister.UnRegister(); - -// 设置值但不触发事件 -health.SetValueWithoutEvent(75); -``` - -**高级功能:** - -```csharp -// 1. 注册并立即获得当前值 -health.RegisterWithInitValue(value => -{ - Console.WriteLine($"Current health: {value}"); // 立即输出当前值 - // 后续值变化时也会调用 -}); - -// 2. 自定义比较器(静态方法) -BindableProperty.Comparer = (a, b) => Math.Abs(a - b) < 1; - -// 3. 使用实例方法设置比较器 -var position = new BindableProperty(Vector3.Zero) - .WithComparer((a, b) => a.DistanceTo(b) < 0.01f); // 距离小于0.01认为相等 - -// 4. 字符串比较器示例 -var name = new BindableProperty("Player") - .WithComparer((a, b) => string.Equals(a, b, StringComparison.OrdinalIgnoreCase)); -``` - -### BindablePropertyUnRegister`` - -可绑定属性的注销器,负责清理监听。 - -**使用示例:** - -```csharp -var unregister = health.Register(OnHealthChanged); -// 当需要取消监听时 -unregister.UnRegister(); -``` - -## BindableProperty 工作原理 - -BindableProperty 基于事件系统实现属性变化通知: - -1. **值设置**:当设置 `Value` 属性时,首先进行值比较 -2. **变化检测**:使用 `EqualityComparer.Default` 或自定义比较器检测值变化 -3. **事件触发**:如果值发生变化,调用所有注册的回调函数 -4. **内存管理**:通过 `IUnRegister` 机制管理监听器的生命周期 - -## 在 Model 中使用 - -### 定义可绑定属性 - -```csharp -public class PlayerModel : AbstractModel -{ - // 可读写属性 - public BindableProperty Name { get; } = new("Player"); - public BindableProperty Level { get; } = new(1); - public BindableProperty Health { get; } = new(100); - public BindableProperty MaxHealth { get; } = new(100); - public BindableProperty Position { get; } = new(Vector3.Zero); - - // 只读属性(外部只能读取和监听) - public IReadonlyBindableProperty ReadonlyHealth => Health; - - protected override void OnInit() - { - // 内部监听属性变化 - Health.Register(hp => - { - if (hp <= 0) - { - this.SendEvent(new PlayerDiedEvent()); - } - else if (hp < MaxHealth.Value * 0.3f) - { - this.SendEvent(new LowHealthWarningEvent()); - } - }); - - // 监听等级变化 - Level.Register(newLevel => - { - this.SendEvent(new PlayerLevelUpEvent { NewLevel = newLevel }); - }); - } - - // 业务方法 - public void TakeDamage(int damage) - { - Health.Value = Math.Max(0, Health.Value - damage); - } - - public void Heal(int amount) - { - Health.Value = Math.Min(MaxHealth.Value, Health.Value + amount); - } - - public float GetHealthPercentage() - { - return (float)Health.Value / MaxHealth.Value; - } -} -``` - -## 在 Controller 中监听 - -### UI 数据绑定 - -```csharp -public partial class PlayerUI : Control, IController -{ - [Export] private Label _healthLabel; - [Export] private Label _nameLabel; - [Export] private ProgressBar _healthBar; - - private IUnRegisterList _unregisterList = new UnRegisterList(); - - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public override void _Ready() - { - var playerModel = this.GetModel(); - - // 绑定生命值到UI(立即显示当前值) - playerModel.Health - .RegisterWithInitValue(health => - { - _healthLabel.Text = $"HP: {health}/{playerModel.MaxHealth.Value}"; - _healthBar.Value = (float)health / playerModel.MaxHealth.Value * 100; - }) - .AddToUnregisterList(_unregisterList); - - // 绑定最大生命值 - playerModel.MaxHealth - .RegisterWithInitValue(maxHealth => - { - _healthBar.MaxValue = maxHealth; - }) - .AddToUnregisterList(_unregisterList); - - // 绑定名称 - playerModel.Name - .RegisterWithInitValue(name => - { - _nameLabel.Text = name; - }) - .AddToUnregisterList(_unregisterList); - - // 绑定位置(仅用于调试显示) - playerModel.Position - .RegisterWithInitValue(pos => - { - // 仅在调试模式下显示 - #if DEBUG - Console.WriteLine($"Player position: {pos}"); - #endif - }) - .AddToUnregisterList(_unregisterList); - } - - public override void _ExitTree() - { - _unregisterList.UnRegisterAll(); - } -} -``` - -## 常见使用模式 - -### 1. 双向绑定 - -```c# -// Model -public class SettingsModel : AbstractModel -{ - public BindableProperty MasterVolume { get; } = new(1.0f); - protected override void OnInit() { } -} - -// UI Controller -public partial class VolumeSlider : HSlider, IController -{ - private BindableProperty _volumeProperty; - - public override void _Ready() - { - _volumeProperty = this.GetModel().MasterVolume; - - // Model -> UI - _volumeProperty.RegisterWithInitValue(vol => Value = vol) - .UnRegisterWhenNodeExitTree(this); - - // UI -> Model - ValueChanged += newValue => _volumeProperty.Value = (float)newValue; - } -} -``` - -### 2. 计算属性 - -```c# -public class PlayerModel : AbstractModel -{ - public BindableProperty Health { get; } = new(100); - public BindableProperty MaxHealth { get; } = new(100); - public BindableProperty HealthPercent { get; } = new(1.0f); - - protected override void OnInit() - { - // 自动计算百分比 - Action updatePercent = () => - { - HealthPercent.Value = (float)Health.Value / MaxHealth.Value; - }; - - Health.Register(_ => updatePercent()); - MaxHealth.Register(_ => updatePercent()); - - updatePercent(); // 初始计算 - } -} -``` - -### 3. 属性验证 - -```c# -public class PlayerModel : AbstractModel -{ - private BindableProperty _health = new(100); - - public BindableProperty Health - { - get => _health; - set - { - // 限制范围 - var clampedValue = Math.Clamp(value.Value, 0, MaxHealth.Value); - _health.Value = clampedValue; - } - } - - public BindableProperty MaxHealth { get; } = new(100); - - protected override void OnInit() { } -} -``` - -### 4. 条件监听 - -```c# -public class CombatController : Node, IController -{ - public override void _Ready() - { - var playerModel = this.GetModel(); - - // 只在生命值低于30%时显示警告 - playerModel.Health.Register(hp => - { - if (hp < playerModel.MaxHealth.Value * 0.3f) - { - ShowLowHealthWarning(); - } - else - { - HideLowHealthWarning(); - } - }).UnRegisterWhenNodeExitTree(this); - } -} -``` - -## 性能优化 - -### 1. 避免频繁触发 - -```c# -// 使用 SetValueWithoutEvent 批量修改 -public void LoadPlayerData(SaveData data) -{ - // 临时关闭事件 - Health.SetValueWithoutEvent(data.Health); - Mana.SetValueWithoutEvent(data.Mana); - Gold.SetValueWithoutEvent(data.Gold); - - // 最后统一触发一次更新事件 - this.SendEvent(new PlayerDataLoadedEvent()); -} -``` - -### 2. 自定义比较器 - -```c# -// 避免浮点数精度问题导致的频繁触发 -var position = new BindableProperty() - .WithComparer((a, b) => a.DistanceTo(b) < 0.001f); -``` - -## 实现原理 - -### 值变化检测 - -```c# -// 使用 EqualityComparer.Default 进行比较 -if (!EqualityComparer.Default.Equals(value, MValue)) -{ - MValue = value; - _mOnValueChanged?.Invoke(value); -} -``` - -### 事件触发机制 - -```c# -// 当值变化时触发所有注册的回调 -_mOnValueChanged?.Invoke(value); -``` - -## 最佳实践 - -1. **在 Model 中定义属性** - BindableProperty 主要用于 Model 层 -2. **使用只读接口暴露** - 防止外部随意修改 -3. **及时注销监听** - 使用 UnRegisterList 或 UnRegisterWhenNodeExitTree -4. **使用 RegisterWithInitValue** - UI 绑定时立即获取初始值 -5. **避免循环依赖** - 属性监听器中修改其他属性要小心 -6. **使用自定义比较器** - 对于浮点数等需要精度控制的属性 - -## 相关包 - -- [`model`](./model.md) - Model 中大量使用 BindableProperty -- [`events`](./events.md) - BindableProperty 基于事件系统实现 -- [`extensions`](./extensions.md) - 提供便捷的注销扩展方法 - ---- - -**许可证**: Apache 2.0 +# Property 包使用说明 + +## 概述 + +Property 包提供了可绑定属性(BindableProperty)的实现,支持属性值的监听和响应式编程。这是实现数据绑定和响应式编程的核心组件。 + +BindableProperty 是 GFramework 中 Model 层数据管理的基础,通过事件机制实现属性变化的通知。 + +## 核心接口 + +### IReadonlyBindableProperty`` + +只读可绑定属性接口,提供属性值的读取和变更监听功能。 + +**核心成员:** + +```csharp +// 获取属性值 +T Value { get; } + +// 注册监听(不立即触发回调) +IUnRegister Register(Action onValueChanged); + +// 注册监听并立即触发回调传递当前值 +IUnRegister RegisterWithInitValue(Action action); + +// 取消监听 +void UnRegister(Action onValueChanged); +``` + +### IBindableProperty`` + +可绑定属性接口,继承自只读接口,增加了修改能力。 + +**核心成员:** + +```csharp +// 可读写的属性值 +new T Value { get; set; } + +// 设置值但不触发事件 +void SetValueWithoutEvent(T newValue); +``` + +## 核心类 + +### BindableProperty`` + +可绑定属性的完整实现。 + +**核心方法:** + +```csharp +// 构造函数 +BindableProperty(T defaultValue = default!); + +// 属性值 +T Value { get; set; } + +// 注册监听 +IUnRegister Register(Action onValueChanged); +IUnRegister RegisterWithInitValue(Action action); + +// 取消监听 +void UnRegister(Action onValueChanged); + +// 设置值但不触发事件 +void SetValueWithoutEvent(T newValue); + +// 设置自定义比较器 +BindableProperty WithComparer(Func comparer); +``` + +**使用示例:** + +```csharp +// 创建可绑定属性 +var health = new BindableProperty(100); + +// 监听值变化(不会立即触发) +var unregister = health.Register(newValue => +{ + Console.WriteLine($"Health changed to: {newValue}"); +}); + +// 设置值(会触发监听器) +health.Value = 50; // 输出: Health changed to: 50 + +// 取消监听 +unregister.UnRegister(); + +// 设置值但不触发事件 +health.SetValueWithoutEvent(75); +``` + +**高级功能:** + +```csharp +// 1. 注册并立即获得当前值 +health.RegisterWithInitValue(value => +{ + Console.WriteLine($"Current health: {value}"); // 立即输出当前值 + // 后续值变化时也会调用 +}); + +// 2. 自定义比较器(静态方法) +BindableProperty.Comparer = (a, b) => Math.Abs(a - b) < 1; + +// 3. 使用实例方法设置比较器 +var position = new BindableProperty(Vector3.Zero) + .WithComparer((a, b) => a.DistanceTo(b) < 0.01f); // 距离小于0.01认为相等 + +// 4. 字符串比较器示例 +var name = new BindableProperty("Player") + .WithComparer((a, b) => string.Equals(a, b, StringComparison.OrdinalIgnoreCase)); +``` + +### BindablePropertyUnRegister`` + +可绑定属性的注销器,负责清理监听。 + +**使用示例:** + +```csharp +var unregister = health.Register(OnHealthChanged); +// 当需要取消监听时 +unregister.UnRegister(); +``` + +## BindableProperty 工作原理 + +BindableProperty 基于事件系统实现属性变化通知: + +1. **值设置**:当设置 `Value` 属性时,首先进行值比较 +2. **变化检测**:使用 `EqualityComparer.Default` 或自定义比较器检测值变化 +3. **事件触发**:如果值发生变化,调用所有注册的回调函数 +4. **内存管理**:通过 `IUnRegister` 机制管理监听器的生命周期 + +## 在 Model 中使用 + +### 定义可绑定属性 + +```csharp +public class PlayerModel : AbstractModel +{ + // 可读写属性 + public BindableProperty Name { get; } = new("Player"); + public BindableProperty Level { get; } = new(1); + public BindableProperty Health { get; } = new(100); + public BindableProperty MaxHealth { get; } = new(100); + public BindableProperty Position { get; } = new(Vector3.Zero); + + // 只读属性(外部只能读取和监听) + public IReadonlyBindableProperty ReadonlyHealth => Health; + + protected override void OnInit() + { + // 内部监听属性变化 + Health.Register(hp => + { + if (hp <= 0) + { + this.SendEvent(new PlayerDiedEvent()); + } + else if (hp < MaxHealth.Value * 0.3f) + { + this.SendEvent(new LowHealthWarningEvent()); + } + }); + + // 监听等级变化 + Level.Register(newLevel => + { + this.SendEvent(new PlayerLevelUpEvent { NewLevel = newLevel }); + }); + } + + // 业务方法 + public void TakeDamage(int damage) + { + Health.Value = Math.Max(0, Health.Value - damage); + } + + public void Heal(int amount) + { + Health.Value = Math.Min(MaxHealth.Value, Health.Value + amount); + } + + public float GetHealthPercentage() + { + return (float)Health.Value / MaxHealth.Value; + } +} +``` + +## 在 Controller 中监听 + +### UI 数据绑定 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class PlayerUI : Control, IController +{ + [Export] private Label _healthLabel; + [Export] private Label _nameLabel; + [Export] private ProgressBar _healthBar; + + private IUnRegisterList _unregisterList = new UnRegisterList(); + + public override void _Ready() + { + var playerModel = this.GetModel(); + + // 绑定生命值到UI(立即显示当前值) + playerModel.Health + .RegisterWithInitValue(health => + { + _healthLabel.Text = $"HP: {health}/{playerModel.MaxHealth.Value}"; + _healthBar.Value = (float)health / playerModel.MaxHealth.Value * 100; + }) + .AddToUnregisterList(_unregisterList); + + // 绑定最大生命值 + playerModel.MaxHealth + .RegisterWithInitValue(maxHealth => + { + _healthBar.MaxValue = maxHealth; + }) + .AddToUnregisterList(_unregisterList); + + // 绑定名称 + playerModel.Name + .RegisterWithInitValue(name => + { + _nameLabel.Text = name; + }) + .AddToUnregisterList(_unregisterList); + + // 绑定位置(仅用于调试显示) + playerModel.Position + .RegisterWithInitValue(pos => + { + // 仅在调试模式下显示 + #if DEBUG + Console.WriteLine($"Player position: {pos}"); + #endif + }) + .AddToUnregisterList(_unregisterList); + } + + public override void _ExitTree() + { + _unregisterList.UnRegisterAll(); + } +} +``` + +## 常见使用模式 + +### 1. 双向绑定 + +```c# +// Model +public class SettingsModel : AbstractModel +{ + public BindableProperty MasterVolume { get; } = new(1.0f); + protected override void OnInit() { } +} + +// UI Controller +[ContextAware] +public partial class VolumeSlider : HSlider, IController +{ + private BindableProperty _volumeProperty; + + public override void _Ready() + { + _volumeProperty = this.GetModel().MasterVolume; + + // Model -> UI + _volumeProperty.RegisterWithInitValue(vol => Value = vol) + .UnRegisterWhenNodeExitTree(this); + + // UI -> Model + ValueChanged += newValue => _volumeProperty.Value = (float)newValue; + } +} +``` + +### 2. 计算属性 + +```c# +public class PlayerModel : AbstractModel +{ + public BindableProperty Health { get; } = new(100); + public BindableProperty MaxHealth { get; } = new(100); + public BindableProperty HealthPercent { get; } = new(1.0f); + + protected override void OnInit() + { + // 自动计算百分比 + Action updatePercent = () => + { + HealthPercent.Value = (float)Health.Value / MaxHealth.Value; + }; + + Health.Register(_ => updatePercent()); + MaxHealth.Register(_ => updatePercent()); + + updatePercent(); // 初始计算 + } +} +``` + +### 3. 属性验证 + +```c# +public class PlayerModel : AbstractModel +{ + private BindableProperty _health = new(100); + + public BindableProperty Health + { + get => _health; + set + { + // 限制范围 + var clampedValue = Math.Clamp(value.Value, 0, MaxHealth.Value); + _health.Value = clampedValue; + } + } + + public BindableProperty MaxHealth { get; } = new(100); + + protected override void OnInit() { } +} +``` + +### 4. 条件监听 + +```c# +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class CombatController : Node, IController +{ + public override void _Ready() + { + var playerModel = this.GetModel(); + + // 只在生命值低于30%时显示警告 + playerModel.Health.Register(hp => + { + if (hp < playerModel.MaxHealth.Value * 0.3f) + { + ShowLowHealthWarning(); + } + else + { + HideLowHealthWarning(); + } + }).UnRegisterWhenNodeExitTree(this); + } +} +``` + +## 性能优化 + +### 1. 避免频繁触发 + +```c# +// 使用 SetValueWithoutEvent 批量修改 +public void LoadPlayerData(SaveData data) +{ + // 临时关闭事件 + Health.SetValueWithoutEvent(data.Health); + Mana.SetValueWithoutEvent(data.Mana); + Gold.SetValueWithoutEvent(data.Gold); + + // 最后统一触发一次更新事件 + this.SendEvent(new PlayerDataLoadedEvent()); +} +``` + +### 2. 自定义比较器 + +```c# +// 避免浮点数精度问题导致的频繁触发 +var position = new BindableProperty() + .WithComparer((a, b) => a.DistanceTo(b) < 0.001f); +``` + +## 实现原理 + +### 值变化检测 + +```c# +// 使用 EqualityComparer.Default 进行比较 +if (!EqualityComparer.Default.Equals(value, MValue)) +{ + MValue = value; + _mOnValueChanged?.Invoke(value); +} +``` + +### 事件触发机制 + +```c# +// 当值变化时触发所有注册的回调 +_mOnValueChanged?.Invoke(value); +``` + +## 最佳实践 + +1. **在 Model 中定义属性** - BindableProperty 主要用于 Model 层 +2. **使用只读接口暴露** - 防止外部随意修改 +3. **及时注销监听** - 使用 UnRegisterList 或 UnRegisterWhenNodeExitTree +4. **使用 RegisterWithInitValue** - UI 绑定时立即获取初始值 +5. **避免循环依赖** - 属性监听器中修改其他属性要小心 +6. **使用自定义比较器** - 对于浮点数等需要精度控制的属性 + +## 相关包 + +- [`model`](./model.md) - Model 中大量使用 BindableProperty +- [`events`](./events.md) - BindableProperty 基于事件系统实现 +- [`extensions`](./extensions.md) - 提供便捷的注销扩展方法 + +--- + +**许可证**: Apache 2.0 diff --git a/docs/zh-CN/core/query.md b/docs/zh-CN/core/query.md index 329e237..43b62a6 100644 --- a/docs/zh-CN/core/query.md +++ b/docs/zh-CN/core/query.md @@ -144,13 +144,15 @@ public class LoadPlayerDataQuery : AbstractAsyncQuery GameArchitecture.Interface; - public void OnReady() { _buyButton.Pressed += OnBuyButtonPressed; diff --git a/docs/zh-CN/core/state-machine.md b/docs/zh-CN/core/state-machine.md index 86d88a1..a757199 100644 --- a/docs/zh-CN/core/state-machine.md +++ b/docs/zh-CN/core/state-machine.md @@ -1,576 +1,582 @@ ---- -title: 状态机系统 -description: 状态机系统提供了灵活的状态管理机制,支持状态转换、历史记录和异步操作。 ---- - -# 状态机系统 - -## 概述 - -状态机系统是 GFramework 中用于管理游戏状态的核心组件。通过状态机,你可以清晰地定义游戏的各种状态(如菜单、游戏中、暂停、游戏结束等),以及状态之间的转换规则,使游戏逻辑更加结构化和易于维护。 - -状态机系统支持同步和异步状态操作,提供状态历史记录,并与架构系统深度集成,让你可以在状态中访问所有架构组件。 - -**主要特性**: - -- 类型安全的状态管理 -- 支持同步和异步状态 -- 状态转换验证 -- 状态历史记录和回退 -- 与架构系统集成 -- 线程安全操作 - -## 核心概念 - -### 状态接口 - -`IState` 定义了状态的基本行为: - -```csharp -public interface IState -{ - void OnEnter(IState? from); // 进入状态 - void OnExit(IState? to); // 退出状态 - bool CanTransitionTo(IState target); // 转换验证 -} -``` - -### 状态机 - -`IStateMachine` 管理状态的注册和切换: - -```csharp -public interface IStateMachine -{ - IState? Current { get; } // 当前状态 - IStateMachine Register(IState state); // 注册状态 - Task ChangeToAsync() where T : IState; // 切换状态 -} -``` - -### 状态机系统 - -`IStateMachineSystem` 结合了状态机和系统的能力: - -```csharp -public interface IStateMachineSystem : ISystem, IStateMachine -{ - // 继承 ISystem 和 IStateMachine 的所有功能 -} -``` - -## 基本用法 - -### 定义状态 - -继承 `ContextAwareStateBase` 创建状态: - -```csharp -using GFramework.Core.state; - -// 菜单状态 -public class MenuState : ContextAwareStateBase -{ - public override void OnEnter(IState? from) - { - Console.WriteLine("进入菜单"); - // 显示菜单 UI - } - - public override void OnExit(IState? to) - { - Console.WriteLine("退出菜单"); - // 隐藏菜单 UI - } -} - -// 游戏状态 -public class GameplayState : ContextAwareStateBase -{ - public override void OnEnter(IState? from) - { - Console.WriteLine("开始游戏"); - // 初始化游戏场景 - } - - public override void OnExit(IState? to) - { - Console.WriteLine("结束游戏"); - // 清理游戏场景 - } -} -``` - -### 注册和使用状态机 - -```csharp -using GFramework.Core.state; - -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 创建状态机系统 - var stateMachine = new StateMachineSystem(); - - // 注册状态 - stateMachine - .Register(new MenuState()) - .Register(new GameplayState()) - .Register(new PauseState()); - - // 注册到架构 - RegisterSystem(stateMachine); - } -} -``` - -### 切换状态 - -```csharp -public class GameController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public async Task StartGame() - { - var stateMachine = this.GetSystem(); - - // 切换到游戏状态 - var success = await stateMachine.ChangeToAsync(); - - if (success) - { - Console.WriteLine("成功进入游戏状态"); - } - } -} -``` - -## 高级用法 - -### 状态转换验证 - -控制状态之间的转换规则: - -```csharp -public class GameplayState : ContextAwareStateBase -{ - public override bool CanTransitionTo(IState target) - { - // 只能从游戏状态转换到暂停或游戏结束状态 - return target is PauseState or GameOverState; - } - - public override void OnEnter(IState? from) - { - Console.WriteLine($"从 {from?.GetType().Name ?? "初始"} 进入游戏"); - } -} - -public class PauseState : ContextAwareStateBase -{ - public override bool CanTransitionTo(IState target) - { - // 暂停状态只能返回游戏状态 - return target is GameplayState; - } -} -``` - -### 异步状态 - -处理需要异步操作的状态: - -```csharp -using GFramework.Core.Abstractions.state; - -public class LoadingState : AsyncContextAwareStateBase -{ - public override async Task OnEnterAsync(IState? from) - { - Console.WriteLine("开始加载..."); - - // 异步加载资源 - await LoadResourcesAsync(); - - Console.WriteLine("加载完成"); - - // 自动切换到下一个状态 - var stateMachine = this.GetSystem(); - await stateMachine.ChangeToAsync(); - } - - private async Task LoadResourcesAsync() - { - // 模拟异步加载 - await Task.Delay(2000); - } - - public override async Task OnExitAsync(IState? to) - { - Console.WriteLine("退出加载状态"); - await Task.CompletedTask; - } -} -``` - -### 状态历史和回退 - -```csharp -public class GameController : IController -{ - public async Task NavigateBack() - { - var stateMachine = this.GetSystem(); - - // 回退到上一个状态 - var success = await stateMachine.GoBackAsync(); - - if (success) - { - Console.WriteLine("已返回上一个状态"); - } - } - - public void ShowHistory() - { - var stateMachine = this.GetSystem(); - - // 获取状态历史 - var history = stateMachine.GetStateHistory(); - - Console.WriteLine("状态历史:"); - foreach (var state in history) - { - Console.WriteLine($"- {state.GetType().Name}"); - } - } -} -``` - -### 在状态中访问架构组件 - -```csharp -public class GameplayState : ContextAwareStateBase -{ - public override void OnEnter(IState? from) - { - // 访问 Model - var playerModel = this.GetModel(); - playerModel.Reset(); - - // 访问 System - var audioSystem = this.GetSystem(); - audioSystem.PlayBGM("gameplay"); - - // 发送事件 - this.SendEvent(new GameStartedEvent()); - } - - public override void OnExit(IState? to) - { - // 停止音乐 - var audioSystem = this.GetSystem(); - audioSystem.StopBGM(); - - // 发送事件 - this.SendEvent(new GameEndedEvent()); - } -} -``` - -### 状态数据传递 - -```csharp -// 定义带数据的状态 -public class GameplayState : ContextAwareStateBase -{ - public int Level { get; set; } - public string Difficulty { get; set; } = "Normal"; - - public override void OnEnter(IState? from) - { - Console.WriteLine($"开始关卡 {Level},难度: {Difficulty}"); - } -} - -// 切换状态并设置数据 -public async Task StartLevel(int level, string difficulty) -{ - var stateMachine = this.GetSystem(); - - // 获取状态实例并设置数据 - var gameplayState = stateMachine.GetState(); - if (gameplayState != null) - { - gameplayState.Level = level; - gameplayState.Difficulty = difficulty; - } - - // 切换状态 - await stateMachine.ChangeToAsync(); -} -``` - -### 状态事件通知 - -```csharp -// 定义状态变更事件 -public class StateChangedEvent -{ - public IState? From { get; set; } - public IState To { get; set; } -} - -// 自定义状态机系统 -public class CustomStateMachineSystem : StateMachineSystem -{ - protected override async Task OnStateChangedAsync(IState? from, IState to) - { - // 发送状态变更事件 - this.SendEvent(new StateChangedEvent - { - From = from, - To = to - }); - - await base.OnStateChangedAsync(from, to); - } -} -``` - -### 条件状态转换 - -```csharp -public class BattleState : ContextAwareStateBase -{ - public override bool CanTransitionTo(IState target) - { - // 战斗中不能直接退出,必须先结束战斗 - if (target is MenuState) - { - var battleModel = this.GetModel(); - return battleModel.IsBattleEnded; - } - - return true; - } -} - -// 尝试切换状态 -public async Task TryExitBattle() -{ - var stateMachine = this.GetSystem(); - - // 检查是否可以切换 - var canChange = await stateMachine.CanChangeToAsync(); - - if (canChange) - { - await stateMachine.ChangeToAsync(); - } - else - { - Console.WriteLine("战斗尚未结束,无法退出"); - } -} -``` - -## 最佳实践 - -1. **使用基类创建状态**:继承 `ContextAwareStateBase` 或 `AsyncContextAwareStateBase` - ```csharp - ✓ public class MyState : ContextAwareStateBase { } - ✗ public class MyState : IState { } // 需要手动实现所有接口 - ``` - -2. **在 OnEnter 中初始化,在 OnExit 中清理**:保持状态的独立性 - ```csharp - public override void OnEnter(IState? from) - { - // 初始化状态相关资源 - LoadUI(); - StartBackgroundMusic(); - } - - public override void OnExit(IState? to) - { - // 清理状态相关资源 - UnloadUI(); - StopBackgroundMusic(); - } - ``` - -3. **使用转换验证控制状态流**:避免非法状态转换 - ```csharp - public override bool CanTransitionTo(IState target) - { - // 定义明确的转换规则 - return target is AllowedState1 or AllowedState2; - } - ``` - -4. **异步操作使用异步状态**:避免阻塞主线程 - ```csharp - ✓ public class LoadingState : AsyncContextAwareStateBase - { - public override async Task OnEnterAsync(IState? from) - { - await LoadDataAsync(); - } - } - - ✗ public class LoadingState : ContextAwareStateBase - { - public override void OnEnter(IState? from) - { - LoadDataAsync().Wait(); // 阻塞主线程 - } - } - ``` - -5. **合理使用状态历史**:避免历史记录过大 - ```csharp - // 创建状态机时设置历史大小 - var stateMachine = new StateMachineSystem(maxHistorySize: 10); - ``` - -6. **状态保持单一职责**:每个状态只负责一个场景或功能 - ```csharp - ✓ MenuState, GameplayState, PauseState // 职责清晰 - ✗ GameState // 职责不明确,包含太多逻辑 - ``` - -## 常见问题 - -### 问题:状态切换失败怎么办? - -**解答**: -`ChangeToAsync` 返回 `false` 表示切换失败,通常是因为 `CanTransitionTo` 返回 `false`: - -```csharp -var success = await stateMachine.ChangeToAsync(); -if (!success) -{ - Console.WriteLine("状态切换被拒绝"); - // 检查转换规则 -} -``` - -### 问题:如何在状态之间传递数据? - -**解答**: -有几种方式: - -1. **通过状态属性**: - -```csharp -var state = stateMachine.GetState(); -state.Level = 5; -await stateMachine.ChangeToAsync(); -``` - -2. **通过 Model**: - -```csharp -// 在切换前设置 Model -var gameModel = this.GetModel(); -gameModel.CurrentLevel = 5; - -// 在状态中读取 -public override void OnEnter(IState? from) -{ - var gameModel = this.GetModel(); - var level = gameModel.CurrentLevel; -} -``` - -3. **通过事件**: - -```csharp -this.SendEvent(new LevelSelectedEvent { Level = 5 }); -await stateMachine.ChangeToAsync(); -``` - -### 问题:状态机系统和普通状态机有什么区别? - -**解答**: - -- **StateMachine**:纯状态机,不依赖架构 -- **StateMachineSystem**:集成到架构中,状态可以访问所有架构组件 - -```csharp -// 使用 StateMachineSystem(推荐) -RegisterSystem(new StateMachineSystem()); - -// 使用 StateMachine(独立使用) -var stateMachine = new StateMachine(); -``` - -### 问题:如何处理状态切换动画? - -**解答**: -在 `OnExit` 和 `OnEnter` 中使用协程: - -```csharp -public class MenuState : AsyncContextAwareStateBase -{ - public override async Task OnExitAsync(IState? to) - { - // 播放淡出动画 - await PlayFadeOutAnimation(); - } -} - -public class GameplayState : AsyncContextAwareStateBase -{ - public override async Task OnEnterAsync(IState? from) - { - // 播放淡入动画 - await PlayFadeInAnimation(); - } -} -``` - -### 问题:可以在状态中切换到其他状态吗? - -**解答**: -可以,但要注意避免递归切换: - -```csharp -public override async void OnEnter(IState? from) -{ - // 检查条件后自动切换 - if (ShouldSkip()) - { - var stateMachine = this.GetSystem(); - await stateMachine.ChangeToAsync(); - } -} -``` - -### 问题:状态机是线程安全的吗? - -**解答**: -是的,状态机的所有操作都是线程安全的,使用了内部锁机制。 - -### 问题:如何实现状态栈(多层状态)? - -**解答**: -使用状态历史功能: - -```csharp -// 进入子状态 -await stateMachine.ChangeToAsync(); - -// 返回上一层 -await stateMachine.GoBackAsync(); -``` - -## 相关文档 - -- [生命周期管理](/zh-CN/core/lifecycle) - 状态的初始化和销毁 -- [事件系统](/zh-CN/core/events) - 状态变更通知 -- [协程系统](/zh-CN/core/coroutine) - 异步状态操作 -- [状态机实现教程](/zh-CN/tutorials/state-machine-tutorial) - 完整示例 +--- +title: 状态机系统 +description: 状态机系统提供了灵活的状态管理机制,支持状态转换、历史记录和异步操作。 +--- + +# 状态机系统 + +## 概述 + +状态机系统是 GFramework 中用于管理游戏状态的核心组件。通过状态机,你可以清晰地定义游戏的各种状态(如菜单、游戏中、暂停、游戏结束等),以及状态之间的转换规则,使游戏逻辑更加结构化和易于维护。 + +状态机系统支持同步和异步状态操作,提供状态历史记录,并与架构系统深度集成,让你可以在状态中访问所有架构组件。 + +**主要特性**: + +- 类型安全的状态管理 +- 支持同步和异步状态 +- 状态转换验证 +- 状态历史记录和回退 +- 与架构系统集成 +- 线程安全操作 + +## 核心概念 + +### 状态接口 + +`IState` 定义了状态的基本行为: + +```csharp +public interface IState +{ + void OnEnter(IState? from); // 进入状态 + void OnExit(IState? to); // 退出状态 + bool CanTransitionTo(IState target); // 转换验证 +} +``` + +### 状态机 + +`IStateMachine` 管理状态的注册和切换: + +```csharp +public interface IStateMachine +{ + IState? Current { get; } // 当前状态 + IStateMachine Register(IState state); // 注册状态 + Task ChangeToAsync() where T : IState; // 切换状态 +} +``` + +### 状态机系统 + +`IStateMachineSystem` 结合了状态机和系统的能力: + +```csharp +public interface IStateMachineSystem : ISystem, IStateMachine +{ + // 继承 ISystem 和 IStateMachine 的所有功能 +} +``` + +## 基本用法 + +### 定义状态 + +继承 `ContextAwareStateBase` 创建状态: + +```csharp +using GFramework.Core.state; + +// 菜单状态 +public class MenuState : ContextAwareStateBase +{ + public override void OnEnter(IState? from) + { + Console.WriteLine("进入菜单"); + // 显示菜单 UI + } + + public override void OnExit(IState? to) + { + Console.WriteLine("退出菜单"); + // 隐藏菜单 UI + } +} + +// 游戏状态 +public class GameplayState : ContextAwareStateBase +{ + public override void OnEnter(IState? from) + { + Console.WriteLine("开始游戏"); + // 初始化游戏场景 + } + + public override void OnExit(IState? to) + { + Console.WriteLine("结束游戏"); + // 清理游戏场景 + } +} +``` + +### 注册和使用状态机 + +```csharp +using GFramework.Core.state; + +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 创建状态机系统 + var stateMachine = new StateMachineSystem(); + + // 注册状态 + stateMachine + .Register(new MenuState()) + .Register(new GameplayState()) + .Register(new PauseState()); + + // 注册到架构 + RegisterSystem(stateMachine); + } +} +``` + +### 切换状态 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameController : IController +{ + public async Task StartGame() + { + var stateMachine = this.GetSystem(); + + // 切换到游戏状态 + var success = await stateMachine.ChangeToAsync(); + + if (success) + { + Console.WriteLine("成功进入游戏状态"); + } + } +} +``` + +## 高级用法 + +### 状态转换验证 + +控制状态之间的转换规则: + +```csharp +public class GameplayState : ContextAwareStateBase +{ + public override bool CanTransitionTo(IState target) + { + // 只能从游戏状态转换到暂停或游戏结束状态 + return target is PauseState or GameOverState; + } + + public override void OnEnter(IState? from) + { + Console.WriteLine($"从 {from?.GetType().Name ?? "初始"} 进入游戏"); + } +} + +public class PauseState : ContextAwareStateBase +{ + public override bool CanTransitionTo(IState target) + { + // 暂停状态只能返回游戏状态 + return target is GameplayState; + } +} +``` + +### 异步状态 + +处理需要异步操作的状态: + +```csharp +using GFramework.Core.Abstractions.state; + +public class LoadingState : AsyncContextAwareStateBase +{ + public override async Task OnEnterAsync(IState? from) + { + Console.WriteLine("开始加载..."); + + // 异步加载资源 + await LoadResourcesAsync(); + + Console.WriteLine("加载完成"); + + // 自动切换到下一个状态 + var stateMachine = this.GetSystem(); + await stateMachine.ChangeToAsync(); + } + + private async Task LoadResourcesAsync() + { + // 模拟异步加载 + await Task.Delay(2000); + } + + public override async Task OnExitAsync(IState? to) + { + Console.WriteLine("退出加载状态"); + await Task.CompletedTask; + } +} +``` + +### 状态历史和回退 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameController : IController +{ + public async Task NavigateBack() + { + var stateMachine = this.GetSystem(); + + // 回退到上一个状态 + var success = await stateMachine.GoBackAsync(); + + if (success) + { + Console.WriteLine("已返回上一个状态"); + } + } + + public void ShowHistory() + { + var stateMachine = this.GetSystem(); + + // 获取状态历史 + var history = stateMachine.GetStateHistory(); + + Console.WriteLine("状态历史:"); + foreach (var state in history) + { + Console.WriteLine($"- {state.GetType().Name}"); + } + } +} +``` + +### 在状态中访问架构组件 + +```csharp +public class GameplayState : ContextAwareStateBase +{ + public override void OnEnter(IState? from) + { + // 访问 Model + var playerModel = this.GetModel(); + playerModel.Reset(); + + // 访问 System + var audioSystem = this.GetSystem(); + audioSystem.PlayBGM("gameplay"); + + // 发送事件 + this.SendEvent(new GameStartedEvent()); + } + + public override void OnExit(IState? to) + { + // 停止音乐 + var audioSystem = this.GetSystem(); + audioSystem.StopBGM(); + + // 发送事件 + this.SendEvent(new GameEndedEvent()); + } +} +``` + +### 状态数据传递 + +```csharp +// 定义带数据的状态 +public class GameplayState : ContextAwareStateBase +{ + public int Level { get; set; } + public string Difficulty { get; set; } = "Normal"; + + public override void OnEnter(IState? from) + { + Console.WriteLine($"开始关卡 {Level},难度: {Difficulty}"); + } +} + +// 切换状态并设置数据 +public async Task StartLevel(int level, string difficulty) +{ + var stateMachine = this.GetSystem(); + + // 获取状态实例并设置数据 + var gameplayState = stateMachine.GetState(); + if (gameplayState != null) + { + gameplayState.Level = level; + gameplayState.Difficulty = difficulty; + } + + // 切换状态 + await stateMachine.ChangeToAsync(); +} +``` + +### 状态事件通知 + +```csharp +// 定义状态变更事件 +public class StateChangedEvent +{ + public IState? From { get; set; } + public IState To { get; set; } +} + +// 自定义状态机系统 +public class CustomStateMachineSystem : StateMachineSystem +{ + protected override async Task OnStateChangedAsync(IState? from, IState to) + { + // 发送状态变更事件 + this.SendEvent(new StateChangedEvent + { + From = from, + To = to + }); + + await base.OnStateChangedAsync(from, to); + } +} +``` + +### 条件状态转换 + +```csharp +public class BattleState : ContextAwareStateBase +{ + public override bool CanTransitionTo(IState target) + { + // 战斗中不能直接退出,必须先结束战斗 + if (target is MenuState) + { + var battleModel = this.GetModel(); + return battleModel.IsBattleEnded; + } + + return true; + } +} + +// 尝试切换状态 +public async Task TryExitBattle() +{ + var stateMachine = this.GetSystem(); + + // 检查是否可以切换 + var canChange = await stateMachine.CanChangeToAsync(); + + if (canChange) + { + await stateMachine.ChangeToAsync(); + } + else + { + Console.WriteLine("战斗尚未结束,无法退出"); + } +} +``` + +## 最佳实践 + +1. **使用基类创建状态**:继承 `ContextAwareStateBase` 或 `AsyncContextAwareStateBase` + ```csharp + ✓ public class MyState : ContextAwareStateBase { } + ✗ public class MyState : IState { } // 需要手动实现所有接口 + ``` + +2. **在 OnEnter 中初始化,在 OnExit 中清理**:保持状态的独立性 + ```csharp + public override void OnEnter(IState? from) + { + // 初始化状态相关资源 + LoadUI(); + StartBackgroundMusic(); + } + + public override void OnExit(IState? to) + { + // 清理状态相关资源 + UnloadUI(); + StopBackgroundMusic(); + } + ``` + +3. **使用转换验证控制状态流**:避免非法状态转换 + ```csharp + public override bool CanTransitionTo(IState target) + { + // 定义明确的转换规则 + return target is AllowedState1 or AllowedState2; + } + ``` + +4. **异步操作使用异步状态**:避免阻塞主线程 + ```csharp + ✓ public class LoadingState : AsyncContextAwareStateBase + { + public override async Task OnEnterAsync(IState? from) + { + await LoadDataAsync(); + } + } + + ✗ public class LoadingState : ContextAwareStateBase + { + public override void OnEnter(IState? from) + { + LoadDataAsync().Wait(); // 阻塞主线程 + } + } + ``` + +5. **合理使用状态历史**:避免历史记录过大 + ```csharp + // 创建状态机时设置历史大小 + var stateMachine = new StateMachineSystem(maxHistorySize: 10); + ``` + +6. **状态保持单一职责**:每个状态只负责一个场景或功能 + ```csharp + ✓ MenuState, GameplayState, PauseState // 职责清晰 + ✗ GameState // 职责不明确,包含太多逻辑 + ``` + +## 常见问题 + +### 问题:状态切换失败怎么办? + +**解答**: +`ChangeToAsync` 返回 `false` 表示切换失败,通常是因为 `CanTransitionTo` 返回 `false`: + +```csharp +var success = await stateMachine.ChangeToAsync(); +if (!success) +{ + Console.WriteLine("状态切换被拒绝"); + // 检查转换规则 +} +``` + +### 问题:如何在状态之间传递数据? + +**解答**: +有几种方式: + +1. **通过状态属性**: + +```csharp +var state = stateMachine.GetState(); +state.Level = 5; +await stateMachine.ChangeToAsync(); +``` + +2. **通过 Model**: + +```csharp +// 在切换前设置 Model +var gameModel = this.GetModel(); +gameModel.CurrentLevel = 5; + +// 在状态中读取 +public override void OnEnter(IState? from) +{ + var gameModel = this.GetModel(); + var level = gameModel.CurrentLevel; +} +``` + +3. **通过事件**: + +```csharp +this.SendEvent(new LevelSelectedEvent { Level = 5 }); +await stateMachine.ChangeToAsync(); +``` + +### 问题:状态机系统和普通状态机有什么区别? + +**解答**: + +- **StateMachine**:纯状态机,不依赖架构 +- **StateMachineSystem**:集成到架构中,状态可以访问所有架构组件 + +```csharp +// 使用 StateMachineSystem(推荐) +RegisterSystem(new StateMachineSystem()); + +// 使用 StateMachine(独立使用) +var stateMachine = new StateMachine(); +``` + +### 问题:如何处理状态切换动画? + +**解答**: +在 `OnExit` 和 `OnEnter` 中使用协程: + +```csharp +public class MenuState : AsyncContextAwareStateBase +{ + public override async Task OnExitAsync(IState? to) + { + // 播放淡出动画 + await PlayFadeOutAnimation(); + } +} + +public class GameplayState : AsyncContextAwareStateBase +{ + public override async Task OnEnterAsync(IState? from) + { + // 播放淡入动画 + await PlayFadeInAnimation(); + } +} +``` + +### 问题:可以在状态中切换到其他状态吗? + +**解答**: +可以,但要注意避免递归切换: + +```csharp +public override async void OnEnter(IState? from) +{ + // 检查条件后自动切换 + if (ShouldSkip()) + { + var stateMachine = this.GetSystem(); + await stateMachine.ChangeToAsync(); + } +} +``` + +### 问题:状态机是线程安全的吗? + +**解答**: +是的,状态机的所有操作都是线程安全的,使用了内部锁机制。 + +### 问题:如何实现状态栈(多层状态)? + +**解答**: +使用状态历史功能: + +```csharp +// 进入子状态 +await stateMachine.ChangeToAsync(); + +// 返回上一层 +await stateMachine.GoBackAsync(); +``` + +## 相关文档 + +- [生命周期管理](/zh-CN/core/lifecycle) - 状态的初始化和销毁 +- [事件系统](/zh-CN/core/events) - 状态变更通知 +- [协程系统](/zh-CN/core/coroutine) - 异步状态操作 +- [状态机实现教程](/zh-CN/tutorials/state-machine-tutorial) - 完整示例 diff --git a/docs/zh-CN/core/system.md b/docs/zh-CN/core/system.md index 2b14135..eb93282 100644 --- a/docs/zh-CN/core/system.md +++ b/docs/zh-CN/core/system.md @@ -1,661 +1,663 @@ -# System 包使用说明 - -## 概述 - -System 包定义了业务逻辑层(Business Logic Layer)。System 负责处理游戏的核心业务逻辑,协调 Model 之间的交互,响应事件并执行复杂的业务流程。 - -System 是 GFramework 架构中业务逻辑的核心组件,通过事件系统与 Model 和 Controller 进行通信。 - -## 核心接口 - -### ICanGetSystem - -标记接口,表示该类型可以获取其他 System。 - -**继承关系:** - -```csharp -public interface ICanGetSystem : IBelongToArchitecture -``` - -### ISystem - -System 接口,定义了系统的基本行为。 - -**核心成员:** - -```csharp -void Init(); // 系统初始化方法 -void Destroy(); // 系统销毁方法 -void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件 -``` - -**继承的能力:** - -- `IContextAware` - 上下文感知 -- `IInitializable` - 可初始化 -- `IDisposable` - 可销毁 -- `IArchitecturePhaseAware` - 架构阶段感知 -- `ICanGetModel` - 可获取 Model -- `ICanGetUtility` - 可获取 Utility -- `ICanGetSystem` - 可获取其他 System -- `ICanRegisterEvent` - 可注册事件 -- `ICanSendEvent` - 可发送事件 - -## 核心类 - -### AbstractSystem - -抽象 System 基类,提供了 System 的基础实现。继承自 ContextAwareBase,具有上下文感知能力。 - -**核心方法:** - -```csharp -void IInitializable.Init(); // 实现初始化接口 -void IDisposable.Destroy(); // 实现销毁接口 -protected abstract void OnInit(); // 抽象初始化方法,由子类实现 -protected virtual void OnDestroy(); // 虚拟销毁方法,子类可重写 -public virtual void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件 -``` - -**使用方式:** - -```csharp -public abstract class AbstractSystem : ContextAwareBase, ISystem -{ - void IInitializable.Init() => OnInit(); // 系统初始化,内部调用抽象方法 OnInit() - void IDisposable.Destroy() => OnDestroy(); // 系统销毁,内部调用 OnDestroy() - protected abstract void OnInit(); // 子类实现初始化逻辑 - protected virtual void OnDestroy(); // 子类可选择重写销毁逻辑 - public virtual void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件 -} -``` - -## System 生命周期 - -System 的生命周期由架构管理: - -1. **注册阶段**:通过 `architecture.RegisterSystem()` 注册到架构 -2. **初始化阶段**:架构调用 `Init()` 方法,内部执行 `OnInit()` -3. **运行阶段**:处理事件和执行业务逻辑 -4. **销毁阶段**:架构调用 `Destroy()` 方法,内部执行 `OnDestroy()` - -## 基本使用 - -### 1. 定义 System - -```csharp -// 战斗系统 -public class CombatSystem : AbstractSystem -{ - private ILogger _logger; - - protected override void OnInit() - { - _logger = this.GetUtility(); - _logger.Info("CombatSystem initialized"); - - // 注册事件监听 - this.RegisterEvent(OnEnemyAttack); - this.RegisterEvent(OnPlayerAttack); - } - - private void OnEnemyAttack(EnemyAttackEvent e) - { - var playerModel = this.GetModel(); - - // 计算伤害 - int damage = CalculateDamage(e.AttackPower, playerModel.Defense.Value); - - // 应用伤害 - playerModel.Health.Value -= damage; - - // 发送伤害事件 - this.SendEvent(new PlayerTookDamageEvent { Damage = damage }); - - _logger.Debug($"Player took {damage} damage from enemy attack"); - } - - private void OnPlayerAttack(PlayerAttackEvent e) - { - var playerModel = this.GetModel(); - var enemyModel = this.GetModel(); - - int damage = CalculateDamage(playerModel.AttackPower.Value, e.Enemy.Defense); - e.Enemy.Health -= damage; - - this.SendEvent(new EnemyTookDamageEvent - { - EnemyId = e.Enemy.Id, - Damage = damage - }); - - _logger.Debug($"Enemy {e.Enemy.Id} took {damage} damage from player attack"); - } - - private int CalculateDamage(int attackPower, int defense) - { - return Math.Max(1, attackPower - defense / 2); - } - - protected override void OnDestroy() - { - _logger.Info("CombatSystem destroyed"); - // 清理资源 - base.OnDestroy(); - } - - public override void OnArchitecturePhase(ArchitecturePhase phase) - { - switch (phase) - { - case ArchitecturePhase.AfterSystemInit: - _logger.Info("CombatSystem is ready"); - break; - case ArchitecturePhase.Destroying: - _logger.Info("CombatSystem is shutting down"); - break; - } - } -} -``` - -### 2. 注册 System - -```csharp -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册 Model - this.RegisterModel(new PlayerModel()); - this.RegisterModel(new EnemyModel()); - this.RegisterModel(new InventoryModel()); - - // 注册 System(系统注册后会自动调用 Init) - this.RegisterSystem(new CombatSystem()); - this.RegisterSystem(new InventorySystem()); - this.RegisterSystem(new QuestSystem()); - this.RegisterSystem(new UISystem()); - } -} -``` - -### 3. 在其他组件中获取 System - -```csharp -// 在 Controller 中 -public class GameController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void Start() - { - // 获取 System - var combatSystem = this.GetSystem(); - var questSystem = this.GetSystem(); - - // 使用 System - combatSystem.StartBattle(); - } -} - -// 在 Command 中 -public class StartBattleCommand : AbstractCommand -{ - protected override void OnExecute() - { - var combatSystem = this.GetSystem(); - combatSystem.StartBattle(); - } -} - -// 在其他 System 中 -public class AISystem : AbstractSystem -{ - protected override void OnInit() - { - var combatSystem = this.GetSystem(); - // 与其他 System 协作 - } -} -``` - -## 常见使用模式 - -### 异步 System - -System 支持异步初始化,通过实现 `IAsyncInitializable` 接口可以在初始化时执行异步操作。 - -```csharp -// 异步系统示例 -public class DataLoadSystem : AbstractSystem, IAsyncInitializable -{ - private GameData _gameData; - - protected override void OnInit() - { - // 同步初始化逻辑 - this.RegisterEvent(OnGameStarted); - } - - public async Task InitializeAsync() - { - // 异步加载游戏数据 - var storage = this.GetUtility(); - _gameData = await storage.LoadGameDataAsync(); - - // 数据加载完成后发送事件 - this.SendEvent(new GameDataLoadedEvent { Data = _gameData }); - } - - private void OnGameStarted(GameStartedEvent e) - { - // 使用已加载的数据 - Console.WriteLine($"Game data loaded: {_gameData.Version}"); - } - - protected override void OnDestroy() - { - // 清理资源 - _gameData = null; - } -} - -// 在架构中使用异步 System -public class GameArchitecture : Architecture -{ - protected override void Init() - { - RegisterModel(new PlayerModel()); - RegisterSystem(new DataLoadSystem()); - RegisterSystem(new CombatSystem()); - } -} - -// 异步初始化架构 -var architecture = new GameArchitecture(); -await architecture.InitializeAsync(); -``` - -### 1. 事件驱动的 System - -``` -public class InventorySystem : AbstractSystem -{ - protected override void OnInit() - { - // 监听物品相关事件 - this.RegisterEvent(OnItemAdded); - this.RegisterEvent(OnItemRemoved); - this.RegisterEvent(OnItemUsed); - } - - private void OnItemAdded(ItemAddedEvent e) - { - var inventoryModel = this.GetModel(); - - // 添加物品 - inventoryModel.AddItem(e.ItemId, e.Count); - - // 检查成就 - CheckAchievements(e.ItemId); - - // 发送通知 - this.SendEvent(new ShowNotificationEvent - { - Message = $"获得物品: {e.ItemId} x{e.Count}" - }); - } - - private void OnItemUsed(ItemUsedEvent e) - { - var inventoryModel = this.GetModel(); - var playerModel = this.GetModel(); - - if (inventoryModel.HasItem(e.ItemId)) - { - // 应用物品效果 - ApplyItemEffect(e.ItemId, playerModel); - - // 移除物品 - inventoryModel.RemoveItem(e.ItemId, 1); - - this.SendEvent(new ItemEffectAppliedEvent { ItemId = e.ItemId }); - } - } - - private void ApplyItemEffect(string itemId, PlayerModel player) - { - // 物品效果逻辑... - if (itemId == "health_potion") - { - player.Health.Value = Math.Min( - player.Health.Value + 50, - player.MaxHealth.Value - ); - } - } - - private void CheckAchievements(string itemId) - { - // 成就检查逻辑... - } -} -``` - -### 2. 定时更新的 System - -``` -public class BuffSystem : AbstractSystem -{ - private List _activeBuffs = new(); - - protected override void OnInit() - { - this.RegisterEvent(OnBuffApplied); - this.RegisterEvent(OnUpdate); - } - - private void OnBuffApplied(BuffAppliedEvent e) - { - _activeBuffs.Add(new BuffData - { - BuffId = e.BuffId, - Duration = e.Duration, - RemainingTime = e.Duration - }); - - ApplyBuffEffect(e.BuffId, true); - } - - private void OnUpdate(GameUpdateEvent e) - { - // 更新所有 Buff - for (int i = _activeBuffs.Count - 1; i >= 0; i--) - { - var buff = _activeBuffs[i]; - buff.RemainingTime -= e.DeltaTime; - - if (buff.RemainingTime <= 0) - { - // Buff 过期 - ApplyBuffEffect(buff.BuffId, false); - _activeBuffs.RemoveAt(i); - - this.SendEvent(new BuffExpiredEvent { BuffId = buff.BuffId }); - } - } - } - - private void ApplyBuffEffect(string buffId, bool apply) - { - var playerModel = this.GetModel(); - // 应用或移除 Buff 效果... - } -} -``` - -### 3. 跨 System 协作 - -``` -public class QuestSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnEnemyKilled); - this.RegisterEvent(OnItemCollected); - } - - private void OnEnemyKilled(EnemyKilledEvent e) - { - var questModel = this.GetModel(); - var activeQuests = questModel.GetActiveQuests(); - - foreach (var quest in activeQuests) - { - if (quest.Type == QuestType.KillEnemy && quest.TargetId == e.EnemyType) - { - quest.Progress++; - - if (quest.Progress >= quest.RequiredAmount) - { - // 任务完成 - CompleteQuest(quest.Id); - } - } - } - } - - private void CompleteQuest(string questId) - { - var questModel = this.GetModel(); - var quest = questModel.GetQuest(questId); - - // 标记任务完成 - questModel.CompleteQuest(questId); - - // 发放奖励(通过其他 System) - this.SendEvent(new QuestCompletedEvent - { - QuestId = questId, - Rewards = quest.Rewards - }); - } -} - -public class RewardSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnQuestCompleted); - } - - private void OnQuestCompleted(QuestCompletedEvent e) - { - var playerModel = this.GetModel(); - - // 发放奖励 - foreach (var reward in e.Rewards) - { - switch (reward.Type) - { - case RewardType.Gold: - playerModel.Gold.Value += reward.Amount; - break; - case RewardType.Experience: - playerModel.Experience.Value += reward.Amount; - break; - case RewardType.Item: - this.SendEvent(new ItemAddedEvent - { - ItemId = reward.ItemId, - Count = reward.Amount - }); - break; - } - } - - this.SendEvent(new RewardsGrantedEvent { Rewards = e.Rewards }); - } -} -``` - -### 4. 管理复杂状态机 - -``` -public class GameStateSystem : AbstractSystem -{ - private GameState _currentState = GameState.MainMenu; - - protected override void OnInit() - { - this.RegisterEvent(OnStateChangeRequest); - } - - private void OnStateChangeRequest(GameStateChangeRequestEvent e) - { - if (CanTransition(_currentState, e.TargetState)) - { - ExitState(_currentState); - _currentState = e.TargetState; - EnterState(_currentState); - - this.SendEvent(new GameStateChangedEvent - { - PreviousState = _currentState, - NewState = e.TargetState - }); - } - } - - private bool CanTransition(GameState from, GameState to) - { - // 状态转换规则 - return (from, to) switch - { - (GameState.MainMenu, GameState.Playing) => true, - (GameState.Playing, GameState.Paused) => true, - (GameState.Paused, GameState.Playing) => true, - (GameState.Playing, GameState.GameOver) => true, - _ => false - }; - } - - private void EnterState(GameState state) - { - switch (state) - { - case GameState.Playing: - // 开始游戏 - this.SendCommand(new StartGameCommand()); - break; - case GameState.Paused: - // 暂停游戏 - this.SendEvent(new GamePausedEvent()); - break; - case GameState.GameOver: - // 游戏结束 - this.SendCommand(new GameOverCommand()); - break; - } - } - - private void ExitState(GameState state) - { - // 清理当前状态 - } -} -``` - -## System vs Model - -### Model(数据层) - -- **职责**:存储数据和状态 -- **特点**:被动,等待修改 -- **示例**:PlayerModel、InventoryModel - -### System(逻辑层) - -- **职责**:处理业务逻辑,协调 Model -- **特点**:主动,响应事件 -- **示例**:CombatSystem、QuestSystem - -``` -// ✅ 正确的职责划分 - -// Model: 存储数据 -public class PlayerModel : AbstractModel -{ - public BindableProperty Health { get; } = new(100); - public BindableProperty Mana { get; } = new(50); - - protected override void OnInit() { } -} - -// System: 处理逻辑 -public class CombatSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnAttack); - } - - private void OnAttack(AttackEvent e) - { - var playerModel = this.GetModel(); - - // System 负责计算和决策 - int damage = CalculateDamage(e); - playerModel.Health.Value -= damage; - - if (playerModel.Health.Value <= 0) - { - this.SendEvent(new PlayerDiedEvent()); - } - } -} -``` - -## 最佳实践 - -1. **单一职责** - 每个 System 专注于一个业务领域 -2. **事件驱动** - 通过事件与其他组件通信 -3. **无状态或少状态** - 优先将状态存储在 Model 中 -4. **可组合** - System 之间通过事件松耦合协作 -5. **初始化注册** - 在 `OnInit` 中注册所有事件监听 - -## 性能优化 - -### 1. 避免频繁的 GetModel/GetSystem - -``` -// ❌ 不好:每次都获取 -private void OnUpdate(GameUpdateEvent e) -{ - var model = this.GetModel(); // 频繁调用 - // ... -} - -// ✅ 好:缓存引用 -private PlayerModel _playerModel; - -protected override void OnInit() -{ - _playerModel = this.GetModel(); // 只获取一次 -} - -private void OnUpdate(GameUpdateEvent e) -{ - // 直接使用缓存的引用 - _playerModel.Health.Value += 1; -} -``` - -### 2. 批量处理 - -``` -public class ParticleSystem : AbstractSystem -{ - private List _particles = new(); - - private void OnUpdate(GameUpdateEvent e) - { - // 批量更新,而不是每个粒子发一个事件 - for (int i = _particles.Count - 1; i >= 0; i--) - { - UpdateParticle(_particles[i], e.DeltaTime); - } - } -} -``` - -## 相关包 - -- [`model`](./model.md) - System 操作 Model 的数据 -- [`events`](./events.md) - System 通过事件通信 -- [`command`](./command.md) - System 中可以发送 Command -- [`query`](./query.md) - System 中可以发送 Query -- [`utility`](./utility.md) - System 可以使用 Utility +# System 包使用说明 + +## 概述 + +System 包定义了业务逻辑层(Business Logic Layer)。System 负责处理游戏的核心业务逻辑,协调 Model 之间的交互,响应事件并执行复杂的业务流程。 + +System 是 GFramework 架构中业务逻辑的核心组件,通过事件系统与 Model 和 Controller 进行通信。 + +## 核心接口 + +### ICanGetSystem + +标记接口,表示该类型可以获取其他 System。 + +**继承关系:** + +```csharp +public interface ICanGetSystem : IBelongToArchitecture +``` + +### ISystem + +System 接口,定义了系统的基本行为。 + +**核心成员:** + +```csharp +void Init(); // 系统初始化方法 +void Destroy(); // 系统销毁方法 +void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件 +``` + +**继承的能力:** + +- `IContextAware` - 上下文感知 +- `IInitializable` - 可初始化 +- `IDisposable` - 可销毁 +- `IArchitecturePhaseAware` - 架构阶段感知 +- `ICanGetModel` - 可获取 Model +- `ICanGetUtility` - 可获取 Utility +- `ICanGetSystem` - 可获取其他 System +- `ICanRegisterEvent` - 可注册事件 +- `ICanSendEvent` - 可发送事件 + +## 核心类 + +### AbstractSystem + +抽象 System 基类,提供了 System 的基础实现。继承自 ContextAwareBase,具有上下文感知能力。 + +**核心方法:** + +```csharp +void IInitializable.Init(); // 实现初始化接口 +void IDisposable.Destroy(); // 实现销毁接口 +protected abstract void OnInit(); // 抽象初始化方法,由子类实现 +protected virtual void OnDestroy(); // 虚拟销毁方法,子类可重写 +public virtual void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件 +``` + +**使用方式:** + +```csharp +public abstract class AbstractSystem : ContextAwareBase, ISystem +{ + void IInitializable.Init() => OnInit(); // 系统初始化,内部调用抽象方法 OnInit() + void IDisposable.Destroy() => OnDestroy(); // 系统销毁,内部调用 OnDestroy() + protected abstract void OnInit(); // 子类实现初始化逻辑 + protected virtual void OnDestroy(); // 子类可选择重写销毁逻辑 + public virtual void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件 +} +``` + +## System 生命周期 + +System 的生命周期由架构管理: + +1. **注册阶段**:通过 `architecture.RegisterSystem()` 注册到架构 +2. **初始化阶段**:架构调用 `Init()` 方法,内部执行 `OnInit()` +3. **运行阶段**:处理事件和执行业务逻辑 +4. **销毁阶段**:架构调用 `Destroy()` 方法,内部执行 `OnDestroy()` + +## 基本使用 + +### 1. 定义 System + +```csharp +// 战斗系统 +public class CombatSystem : AbstractSystem +{ + private ILogger _logger; + + protected override void OnInit() + { + _logger = this.GetUtility(); + _logger.Info("CombatSystem initialized"); + + // 注册事件监听 + this.RegisterEvent(OnEnemyAttack); + this.RegisterEvent(OnPlayerAttack); + } + + private void OnEnemyAttack(EnemyAttackEvent e) + { + var playerModel = this.GetModel(); + + // 计算伤害 + int damage = CalculateDamage(e.AttackPower, playerModel.Defense.Value); + + // 应用伤害 + playerModel.Health.Value -= damage; + + // 发送伤害事件 + this.SendEvent(new PlayerTookDamageEvent { Damage = damage }); + + _logger.Debug($"Player took {damage} damage from enemy attack"); + } + + private void OnPlayerAttack(PlayerAttackEvent e) + { + var playerModel = this.GetModel(); + var enemyModel = this.GetModel(); + + int damage = CalculateDamage(playerModel.AttackPower.Value, e.Enemy.Defense); + e.Enemy.Health -= damage; + + this.SendEvent(new EnemyTookDamageEvent + { + EnemyId = e.Enemy.Id, + Damage = damage + }); + + _logger.Debug($"Enemy {e.Enemy.Id} took {damage} damage from player attack"); + } + + private int CalculateDamage(int attackPower, int defense) + { + return Math.Max(1, attackPower - defense / 2); + } + + protected override void OnDestroy() + { + _logger.Info("CombatSystem destroyed"); + // 清理资源 + base.OnDestroy(); + } + + public override void OnArchitecturePhase(ArchitecturePhase phase) + { + switch (phase) + { + case ArchitecturePhase.AfterSystemInit: + _logger.Info("CombatSystem is ready"); + break; + case ArchitecturePhase.Destroying: + _logger.Info("CombatSystem is shutting down"); + break; + } + } +} +``` + +### 2. 注册 System + +```csharp +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 注册 Model + this.RegisterModel(new PlayerModel()); + this.RegisterModel(new EnemyModel()); + this.RegisterModel(new InventoryModel()); + + // 注册 System(系统注册后会自动调用 Init) + this.RegisterSystem(new CombatSystem()); + this.RegisterSystem(new InventorySystem()); + this.RegisterSystem(new QuestSystem()); + this.RegisterSystem(new UISystem()); + } +} +``` + +### 3. 在其他组件中获取 System + +```csharp +// 在 Controller 中 +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameController : IController +{ + public void Start() + { + // 获取 System + var combatSystem = this.GetSystem(); + var questSystem = this.GetSystem(); + + // 使用 System + combatSystem.StartBattle(); + } +} + +// 在 Command 中 +public class StartBattleCommand : AbstractCommand +{ + protected override void OnExecute() + { + var combatSystem = this.GetSystem(); + combatSystem.StartBattle(); + } +} + +// 在其他 System 中 +public class AISystem : AbstractSystem +{ + protected override void OnInit() + { + var combatSystem = this.GetSystem(); + // 与其他 System 协作 + } +} +``` + +## 常见使用模式 + +### 异步 System + +System 支持异步初始化,通过实现 `IAsyncInitializable` 接口可以在初始化时执行异步操作。 + +```csharp +// 异步系统示例 +public class DataLoadSystem : AbstractSystem, IAsyncInitializable +{ + private GameData _gameData; + + protected override void OnInit() + { + // 同步初始化逻辑 + this.RegisterEvent(OnGameStarted); + } + + public async Task InitializeAsync() + { + // 异步加载游戏数据 + var storage = this.GetUtility(); + _gameData = await storage.LoadGameDataAsync(); + + // 数据加载完成后发送事件 + this.SendEvent(new GameDataLoadedEvent { Data = _gameData }); + } + + private void OnGameStarted(GameStartedEvent e) + { + // 使用已加载的数据 + Console.WriteLine($"Game data loaded: {_gameData.Version}"); + } + + protected override void OnDestroy() + { + // 清理资源 + _gameData = null; + } +} + +// 在架构中使用异步 System +public class GameArchitecture : Architecture +{ + protected override void Init() + { + RegisterModel(new PlayerModel()); + RegisterSystem(new DataLoadSystem()); + RegisterSystem(new CombatSystem()); + } +} + +// 异步初始化架构 +var architecture = new GameArchitecture(); +await architecture.InitializeAsync(); +``` + +### 1. 事件驱动的 System + +``` +public class InventorySystem : AbstractSystem +{ + protected override void OnInit() + { + // 监听物品相关事件 + this.RegisterEvent(OnItemAdded); + this.RegisterEvent(OnItemRemoved); + this.RegisterEvent(OnItemUsed); + } + + private void OnItemAdded(ItemAddedEvent e) + { + var inventoryModel = this.GetModel(); + + // 添加物品 + inventoryModel.AddItem(e.ItemId, e.Count); + + // 检查成就 + CheckAchievements(e.ItemId); + + // 发送通知 + this.SendEvent(new ShowNotificationEvent + { + Message = $"获得物品: {e.ItemId} x{e.Count}" + }); + } + + private void OnItemUsed(ItemUsedEvent e) + { + var inventoryModel = this.GetModel(); + var playerModel = this.GetModel(); + + if (inventoryModel.HasItem(e.ItemId)) + { + // 应用物品效果 + ApplyItemEffect(e.ItemId, playerModel); + + // 移除物品 + inventoryModel.RemoveItem(e.ItemId, 1); + + this.SendEvent(new ItemEffectAppliedEvent { ItemId = e.ItemId }); + } + } + + private void ApplyItemEffect(string itemId, PlayerModel player) + { + // 物品效果逻辑... + if (itemId == "health_potion") + { + player.Health.Value = Math.Min( + player.Health.Value + 50, + player.MaxHealth.Value + ); + } + } + + private void CheckAchievements(string itemId) + { + // 成就检查逻辑... + } +} +``` + +### 2. 定时更新的 System + +``` +public class BuffSystem : AbstractSystem +{ + private List _activeBuffs = new(); + + protected override void OnInit() + { + this.RegisterEvent(OnBuffApplied); + this.RegisterEvent(OnUpdate); + } + + private void OnBuffApplied(BuffAppliedEvent e) + { + _activeBuffs.Add(new BuffData + { + BuffId = e.BuffId, + Duration = e.Duration, + RemainingTime = e.Duration + }); + + ApplyBuffEffect(e.BuffId, true); + } + + private void OnUpdate(GameUpdateEvent e) + { + // 更新所有 Buff + for (int i = _activeBuffs.Count - 1; i >= 0; i--) + { + var buff = _activeBuffs[i]; + buff.RemainingTime -= e.DeltaTime; + + if (buff.RemainingTime <= 0) + { + // Buff 过期 + ApplyBuffEffect(buff.BuffId, false); + _activeBuffs.RemoveAt(i); + + this.SendEvent(new BuffExpiredEvent { BuffId = buff.BuffId }); + } + } + } + + private void ApplyBuffEffect(string buffId, bool apply) + { + var playerModel = this.GetModel(); + // 应用或移除 Buff 效果... + } +} +``` + +### 3. 跨 System 协作 + +``` +public class QuestSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnEnemyKilled); + this.RegisterEvent(OnItemCollected); + } + + private void OnEnemyKilled(EnemyKilledEvent e) + { + var questModel = this.GetModel(); + var activeQuests = questModel.GetActiveQuests(); + + foreach (var quest in activeQuests) + { + if (quest.Type == QuestType.KillEnemy && quest.TargetId == e.EnemyType) + { + quest.Progress++; + + if (quest.Progress >= quest.RequiredAmount) + { + // 任务完成 + CompleteQuest(quest.Id); + } + } + } + } + + private void CompleteQuest(string questId) + { + var questModel = this.GetModel(); + var quest = questModel.GetQuest(questId); + + // 标记任务完成 + questModel.CompleteQuest(questId); + + // 发放奖励(通过其他 System) + this.SendEvent(new QuestCompletedEvent + { + QuestId = questId, + Rewards = quest.Rewards + }); + } +} + +public class RewardSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnQuestCompleted); + } + + private void OnQuestCompleted(QuestCompletedEvent e) + { + var playerModel = this.GetModel(); + + // 发放奖励 + foreach (var reward in e.Rewards) + { + switch (reward.Type) + { + case RewardType.Gold: + playerModel.Gold.Value += reward.Amount; + break; + case RewardType.Experience: + playerModel.Experience.Value += reward.Amount; + break; + case RewardType.Item: + this.SendEvent(new ItemAddedEvent + { + ItemId = reward.ItemId, + Count = reward.Amount + }); + break; + } + } + + this.SendEvent(new RewardsGrantedEvent { Rewards = e.Rewards }); + } +} +``` + +### 4. 管理复杂状态机 + +``` +public class GameStateSystem : AbstractSystem +{ + private GameState _currentState = GameState.MainMenu; + + protected override void OnInit() + { + this.RegisterEvent(OnStateChangeRequest); + } + + private void OnStateChangeRequest(GameStateChangeRequestEvent e) + { + if (CanTransition(_currentState, e.TargetState)) + { + ExitState(_currentState); + _currentState = e.TargetState; + EnterState(_currentState); + + this.SendEvent(new GameStateChangedEvent + { + PreviousState = _currentState, + NewState = e.TargetState + }); + } + } + + private bool CanTransition(GameState from, GameState to) + { + // 状态转换规则 + return (from, to) switch + { + (GameState.MainMenu, GameState.Playing) => true, + (GameState.Playing, GameState.Paused) => true, + (GameState.Paused, GameState.Playing) => true, + (GameState.Playing, GameState.GameOver) => true, + _ => false + }; + } + + private void EnterState(GameState state) + { + switch (state) + { + case GameState.Playing: + // 开始游戏 + this.SendCommand(new StartGameCommand()); + break; + case GameState.Paused: + // 暂停游戏 + this.SendEvent(new GamePausedEvent()); + break; + case GameState.GameOver: + // 游戏结束 + this.SendCommand(new GameOverCommand()); + break; + } + } + + private void ExitState(GameState state) + { + // 清理当前状态 + } +} +``` + +## System vs Model + +### Model(数据层) + +- **职责**:存储数据和状态 +- **特点**:被动,等待修改 +- **示例**:PlayerModel、InventoryModel + +### System(逻辑层) + +- **职责**:处理业务逻辑,协调 Model +- **特点**:主动,响应事件 +- **示例**:CombatSystem、QuestSystem + +``` +// ✅ 正确的职责划分 + +// Model: 存储数据 +public class PlayerModel : AbstractModel +{ + public BindableProperty Health { get; } = new(100); + public BindableProperty Mana { get; } = new(50); + + protected override void OnInit() { } +} + +// System: 处理逻辑 +public class CombatSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnAttack); + } + + private void OnAttack(AttackEvent e) + { + var playerModel = this.GetModel(); + + // System 负责计算和决策 + int damage = CalculateDamage(e); + playerModel.Health.Value -= damage; + + if (playerModel.Health.Value <= 0) + { + this.SendEvent(new PlayerDiedEvent()); + } + } +} +``` + +## 最佳实践 + +1. **单一职责** - 每个 System 专注于一个业务领域 +2. **事件驱动** - 通过事件与其他组件通信 +3. **无状态或少状态** - 优先将状态存储在 Model 中 +4. **可组合** - System 之间通过事件松耦合协作 +5. **初始化注册** - 在 `OnInit` 中注册所有事件监听 + +## 性能优化 + +### 1. 避免频繁的 GetModel/GetSystem + +``` +// ❌ 不好:每次都获取 +private void OnUpdate(GameUpdateEvent e) +{ + var model = this.GetModel(); // 频繁调用 + // ... +} + +// ✅ 好:缓存引用 +private PlayerModel _playerModel; + +protected override void OnInit() +{ + _playerModel = this.GetModel(); // 只获取一次 +} + +private void OnUpdate(GameUpdateEvent e) +{ + // 直接使用缓存的引用 + _playerModel.Health.Value += 1; +} +``` + +### 2. 批量处理 + +``` +public class ParticleSystem : AbstractSystem +{ + private List _particles = new(); + + private void OnUpdate(GameUpdateEvent e) + { + // 批量更新,而不是每个粒子发一个事件 + for (int i = _particles.Count - 1; i >= 0; i--) + { + UpdateParticle(_particles[i], e.DeltaTime); + } + } +} +``` + +## 相关包 + +- [`model`](./model.md) - System 操作 Model 的数据 +- [`events`](./events.md) - System 通过事件通信 +- [`command`](./command.md) - System 中可以发送 Command +- [`query`](./query.md) - System 中可以发送 Query +- [`utility`](./utility.md) - System 可以使用 Utility - [`architecture`](./architecture.md) - 在架构中注册 System \ No newline at end of file diff --git a/docs/zh-CN/game/data.md b/docs/zh-CN/game/data.md index c04445e..cd82606 100644 --- a/docs/zh-CN/game/data.md +++ b/docs/zh-CN/game/data.md @@ -1,588 +1,598 @@ ---- -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/setting) - 游戏设置管理 -- [场景系统](/zh-CN/game/scene) - 场景切换时保存 -- [存档系统实现教程](/zh-CN/tutorials/save-system) - 完整示例 -- [Godot 集成](/zh-CN/godot/index) - Godot 中的数据管理 +--- +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 +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class SaveController : IController +{ + 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 +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial 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 +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial 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/setting) - 游戏设置管理 +- [场景系统](/zh-CN/game/scene) - 场景切换时保存 +- [存档系统实现教程](/zh-CN/tutorials/save-system) - 完整示例 +- [Godot 集成](/zh-CN/godot/index) - Godot 中的数据管理 diff --git a/docs/zh-CN/game/index.md b/docs/zh-CN/game/index.md index dca6922..30ee558 100644 --- a/docs/zh-CN/game/index.md +++ b/docs/zh-CN/game/index.md @@ -937,7 +937,7 @@ public partial class GameManager : Node, IController // 加载初始场景 LoadInitialScene(); - Context.SendEvent(new NewGameStartedEvent { PlayerName = playerName }); + this.SendEvent(new NewGameStartedEvent { PlayerName = playerName }); } public void LoadGame(int slotId) @@ -952,19 +952,19 @@ public partial class GameManager : Node, IController // 恢复游戏状态 RestoreGameState(saveData); - Context.SendEvent(new GameLoadedEvent { SlotId = slotId }); + this.SendEvent(new GameLoadedEvent { SlotId = slotId }); Logger.Info("Game loaded successfully"); } else { Logger.Warning($"No save data found in slot {slotId}"); - Context.SendEvent(new GameLoadFailedEvent { SlotId = slotId }); + this.SendEvent(new GameLoadFailedEvent { SlotId = slotId }); } } catch (Exception ex) { Logger.Error($"Failed to load game: {ex.Message}"); - Context.SendEvent(new GameLoadFailedEvent { SlotId = slotId, Error = ex.Message }); + this.SendEvent(new GameLoadFailedEvent { SlotId = slotId, Error = ex.Message }); } } @@ -977,13 +977,13 @@ public partial class GameManager : Node, IController var saveData = CreateSaveData(); _dataManager.SaveGame(slotId, saveData); - Context.SendEvent(new GameSavedEvent { SlotId = slotId }); + this.SendEvent(new GameSavedEvent { SlotId = slotId }); Logger.Info("Game saved successfully"); } catch (Exception ex) { Logger.Error($"Failed to save game: {ex.Message}"); - Context.SendEvent(new GameSaveFailedEvent { SlotId = slotId, Error = ex.Message }); + this.SendEvent(new GameSaveFailedEvent { SlotId = slotId, Error = ex.Message }); } } @@ -1014,9 +1014,9 @@ public partial class GameManager : Node, IController gameWorld.AddChild(player); // 恢复其他游戏状态 - Context.GetModel().Health.Value = saveData.PlayerHealth; - Context.GetModel().CurrentLevel.Value = saveData.CurrentLevel; - Context.GetModel().LoadFromData(saveData.Inventory); + this.GetModel().Health.Value = saveData.PlayerHealth; + this.GetModel().CurrentLevel.Value = saveData.CurrentLevel; + this.GetModel().LoadFromData(saveData.Inventory); } private SaveData CreateSaveData() @@ -1026,9 +1026,9 @@ public partial class GameManager : Node, IController return new SaveData { PlayerPosition = player?.Position ?? Vector2.Zero, - PlayerHealth = Context.GetModel().Health.Value, - CurrentLevel = Context.GetModel().CurrentLevel.Value, - Inventory = Context.GetModel().GetData(), + PlayerHealth = this.GetModel().Health.Value, + CurrentLevel = this.GetModel().CurrentLevel.Value, + Inventory = this.GetModel().GetData(), Timestamp = DateTime.UtcNow, Version = 1 }; @@ -1094,7 +1094,7 @@ public class AutoSaveSystem : AbstractSystem var saveData = CreateAutoSaveData(); // 保存到自动存档槽 - var storage = Context.GetUtility(); + var storage = this.GetUtility(); storage.Write("autosave", saveData); storage.Write("autosave/timestamp", DateTime.UtcNow); @@ -1330,7 +1330,7 @@ public class PlayerModule : AbstractModule private void OnPlayerDeath(PlayerDeathEvent e) { // 触发保存模块的事件 - Context.SendEvent(new RequestAutoSaveEvent { Reason = "Player Death" }); + this.SendEvent(new RequestAutoSaveEvent { Reason = "Player Death" }); } } ``` diff --git a/docs/zh-CN/game/scene.md b/docs/zh-CN/game/scene.md index 0a226ed..e9062ba 100644 --- a/docs/zh-CN/game/scene.md +++ b/docs/zh-CN/game/scene.md @@ -1,652 +1,658 @@ ---- -title: 场景系统 -description: 场景系统提供了完整的场景生命周期管理、路由导航和转换控制功能。 ---- - -# 场景系统 - -## 概述 - -场景系统是 GFramework.Game 中用于管理游戏场景的核心组件。它提供了场景的加载、卸载、切换、暂停和恢复等完整生命周期管理,以及基于栈的场景导航机制。 - -通过场景系统,你可以轻松实现场景之间的平滑切换,管理场景栈(如主菜单 -> 游戏 -> 暂停菜单),并在场景转换时执行自定义逻辑。 - -**主要特性**: - -- 完整的场景生命周期管理 -- 基于栈的场景导航 -- 场景转换管道和钩子 -- 路由守卫(Route Guard) -- 场景工厂和行为模式 -- 异步加载和卸载 - -## 核心概念 - -### 场景接口 - -`IScene` 定义了场景的完整生命周期: - -```csharp -public interface IScene -{ - ValueTask OnLoadAsync(ISceneEnterParam? param); // 加载资源 - ValueTask OnEnterAsync(); // 进入场景 - ValueTask OnPauseAsync(); // 暂停场景 - ValueTask OnResumeAsync(); // 恢复场景 - ValueTask OnExitAsync(); // 退出场景 - ValueTask OnUnloadAsync(); // 卸载资源 -} -``` - -### 场景路由 - -`ISceneRouter` 管理场景的导航和切换: - -```csharp -public interface ISceneRouter : ISystem -{ - ISceneBehavior? Current { get; } // 当前场景 - string? CurrentKey { get; } // 当前场景键 - IEnumerable Stack { get; } // 场景栈 - bool IsTransitioning { get; } // 是否正在切换 - - ValueTask ReplaceAsync(string sceneKey, ISceneEnterParam? param = null); - ValueTask PushAsync(string sceneKey, ISceneEnterParam? param = null); - ValueTask PopAsync(); - ValueTask ClearAsync(); -} -``` - -### 场景行为 - -`ISceneBehavior` 封装了场景的具体实现和引擎集成: - -```csharp -public interface ISceneBehavior -{ - string Key { get; } // 场景唯一标识 - IScene Scene { get; } // 场景实例 - ValueTask LoadAsync(ISceneEnterParam? param); - ValueTask UnloadAsync(); -} -``` - -## 基本用法 - -### 定义场景 - -实现 `IScene` 接口创建场景: - -```csharp -using GFramework.Game.Abstractions.scene; - -public class MainMenuScene : IScene -{ - public async ValueTask OnLoadAsync(ISceneEnterParam? param) - { - // 加载场景资源 - Console.WriteLine("加载主菜单资源"); - await Task.Delay(100); // 模拟加载 - } - - public async ValueTask OnEnterAsync() - { - // 进入场景 - Console.WriteLine("进入主菜单"); - // 显示 UI、播放音乐等 - await Task.CompletedTask; - } - - public async ValueTask OnPauseAsync() - { - // 暂停场景 - Console.WriteLine("暂停主菜单"); - await Task.CompletedTask; - } - - public async ValueTask OnResumeAsync() - { - // 恢复场景 - Console.WriteLine("恢复主菜单"); - await Task.CompletedTask; - } - - public async ValueTask OnExitAsync() - { - // 退出场景 - Console.WriteLine("退出主菜单"); - // 隐藏 UI、停止音乐等 - await Task.CompletedTask; - } - - public async ValueTask OnUnloadAsync() - { - // 卸载场景资源 - Console.WriteLine("卸载主菜单资源"); - await Task.Delay(50); // 模拟卸载 - } -} -``` - -### 注册场景 - -在场景注册表中注册场景: - -```csharp -using GFramework.Game.Abstractions.scene; - -public class GameSceneRegistry : IGameSceneRegistry -{ - private readonly Dictionary _scenes = new(); - - public GameSceneRegistry() - { - // 注册场景 - Register("MainMenu", typeof(MainMenuScene)); - Register("Gameplay", typeof(GameplayScene)); - Register("Pause", typeof(PauseScene)); - } - - public void Register(string key, Type sceneType) - { - _scenes[key] = sceneType; - } - - public Type? GetSceneType(string key) - { - return _scenes.TryGetValue(key, out var type) ? type : null; - } -} -``` - -### 切换场景 - -使用场景路由进行导航: - -```csharp -public class GameController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public async Task StartGame() - { - var sceneRouter = this.GetSystem(); - - // 替换当前场景(清空场景栈) - await sceneRouter.ReplaceAsync("Gameplay"); - } - - public async Task ShowPauseMenu() - { - var sceneRouter = this.GetSystem(); - - // 压入新场景(保留当前场景) - await sceneRouter.PushAsync("Pause"); - } - - public async Task ClosePauseMenu() - { - var sceneRouter = this.GetSystem(); - - // 弹出当前场景(恢复上一个场景) - await sceneRouter.PopAsync(); - } -} -``` - -## 高级用法 - -### 场景参数传递 - -通过 `ISceneEnterParam` 传递数据: - -```csharp -// 定义场景参数 -public class GameplayEnterParam : ISceneEnterParam -{ - public int Level { get; set; } - public string Difficulty { get; set; } -} - -// 在场景中接收参数 -public class GameplayScene : IScene -{ - private int _level; - private string _difficulty; - - public async ValueTask OnLoadAsync(ISceneEnterParam? param) - { - if (param is GameplayEnterParam gameplayParam) - { - _level = gameplayParam.Level; - _difficulty = gameplayParam.Difficulty; - Console.WriteLine($"加载关卡 {_level},难度: {_difficulty}"); - } - - await Task.CompletedTask; - } - - // ... 其他生命周期方法 -} - -// 切换场景时传递参数 -await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam -{ - Level = 1, - Difficulty = "Normal" -}); -``` - -### 路由守卫 - -使用路由守卫控制场景切换: - -```csharp -using GFramework.Game.Abstractions.scene; - -public class SaveGameGuard : ISceneRouteGuard -{ - public async ValueTask CanLeaveAsync( - ISceneBehavior from, - string toKey, - ISceneEnterParam? param) - { - // 离开游戏场景前检查是否需要保存 - if (from.Key == "Gameplay") - { - var needsSave = CheckIfNeedsSave(); - if (needsSave) - { - await SaveGameAsync(); - } - } - - return true; // 允许离开 - } - - public async ValueTask CanEnterAsync( - string toKey, - ISceneEnterParam? param) - { - // 进入场景前的验证 - if (toKey == "Gameplay") - { - // 检查是否满足进入条件 - var canEnter = CheckGameplayRequirements(); - return canEnter; - } - - return true; - } - - private bool CheckIfNeedsSave() => true; - private async Task SaveGameAsync() => await Task.Delay(100); - private bool CheckGameplayRequirements() => true; -} - -// 注册守卫 -sceneRouter.AddGuard(new SaveGameGuard()); -``` - -### 场景转换处理器 - -自定义场景转换逻辑: - -```csharp -using GFramework.Game.Abstractions.scene; - -public class FadeTransitionHandler : ISceneTransitionHandler -{ - public async ValueTask OnBeforeLoadAsync(SceneTransitionEvent @event) - { - Console.WriteLine($"准备加载场景: {@event.ToKey}"); - // 显示加载画面 - await ShowLoadingScreen(); - } - - public async ValueTask OnAfterLoadAsync(SceneTransitionEvent @event) - { - Console.WriteLine($"场景加载完成: {@event.ToKey}"); - await Task.CompletedTask; - } - - public async ValueTask OnBeforeEnterAsync(SceneTransitionEvent @event) - { - Console.WriteLine($"准备进入场景: {@event.ToKey}"); - // 播放淡入动画 - await PlayFadeIn(); - } - - public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event) - { - Console.WriteLine($"已进入场景: {@event.ToKey}"); - // 隐藏加载画面 - await HideLoadingScreen(); - } - - public async ValueTask OnBeforeExitAsync(SceneTransitionEvent @event) - { - Console.WriteLine($"准备退出场景: {@event.FromKey}"); - // 播放淡出动画 - await PlayFadeOut(); - } - - public async ValueTask OnAfterExitAsync(SceneTransitionEvent @event) - { - Console.WriteLine($"已退出场景: {@event.FromKey}"); - await Task.CompletedTask; - } - - private async Task ShowLoadingScreen() => await Task.Delay(100); - private async Task HideLoadingScreen() => await Task.Delay(100); - private async Task PlayFadeIn() => await Task.Delay(200); - private async Task PlayFadeOut() => await Task.Delay(200); -} - -// 注册转换处理器 -sceneRouter.AddTransitionHandler(new FadeTransitionHandler()); -``` - -### 场景栈管理 - -```csharp -public class SceneNavigationController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public async Task NavigateToSettings() - { - var sceneRouter = this.GetSystem(); - - // 检查场景是否已在栈中 - if (sceneRouter.Contains("Settings")) - { - Console.WriteLine("设置场景已打开"); - return; - } - - // 压入设置场景 - await sceneRouter.PushAsync("Settings"); - } - - public void ShowSceneStack() - { - var sceneRouter = this.GetSystem(); - - Console.WriteLine("当前场景栈:"); - foreach (var scene in sceneRouter.Stack) - { - Console.WriteLine($"- {scene.Key}"); - } - } - - public async Task ReturnToMainMenu() - { - var sceneRouter = this.GetSystem(); - - // 清空所有场景并加载主菜单 - await sceneRouter.ClearAsync(); - await sceneRouter.ReplaceAsync("MainMenu"); - } -} -``` - -### 场景加载进度 - -```csharp -public class GameplayScene : IScene -{ - public async ValueTask OnLoadAsync(ISceneEnterParam? param) - { - var resourceManager = GetResourceManager(); - - // 加载多个资源并报告进度 - var resources = new[] - { - "textures/player.png", - "textures/enemy.png", - "audio/bgm.mp3", - "models/level.obj" - }; - - for (int i = 0; i < resources.Length; i++) - { - await resourceManager.LoadAsync(resources[i]); - - // 报告进度 - var progress = (i + 1) / (float)resources.Length; - ReportProgress(progress); - } - } - - private void ReportProgress(float progress) - { - // 发送进度事件 - Console.WriteLine($"加载进度: {progress * 100:F0}%"); - } - - // ... 其他生命周期方法 -} -``` - -### 场景预加载 - -```csharp -public class PreloadController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public async Task PreloadNextLevel() - { - var sceneFactory = this.GetUtility(); - - // 预加载下一关场景 - var scene = sceneFactory.Create("Level2"); - await scene.OnLoadAsync(null); - - Console.WriteLine("下一关预加载完成"); - } -} -``` - -## 最佳实践 - -1. **在 OnLoad 中加载资源,在 OnUnload 中释放**:保持资源管理清晰 - ```csharp - public async ValueTask OnLoadAsync(ISceneEnterParam? param) - { - _texture = await LoadTextureAsync("player.png"); - } - - public async ValueTask OnUnloadAsync() - { - _texture?.Dispose(); - _texture = null; - } - ``` - -2. **使用 Push/Pop 管理临时场景**:如暂停菜单、设置界面 - ```csharp - // 打开暂停菜单(保留游戏场景) - await sceneRouter.PushAsync("Pause"); - - // 关闭暂停菜单(恢复游戏场景) - await sceneRouter.PopAsync(); - ``` - -3. **使用 Replace 切换主要场景**:如从菜单到游戏 - ```csharp - // 开始游戏(清空场景栈) - await sceneRouter.ReplaceAsync("Gameplay"); - ``` - -4. **在 OnPause/OnResume 中管理状态**:暂停和恢复游戏逻辑 - ```csharp - public async ValueTask OnPauseAsync() - { - // 暂停游戏逻辑 - _gameTimer.Pause(); - _audioSystem.PauseBGM(); - } - - public async ValueTask OnResumeAsync() - { - // 恢复游戏逻辑 - _gameTimer.Resume(); - _audioSystem.ResumeBGM(); - } - ``` - -5. **使用路由守卫处理业务逻辑**:如保存检查、权限验证 - ```csharp - public async ValueTask CanLeaveAsync(...) - { - if (HasUnsavedChanges()) - { - var confirmed = await ShowSaveDialog(); - if (confirmed) - { - await SaveAsync(); - } - return confirmed; - } - return true; - } - ``` - -6. **避免在场景切换时阻塞**:使用异步操作 - ```csharp - ✓ await sceneRouter.ReplaceAsync("Gameplay"); - ✗ sceneRouter.ReplaceAsync("Gameplay").Wait(); // 可能死锁 - ``` - -## 常见问题 - -### 问题:Replace、Push、Pop 有什么区别? - -**解答**: - -- **Replace**:清空场景栈,加载新场景(用于主要场景切换) -- **Push**:压入新场景,暂停当前场景(用于临时场景) -- **Pop**:弹出当前场景,恢复上一个场景(用于关闭临时场景) - -```csharp -// 场景栈示例 -await sceneRouter.ReplaceAsync("MainMenu"); // [MainMenu] -await sceneRouter.PushAsync("Settings"); // [MainMenu, Settings] -await sceneRouter.PushAsync("About"); // [MainMenu, Settings, About] -await sceneRouter.PopAsync(); // [MainMenu, Settings] -await sceneRouter.PopAsync(); // [MainMenu] -``` - -### 问题:如何在场景之间传递数据? - -**解答**: -有几种方式: - -1. **通过场景参数**: - -```csharp -await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam -{ - Level = 5 -}); -``` - -2. **通过 Model**: - -```csharp -var gameModel = this.GetModel(); -gameModel.CurrentLevel = 5; -await sceneRouter.ReplaceAsync("Gameplay"); -``` - -3. **通过事件**: - -```csharp -this.SendEvent(new LevelSelectedEvent { Level = 5 }); -await sceneRouter.ReplaceAsync("Gameplay"); -``` - -### 问题:场景切换时如何显示加载画面? - -**解答**: -使用场景转换处理器: - -```csharp -public class LoadingScreenHandler : ISceneTransitionHandler -{ - public async ValueTask OnBeforeLoadAsync(SceneTransitionEvent @event) - { - await ShowLoadingScreen(); - } - - public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event) - { - await HideLoadingScreen(); - } - - // ... 其他方法 -} -``` - -### 问题:如何防止用户在场景切换时操作? - -**解答**: -检查 `IsTransitioning` 状态: - -```csharp -public async Task ChangeScene(string sceneKey) -{ - var sceneRouter = this.GetSystem(); - - if (sceneRouter.IsTransitioning) - { - Console.WriteLine("场景正在切换中,请稍候"); - return; - } - - await sceneRouter.ReplaceAsync(sceneKey); -} -``` - -### 问题:场景切换失败怎么办? - -**解答**: -使用 try-catch 捕获异常: - -```csharp -try -{ - await sceneRouter.ReplaceAsync("Gameplay"); -} -catch (Exception ex) -{ - Console.WriteLine($"场景切换失败: {ex.Message}"); - // 回退到安全场景 - await sceneRouter.ReplaceAsync("MainMenu"); -} -``` - -### 问题:如何实现场景预加载? - -**解答**: -在后台预先加载场景资源: - -```csharp -// 在当前场景中预加载下一个场景 -var factory = this.GetUtility(); -var nextScene = factory.Create("NextLevel"); -await nextScene.OnLoadAsync(null); - -// 稍后快速切换 -await sceneRouter.ReplaceAsync("NextLevel"); -``` - -## 相关文档 - -- [UI 系统](/zh-CN/game/ui) - UI 页面管理 -- [资源管理系统](/zh-CN/core/resource) - 场景资源加载 -- [状态机系统](/zh-CN/core/state-machine) - 场景状态管理 -- [Godot 场景系统](/zh-CN/godot/scene) - Godot 引擎集成 -- [存档系统实现教程](/zh-CN/tutorials/save-system) - 场景切换时保存数据 +--- +title: 场景系统 +description: 场景系统提供了完整的场景生命周期管理、路由导航和转换控制功能。 +--- + +# 场景系统 + +## 概述 + +场景系统是 GFramework.Game 中用于管理游戏场景的核心组件。它提供了场景的加载、卸载、切换、暂停和恢复等完整生命周期管理,以及基于栈的场景导航机制。 + +通过场景系统,你可以轻松实现场景之间的平滑切换,管理场景栈(如主菜单 -> 游戏 -> 暂停菜单),并在场景转换时执行自定义逻辑。 + +**主要特性**: + +- 完整的场景生命周期管理 +- 基于栈的场景导航 +- 场景转换管道和钩子 +- 路由守卫(Route Guard) +- 场景工厂和行为模式 +- 异步加载和卸载 + +## 核心概念 + +### 场景接口 + +`IScene` 定义了场景的完整生命周期: + +```csharp +public interface IScene +{ + ValueTask OnLoadAsync(ISceneEnterParam? param); // 加载资源 + ValueTask OnEnterAsync(); // 进入场景 + ValueTask OnPauseAsync(); // 暂停场景 + ValueTask OnResumeAsync(); // 恢复场景 + ValueTask OnExitAsync(); // 退出场景 + ValueTask OnUnloadAsync(); // 卸载资源 +} +``` + +### 场景路由 + +`ISceneRouter` 管理场景的导航和切换: + +```csharp +public interface ISceneRouter : ISystem +{ + ISceneBehavior? Current { get; } // 当前场景 + string? CurrentKey { get; } // 当前场景键 + IEnumerable Stack { get; } // 场景栈 + bool IsTransitioning { get; } // 是否正在切换 + + ValueTask ReplaceAsync(string sceneKey, ISceneEnterParam? param = null); + ValueTask PushAsync(string sceneKey, ISceneEnterParam? param = null); + ValueTask PopAsync(); + ValueTask ClearAsync(); +} +``` + +### 场景行为 + +`ISceneBehavior` 封装了场景的具体实现和引擎集成: + +```csharp +public interface ISceneBehavior +{ + string Key { get; } // 场景唯一标识 + IScene Scene { get; } // 场景实例 + ValueTask LoadAsync(ISceneEnterParam? param); + ValueTask UnloadAsync(); +} +``` + +## 基本用法 + +### 定义场景 + +实现 `IScene` 接口创建场景: + +```csharp +using GFramework.Game.Abstractions.scene; + +public class MainMenuScene : IScene +{ + public async ValueTask OnLoadAsync(ISceneEnterParam? param) + { + // 加载场景资源 + Console.WriteLine("加载主菜单资源"); + await Task.Delay(100); // 模拟加载 + } + + public async ValueTask OnEnterAsync() + { + // 进入场景 + Console.WriteLine("进入主菜单"); + // 显示 UI、播放音乐等 + await Task.CompletedTask; + } + + public async ValueTask OnPauseAsync() + { + // 暂停场景 + Console.WriteLine("暂停主菜单"); + await Task.CompletedTask; + } + + public async ValueTask OnResumeAsync() + { + // 恢复场景 + Console.WriteLine("恢复主菜单"); + await Task.CompletedTask; + } + + public async ValueTask OnExitAsync() + { + // 退出场景 + Console.WriteLine("退出主菜单"); + // 隐藏 UI、停止音乐等 + await Task.CompletedTask; + } + + public async ValueTask OnUnloadAsync() + { + // 卸载场景资源 + Console.WriteLine("卸载主菜单资源"); + await Task.Delay(50); // 模拟卸载 + } +} +``` + +### 注册场景 + +在场景注册表中注册场景: + +```csharp +using GFramework.Game.Abstractions.scene; + +public class GameSceneRegistry : IGameSceneRegistry +{ + private readonly Dictionary _scenes = new(); + + public GameSceneRegistry() + { + // 注册场景 + Register("MainMenu", typeof(MainMenuScene)); + Register("Gameplay", typeof(GameplayScene)); + Register("Pause", typeof(PauseScene)); + } + + public void Register(string key, Type sceneType) + { + _scenes[key] = sceneType; + } + + public Type? GetSceneType(string key) + { + return _scenes.TryGetValue(key, out var type) ? type : null; + } +} +``` + +### 切换场景 + +使用场景路由进行导航: + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameController : IController +{ + public async Task StartGame() + { + var sceneRouter = this.GetSystem(); + + // 替换当前场景(清空场景栈) + await sceneRouter.ReplaceAsync("Gameplay"); + } + + public async Task ShowPauseMenu() + { + var sceneRouter = this.GetSystem(); + + // 压入新场景(保留当前场景) + await sceneRouter.PushAsync("Pause"); + } + + public async Task ClosePauseMenu() + { + var sceneRouter = this.GetSystem(); + + // 弹出当前场景(恢复上一个场景) + await sceneRouter.PopAsync(); + } +} +``` + +## 高级用法 + +### 场景参数传递 + +通过 `ISceneEnterParam` 传递数据: + +```csharp +// 定义场景参数 +public class GameplayEnterParam : ISceneEnterParam +{ + public int Level { get; set; } + public string Difficulty { get; set; } +} + +// 在场景中接收参数 +public class GameplayScene : IScene +{ + private int _level; + private string _difficulty; + + public async ValueTask OnLoadAsync(ISceneEnterParam? param) + { + if (param is GameplayEnterParam gameplayParam) + { + _level = gameplayParam.Level; + _difficulty = gameplayParam.Difficulty; + Console.WriteLine($"加载关卡 {_level},难度: {_difficulty}"); + } + + await Task.CompletedTask; + } + + // ... 其他生命周期方法 +} + +// 切换场景时传递参数 +await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam +{ + Level = 1, + Difficulty = "Normal" +}); +``` + +### 路由守卫 + +使用路由守卫控制场景切换: + +```csharp +using GFramework.Game.Abstractions.scene; + +public class SaveGameGuard : ISceneRouteGuard +{ + public async ValueTask CanLeaveAsync( + ISceneBehavior from, + string toKey, + ISceneEnterParam? param) + { + // 离开游戏场景前检查是否需要保存 + if (from.Key == "Gameplay") + { + var needsSave = CheckIfNeedsSave(); + if (needsSave) + { + await SaveGameAsync(); + } + } + + return true; // 允许离开 + } + + public async ValueTask CanEnterAsync( + string toKey, + ISceneEnterParam? param) + { + // 进入场景前的验证 + if (toKey == "Gameplay") + { + // 检查是否满足进入条件 + var canEnter = CheckGameplayRequirements(); + return canEnter; + } + + return true; + } + + private bool CheckIfNeedsSave() => true; + private async Task SaveGameAsync() => await Task.Delay(100); + private bool CheckGameplayRequirements() => true; +} + +// 注册守卫 +sceneRouter.AddGuard(new SaveGameGuard()); +``` + +### 场景转换处理器 + +自定义场景转换逻辑: + +```csharp +using GFramework.Game.Abstractions.scene; + +public class FadeTransitionHandler : ISceneTransitionHandler +{ + public async ValueTask OnBeforeLoadAsync(SceneTransitionEvent @event) + { + Console.WriteLine($"准备加载场景: {@event.ToKey}"); + // 显示加载画面 + await ShowLoadingScreen(); + } + + public async ValueTask OnAfterLoadAsync(SceneTransitionEvent @event) + { + Console.WriteLine($"场景加载完成: {@event.ToKey}"); + await Task.CompletedTask; + } + + public async ValueTask OnBeforeEnterAsync(SceneTransitionEvent @event) + { + Console.WriteLine($"准备进入场景: {@event.ToKey}"); + // 播放淡入动画 + await PlayFadeIn(); + } + + public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event) + { + Console.WriteLine($"已进入场景: {@event.ToKey}"); + // 隐藏加载画面 + await HideLoadingScreen(); + } + + public async ValueTask OnBeforeExitAsync(SceneTransitionEvent @event) + { + Console.WriteLine($"准备退出场景: {@event.FromKey}"); + // 播放淡出动画 + await PlayFadeOut(); + } + + public async ValueTask OnAfterExitAsync(SceneTransitionEvent @event) + { + Console.WriteLine($"已退出场景: {@event.FromKey}"); + await Task.CompletedTask; + } + + private async Task ShowLoadingScreen() => await Task.Delay(100); + private async Task HideLoadingScreen() => await Task.Delay(100); + private async Task PlayFadeIn() => await Task.Delay(200); + private async Task PlayFadeOut() => await Task.Delay(200); +} + +// 注册转换处理器 +sceneRouter.AddTransitionHandler(new FadeTransitionHandler()); +``` + +### 场景栈管理 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class SceneNavigationController : IController +{ + public async Task NavigateToSettings() + { + var sceneRouter = this.GetSystem(); + + // 检查场景是否已在栈中 + if (sceneRouter.Contains("Settings")) + { + Console.WriteLine("设置场景已打开"); + return; + } + + // 压入设置场景 + await sceneRouter.PushAsync("Settings"); + } + + public void ShowSceneStack() + { + var sceneRouter = this.GetSystem(); + + Console.WriteLine("当前场景栈:"); + foreach (var scene in sceneRouter.Stack) + { + Console.WriteLine($"- {scene.Key}"); + } + } + + public async Task ReturnToMainMenu() + { + var sceneRouter = this.GetSystem(); + + // 清空所有场景并加载主菜单 + await sceneRouter.ClearAsync(); + await sceneRouter.ReplaceAsync("MainMenu"); + } +} +``` + +### 场景加载进度 + +```csharp +public class GameplayScene : IScene +{ + public async ValueTask OnLoadAsync(ISceneEnterParam? param) + { + var resourceManager = GetResourceManager(); + + // 加载多个资源并报告进度 + var resources = new[] + { + "textures/player.png", + "textures/enemy.png", + "audio/bgm.mp3", + "models/level.obj" + }; + + for (int i = 0; i < resources.Length; i++) + { + await resourceManager.LoadAsync(resources[i]); + + // 报告进度 + var progress = (i + 1) / (float)resources.Length; + ReportProgress(progress); + } + } + + private void ReportProgress(float progress) + { + // 发送进度事件 + Console.WriteLine($"加载进度: {progress * 100:F0}%"); + } + + // ... 其他生命周期方法 +} +``` + +### 场景预加载 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class PreloadController : IController +{ + public async Task PreloadNextLevel() + { + var sceneFactory = this.GetUtility(); + + // 预加载下一关场景 + var scene = sceneFactory.Create("Level2"); + await scene.OnLoadAsync(null); + + Console.WriteLine("下一关预加载完成"); + } +} +``` + +## 最佳实践 + +1. **在 OnLoad 中加载资源,在 OnUnload 中释放**:保持资源管理清晰 + ```csharp + public async ValueTask OnLoadAsync(ISceneEnterParam? param) + { + _texture = await LoadTextureAsync("player.png"); + } + + public async ValueTask OnUnloadAsync() + { + _texture?.Dispose(); + _texture = null; + } + ``` + +2. **使用 Push/Pop 管理临时场景**:如暂停菜单、设置界面 + ```csharp + // 打开暂停菜单(保留游戏场景) + await sceneRouter.PushAsync("Pause"); + + // 关闭暂停菜单(恢复游戏场景) + await sceneRouter.PopAsync(); + ``` + +3. **使用 Replace 切换主要场景**:如从菜单到游戏 + ```csharp + // 开始游戏(清空场景栈) + await sceneRouter.ReplaceAsync("Gameplay"); + ``` + +4. **在 OnPause/OnResume 中管理状态**:暂停和恢复游戏逻辑 + ```csharp + public async ValueTask OnPauseAsync() + { + // 暂停游戏逻辑 + _gameTimer.Pause(); + _audioSystem.PauseBGM(); + } + + public async ValueTask OnResumeAsync() + { + // 恢复游戏逻辑 + _gameTimer.Resume(); + _audioSystem.ResumeBGM(); + } + ``` + +5. **使用路由守卫处理业务逻辑**:如保存检查、权限验证 + ```csharp + public async ValueTask CanLeaveAsync(...) + { + if (HasUnsavedChanges()) + { + var confirmed = await ShowSaveDialog(); + if (confirmed) + { + await SaveAsync(); + } + return confirmed; + } + return true; + } + ``` + +6. **避免在场景切换时阻塞**:使用异步操作 + ```csharp + ✓ await sceneRouter.ReplaceAsync("Gameplay"); + ✗ sceneRouter.ReplaceAsync("Gameplay").Wait(); // 可能死锁 + ``` + +## 常见问题 + +### 问题:Replace、Push、Pop 有什么区别? + +**解答**: + +- **Replace**:清空场景栈,加载新场景(用于主要场景切换) +- **Push**:压入新场景,暂停当前场景(用于临时场景) +- **Pop**:弹出当前场景,恢复上一个场景(用于关闭临时场景) + +```csharp +// 场景栈示例 +await sceneRouter.ReplaceAsync("MainMenu"); // [MainMenu] +await sceneRouter.PushAsync("Settings"); // [MainMenu, Settings] +await sceneRouter.PushAsync("About"); // [MainMenu, Settings, About] +await sceneRouter.PopAsync(); // [MainMenu, Settings] +await sceneRouter.PopAsync(); // [MainMenu] +``` + +### 问题:如何在场景之间传递数据? + +**解答**: +有几种方式: + +1. **通过场景参数**: + +```csharp +await sceneRouter.ReplaceAsync("Gameplay", new GameplayEnterParam +{ + Level = 5 +}); +``` + +2. **通过 Model**: + +```csharp +var gameModel = this.GetModel(); +gameModel.CurrentLevel = 5; +await sceneRouter.ReplaceAsync("Gameplay"); +``` + +3. **通过事件**: + +```csharp +this.SendEvent(new LevelSelectedEvent { Level = 5 }); +await sceneRouter.ReplaceAsync("Gameplay"); +``` + +### 问题:场景切换时如何显示加载画面? + +**解答**: +使用场景转换处理器: + +```csharp +public class LoadingScreenHandler : ISceneTransitionHandler +{ + public async ValueTask OnBeforeLoadAsync(SceneTransitionEvent @event) + { + await ShowLoadingScreen(); + } + + public async ValueTask OnAfterEnterAsync(SceneTransitionEvent @event) + { + await HideLoadingScreen(); + } + + // ... 其他方法 +} +``` + +### 问题:如何防止用户在场景切换时操作? + +**解答**: +检查 `IsTransitioning` 状态: + +```csharp +public async Task ChangeScene(string sceneKey) +{ + var sceneRouter = this.GetSystem(); + + if (sceneRouter.IsTransitioning) + { + Console.WriteLine("场景正在切换中,请稍候"); + return; + } + + await sceneRouter.ReplaceAsync(sceneKey); +} +``` + +### 问题:场景切换失败怎么办? + +**解答**: +使用 try-catch 捕获异常: + +```csharp +try +{ + await sceneRouter.ReplaceAsync("Gameplay"); +} +catch (Exception ex) +{ + Console.WriteLine($"场景切换失败: {ex.Message}"); + // 回退到安全场景 + await sceneRouter.ReplaceAsync("MainMenu"); +} +``` + +### 问题:如何实现场景预加载? + +**解答**: +在后台预先加载场景资源: + +```csharp +// 在当前场景中预加载下一个场景 +var factory = this.GetUtility(); +var nextScene = factory.Create("NextLevel"); +await nextScene.OnLoadAsync(null); + +// 稍后快速切换 +await sceneRouter.ReplaceAsync("NextLevel"); +``` + +## 相关文档 + +- [UI 系统](/zh-CN/game/ui) - UI 页面管理 +- [资源管理系统](/zh-CN/core/resource) - 场景资源加载 +- [状态机系统](/zh-CN/core/state-machine) - 场景状态管理 +- [Godot 场景系统](/zh-CN/godot/scene) - Godot 引擎集成 +- [存档系统实现教程](/zh-CN/tutorials/save-system) - 场景切换时保存数据 diff --git a/docs/zh-CN/game/serialization.md b/docs/zh-CN/game/serialization.md index 2e08d97..4cb0537 100644 --- a/docs/zh-CN/game/serialization.md +++ b/docs/zh-CN/game/serialization.md @@ -1,756 +1,757 @@ ---- -title: 序列化系统 -description: 序列化系统提供了统一的对象序列化和反序列化接口,支持 JSON 格式和运行时类型处理。 ---- - -# 序列化系统 - -## 概述 - -序列化系统是 GFramework.Game 中用于对象序列化和反序列化的核心组件。它提供了统一的序列化接口,支持将对象转换为字符串格式(如 -JSON)进行存储或传输,并能够将字符串数据还原为对象。 - -序列化系统与数据存储、配置管理、存档系统等模块深度集成,为游戏数据的持久化提供了基础支持。 - -**主要特性**: - -- 统一的序列化接口 -- JSON 格式支持 -- 运行时类型序列化 -- 泛型和非泛型 API -- 与存储系统无缝集成 -- 类型安全的反序列化 - -## 核心概念 - -### 序列化器接口 - -`ISerializer` 定义了基本的序列化操作: - -```csharp -public interface ISerializer : IUtility -{ - // 将对象序列化为字符串 - string Serialize<T>(T value); - - // 将字符串反序列化为对象 - T Deserialize<T>(string data); -} -``` - -### 运行时类型序列化器 - -`IRuntimeTypeSerializer` 扩展了基本接口,支持运行时类型处理: - -```csharp -public interface IRuntimeTypeSerializer : ISerializer -{ - // 使用运行时类型序列化对象 - string Serialize(object obj, Type type); - - // 使用运行时类型反序列化对象 - object Deserialize(string data, Type type); -} -``` - -### JSON 序列化器 - -`JsonSerializer` 是基于 Newtonsoft.Json 的实现: - -```csharp -public sealed class JsonSerializer : IRuntimeTypeSerializer -{ - string Serialize<T>(T value); - T Deserialize<T>(string data); - string Serialize(object obj, Type type); - object Deserialize(string data, Type type); -} -``` - -## 基本用法 - -### 注册序列化器 - -在架构中注册序列化器: - -```csharp -using GFramework.Core.Abstractions.serializer; -using GFramework.Game.serializer; - -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册 JSON 序列化器 - var jsonSerializer = new JsonSerializer(); - RegisterUtility(jsonSerializer); - RegisterUtility(jsonSerializer); - } -} -``` - -### 序列化对象 - -使用泛型 API 序列化对象: - -```csharp -public class PlayerData -{ - public string Name { get; set; } - public int Level { get; set; } - public int Experience { get; set; } -} - -public class SaveController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void SavePlayer() - { - var serializer = this.GetUtility(); - - var player = new PlayerData - { - Name = "Player1", - Level = 10, - Experience = 1000 - }; - - // 序列化为 JSON 字符串 - string json = serializer.Serialize(player); - Console.WriteLine(json); - // 输出: {"Name":"Player1","Level":10,"Experience":1000} - } -} -``` - -### 反序列化对象 - -从字符串还原对象: - -```csharp -public void LoadPlayer() -{ - var serializer = this.GetUtility(); - - string json = "{\"Name\":\"Player1\",\"Level\":10,\"Experience\":1000}"; - - // 反序列化为对象 - var player = serializer.Deserialize(json); - - Console.WriteLine($"玩家: {player.Name}, 等级: {player.Level}"); -} -``` - -### 运行时类型序列化 - -处理不确定类型的对象: - -```csharp -public void SerializeRuntimeType() -{ - var serializer = this.GetUtility(); - - object data = new PlayerData { Name = "Player1", Level = 10 }; - Type dataType = data.GetType(); - - // 使用运行时类型序列化 - string json = serializer.Serialize(data, dataType); - - // 使用运行时类型反序列化 - object restored = serializer.Deserialize(json, dataType); - - var player = restored as PlayerData; - Console.WriteLine($"玩家: {player?.Name}"); -} -``` - -## 高级用法 - -### 与存储系统集成 - -序列化器与存储系统配合使用: - -```csharp -using GFramework.Core.Abstractions.storage; -using GFramework.Game.storage; - -public class DataManager : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public async Task SaveData() - { - var serializer = this.GetUtility(); - var storage = this.GetUtility(); - - var gameData = new GameData - { - Score = 1000, - Coins = 500 - }; - - // 序列化数据 - string json = serializer.Serialize(gameData); - - // 写入存储 - await storage.WriteAsync("game_data", json); - } - - public async Task LoadData() - { - var serializer = this.GetUtility(); - var storage = this.GetUtility(); - - // 从存储读取 - string json = await storage.ReadAsync("game_data"); - - // 反序列化数据 - return serializer.Deserialize(json); - } -} -``` - -### 序列化复杂对象 - -处理嵌套和集合类型: - -```csharp -public class InventoryData -{ - public List Items { get; set; } - public Dictionary Resources { get; set; } -} - -public class ItemData -{ - public string Id { get; set; } - public string Name { get; set; } - public int Quantity { get; set; } -} - -public void SerializeComplexData() -{ - var serializer = this.GetUtility(); - - var inventory = new InventoryData - { - Items = new List - { - new ItemData { Id = "sword_01", Name = "铁剑", Quantity = 1 }, - new ItemData { Id = "potion_hp", Name = "生命药水", Quantity = 5 } - }, - Resources = new Dictionary - { - { "gold", 1000 }, - { "wood", 500 } - } - }; - - // 序列化复杂对象 - string json = serializer.Serialize(inventory); - - // 反序列化 - var restored = serializer.Deserialize(json); - - Console.WriteLine($"物品数量: {restored.Items.Count}"); - Console.WriteLine($"金币: {restored.Resources["gold"]}"); -} -``` - -### 处理多态类型 - -序列化继承层次结构: - -```csharp -public abstract class EntityData -{ - public string Id { get; set; } - public string Type { get; set; } -} - -public class PlayerEntityData : EntityData -{ - public int Level { get; set; } - public int Experience { get; set; } -} - -public class EnemyEntityData : EntityData -{ - public int Health { get; set; } - public int Damage { get; set; } -} - -public void SerializePolymorphic() -{ - var serializer = this.GetUtility(); - - // 创建不同类型的实体 - EntityData player = new PlayerEntityData - { - Id = "player_1", - Type = "Player", - Level = 10, - Experience = 1000 - }; - - EntityData enemy = new EnemyEntityData - { - Id = "enemy_1", - Type = "Enemy", - Health = 100, - Damage = 20 - }; - - // 使用运行时类型序列化 - string playerJson = serializer.Serialize(player, player.GetType()); - string enemyJson = serializer.Serialize(enemy, enemy.GetType()); - - // 根据类型反序列化 - var restoredPlayer = serializer.Deserialize(playerJson, typeof(PlayerEntityData)); - var restoredEnemy = serializer.Deserialize(enemyJson, typeof(EnemyEntityData)); -} -``` - -### 自定义序列化逻辑 - -虽然 GFramework 使用 Newtonsoft.Json,但你可以通过特性控制序列化行为: - -```csharp -using Newtonsoft.Json; - -public class CustomData -{ - // 忽略此属性 - [JsonIgnore] - public string InternalId { get; set; } - - // 使用不同的属性名 - [JsonProperty("player_name")] - public string Name { get; set; } - - // 仅在值不为 null 时序列化 - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public string? OptionalField { get; set; } - - // 格式化日期 - [JsonProperty("created_at")] - [JsonConverter(typeof(IsoDateTimeConverter))] - public DateTime CreatedAt { get; set; } -} -``` - -### 批量序列化 - -处理多个对象的序列化: - -```csharp -public async Task SaveMultipleData() -{ - var serializer = this.GetUtility(); - var storage = this.GetUtility(); - - var dataList = new Dictionary - { - { "player", new PlayerData { Name = "Player1", Level = 10 } }, - { "inventory", new InventoryData { Items = new List() } }, - { "settings", new SettingsData { Volume = 0.8f } } - }; - - // 批量序列化和保存 - foreach (var (key, data) in dataList) - { - string json = serializer.Serialize(data); - await storage.WriteAsync(key, json); - } - - Console.WriteLine($"已保存 {dataList.Count} 个数据文件"); -} -``` - -### 错误处理 - -处理序列化和反序列化错误: - -```csharp -public void SafeDeserialize() -{ - var serializer = this.GetUtility(); - - string json = "{\"Name\":\"Player1\",\"Level\":\"invalid\"}"; // 错误的数据 - - try - { - var player = serializer.Deserialize(json); - } - catch (ArgumentException ex) - { - Console.WriteLine($"反序列化失败: {ex.Message}"); - // 返回默认值或重新尝试 - } - catch (JsonException ex) - { - Console.WriteLine($"JSON 格式错误: {ex.Message}"); - } -} - -public PlayerData DeserializeWithFallback(string json) -{ - var serializer = this.GetUtility(); - - try - { - return serializer.Deserialize(json); - } - catch - { - // 返回默认数据 - return new PlayerData - { - Name = "DefaultPlayer", - Level = 1, - Experience = 0 - }; - } -} -``` - -### 版本兼容性 - -处理数据结构变化: - -```csharp -// 旧版本数据 -public class PlayerDataV1 -{ - public string Name { get; set; } - public int Level { get; set; } -} - -// 新版本数据(添加了新字段) -public class PlayerDataV2 -{ - public string Name { get; set; } - public int Level { get; set; } - public int Experience { get; set; } = 0; // 新增字段,提供默认值 - public DateTime LastLogin { get; set; } = DateTime.Now; // 新增字段 -} - -public PlayerDataV2 LoadWithMigration(string json) -{ - var serializer = this.GetUtility(); - - try - { - // 尝试加载新版本 - return serializer.Deserialize(json); - } - catch - { - // 如果失败,尝试加载旧版本并迁移 - var oldData = serializer.Deserialize(json); - return new PlayerDataV2 - { - Name = oldData.Name, - Level = oldData.Level, - Experience = oldData.Level * 100, // 根据等级计算经验 - LastLogin = DateTime.Now - }; - } -} -``` - -## 最佳实践 - -1. **使用接口而非具体类型**:依赖 `ISerializer` 接口 - ```csharp - ✓ var serializer = this.GetUtility(); - ✗ var serializer = new JsonSerializer(); // 避免直接实例化 - ``` - -2. **为数据类提供默认值**:确保反序列化的健壮性 - ```csharp - public class GameData - { - public string Name { get; set; } = "Default"; - public int Score { get; set; } = 0; - public List Items { get; set; } = new(); - } - ``` - -3. **处理反序列化异常**:避免程序崩溃 - ```csharp - try - { - var data = serializer.Deserialize(json); - } - catch (Exception ex) - { - Logger.Error($"反序列化失败: {ex.Message}"); - return GetDefaultData(); - } - ``` - -4. **避免序列化敏感数据**:使用 `[JsonIgnore]` 标记 - ```csharp - public class UserData - { - public string Username { get; set; } - - [JsonIgnore] - public string Password { get; set; } // 不序列化密码 - } - ``` - -5. **使用运行时类型处理多态**:保持类型信息 - ```csharp - var serializer = this.GetUtility(); - string json = serializer.Serialize(obj, obj.GetType()); - ``` - -6. **验证反序列化的数据**:确保数据完整性 - ```csharp - var data = serializer.Deserialize(json); - if (string.IsNullOrEmpty(data.Name) || data.Score < 0) - { - throw new InvalidDataException("数据验证失败"); - } - ``` - -## 性能优化 - -### 减少序列化开销 - -```csharp -// 避免频繁序列化大对象 -public class CachedSerializer -{ - private string? _cachedJson; - private GameData? _cachedData; - - public string GetJson(GameData data) - { - if (_cachedData == data && _cachedJson != null) - { - return _cachedJson; - } - - var serializer = GetSerializer(); - _cachedJson = serializer.Serialize(data); - _cachedData = data; - return _cachedJson; - } -} -``` - -### 异步序列化 - -```csharp -public async Task SaveDataAsync() -{ - var serializer = this.GetUtility(); - var storage = this.GetUtility(); - - var data = GetLargeData(); - - // 在后台线程序列化 - string json = await Task.Run(() => serializer.Serialize(data)); - - // 异步写入存储 - await storage.WriteAsync("large_data", json); -} -``` - -### 分块序列化 - -```csharp -public async Task SaveLargeDataset() -{ - var serializer = this.GetUtility(); - var storage = this.GetUtility(); - - var largeDataset = GetLargeDataset(); - - // 分块保存 - const int chunkSize = 100; - for (int i = 0; i < largeDataset.Count; i += chunkSize) - { - var chunk = largeDataset.Skip(i).Take(chunkSize).ToList(); - string json = serializer.Serialize(chunk); - await storage.WriteAsync($"data_chunk_{i / chunkSize}", json); - } -} -``` - -## 常见问题 - -### 问题:如何序列化循环引用的对象? - -**解答**: -Newtonsoft.Json 默认不支持循环引用,需要配置: - -```csharp -// 注意:GFramework 的 JsonSerializer 使用默认设置 -// 如需处理循环引用,避免创建循环引用的数据结构 -// 或使用 [JsonIgnore] 打破循环 - -public class Node -{ - public string Name { get; set; } - public List Children { get; set; } - - [JsonIgnore] // 忽略父节点引用,避免循环 - public Node? Parent { get; set; } -} -``` - -### 问题:序列化后的 JSON 太大怎么办? - -**解答**: -使用压缩或分块存储: - -```csharp -public async Task SaveCompressed() -{ - var serializer = this.GetUtility(); - var storage = this.GetUtility(); - - var data = GetLargeData(); - string json = serializer.Serialize(data); - - // 压缩 JSON - byte[] compressed = Compress(json); - - // 保存压缩数据 - await storage.WriteAsync("data_compressed", compressed); -} - -private byte[] Compress(string text) -{ - using var output = new MemoryStream(); - using (var gzip = new GZipStream(output, CompressionMode.Compress)) - using (var writer = new StreamWriter(gzip)) - { - writer.Write(text); - } - return output.ToArray(); -} -``` - -### 问题:如何处理不同平台的序列化差异? - -**解答**: -使用平台无关的数据类型: - -```csharp -public class CrossPlatformData -{ - // 使用 string 而非 DateTime(避免时区问题) - public string CreatedAt { get; set; } = DateTime.UtcNow.ToString("O"); - - // 使用 double 而非 float(精度一致) - public double Score { get; set; } - - // 明确指定编码 - public string Text { get; set; } -} -``` - -### 问题:反序列化失败时如何恢复? - -**解答**: -实现备份和恢复机制: - -```csharp -public async Task LoadWithBackup(string key) -{ - var serializer = this.GetUtility(); - var storage = this.GetUtility(); - - try - { - // 尝试加载主数据 - string json = await storage.ReadAsync(key); - return serializer.Deserialize(json); - } - catch - { - // 尝试加载备份 - try - { - string backupJson = await storage.ReadAsync($"{key}_backup"); - return serializer.Deserialize(backupJson); - } - catch - { - // 返回默认数据 - return new GameData(); - } - } -} -``` - -### 问题:如何加密序列化的数据? - -**解答**: -在序列化后加密: - -```csharp -public async Task SaveEncrypted(string key, GameData data) -{ - var serializer = this.GetUtility(); - var storage = this.GetUtility(); - - // 序列化 - string json = serializer.Serialize(data); - - // 加密 - byte[] encrypted = EncryptString(json); - - // 保存 - await storage.WriteAsync(key, encrypted); -} - -public async Task LoadEncrypted(string key) -{ - var serializer = this.GetUtility(); - var storage = this.GetUtility(); - - // 读取 - byte[] encrypted = await storage.ReadAsync(key); - - // 解密 - string json = DecryptToString(encrypted); - - // 反序列化 - return serializer.Deserialize(json); -} -``` - -### 问题:序列化器是线程安全的吗? - -**解答**: -`JsonSerializer` 本身是线程安全的,但建议通过架构的 Utility 系统访问: - -```csharp -// 线程安全的访问方式 -public async Task ParallelSave() -{ - var tasks = Enumerable.Range(0, 10).Select(async i => - { - var serializer = this.GetUtility(); - var data = new GameData { Score = i }; - string json = serializer.Serialize(data); - await SaveToStorage($"data_{i}", json); - }); - - await Task.WhenAll(tasks); -} -``` - -## 相关文档 - -- [数据与存档系统](/zh-CN/game/data) - 数据持久化 -- [存储系统](/zh-CN/game/storage) - 文件存储 -- [设置系统](/zh-CN/game/setting) - 设置数据序列化 -- [Utility 系统](/zh-CN/core/utility) - 工具类注册 +--- +title: 序列化系统 +description: 序列化系统提供了统一的对象序列化和反序列化接口,支持 JSON 格式和运行时类型处理。 +--- + +# 序列化系统 + +## 概述 + +序列化系统是 GFramework.Game 中用于对象序列化和反序列化的核心组件。它提供了统一的序列化接口,支持将对象转换为字符串格式(如 +JSON)进行存储或传输,并能够将字符串数据还原为对象。 + +序列化系统与数据存储、配置管理、存档系统等模块深度集成,为游戏数据的持久化提供了基础支持。 + +**主要特性**: + +- 统一的序列化接口 +- JSON 格式支持 +- 运行时类型序列化 +- 泛型和非泛型 API +- 与存储系统无缝集成 +- 类型安全的反序列化 + +## 核心概念 + +### 序列化器接口 + +`ISerializer` 定义了基本的序列化操作: + +```csharp +public interface ISerializer : IUtility +{ + // 将对象序列化为字符串 + string Serialize<T>(T value); + + // 将字符串反序列化为对象 + T Deserialize<T>(string data); +} +``` + +### 运行时类型序列化器 + +`IRuntimeTypeSerializer` 扩展了基本接口,支持运行时类型处理: + +```csharp +public interface IRuntimeTypeSerializer : ISerializer +{ + // 使用运行时类型序列化对象 + string Serialize(object obj, Type type); + + // 使用运行时类型反序列化对象 + object Deserialize(string data, Type type); +} +``` + +### JSON 序列化器 + +`JsonSerializer` 是基于 Newtonsoft.Json 的实现: + +```csharp +public sealed class JsonSerializer : IRuntimeTypeSerializer +{ + string Serialize<T>(T value); + T Deserialize<T>(string data); + string Serialize(object obj, Type type); + object Deserialize(string data, Type type); +} +``` + +## 基本用法 + +### 注册序列化器 + +在架构中注册序列化器: + +```csharp +using GFramework.Core.Abstractions.serializer; +using GFramework.Game.serializer; + +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 注册 JSON 序列化器 + var jsonSerializer = new JsonSerializer(); + RegisterUtility(jsonSerializer); + RegisterUtility(jsonSerializer); + } +} +``` + +### 序列化对象 + +使用泛型 API 序列化对象: + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +public class PlayerData +{ + public string Name { get; set; } + public int Level { get; set; } + public int Experience { get; set; } +} + +[ContextAware] +public partial class SaveController : IController +{ + public void SavePlayer() + { + var serializer = this.GetUtility(); + + var player = new PlayerData + { + Name = "Player1", + Level = 10, + Experience = 1000 + }; + + // 序列化为 JSON 字符串 + string json = serializer.Serialize(player); + Console.WriteLine(json); + // 输出: {"Name":"Player1","Level":10,"Experience":1000} + } +} +``` + +### 反序列化对象 + +从字符串还原对象: + +```csharp +public void LoadPlayer() +{ + var serializer = this.GetUtility(); + + string json = "{\"Name\":\"Player1\",\"Level\":10,\"Experience\":1000}"; + + // 反序列化为对象 + var player = serializer.Deserialize(json); + + Console.WriteLine($"玩家: {player.Name}, 等级: {player.Level}"); +} +``` + +### 运行时类型序列化 + +处理不确定类型的对象: + +```csharp +public void SerializeRuntimeType() +{ + var serializer = this.GetUtility(); + + object data = new PlayerData { Name = "Player1", Level = 10 }; + Type dataType = data.GetType(); + + // 使用运行时类型序列化 + string json = serializer.Serialize(data, dataType); + + // 使用运行时类型反序列化 + object restored = serializer.Deserialize(json, dataType); + + var player = restored as PlayerData; + Console.WriteLine($"玩家: {player?.Name}"); +} +``` + +## 高级用法 + +### 与存储系统集成 + +序列化器与存储系统配合使用: + +```csharp +using GFramework.Core.Abstractions.storage; +using GFramework.Game.storage; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class DataManager : IController +{ + public async Task SaveData() + { + var serializer = this.GetUtility(); + var storage = this.GetUtility(); + + var gameData = new GameData + { + Score = 1000, + Coins = 500 + }; + + // 序列化数据 + string json = serializer.Serialize(gameData); + + // 写入存储 + await storage.WriteAsync("game_data", json); + } + + public async Task LoadData() + { + var serializer = this.GetUtility(); + var storage = this.GetUtility(); + + // 从存储读取 + string json = await storage.ReadAsync("game_data"); + + // 反序列化数据 + return serializer.Deserialize(json); + } +} +``` + +### 序列化复杂对象 + +处理嵌套和集合类型: + +```csharp +public class InventoryData +{ + public List Items { get; set; } + public Dictionary Resources { get; set; } +} + +public class ItemData +{ + public string Id { get; set; } + public string Name { get; set; } + public int Quantity { get; set; } +} + +public void SerializeComplexData() +{ + var serializer = this.GetUtility(); + + var inventory = new InventoryData + { + Items = new List + { + new ItemData { Id = "sword_01", Name = "铁剑", Quantity = 1 }, + new ItemData { Id = "potion_hp", Name = "生命药水", Quantity = 5 } + }, + Resources = new Dictionary + { + { "gold", 1000 }, + { "wood", 500 } + } + }; + + // 序列化复杂对象 + string json = serializer.Serialize(inventory); + + // 反序列化 + var restored = serializer.Deserialize(json); + + Console.WriteLine($"物品数量: {restored.Items.Count}"); + Console.WriteLine($"金币: {restored.Resources["gold"]}"); +} +``` + +### 处理多态类型 + +序列化继承层次结构: + +```csharp +public abstract class EntityData +{ + public string Id { get; set; } + public string Type { get; set; } +} + +public class PlayerEntityData : EntityData +{ + public int Level { get; set; } + public int Experience { get; set; } +} + +public class EnemyEntityData : EntityData +{ + public int Health { get; set; } + public int Damage { get; set; } +} + +public void SerializePolymorphic() +{ + var serializer = this.GetUtility(); + + // 创建不同类型的实体 + EntityData player = new PlayerEntityData + { + Id = "player_1", + Type = "Player", + Level = 10, + Experience = 1000 + }; + + EntityData enemy = new EnemyEntityData + { + Id = "enemy_1", + Type = "Enemy", + Health = 100, + Damage = 20 + }; + + // 使用运行时类型序列化 + string playerJson = serializer.Serialize(player, player.GetType()); + string enemyJson = serializer.Serialize(enemy, enemy.GetType()); + + // 根据类型反序列化 + var restoredPlayer = serializer.Deserialize(playerJson, typeof(PlayerEntityData)); + var restoredEnemy = serializer.Deserialize(enemyJson, typeof(EnemyEntityData)); +} +``` + +### 自定义序列化逻辑 + +虽然 GFramework 使用 Newtonsoft.Json,但你可以通过特性控制序列化行为: + +```csharp +using Newtonsoft.Json; + +public class CustomData +{ + // 忽略此属性 + [JsonIgnore] + public string InternalId { get; set; } + + // 使用不同的属性名 + [JsonProperty("player_name")] + public string Name { get; set; } + + // 仅在值不为 null 时序列化 + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string? OptionalField { get; set; } + + // 格式化日期 + [JsonProperty("created_at")] + [JsonConverter(typeof(IsoDateTimeConverter))] + public DateTime CreatedAt { get; set; } +} +``` + +### 批量序列化 + +处理多个对象的序列化: + +```csharp +public async Task SaveMultipleData() +{ + var serializer = this.GetUtility(); + var storage = this.GetUtility(); + + var dataList = new Dictionary + { + { "player", new PlayerData { Name = "Player1", Level = 10 } }, + { "inventory", new InventoryData { Items = new List() } }, + { "settings", new SettingsData { Volume = 0.8f } } + }; + + // 批量序列化和保存 + foreach (var (key, data) in dataList) + { + string json = serializer.Serialize(data); + await storage.WriteAsync(key, json); + } + + Console.WriteLine($"已保存 {dataList.Count} 个数据文件"); +} +``` + +### 错误处理 + +处理序列化和反序列化错误: + +```csharp +public void SafeDeserialize() +{ + var serializer = this.GetUtility(); + + string json = "{\"Name\":\"Player1\",\"Level\":\"invalid\"}"; // 错误的数据 + + try + { + var player = serializer.Deserialize(json); + } + catch (ArgumentException ex) + { + Console.WriteLine($"反序列化失败: {ex.Message}"); + // 返回默认值或重新尝试 + } + catch (JsonException ex) + { + Console.WriteLine($"JSON 格式错误: {ex.Message}"); + } +} + +public PlayerData DeserializeWithFallback(string json) +{ + var serializer = this.GetUtility(); + + try + { + return serializer.Deserialize(json); + } + catch + { + // 返回默认数据 + return new PlayerData + { + Name = "DefaultPlayer", + Level = 1, + Experience = 0 + }; + } +} +``` + +### 版本兼容性 + +处理数据结构变化: + +```csharp +// 旧版本数据 +public class PlayerDataV1 +{ + public string Name { get; set; } + public int Level { get; set; } +} + +// 新版本数据(添加了新字段) +public class PlayerDataV2 +{ + public string Name { get; set; } + public int Level { get; set; } + public int Experience { get; set; } = 0; // 新增字段,提供默认值 + public DateTime LastLogin { get; set; } = DateTime.Now; // 新增字段 +} + +public PlayerDataV2 LoadWithMigration(string json) +{ + var serializer = this.GetUtility(); + + try + { + // 尝试加载新版本 + return serializer.Deserialize(json); + } + catch + { + // 如果失败,尝试加载旧版本并迁移 + var oldData = serializer.Deserialize(json); + return new PlayerDataV2 + { + Name = oldData.Name, + Level = oldData.Level, + Experience = oldData.Level * 100, // 根据等级计算经验 + LastLogin = DateTime.Now + }; + } +} +``` + +## 最佳实践 + +1. **使用接口而非具体类型**:依赖 `ISerializer` 接口 + ```csharp + ✓ var serializer = this.GetUtility(); + ✗ var serializer = new JsonSerializer(); // 避免直接实例化 + ``` + +2. **为数据类提供默认值**:确保反序列化的健壮性 + ```csharp + public class GameData + { + public string Name { get; set; } = "Default"; + public int Score { get; set; } = 0; + public List Items { get; set; } = new(); + } + ``` + +3. **处理反序列化异常**:避免程序崩溃 + ```csharp + try + { + var data = serializer.Deserialize(json); + } + catch (Exception ex) + { + Logger.Error($"反序列化失败: {ex.Message}"); + return GetDefaultData(); + } + ``` + +4. **避免序列化敏感数据**:使用 `[JsonIgnore]` 标记 + ```csharp + public class UserData + { + public string Username { get; set; } + + [JsonIgnore] + public string Password { get; set; } // 不序列化密码 + } + ``` + +5. **使用运行时类型处理多态**:保持类型信息 + ```csharp + var serializer = this.GetUtility(); + string json = serializer.Serialize(obj, obj.GetType()); + ``` + +6. **验证反序列化的数据**:确保数据完整性 + ```csharp + var data = serializer.Deserialize(json); + if (string.IsNullOrEmpty(data.Name) || data.Score < 0) + { + throw new InvalidDataException("数据验证失败"); + } + ``` + +## 性能优化 + +### 减少序列化开销 + +```csharp +// 避免频繁序列化大对象 +public class CachedSerializer +{ + private string? _cachedJson; + private GameData? _cachedData; + + public string GetJson(GameData data) + { + if (_cachedData == data && _cachedJson != null) + { + return _cachedJson; + } + + var serializer = GetSerializer(); + _cachedJson = serializer.Serialize(data); + _cachedData = data; + return _cachedJson; + } +} +``` + +### 异步序列化 + +```csharp +public async Task SaveDataAsync() +{ + var serializer = this.GetUtility(); + var storage = this.GetUtility(); + + var data = GetLargeData(); + + // 在后台线程序列化 + string json = await Task.Run(() => serializer.Serialize(data)); + + // 异步写入存储 + await storage.WriteAsync("large_data", json); +} +``` + +### 分块序列化 + +```csharp +public async Task SaveLargeDataset() +{ + var serializer = this.GetUtility(); + var storage = this.GetUtility(); + + var largeDataset = GetLargeDataset(); + + // 分块保存 + const int chunkSize = 100; + for (int i = 0; i < largeDataset.Count; i += chunkSize) + { + var chunk = largeDataset.Skip(i).Take(chunkSize).ToList(); + string json = serializer.Serialize(chunk); + await storage.WriteAsync($"data_chunk_{i / chunkSize}", json); + } +} +``` + +## 常见问题 + +### 问题:如何序列化循环引用的对象? + +**解答**: +Newtonsoft.Json 默认不支持循环引用,需要配置: + +```csharp +// 注意:GFramework 的 JsonSerializer 使用默认设置 +// 如需处理循环引用,避免创建循环引用的数据结构 +// 或使用 [JsonIgnore] 打破循环 + +public class Node +{ + public string Name { get; set; } + public List Children { get; set; } + + [JsonIgnore] // 忽略父节点引用,避免循环 + public Node? Parent { get; set; } +} +``` + +### 问题:序列化后的 JSON 太大怎么办? + +**解答**: +使用压缩或分块存储: + +```csharp +public async Task SaveCompressed() +{ + var serializer = this.GetUtility(); + var storage = this.GetUtility(); + + var data = GetLargeData(); + string json = serializer.Serialize(data); + + // 压缩 JSON + byte[] compressed = Compress(json); + + // 保存压缩数据 + await storage.WriteAsync("data_compressed", compressed); +} + +private byte[] Compress(string text) +{ + using var output = new MemoryStream(); + using (var gzip = new GZipStream(output, CompressionMode.Compress)) + using (var writer = new StreamWriter(gzip)) + { + writer.Write(text); + } + return output.ToArray(); +} +``` + +### 问题:如何处理不同平台的序列化差异? + +**解答**: +使用平台无关的数据类型: + +```csharp +public class CrossPlatformData +{ + // 使用 string 而非 DateTime(避免时区问题) + public string CreatedAt { get; set; } = DateTime.UtcNow.ToString("O"); + + // 使用 double 而非 float(精度一致) + public double Score { get; set; } + + // 明确指定编码 + public string Text { get; set; } +} +``` + +### 问题:反序列化失败时如何恢复? + +**解答**: +实现备份和恢复机制: + +```csharp +public async Task LoadWithBackup(string key) +{ + var serializer = this.GetUtility(); + var storage = this.GetUtility(); + + try + { + // 尝试加载主数据 + string json = await storage.ReadAsync(key); + return serializer.Deserialize(json); + } + catch + { + // 尝试加载备份 + try + { + string backupJson = await storage.ReadAsync($"{key}_backup"); + return serializer.Deserialize(backupJson); + } + catch + { + // 返回默认数据 + return new GameData(); + } + } +} +``` + +### 问题:如何加密序列化的数据? + +**解答**: +在序列化后加密: + +```csharp +public async Task SaveEncrypted(string key, GameData data) +{ + var serializer = this.GetUtility(); + var storage = this.GetUtility(); + + // 序列化 + string json = serializer.Serialize(data); + + // 加密 + byte[] encrypted = EncryptString(json); + + // 保存 + await storage.WriteAsync(key, encrypted); +} + +public async Task LoadEncrypted(string key) +{ + var serializer = this.GetUtility(); + var storage = this.GetUtility(); + + // 读取 + byte[] encrypted = await storage.ReadAsync(key); + + // 解密 + string json = DecryptToString(encrypted); + + // 反序列化 + return serializer.Deserialize(json); +} +``` + +### 问题:序列化器是线程安全的吗? + +**解答**: +`JsonSerializer` 本身是线程安全的,但建议通过架构的 Utility 系统访问: + +```csharp +// 线程安全的访问方式 +public async Task ParallelSave() +{ + var tasks = Enumerable.Range(0, 10).Select(async i => + { + var serializer = this.GetUtility(); + var data = new GameData { Score = i }; + string json = serializer.Serialize(data); + await SaveToStorage($"data_{i}", json); + }); + + await Task.WhenAll(tasks); +} +``` + +## 相关文档 + +- [数据与存档系统](/zh-CN/game/data) - 数据持久化 +- [存储系统](/zh-CN/game/storage) - 文件存储 +- [设置系统](/zh-CN/game/setting) - 设置数据序列化 +- [Utility 系统](/zh-CN/core/utility) - 工具类注册 diff --git a/docs/zh-CN/game/ui.md b/docs/zh-CN/game/ui.md index 7113bb4..6597eef 100644 --- a/docs/zh-CN/game/ui.md +++ b/docs/zh-CN/game/ui.md @@ -1,496 +1,509 @@ ---- -title: UI 系统 -description: UI 系统提供了完整的 UI 页面管理、路由导航和多层级显示功能。 ---- - -# UI 系统 - -## 概述 - -UI 系统是 GFramework.Game 中用于管理游戏 UI 界面的核心组件。它提供了 UI 页面的生命周期管理、基于栈的导航机制,以及多层级的 -UI 显示系统(Page、Overlay、Modal、Toast、Topmost)。 - -通过 UI 系统,你可以轻松实现 UI 页面之间的切换,管理 UI 栈(如主菜单 -> 设置 -> 关于),以及在不同层级显示各种类型的 -UI(对话框、提示、加载界面等)。 - -**主要特性**: - -- 完整的 UI 生命周期管理 -- 基于栈的 UI 导航 -- 多层级 UI 显示(5 个层级) -- UI 转换管道和钩子 -- 路由守卫(Route Guard) -- UI 工厂和行为模式 - -## 核心概念 - -### UI 页面接口 - -`IUiPage` 定义了 UI 页面的生命周期: - -```csharp -public interface IUiPage -{ - void OnEnter(IUiPageEnterParam? param); // 进入页面 - void OnExit(); // 退出页面 - void OnPause(); // 暂停页面 - void OnResume(); // 恢复页面 - void OnShow(); // 显示页面 - void OnHide(); // 隐藏页面 -} -``` - -### UI 路由 - -`IUiRouter` 管理 UI 的导航和切换: - -```csharp -public interface IUiRouter : ISystem -{ - int Count { get; } // UI 栈深度 - IUiPageBehavior? Peek(); // 栈顶 UI - - ValueTask PushAsync(string uiKey, IUiPageEnterParam? param = null); - ValueTask PopAsync(UiPopPolicy policy = UiPopPolicy.Destroy); - ValueTask ReplaceAsync(string uiKey, IUiPageEnterParam? param = null); - ValueTask ClearAsync(); -} -``` - -### UI 层级 - -UI 系统支持 5 个显示层级: - -```csharp -public enum UiLayer -{ - Page, // 页面层(栈管理,不可重入) - Overlay, // 浮层(可重入,对话框等) - Modal, // 模态层(可重入,带遮罩) - Toast, // 提示层(可重入,轻量提示) - Topmost // 顶层(不可重入,系统级) -} -``` - -## 基本用法 - -### 定义 UI 页面 - -实现 `IUiPage` 接口创建 UI 页面: - -```csharp -using GFramework.Game.Abstractions.ui; - -public class MainMenuPage : IUiPage -{ - public void OnEnter(IUiPageEnterParam? param) - { - Console.WriteLine("进入主菜单"); - // 初始化 UI、绑定事件 - } - - public void OnExit() - { - Console.WriteLine("退出主菜单"); - // 清理资源、解绑事件 - } - - public void OnPause() - { - Console.WriteLine("暂停主菜单"); - // 暂停动画、停止交互 - } - - public void OnResume() - { - Console.WriteLine("恢复主菜单"); - // 恢复动画、启用交互 - } - - public void OnShow() - { - Console.WriteLine("显示主菜单"); - // 显示 UI 元素 - } - - public void OnHide() - { - Console.WriteLine("隐藏主菜单"); - // 隐藏 UI 元素 - } -} -``` - -### 切换 UI 页面 - -使用 UI 路由进行导航: - -```csharp -public class UiController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public async Task ShowSettings() - { - var uiRouter = this.GetSystem(); - - // 压入设置页面(保留当前页面) - await uiRouter.PushAsync("Settings"); - } - - public async Task CloseSettings() - { - var uiRouter = this.GetSystem(); - - // 弹出当前页面(返回上一页) - await uiRouter.PopAsync(); - } - - public async Task ShowMainMenu() - { - var uiRouter = this.GetSystem(); - - // 替换所有页面(清空 UI 栈) - await uiRouter.ReplaceAsync("MainMenu"); - } -} -``` - -### 显示不同层级的 UI - -```csharp -public class UiController : IController -{ - public IArchitecture GetArchitecture() => GameArchitecture.Interface; - - public void ShowDialog() - { - var uiRouter = this.GetSystem(); - - // 在 Modal 层显示对话框 - var handle = uiRouter.Show("ConfirmDialog", UiLayer.Modal); - } - - public void ShowToast(string message) - { - var uiRouter = this.GetSystem(); - - // 在 Toast 层显示提示 - var handle = uiRouter.Show("ToastMessage", UiLayer.Toast, - new ToastParam { Message = message }); - } - - public void ShowLoading() - { - var uiRouter = this.GetSystem(); - - // 在 Topmost 层显示加载界面 - var handle = uiRouter.Show("LoadingScreen", UiLayer.Topmost); - } -} -``` - -## 高级用法 - -### UI 参数传递 - -```csharp -// 定义 UI 参数 -public class SettingsEnterParam : IUiPageEnterParam -{ - public string Category { get; set; } -} - -// 在 UI 中接收参数 -public class SettingsPage : IUiPage -{ - private string _category; - - public void OnEnter(IUiPageEnterParam? param) - { - if (param is SettingsEnterParam settingsParam) - { - _category = settingsParam.Category; - Console.WriteLine($"打开设置分类: {_category}"); - } - } - - // ... 其他生命周期方法 -} - -// 传递参数 -await uiRouter.PushAsync("Settings", new SettingsEnterParam -{ - Category = "Audio" -}); -``` - -### 路由守卫 - -```csharp -using GFramework.Game.Abstractions.ui; - -public class UnsavedChangesGuard : IUiRouteGuard -{ - public async ValueTask CanLeaveAsync( - IUiPageBehavior from, - string toKey, - IUiPageEnterParam? param) - { - // 检查是否有未保存的更改 - if (from.Key == "Settings" && HasUnsavedChanges()) - { - var confirmed = await ShowConfirmDialog(); - return confirmed; - } - - return true; - } - - public async ValueTask CanEnterAsync( - string toKey, - IUiPageEnterParam? param) - { - // 进入前的验证 - return true; - } - - private bool HasUnsavedChanges() => true; - private async Task ShowConfirmDialog() => await Task.FromResult(true); -} - -// 注册守卫 -uiRouter.AddGuard(new UnsavedChangesGuard()); -``` - -### UI 转换处理器 - -```csharp -using GFramework.Game.Abstractions.ui; - -public class FadeTransitionHandler : IUiTransitionHandler -{ - public async ValueTask OnBeforeEnterAsync(UiTransitionEvent @event) - { - Console.WriteLine($"准备进入 UI: {@event.ToKey}"); - await PlayFadeIn(); - } - - public async ValueTask OnAfterEnterAsync(UiTransitionEvent @event) - { - Console.WriteLine($"已进入 UI: {@event.ToKey}"); - } - - public async ValueTask OnBeforeExitAsync(UiTransitionEvent @event) - { - Console.WriteLine($"准备退出 UI: {@event.FromKey}"); - await PlayFadeOut(); - } - - public async ValueTask OnAfterExitAsync(UiTransitionEvent @event) - { - Console.WriteLine($"已退出 UI: {@event.FromKey}"); - } - - private async Task PlayFadeIn() => await Task.Delay(200); - private async Task PlayFadeOut() => await Task.Delay(200); -} - -// 注册转换处理器 -uiRouter.RegisterHandler(new FadeTransitionHandler()); -``` - -### UI 句柄管理 - -```csharp -public class DialogController : IController -{ - private UiHandle? _dialogHandle; - - public void ShowDialog() - { - var uiRouter = this.GetSystem(); - - // 显示对话框并保存句柄 - _dialogHandle = uiRouter.Show("ConfirmDialog", UiLayer.Modal); - } - - public void CloseDialog() - { - if (_dialogHandle.HasValue) - { - var uiRouter = this.GetSystem(); - - // 使用句柄关闭对话框 - uiRouter.Hide(_dialogHandle.Value, UiLayer.Modal, destroy: true); - _dialogHandle = null; - } - } -} -``` - -### UI 栈管理 - -```csharp -public class NavigationController : IController -{ - public void ShowUiStack() - { - var uiRouter = this.GetSystem(); - - Console.WriteLine($"UI 栈深度: {uiRouter.Count}"); - - var current = uiRouter.Peek(); - if (current != null) - { - Console.WriteLine($"当前 UI: {current.Key}"); - } - } - - public bool IsSettingsOpen() - { - var uiRouter = this.GetSystem(); - return uiRouter.Contains("Settings"); - } - - public bool IsTopPage(string uiKey) - { - var uiRouter = this.GetSystem(); - return uiRouter.IsTop(uiKey); - } -} -``` - -### 多层级 UI 管理 - -```csharp -public class LayerController : IController -{ - public void ShowMultipleToasts() - { - var uiRouter = this.GetSystem(); - - // Toast 层支持重入,可以同时显示多个 - uiRouter.Show("Toast1", UiLayer.Toast); - uiRouter.Show("Toast2", UiLayer.Toast); - uiRouter.Show("Toast3", UiLayer.Toast); - } - - public void ClearAllToasts() - { - var uiRouter = this.GetSystem(); - - // 清空 Toast 层的所有 UI - uiRouter.ClearLayer(UiLayer.Toast, destroy: true); - } - - public void HideAllDialogs() - { - var uiRouter = this.GetSystem(); - - // 隐藏 Modal 层的所有对话框 - uiRouter.HideByKey("ConfirmDialog", UiLayer.Modal, hideAll: true); - } -} -``` - -## 最佳实践 - -1. **使用合适的层级**:根据 UI 类型选择正确的层级 - ```csharp - ✓ Page: 主要页面(主菜单、设置、游戏界面) - ✓ Overlay: 浮层(信息面板、小窗口) - ✓ Modal: 模态对话框(确认框、输入框) - ✓ Toast: 轻量提示(消息、通知) - ✓ Topmost: 系统级(加载界面、全屏遮罩) - ``` - -2. **使用 Push/Pop 管理临时 UI**:如设置、帮助页面 - ```csharp - // 打开设置(保留当前页面) - await uiRouter.PushAsync("Settings"); - - // 关闭设置(返回上一页) - await uiRouter.PopAsync(); - ``` - -3. **使用 Replace 切换主要页面**:如从菜单到游戏 - ```csharp - // 开始游戏(清空 UI 栈) - await uiRouter.ReplaceAsync("Gameplay"); - ``` - -4. **在 OnEnter/OnExit 中管理资源**:保持资源管理清晰 - ```csharp - public void OnEnter(IUiPageEnterParam? param) - { - // 加载资源、绑定事件 - BindEvents(); - } - - public void OnExit() - { - // 清理资源、解绑事件 - UnbindEvents(); - } - ``` - -5. **使用句柄管理非栈 UI**:对于 Overlay、Modal、Toast 层 - ```csharp - // 保存句柄 - var handle = uiRouter.Show("Dialog", UiLayer.Modal); - - // 使用句柄关闭 - uiRouter.Hide(handle, UiLayer.Modal, destroy: true); - ``` - -6. **避免在 UI 切换时阻塞**:使用异步操作 - ```csharp - ✓ await uiRouter.PushAsync("Settings"); - ✗ uiRouter.PushAsync("Settings").Wait(); // 可能死锁 - ``` - -## 常见问题 - -### 问题:Push、Pop、Replace 有什么区别? - -**解答**: - -- **Push**:压入新 UI,暂停当前 UI(用于临时页面) -- **Pop**:弹出当前 UI,恢复上一个 UI(用于关闭临时页面) -- **Replace**:清空 UI 栈,加载新 UI(用于主要页面切换) - -### 问题:什么时候使用不同的 UI 层级? - -**解答**: - -- **Page**:主要页面,使用栈管理 -- **Overlay**:浮层,可叠加显示 -- **Modal**:模态对话框,阻挡下层交互 -- **Toast**:轻量提示,不阻挡交互 -- **Topmost**:系统级,最高优先级 - -### 问题:如何在 UI 之间传递数据? - -**解答**: - -1. 通过 UI 参数 -2. 通过 Model -3. 通过事件 - -### 问题:UI 切换时如何显示过渡动画? - -**解答**: -使用 UI 转换处理器在 `OnBeforeEnter`/`OnAfterExit` 中播放动画。 - -### 问题:如何防止用户在 UI 切换时操作? - -**解答**: -在转换处理器中显示遮罩或禁用输入。 - -## 相关文档 - -- [场景系统](/zh-CN/game/scene) - 场景管理 -- [Godot UI 系统](/zh-CN/godot/ui) - Godot 引擎集成 -- [事件系统](/zh-CN/core/events) - UI 事件通信 -- [状态机系统](/zh-CN/core/state-machine) - UI 状态管理 +--- +title: UI 系统 +description: UI 系统提供了完整的 UI 页面管理、路由导航和多层级显示功能。 +--- + +# UI 系统 + +## 概述 + +UI 系统是 GFramework.Game 中用于管理游戏 UI 界面的核心组件。它提供了 UI 页面的生命周期管理、基于栈的导航机制,以及多层级的 +UI 显示系统(Page、Overlay、Modal、Toast、Topmost)。 + +通过 UI 系统,你可以轻松实现 UI 页面之间的切换,管理 UI 栈(如主菜单 -> 设置 -> 关于),以及在不同层级显示各种类型的 +UI(对话框、提示、加载界面等)。 + +**主要特性**: + +- 完整的 UI 生命周期管理 +- 基于栈的 UI 导航 +- 多层级 UI 显示(5 个层级) +- UI 转换管道和钩子 +- 路由守卫(Route Guard) +- UI 工厂和行为模式 + +## 核心概念 + +### UI 页面接口 + +`IUiPage` 定义了 UI 页面的生命周期: + +```csharp +public interface IUiPage +{ + void OnEnter(IUiPageEnterParam? param); // 进入页面 + void OnExit(); // 退出页面 + void OnPause(); // 暂停页面 + void OnResume(); // 恢复页面 + void OnShow(); // 显示页面 + void OnHide(); // 隐藏页面 +} +``` + +### UI 路由 + +`IUiRouter` 管理 UI 的导航和切换: + +```csharp +public interface IUiRouter : ISystem +{ + int Count { get; } // UI 栈深度 + IUiPageBehavior? Peek(); // 栈顶 UI + + ValueTask PushAsync(string uiKey, IUiPageEnterParam? param = null); + ValueTask PopAsync(UiPopPolicy policy = UiPopPolicy.Destroy); + ValueTask ReplaceAsync(string uiKey, IUiPageEnterParam? param = null); + ValueTask ClearAsync(); +} +``` + +### UI 层级 + +UI 系统支持 5 个显示层级: + +```csharp +public enum UiLayer +{ + Page, // 页面层(栈管理,不可重入) + Overlay, // 浮层(可重入,对话框等) + Modal, // 模态层(可重入,带遮罩) + Toast, // 提示层(可重入,轻量提示) + Topmost // 顶层(不可重入,系统级) +} +``` + +## 基本用法 + +### 定义 UI 页面 + +实现 `IUiPage` 接口创建 UI 页面: + +```csharp +using GFramework.Game.Abstractions.ui; + +public class MainMenuPage : IUiPage +{ + public void OnEnter(IUiPageEnterParam? param) + { + Console.WriteLine("进入主菜单"); + // 初始化 UI、绑定事件 + } + + public void OnExit() + { + Console.WriteLine("退出主菜单"); + // 清理资源、解绑事件 + } + + public void OnPause() + { + Console.WriteLine("暂停主菜单"); + // 暂停动画、停止交互 + } + + public void OnResume() + { + Console.WriteLine("恢复主菜单"); + // 恢复动画、启用交互 + } + + public void OnShow() + { + Console.WriteLine("显示主菜单"); + // 显示 UI 元素 + } + + public void OnHide() + { + Console.WriteLine("隐藏主菜单"); + // 隐藏 UI 元素 + } +} +``` + +### 切换 UI 页面 + +使用 UI 路由进行导航: + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class UiController : IController +{ + public async Task ShowSettings() + { + var uiRouter = this.GetSystem(); + + // 压入设置页面(保留当前页面) + await uiRouter.PushAsync("Settings"); + } + + public async Task CloseSettings() + { + var uiRouter = this.GetSystem(); + + // 弹出当前页面(返回上一页) + await uiRouter.PopAsync(); + } + + public async Task ShowMainMenu() + { + var uiRouter = this.GetSystem(); + + // 替换所有页面(清空 UI 栈) + await uiRouter.ReplaceAsync("MainMenu"); + } +} +``` + +### 显示不同层级的 UI + +```csharp +[ContextAware] +public partial class UiController : IController +{ + public void ShowDialog() + { + var uiRouter = this.GetSystem(); + + // 在 Modal 层显示对话框 + var handle = uiRouter.Show("ConfirmDialog", UiLayer.Modal); + } + + public void ShowToast(string message) + { + var uiRouter = this.GetSystem(); + + // 在 Toast 层显示提示 + var handle = uiRouter.Show("ToastMessage", UiLayer.Toast, + new ToastParam { Message = message }); + } + + public void ShowLoading() + { + var uiRouter = this.GetSystem(); + + // 在 Topmost 层显示加载界面 + var handle = uiRouter.Show("LoadingScreen", UiLayer.Topmost); + } +} +``` + +## 高级用法 + +### UI 参数传递 + +```csharp +// 定义 UI 参数 +public class SettingsEnterParam : IUiPageEnterParam +{ + public string Category { get; set; } +} + +// 在 UI 中接收参数 +public class SettingsPage : IUiPage +{ + private string _category; + + public void OnEnter(IUiPageEnterParam? param) + { + if (param is SettingsEnterParam settingsParam) + { + _category = settingsParam.Category; + Console.WriteLine($"打开设置分类: {_category}"); + } + } + + // ... 其他生命周期方法 +} + +// 传递参数 +await uiRouter.PushAsync("Settings", new SettingsEnterParam +{ + Category = "Audio" +}); +``` + +### 路由守卫 + +```csharp +using GFramework.Game.Abstractions.ui; + +public class UnsavedChangesGuard : IUiRouteGuard +{ + public async ValueTask CanLeaveAsync( + IUiPageBehavior from, + string toKey, + IUiPageEnterParam? param) + { + // 检查是否有未保存的更改 + if (from.Key == "Settings" && HasUnsavedChanges()) + { + var confirmed = await ShowConfirmDialog(); + return confirmed; + } + + return true; + } + + public async ValueTask CanEnterAsync( + string toKey, + IUiPageEnterParam? param) + { + // 进入前的验证 + return true; + } + + private bool HasUnsavedChanges() => true; + private async Task ShowConfirmDialog() => await Task.FromResult(true); +} + +// 注册守卫 +uiRouter.AddGuard(new UnsavedChangesGuard()); +``` + +### UI 转换处理器 + +```csharp +using GFramework.Game.Abstractions.ui; + +public class FadeTransitionHandler : IUiTransitionHandler +{ + public async ValueTask OnBeforeEnterAsync(UiTransitionEvent @event) + { + Console.WriteLine($"准备进入 UI: {@event.ToKey}"); + await PlayFadeIn(); + } + + public async ValueTask OnAfterEnterAsync(UiTransitionEvent @event) + { + Console.WriteLine($"已进入 UI: {@event.ToKey}"); + } + + public async ValueTask OnBeforeExitAsync(UiTransitionEvent @event) + { + Console.WriteLine($"准备退出 UI: {@event.FromKey}"); + await PlayFadeOut(); + } + + public async ValueTask OnAfterExitAsync(UiTransitionEvent @event) + { + Console.WriteLine($"已退出 UI: {@event.FromKey}"); + } + + private async Task PlayFadeIn() => await Task.Delay(200); + private async Task PlayFadeOut() => await Task.Delay(200); +} + +// 注册转换处理器 +uiRouter.RegisterHandler(new FadeTransitionHandler()); +``` + +### UI 句柄管理 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class DialogController : IController +{ + private UiHandle? _dialogHandle; + + public void ShowDialog() + { + var uiRouter = this.GetSystem(); + + // 显示对话框并保存句柄 + _dialogHandle = uiRouter.Show("ConfirmDialog", UiLayer.Modal); + } + + public void CloseDialog() + { + if (_dialogHandle.HasValue) + { + var uiRouter = this.GetSystem(); + + // 使用句柄关闭对话框 + uiRouter.Hide(_dialogHandle.Value, UiLayer.Modal, destroy: true); + _dialogHandle = null; + } + } +} +``` + +### UI 栈管理 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class NavigationController : IController +{ + public void ShowUiStack() + { + var uiRouter = this.GetSystem(); + + Console.WriteLine($"UI 栈深度: {uiRouter.Count}"); + + var current = uiRouter.Peek(); + if (current != null) + { + Console.WriteLine($"当前 UI: {current.Key}"); + } + } + + public bool IsSettingsOpen() + { + var uiRouter = this.GetSystem(); + return uiRouter.Contains("Settings"); + } + + public bool IsTopPage(string uiKey) + { + var uiRouter = this.GetSystem(); + return uiRouter.IsTop(uiKey); + } +} +``` + +### 多层级 UI 管理 + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class LayerController : IController +{ + public void ShowMultipleToasts() + { + var uiRouter = this.GetSystem(); + + // Toast 层支持重入,可以同时显示多个 + uiRouter.Show("Toast1", UiLayer.Toast); + uiRouter.Show("Toast2", UiLayer.Toast); + uiRouter.Show("Toast3", UiLayer.Toast); + } + + public void ClearAllToasts() + { + var uiRouter = this.GetSystem(); + + // 清空 Toast 层的所有 UI + uiRouter.ClearLayer(UiLayer.Toast, destroy: true); + } + + public void HideAllDialogs() + { + var uiRouter = this.GetSystem(); + + // 隐藏 Modal 层的所有对话框 + uiRouter.HideByKey("ConfirmDialog", UiLayer.Modal, hideAll: true); + } +} +``` + +## 最佳实践 + +1. **使用合适的层级**:根据 UI 类型选择正确的层级 + ```csharp + ✓ Page: 主要页面(主菜单、设置、游戏界面) + ✓ Overlay: 浮层(信息面板、小窗口) + ✓ Modal: 模态对话框(确认框、输入框) + ✓ Toast: 轻量提示(消息、通知) + ✓ Topmost: 系统级(加载界面、全屏遮罩) + ``` + +2. **使用 Push/Pop 管理临时 UI**:如设置、帮助页面 + ```csharp + // 打开设置(保留当前页面) + await uiRouter.PushAsync("Settings"); + + // 关闭设置(返回上一页) + await uiRouter.PopAsync(); + ``` + +3. **使用 Replace 切换主要页面**:如从菜单到游戏 + ```csharp + // 开始游戏(清空 UI 栈) + await uiRouter.ReplaceAsync("Gameplay"); + ``` + +4. **在 OnEnter/OnExit 中管理资源**:保持资源管理清晰 + ```csharp + public void OnEnter(IUiPageEnterParam? param) + { + // 加载资源、绑定事件 + BindEvents(); + } + + public void OnExit() + { + // 清理资源、解绑事件 + UnbindEvents(); + } + ``` + +5. **使用句柄管理非栈 UI**:对于 Overlay、Modal、Toast 层 + ```csharp + // 保存句柄 + var handle = uiRouter.Show("Dialog", UiLayer.Modal); + + // 使用句柄关闭 + uiRouter.Hide(handle, UiLayer.Modal, destroy: true); + ``` + +6. **避免在 UI 切换时阻塞**:使用异步操作 + ```csharp + ✓ await uiRouter.PushAsync("Settings"); + ✗ uiRouter.PushAsync("Settings").Wait(); // 可能死锁 + ``` + +## 常见问题 + +### 问题:Push、Pop、Replace 有什么区别? + +**解答**: + +- **Push**:压入新 UI,暂停当前 UI(用于临时页面) +- **Pop**:弹出当前 UI,恢复上一个 UI(用于关闭临时页面) +- **Replace**:清空 UI 栈,加载新 UI(用于主要页面切换) + +### 问题:什么时候使用不同的 UI 层级? + +**解答**: + +- **Page**:主要页面,使用栈管理 +- **Overlay**:浮层,可叠加显示 +- **Modal**:模态对话框,阻挡下层交互 +- **Toast**:轻量提示,不阻挡交互 +- **Topmost**:系统级,最高优先级 + +### 问题:如何在 UI 之间传递数据? + +**解答**: + +1. 通过 UI 参数 +2. 通过 Model +3. 通过事件 + +### 问题:UI 切换时如何显示过渡动画? + +**解答**: +使用 UI 转换处理器在 `OnBeforeEnter`/`OnAfterExit` 中播放动画。 + +### 问题:如何防止用户在 UI 切换时操作? + +**解答**: +在转换处理器中显示遮罩或禁用输入。 + +## 相关文档 + +- [场景系统](/zh-CN/game/scene) - 场景管理 +- [Godot UI 系统](/zh-CN/godot/ui) - Godot 引擎集成 +- [事件系统](/zh-CN/core/events) - UI 事件通信 +- [状态机系统](/zh-CN/core/state-machine) - UI 状态管理 diff --git a/docs/zh-CN/getting-started/quick-start.md b/docs/zh-CN/getting-started/quick-start.md index f9e7d65..824b01b 100644 --- a/docs/zh-CN/getting-started/quick-start.md +++ b/docs/zh-CN/getting-started/quick-start.md @@ -1,320 +1,323 @@ -# 快速开始 - -本指南将帮助您快速构建第一个基于 GFramework 的应用程序。 - -## 1. 创建项目架构 - -首先定义您的应用架构: - -```csharp -using GFramework.Core.architecture; - -public class GameArchitecture : Architecture -{ - protected override void Init() - { - // 注册模型 - 存储应用状态 - RegisterModel(new PlayerModel()); - RegisterModel(new GameStateModel()); - - // 注册系统 - 处理业务逻辑 - RegisterSystem(new PlayerSystem()); - RegisterSystem(new GameLogicSystem()); - - // 注册工具类 - 提供辅助功能 - RegisterUtility(new StorageUtility()); - } -} -``` - -## 2. 定义数据模型 - -创建您的数据模型: - -```csharp -public class PlayerModel : AbstractModel -{ - // 使用可绑定属性实现响应式数据 - public BindableProperty Name { get; } = new("Player"); - public BindableProperty Health { get; } = new(100); - public BindableProperty Score { get; } = new(0); - - protected override void OnInit() - { - // 监听健康值变化 - Health.Register(OnHealthChanged); - } - - private void OnHealthChanged(int newHealth) - { - if (newHealth <= 0) - { - this.SendEvent(new PlayerDiedEvent()); - } - } -} - -public class GameStateModel : AbstractModel -{ - public BindableProperty IsGameRunning { get; } = new(false); - public BindableProperty CurrentLevel { get; } = new(1); -} -``` - -## 3. 实现业务逻辑 - -创建处理业务逻辑的系统: - -```csharp -public class PlayerSystem : AbstractSystem -{ - protected override void OnInit() - { - // 监听玩家输入事件 - this.RegisterEvent(OnPlayerMove); - this.RegisterEvent(OnPlayerAttack); - } - - private void OnPlayerMove(PlayerMoveEvent e) - { - var playerModel = this.GetModel(); - // 处理移动逻辑 - Console.WriteLine($"Player moved to {e.Direction}"); - } - - private void OnPlayerAttack(PlayerAttackEvent e) - { - var playerModel = this.GetModel(); - // 处理攻击逻辑 - playerModel.Score.Value += 10; - this.SendEvent(new EnemyDamagedEvent { Damage = 25 }); - } -} - -public class GameLogicSystem : AbstractSystem -{ - protected override void OnInit() - { - this.RegisterEvent(OnEnemyDamaged); - this.RegisterEvent(OnPlayerDied); - } - - private void OnEnemyDamaged(EnemyDamagedEvent e) - { - Console.WriteLine($"Enemy took {e.Damage} damage"); - // 检查是否需要升级关卡 - CheckLevelProgress(); - } - - private void OnPlayerDied(PlayerDiedEvent e) - { - var gameState = this.GetModel(); - gameState.IsGameRunning.Value = false; - Console.WriteLine("Game Over!"); - } - - private void CheckLevelProgress() - { - // 实现关卡进度检查逻辑 - } -} -``` - -## 4. 定义事件 - -创建应用中使用的事件: - -```csharp -public class PlayerMoveEvent : IEvent -{ - public Vector2 Direction { get; set; } -} - -public class PlayerAttackEvent : IEvent -{ - public Vector2 TargetPosition { get; set; } -} - -public class PlayerDiedEvent : IEvent -{ - // 玩家死亡事件 -} - -public class EnemyDamagedEvent : IEvent -{ - public int Damage { get; set; } -} -``` - -## 5. 创建控制器 - -实现控制器来连接 UI 和业务逻辑: - -```csharp -public class GameController : IController -{ - private IArchitecture _architecture; - private PlayerModel _playerModel; - private GameStateModel _gameStateModel; - - public GameController(IArchitecture architecture) - { - _architecture = architecture; - _playerModel = architecture.GetModel(); - _gameStateModel = architecture.GetModel(); - - // 初始化事件监听 - InitializeEventListeners(); - } - - private void InitializeEventListeners() - { - // 监听模型变化并更新 UI - _playerModel.Health.RegisterWithInitValue(OnHealthChanged); - _playerModel.Score.RegisterWithInitValue(OnScoreChanged); - _gameStateModel.IsGameRunning.Register(OnGameStateChanged); - } - - public void StartGame() - { - _gameStateModel.IsGameRunning.Value = true; - _architecture.SendEvent(new GameStartEvent()); - Console.WriteLine("Game started!"); - } - - public void MovePlayer(Vector2 direction) - { - _architecture.SendCommand(new MovePlayerCommand { Direction = direction }); - } - - public void PlayerAttack(Vector2 target) - { - _architecture.SendCommand(new AttackCommand { TargetPosition = target }); - } - - // UI 更新回调 - private void OnHealthChanged(int health) - { - UpdateHealthDisplay(health); - } - - private void OnScoreChanged(int score) - { - UpdateScoreDisplay(score); - } - - private void OnGameStateChanged(bool isRunning) - { - UpdateGameStatusDisplay(isRunning); - } - - private void UpdateHealthDisplay(int health) - { - // 更新血条 UI - Console.WriteLine($"Health: {health}"); - } - - private void UpdateScoreDisplay(int score) - { - // 更新分数显示 - Console.WriteLine($"Score: {score}"); - } - - private void UpdateGameStatusDisplay(bool isRunning) - { - // 更新游戏状态显示 - Console.WriteLine($"Game running: {isRunning}"); - } -} -``` - -## 6. 定义命令 - -创建命令来封装用户操作: - -```csharp -public class MovePlayerCommand : AbstractCommand -{ - public Vector2 Direction { get; set; } - - protected override void OnDo() - { - // 发送移动事件 - this.SendEvent(new PlayerMoveEvent { Direction = Direction }); - } -} - -public class AttackCommand : AbstractCommand -{ - public Vector2 TargetPosition { get; set; } - - protected override void OnDo() - { - // 发送攻击事件 - this.SendEvent(new PlayerAttackEvent { TargetPosition = TargetPosition }); - } -} -``` - -## 7. 运行应用 - -现在让我们运行这个简单的应用: - -```csharp -class Program -{ - static void Main(string[] args) - { - // 创建并初始化架构 - var architecture = new GameArchitecture(); - architecture.Initialize(); - - // 创建控制器 - var gameController = new GameController(architecture); - - // 开始游戏 - gameController.StartGame(); - - // 模拟玩家操作 - gameController.MovePlayer(new Vector2(1, 0)); - gameController.PlayerAttack(new Vector2(5, 5)); - - // 模拟玩家受伤 - var playerModel = architecture.GetModel(); - playerModel.Health.Value = 50; - - // 模拟玩家死亡 - playerModel.Health.Value = 0; - - Console.WriteLine("Press any key to exit..."); - Console.ReadKey(); - } -} -``` - -## 8. 运行结果 - -执行程序后,您应该看到类似以下输出: - -``` -Game started! -Game running: True -Player moved to (1, 0) -Player took 25 damage -Score: 10 -Health: 50 -Health: 0 -Player died -Game Over! -Game running: False -Press any key to exit... -``` - -## 下一步 - -这个简单的示例展示了 GFramework 的核心概念: - -1. **架构模式** - 清晰的分层结构 -2. **响应式数据** - BindableProperty 自动更新 -3. **事件驱动** - 松耦合的组件通信 -4. **命令模式** - 封装用户操作 +# 快速开始 + +本指南将帮助您快速构建第一个基于 GFramework 的应用程序。 + +## 1. 创建项目架构 + +首先定义您的应用架构: + +```csharp +using GFramework.Core.architecture; + +public class GameArchitecture : Architecture +{ + protected override void Init() + { + // 注册模型 - 存储应用状态 + RegisterModel(new PlayerModel()); + RegisterModel(new GameStateModel()); + + // 注册系统 - 处理业务逻辑 + RegisterSystem(new PlayerSystem()); + RegisterSystem(new GameLogicSystem()); + + // 注册工具类 - 提供辅助功能 + RegisterUtility(new StorageUtility()); + } +} +``` + +## 2. 定义数据模型 + +创建您的数据模型: + +```csharp +public class PlayerModel : AbstractModel +{ + // 使用可绑定属性实现响应式数据 + public BindableProperty Name { get; } = new("Player"); + public BindableProperty Health { get; } = new(100); + public BindableProperty Score { get; } = new(0); + + protected override void OnInit() + { + // 监听健康值变化 + Health.Register(OnHealthChanged); + } + + private void OnHealthChanged(int newHealth) + { + if (newHealth <= 0) + { + this.SendEvent(new PlayerDiedEvent()); + } + } +} + +public class GameStateModel : AbstractModel +{ + public BindableProperty IsGameRunning { get; } = new(false); + public BindableProperty CurrentLevel { get; } = new(1); +} +``` + +## 3. 实现业务逻辑 + +创建处理业务逻辑的系统: + +```csharp +public class PlayerSystem : AbstractSystem +{ + protected override void OnInit() + { + // 监听玩家输入事件 + this.RegisterEvent(OnPlayerMove); + this.RegisterEvent(OnPlayerAttack); + } + + private void OnPlayerMove(PlayerMoveEvent e) + { + var playerModel = this.GetModel(); + // 处理移动逻辑 + Console.WriteLine($"Player moved to {e.Direction}"); + } + + private void OnPlayerAttack(PlayerAttackEvent e) + { + var playerModel = this.GetModel(); + // 处理攻击逻辑 + playerModel.Score.Value += 10; + this.SendEvent(new EnemyDamagedEvent { Damage = 25 }); + } +} + +public class GameLogicSystem : AbstractSystem +{ + protected override void OnInit() + { + this.RegisterEvent(OnEnemyDamaged); + this.RegisterEvent(OnPlayerDied); + } + + private void OnEnemyDamaged(EnemyDamagedEvent e) + { + Console.WriteLine($"Enemy took {e.Damage} damage"); + // 检查是否需要升级关卡 + CheckLevelProgress(); + } + + private void OnPlayerDied(PlayerDiedEvent e) + { + var gameState = this.GetModel(); + gameState.IsGameRunning.Value = false; + Console.WriteLine("Game Over!"); + } + + private void CheckLevelProgress() + { + // 实现关卡进度检查逻辑 + } +} +``` + +## 4. 定义事件 + +创建应用中使用的事件: + +```csharp +public class PlayerMoveEvent : IEvent +{ + public Vector2 Direction { get; set; } +} + +public class PlayerAttackEvent : IEvent +{ + public Vector2 TargetPosition { get; set; } +} + +public class PlayerDiedEvent : IEvent +{ + // 玩家死亡事件 +} + +public class EnemyDamagedEvent : IEvent +{ + public int Damage { get; set; } +} +``` + +## 5. 创建控制器 + +实现控制器来连接 UI 和业务逻辑: + +```csharp +using GFramework.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class GameController : IController +{ + private PlayerModel _playerModel; + private GameStateModel _gameStateModel; + + public void Initialize() + { + _playerModel = this.GetModel(); + _gameStateModel = this.GetModel(); + + // 初始化事件监听 + InitializeEventListeners(); + } + + private void InitializeEventListeners() + { + // 监听模型变化并更新 UI + _playerModel.Health.RegisterWithInitValue(OnHealthChanged); + _playerModel.Score.RegisterWithInitValue(OnScoreChanged); + _gameStateModel.IsGameRunning.Register(OnGameStateChanged); + } + + public void StartGame() + { + _gameStateModel.IsGameRunning.Value = true; + this.SendEvent(new GameStartEvent()); + Console.WriteLine("Game started!"); + } + + public void MovePlayer(Vector2 direction) + { + this.SendCommand(new MovePlayerCommand { Direction = direction }); + } + + public void PlayerAttack(Vector2 target) + { + this.SendCommand(new AttackCommand { TargetPosition = target }); + } + + // UI 更新回调 + private void OnHealthChanged(int health) + { + UpdateHealthDisplay(health); + } + + private void OnScoreChanged(int score) + { + UpdateScoreDisplay(score); + } + + private void OnGameStateChanged(bool isRunning) + { + UpdateGameStatusDisplay(isRunning); + } + + private void UpdateHealthDisplay(int health) + { + // 更新血条 UI + Console.WriteLine($"Health: {health}"); + } + + private void UpdateScoreDisplay(int score) + { + // 更新分数显示 + Console.WriteLine($"Score: {score}"); + } + + private void UpdateGameStatusDisplay(bool isRunning) + { + // 更新游戏状态显示 + Console.WriteLine($"Game running: {isRunning}"); + } +} +``` + +## 6. 定义命令 + +创建命令来封装用户操作: + +```csharp +public class MovePlayerCommand : AbstractCommand +{ + public Vector2 Direction { get; set; } + + protected override void OnDo() + { + // 发送移动事件 + this.SendEvent(new PlayerMoveEvent { Direction = Direction }); + } +} + +public class AttackCommand : AbstractCommand +{ + public Vector2 TargetPosition { get; set; } + + protected override void OnDo() + { + // 发送攻击事件 + this.SendEvent(new PlayerAttackEvent { TargetPosition = TargetPosition }); + } +} +``` + +## 7. 运行应用 + +现在让我们运行这个简单的应用: + +```csharp +class Program +{ + static void Main(string[] args) + { + // 创建并初始化架构 + var architecture = new GameArchitecture(); + architecture.Initialize(); + + // 创建控制器 + var gameController = new GameController(); + gameController.Initialize(); + + // 开始游戏 + gameController.StartGame(); + + // 模拟玩家操作 + gameController.MovePlayer(new Vector2(1, 0)); + gameController.PlayerAttack(new Vector2(5, 5)); + + // 模拟玩家受伤 + var playerModel = architecture.GetModel(); + playerModel.Health.Value = 50; + + // 模拟玩家死亡 + playerModel.Health.Value = 0; + + Console.WriteLine("Press any key to exit..."); + Console.ReadKey(); + } +} +``` + +## 8. 运行结果 + +执行程序后,您应该看到类似以下输出: + +``` +Game started! +Game running: True +Player moved to (1, 0) +Player took 25 damage +Score: 10 +Health: 50 +Health: 0 +Player died +Game Over! +Game running: False +Press any key to exit... +``` + +## 下一步 + +这个简单的示例展示了 GFramework 的核心概念: + +1. **架构模式** - 清晰的分层结构 +2. **响应式数据** - BindableProperty 自动更新 +3. **事件驱动** - 松耦合的组件通信 +4. **命令模式** - 封装用户操作 diff --git a/docs/zh-CN/godot/architecture.md b/docs/zh-CN/godot/architecture.md index 7ca9a50..28af1b0 100644 --- a/docs/zh-CN/godot/architecture.md +++ b/docs/zh-CN/godot/architecture.md @@ -1,590 +1,612 @@ ---- -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 扩展方法 +--- +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.Core.Abstractions.controller; +using GFramework.SourceGenerators.Abstractions.rule; + +[ContextAware] +public partial class Player : CharacterBody2D, IController +{ + public override void _Ready() + { + // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口) + 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()); + } +} + +// 在不同节点中使用不同架构 +[ContextAware] +public partial class GameNode : Node, IController +{ + // 配置使用 GameArchitecture 的上下文提供者 + static GameNode() + { + SetContextProvider(new GameContextProvider()); + } +} + +[ContextAware] +public partial class UiNode : Control, IController +{ + // 配置使用 UiArchitecture 的上下文提供者 + static UiNode() + { + SetContextProvider(new UiContextProvider()); + } +} +``` + +## 最佳实践 + +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(); + } +} +``` + +### 问题:如何在节点中访问架构? + +**解答**: +使用 `[ContextAware]` 特性或直接使用单例: + +```csharp +using GFramework.SourceGenerators.Abstractions.rule; + +// 方式 1: 使用 [ContextAware] 特性(推荐) +[ContextAware] +public partial class Player : Node, IController +{ + public override void _Ready() + { + // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口) + var model = this.GetModel(); + var system = this.GetSystem(); + } +} + +// 方式 2: 直接使用单例 +public partial class Enemy : Node +{ + public override void _Ready() + { + var model = GameArchitecture.Interface.GetModel(); + } +} +``` + +**注意**: + +- `IController` 是标记接口,不包含任何方法 +- 架构访问能力由 `[ContextAware]` 特性提供 +- `[ContextAware]` 会自动生成 `Context` 属性和实现 `IContextAware` 接口 +- 扩展方法(如 `this.GetModel()`)基于 `IContextAware` 接口,而非 `IController` + +### 问题:架构锚点节点是什么? + +**解答**: +架构锚点是一个隐藏的节点,用于将架构绑定到 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/coroutine.md b/docs/zh-CN/godot/coroutine.md index b994f96..fd36b24 100644 --- a/docs/zh-CN/godot/coroutine.md +++ b/docs/zh-CN/godot/coroutine.md @@ -75,7 +75,7 @@ private System.Collections.IEnumerator WaitUntilCondition() GD.Print("等待生命值恢复"); // 等待生命值大于 50 - var playerModel = Context.GetModel(); + var playerModel = this.GetModel(); yield return new WaitUntil(() => playerModel.Health.Value > 50); GD.Print("生命值已恢复!"); @@ -140,7 +140,7 @@ private System.Collections.IEnumerator EndOfFrameExample() ```csharp private System.Collections.IEnumerator WaitUntilExample() { - var health = Context.GetModel().Health; + var health = this.GetModel().Health; // 持续等待直到条件满足 yield return new WaitUntil(() => health.Value > 0); @@ -156,7 +156,7 @@ private System.Collections.IEnumerator WaitUntilExample() ```csharp private System.Collections.IEnumerator WaitWhileExample() { - var gameState = Context.GetModel(); + var gameState = this.GetModel(); // 等待游戏不再暂停 yield return new WaitWhile(() => gameState.IsPaused.Value); @@ -174,7 +174,7 @@ private System.Collections.IEnumerator WaitWhileExample() ```csharp private System.Collections.IEnumerator CombinedWait() { - var health = Context.GetModel().Health; + var health = this.GetModel().Health; var button = GetNode