mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-25 21:34:28 +08:00
docs(architecture): 更新架构设计模式指南文档
- 将标题从"架构模式最佳实践"改为"架构设计模式指南" - 添加全面的架构设计模式介绍和概述 - 新增MVC模式详细说明,包括概念、GFramework实现示例和最佳实践 - 新增MVVM模式详细说明,包括概念、GFramework实现示例和最佳实践 - 新增命令模式详细说明,包括概念、实现示例和撤销功能支持 - 新增查询模式详细说明,包括CQRS概念和复杂查询示例 - 新增事件驱动模式详细说明,包括事件定义和监听实现 - 新增依赖注入模式详细说明,包括构造函数注入示例 - 新增服务定位器模式详细说明,包括与依赖注入对比 - 新增对象池模式详细说明,包括通用对象池实现 - 新增状态模式详细说明,包括异步状态和状态机系统 - 补充模式选择与组合建议,针对小型、中型、大型项目提供不同方案 - 更新代码示例中的泛型语法格式,统一使用尖括号表示法
This commit is contained in:
parent
739565d278
commit
9edf64193f
File diff suppressed because it is too large
Load Diff
1303
docs/zh-CN/best-practices/error-handling.md
Normal file
1303
docs/zh-CN/best-practices/error-handling.md
Normal file
File diff suppressed because it is too large
Load Diff
384
docs/zh-CN/best-practices/mobile-optimization.md
Normal file
384
docs/zh-CN/best-practices/mobile-optimization.md
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
---
|
||||||
|
title: 移动平台优化指南
|
||||||
|
description: 针对移动平台的性能优化、内存管理和电池优化最佳实践
|
||||||
|
---
|
||||||
|
|
||||||
|
# 移动平台优化指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
移动平台游戏开发面临着独特的挑战:有限的内存、较弱的处理器、电池续航限制、触摸输入、多样的屏幕尺寸等。本指南将帮助你使用
|
||||||
|
GFramework 开发高性能的移动游戏,提供针对性的优化策略和最佳实践。
|
||||||
|
|
||||||
|
**移动平台的主要限制**:
|
||||||
|
|
||||||
|
- **内存限制**:移动设备内存通常在 2-8GB,远低于 PC
|
||||||
|
- **CPU 性能**:移动 CPU 性能较弱,且受热量限制
|
||||||
|
- **GPU 性能**:移动 GPU 功能有限,填充率和带宽受限
|
||||||
|
- **电池续航**:高性能运行会快速消耗电池
|
||||||
|
- **存储空间**:应用包大小受限,用户存储空间有限
|
||||||
|
- **网络环境**:移动网络不稳定,延迟较高
|
||||||
|
|
||||||
|
**优化目标**:
|
||||||
|
|
||||||
|
- 减少内存占用(目标:<200MB)
|
||||||
|
- 降低 CPU 使用率(目标:<30%)
|
||||||
|
- 优化 GPU 渲染(目标:60 FPS)
|
||||||
|
- 延长电池续航(目标:3+ 小时)
|
||||||
|
- 减小包体大小(目标:<100MB)
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### 1. 内存管理
|
||||||
|
|
||||||
|
移动设备内存有限,需要精细管理:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 监控内存使用
|
||||||
|
public class MemoryMonitor : AbstractSystem
|
||||||
|
{
|
||||||
|
private const long MemoryWarningThreshold = 150 * 1024 * 1024; // 150MB
|
||||||
|
private const long MemoryCriticalThreshold = 200 * 1024 * 1024; // 200MB
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
this.RegisterEvent<GameUpdateEvent>(OnUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUpdate(GameUpdateEvent e)
|
||||||
|
{
|
||||||
|
// 每 5 秒检查一次内存
|
||||||
|
if (e.TotalTime % 5.0 < e.DeltaTime)
|
||||||
|
{
|
||||||
|
CheckMemoryUsage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckMemoryUsage()
|
||||||
|
{
|
||||||
|
var memoryUsage = GC.GetTotalMemory(false);
|
||||||
|
|
||||||
|
if (memoryUsage > MemoryCriticalThreshold)
|
||||||
|
{
|
||||||
|
// 内存严重不足,强制清理
|
||||||
|
SendEvent(new MemoryCriticalEvent());
|
||||||
|
ForceMemoryCleanup();
|
||||||
|
}
|
||||||
|
else if (memoryUsage > MemoryWarningThreshold)
|
||||||
|
{
|
||||||
|
// 内存警告,温和清理
|
||||||
|
SendEvent(new MemoryWarningEvent());
|
||||||
|
SoftMemoryCleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ForceMemoryCleanup()
|
||||||
|
{
|
||||||
|
// 卸载不必要的资源
|
||||||
|
var resourceManager = this.GetUtility<IResourceManager>();
|
||||||
|
resourceManager.UnloadUnusedResources();
|
||||||
|
|
||||||
|
// 清理对象池
|
||||||
|
var poolSystem = this.GetSystem<ObjectPoolSystem>();
|
||||||
|
poolSystem.TrimPools();
|
||||||
|
|
||||||
|
// 强制 GC
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
GC.Collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SoftMemoryCleanup()
|
||||||
|
{
|
||||||
|
// 温和清理:只清理明确不需要的资源
|
||||||
|
var resourceManager = this.GetUtility<IResourceManager>();
|
||||||
|
resourceManager.UnloadUnusedResources();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 性能分析
|
||||||
|
|
||||||
|
使用性能分析工具识别瓶颈:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class PerformanceProfiler : AbstractSystem
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, PerformanceMetrics> _metrics = new();
|
||||||
|
|
||||||
|
public IDisposable Profile(string name)
|
||||||
|
{
|
||||||
|
return new ProfileScope(name, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecordMetric(string name, double duration)
|
||||||
|
{
|
||||||
|
if (!_metrics.TryGetValue(name, out var metrics))
|
||||||
|
{
|
||||||
|
metrics = new PerformanceMetrics();
|
||||||
|
_metrics[name] = metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics.AddSample(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintReport()
|
||||||
|
{
|
||||||
|
Console.WriteLine("\n=== 性能报告 ===");
|
||||||
|
foreach (var (name, metrics) in _metrics.OrderByDescending(x => x.Value.AverageMs))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{name}:");
|
||||||
|
Console.WriteLine($" 平均: {metrics.AverageMs:F2}ms");
|
||||||
|
Console.WriteLine($" 最大: {metrics.MaxMs:F2}ms");
|
||||||
|
Console.WriteLine($" 最小: {metrics.MinMs:F2}ms");
|
||||||
|
Console.WriteLine($" 调用次数: {metrics.SampleCount}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProfileScope : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _name;
|
||||||
|
private readonly PerformanceProfiler _profiler;
|
||||||
|
private readonly Stopwatch _stopwatch;
|
||||||
|
|
||||||
|
public ProfileScope(string name, PerformanceProfiler profiler)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
_profiler = profiler;
|
||||||
|
_stopwatch = Stopwatch.StartNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_stopwatch.Stop();
|
||||||
|
_profiler.RecordMetric(_name, _stopwatch.Elapsed.TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
public class GameSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
private PerformanceProfiler _profiler;
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
_profiler = this.GetSystem<PerformanceProfiler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateGame()
|
||||||
|
{
|
||||||
|
using (_profiler.Profile("GameUpdate"))
|
||||||
|
{
|
||||||
|
// 游戏更新逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 电池优化
|
||||||
|
|
||||||
|
减少不必要的计算和渲染:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class PowerSavingSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
private bool _isPowerSavingMode;
|
||||||
|
private int _targetFrameRate = 60;
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
this.RegisterEvent<BatteryLowEvent>(OnBatteryLow);
|
||||||
|
this.RegisterEvent<BatteryNormalEvent>(OnBatteryNormal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBatteryLow(BatteryLowEvent e)
|
||||||
|
{
|
||||||
|
EnablePowerSavingMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBatteryNormal(BatteryNormalEvent e)
|
||||||
|
{
|
||||||
|
DisablePowerSavingMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnablePowerSavingMode()
|
||||||
|
{
|
||||||
|
_isPowerSavingMode = true;
|
||||||
|
|
||||||
|
// 降低帧率
|
||||||
|
_targetFrameRate = 30;
|
||||||
|
Application.targetFrameRate = _targetFrameRate;
|
||||||
|
|
||||||
|
// 降低渲染质量
|
||||||
|
QualitySettings.SetQualityLevel(0);
|
||||||
|
|
||||||
|
// 减少粒子效果
|
||||||
|
SendEvent(new ReduceEffectsEvent());
|
||||||
|
|
||||||
|
// 暂停非关键系统
|
||||||
|
PauseNonCriticalSystems();
|
||||||
|
|
||||||
|
Console.WriteLine("省电模式已启用");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisablePowerSavingMode()
|
||||||
|
{
|
||||||
|
_isPowerSavingMode = false;
|
||||||
|
|
||||||
|
// 恢复帧率
|
||||||
|
_targetFrameRate = 60;
|
||||||
|
Application.targetFrameRate = _targetFrameRate;
|
||||||
|
|
||||||
|
// 恢复渲染质量
|
||||||
|
QualitySettings.SetQualityLevel(2);
|
||||||
|
|
||||||
|
// 恢复粒子效果
|
||||||
|
SendEvent(new RestoreEffectsEvent());
|
||||||
|
|
||||||
|
// 恢复非关键系统
|
||||||
|
ResumeNonCriticalSystems();
|
||||||
|
|
||||||
|
Console.WriteLine("省电模式已禁用");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PauseNonCriticalSystems()
|
||||||
|
{
|
||||||
|
// 暂停动画系统
|
||||||
|
var animationSystem = this.GetSystem<AnimationSystem>();
|
||||||
|
animationSystem?.Pause();
|
||||||
|
|
||||||
|
// 暂停音效系统(保留音乐)
|
||||||
|
var audioSystem = this.GetSystem<AudioSystem>();
|
||||||
|
audioSystem?.PauseSoundEffects();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResumeNonCriticalSystems()
|
||||||
|
{
|
||||||
|
var animationSystem = this.GetSystem<AnimationSystem>();
|
||||||
|
animationSystem?.Resume();
|
||||||
|
|
||||||
|
var audioSystem = this.GetSystem<AudioSystem>();
|
||||||
|
audioSystem?.ResumeSoundEffects();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 内存优化
|
||||||
|
|
||||||
|
### 1. 资源管理策略
|
||||||
|
|
||||||
|
实现智能资源加载和卸载:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MobileResourceManager : AbstractSystem
|
||||||
|
{
|
||||||
|
private readonly IResourceManager _resourceManager;
|
||||||
|
private readonly Dictionary<string, ResourcePriority> _resourcePriorities = new();
|
||||||
|
private readonly HashSet<string> _loadedResources = new();
|
||||||
|
|
||||||
|
public MobileResourceManager(IResourceManager resourceManager)
|
||||||
|
{
|
||||||
|
_resourceManager = resourceManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
// 配置资源优先级
|
||||||
|
ConfigureResourcePriorities();
|
||||||
|
|
||||||
|
// 监听场景切换事件
|
||||||
|
this.RegisterEvent<SceneChangedEvent>(OnSceneChanged);
|
||||||
|
|
||||||
|
// 监听内存警告
|
||||||
|
this.RegisterEvent<MemoryWarningEvent>(OnMemoryWarning);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureResourcePriorities()
|
||||||
|
{
|
||||||
|
// 高优先级:UI、玩家资源
|
||||||
|
_resourcePriorities["ui/"] = ResourcePriority.High;
|
||||||
|
_resourcePriorities["player/"] = ResourcePriority.High;
|
||||||
|
|
||||||
|
// 中优先级:敌人、道具
|
||||||
|
_resourcePriorities["enemy/"] = ResourcePriority.Medium;
|
||||||
|
_resourcePriorities["item/"] = ResourcePriority.Medium;
|
||||||
|
|
||||||
|
// 低优先级:特效、装饰
|
||||||
|
_resourcePriorities["effect/"] = ResourcePriority.Low;
|
||||||
|
_resourcePriorities["decoration/"] = ResourcePriority.Low;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<T> LoadResourceAsync<T>(string path) where T : class
|
||||||
|
{
|
||||||
|
// 检查内存
|
||||||
|
if (IsMemoryLow())
|
||||||
|
{
|
||||||
|
// 内存不足,先清理低优先级资源
|
||||||
|
UnloadLowPriorityResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
var resource = await _resourceManager.LoadAsync<T>(path);
|
||||||
|
_loadedResources.Add(path);
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSceneChanged(SceneChangedEvent e)
|
||||||
|
{
|
||||||
|
// 场景切换时,卸载旧场景资源
|
||||||
|
UnloadSceneResources(e.PreviousScene);
|
||||||
|
|
||||||
|
// 预加载新场景资源
|
||||||
|
PreloadSceneResources(e.NewScene);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMemoryWarning(MemoryWarningEvent e)
|
||||||
|
{
|
||||||
|
// 内存警告,卸载低优先级资源
|
||||||
|
UnloadLowPriorityResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnloadLowPriorityResources()
|
||||||
|
{
|
||||||
|
var resourcesToUnload = _loadedResources
|
||||||
|
.Where(path => GetResourcePriority(path) == ResourcePriority.Low)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var path in resourcesToUnload)
|
||||||
|
{
|
||||||
|
_resourceManager.Unload(path);
|
||||||
|
_loadedResources.Remove(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"卸载了 {resourcesToUnload.Count} 个低优先级资源");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourcePriority GetResourcePriority(string path)
|
||||||
|
{
|
||||||
|
foreach (var (prefix, priority) in _resourcePriorities)
|
||||||
|
{
|
||||||
|
if (path.StartsWith(prefix))
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResourcePriority.Medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsMemoryLow()
|
||||||
|
{
|
||||||
|
var memoryUsage = GC.GetTotalMemory(false);
|
||||||
|
return memoryUsage > 150 * 1024 * 1024; // 150MB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ResourcePriority
|
||||||
|
{
|
||||||
|
Low,
|
||||||
|
Medium,
|
||||||
|
High
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 纹理压缩和优化
|
||||||
|
|
||||||
546
docs/zh-CN/best-practices/multiplayer.md
Normal file
546
docs/zh-CN/best-practices/multiplayer.md
Normal file
@ -0,0 +1,546 @@
|
|||||||
|
# 多人游戏架构指南
|
||||||
|
|
||||||
|
> 基于 GFramework 架构设计高性能、可扩展的多人游戏系统。
|
||||||
|
|
||||||
|
## 📋 目录
|
||||||
|
|
||||||
|
- [概述](#概述)
|
||||||
|
- [核心概念](#核心概念)
|
||||||
|
- [架构设计](#架构设计)
|
||||||
|
- [状态管理](#状态管理)
|
||||||
|
- [命令模式与输入处理](#命令模式与输入处理)
|
||||||
|
- [事件同步](#事件同步)
|
||||||
|
- [网络优化](#网络优化)
|
||||||
|
- [安全考虑](#安全考虑)
|
||||||
|
- [最佳实践](#最佳实践)
|
||||||
|
- [常见问题](#常见问题)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
多人游戏开发面临着单机游戏所没有的独特挑战:
|
||||||
|
|
||||||
|
### 主要挑战
|
||||||
|
|
||||||
|
1. **网络延迟** - 玩家操作和服务器响应之间存在不可避免的延迟
|
||||||
|
2. **状态同步** - 确保所有客户端看到一致的游戏状态
|
||||||
|
3. **带宽限制** - 需要高效传输游戏数据,避免网络拥塞
|
||||||
|
4. **作弊防护** - 防止客户端篡改游戏逻辑和数据
|
||||||
|
5. **并发处理** - 同时处理多个玩家的输入和状态更新
|
||||||
|
6. **断线重连** - 优雅处理网络中断和玩家重新连接
|
||||||
|
|
||||||
|
### GFramework 的优势
|
||||||
|
|
||||||
|
GFramework 的架构设计天然适合多人游戏开发:
|
||||||
|
|
||||||
|
- **分层架构** - 清晰分离客户端逻辑、网络层和服务器逻辑
|
||||||
|
- **事件系统** - 松耦合的事件驱动架构便于状态同步
|
||||||
|
- **命令模式** - 统一的输入处理和验证机制
|
||||||
|
- **Model-System 分离** - 数据和逻辑分离便于状态管理
|
||||||
|
- **模块化设计** - 网络功能可以作为独立模块集成
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
### 1. 客户端-服务器架构
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 服务器架构
|
||||||
|
public class ServerArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
// 注册服务器专用的 Model
|
||||||
|
RegisterModel(new ServerGameStateModel());
|
||||||
|
RegisterModel(new PlayerConnectionModel());
|
||||||
|
|
||||||
|
// 注册服务器专用的 System
|
||||||
|
RegisterSystem(new ServerNetworkSystem());
|
||||||
|
RegisterSystem(new AuthorityGameLogicSystem());
|
||||||
|
RegisterSystem(new StateReplicationSystem());
|
||||||
|
RegisterSystem(new AntiCheatSystem());
|
||||||
|
|
||||||
|
// 注册工具
|
||||||
|
RegisterUtility(new NetworkUtility());
|
||||||
|
RegisterUtility(new ValidationUtility());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端架构
|
||||||
|
public class ClientArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
// 注册客户端专用的 Model
|
||||||
|
RegisterModel(new ClientGameStateModel());
|
||||||
|
RegisterModel(new PredictionModel());
|
||||||
|
|
||||||
|
// 注册客户端专用的 System
|
||||||
|
RegisterSystem(new ClientNetworkSystem());
|
||||||
|
RegisterSystem(new PredictionSystem());
|
||||||
|
RegisterSystem(new InterpolationSystem());
|
||||||
|
RegisterSystem(new ClientInputSystem());
|
||||||
|
|
||||||
|
// 注册工具
|
||||||
|
RegisterUtility(new NetworkUtility());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 状态同步策略
|
||||||
|
|
||||||
|
#### 状态同步 (State Synchronization)
|
||||||
|
|
||||||
|
服务器定期向客户端发送完整的游戏状态。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 游戏状态快照
|
||||||
|
public struct GameStateSnapshot
|
||||||
|
{
|
||||||
|
public uint Tick { get; set; }
|
||||||
|
public long Timestamp { get; set; }
|
||||||
|
public PlayerState[] Players { get; set; }
|
||||||
|
public EntityState[] Entities { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PlayerState
|
||||||
|
{
|
||||||
|
public string PlayerId { get; set; }
|
||||||
|
public Vector3 Position { get; set; }
|
||||||
|
public Quaternion Rotation { get; set; }
|
||||||
|
public int Health { get; set; }
|
||||||
|
public PlayerAnimationState AnimationState { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态复制系统
|
||||||
|
public class StateReplicationSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
private ServerGameStateModel _gameState;
|
||||||
|
private PlayerConnectionModel _connections;
|
||||||
|
private uint _currentTick;
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
_gameState = this.GetModel<ServerGameStateModel>();
|
||||||
|
_connections = this.GetModel<PlayerConnectionModel>();
|
||||||
|
|
||||||
|
// 每个 tick 复制状态
|
||||||
|
this.RegisterEvent<ServerTickEvent>(OnServerTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnServerTick(ServerTickEvent e)
|
||||||
|
{
|
||||||
|
_currentTick++;
|
||||||
|
|
||||||
|
// 创建状态快照
|
||||||
|
var snapshot = CreateSnapshot();
|
||||||
|
|
||||||
|
// 发送给所有连接的客户端
|
||||||
|
foreach (var connection in _connections.ActiveConnections)
|
||||||
|
{
|
||||||
|
SendSnapshotToClient(connection, snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameStateSnapshot CreateSnapshot()
|
||||||
|
{
|
||||||
|
return new GameStateSnapshot
|
||||||
|
{
|
||||||
|
Tick = _currentTick,
|
||||||
|
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||||
|
Players = _gameState.Players.Select(p => new PlayerState
|
||||||
|
{
|
||||||
|
PlayerId = p.Id,
|
||||||
|
Position = p.Position,
|
||||||
|
Rotation = p.Rotation,
|
||||||
|
Health = p.Health,
|
||||||
|
AnimationState = p.AnimationState
|
||||||
|
}).ToArray(),
|
||||||
|
Entities = _gameState.Entities.Select(e => CreateEntityState(e)).ToArray()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 增量同步 (Delta Synchronization)
|
||||||
|
|
||||||
|
只发送状态变化,减少带宽消耗。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 增量状态
|
||||||
|
public struct DeltaState
|
||||||
|
{
|
||||||
|
public uint Tick { get; set; }
|
||||||
|
public uint BaseTick { get; set; }
|
||||||
|
public PlayerDelta[] PlayerDeltas { get; set; }
|
||||||
|
public EntityDelta[] EntityDeltas { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PlayerDelta
|
||||||
|
{
|
||||||
|
public string PlayerId { get; set; }
|
||||||
|
public DeltaFlags Flags { get; set; }
|
||||||
|
public Vector3? Position { get; set; }
|
||||||
|
public Quaternion? Rotation { get; set; }
|
||||||
|
public int? Health { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum DeltaFlags
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Position = 1 << 0,
|
||||||
|
Rotation = 1 << 1,
|
||||||
|
Health = 1 << 2,
|
||||||
|
Animation = 1 << 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// 增量复制系统
|
||||||
|
public class DeltaReplicationSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
private readonly Dictionary<uint, GameStateSnapshot> _snapshotHistory = new();
|
||||||
|
private const int MaxHistorySize = 60; // 保留 60 个快照
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
this.RegisterEvent<ServerTickEvent>(OnServerTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnServerTick(ServerTickEvent e)
|
||||||
|
{
|
||||||
|
var currentSnapshot = CreateSnapshot();
|
||||||
|
_snapshotHistory[e.Tick] = currentSnapshot;
|
||||||
|
|
||||||
|
// 清理旧快照
|
||||||
|
CleanupOldSnapshots(e.Tick);
|
||||||
|
|
||||||
|
// 为每个客户端生成增量
|
||||||
|
foreach (var connection in GetConnections())
|
||||||
|
{
|
||||||
|
var lastAckedTick = connection.LastAcknowledgedTick;
|
||||||
|
|
||||||
|
if (_snapshotHistory.TryGetValue(lastAckedTick, out var baseSnapshot))
|
||||||
|
{
|
||||||
|
var delta = CreateDelta(baseSnapshot, currentSnapshot);
|
||||||
|
SendDeltaToClient(connection, delta);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 客户端太落后,发送完整快照
|
||||||
|
SendSnapshotToClient(connection, currentSnapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeltaState CreateDelta(GameStateSnapshot baseSnapshot, GameStateSnapshot currentSnapshot)
|
||||||
|
{
|
||||||
|
var delta = new DeltaState
|
||||||
|
{
|
||||||
|
Tick = currentSnapshot.Tick,
|
||||||
|
BaseTick = baseSnapshot.Tick,
|
||||||
|
PlayerDeltas = new List<PlayerDelta>()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 比较玩家状态
|
||||||
|
foreach (var currentPlayer in currentSnapshot.Players)
|
||||||
|
{
|
||||||
|
var basePlayer = baseSnapshot.Players.FirstOrDefault(p => p.PlayerId == currentPlayer.PlayerId);
|
||||||
|
|
||||||
|
if (basePlayer.PlayerId == null)
|
||||||
|
{
|
||||||
|
// 新玩家,发送完整状态
|
||||||
|
delta.PlayerDeltas.Add(CreateFullPlayerDelta(currentPlayer));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 计算差异
|
||||||
|
var playerDelta = CreatePlayerDelta(basePlayer, currentPlayer);
|
||||||
|
if (playerDelta.Flags != DeltaFlags.None)
|
||||||
|
{
|
||||||
|
delta.PlayerDeltas.Add(playerDelta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayerDelta CreatePlayerDelta(PlayerState baseState, PlayerState currentState)
|
||||||
|
{
|
||||||
|
var delta = new PlayerDelta { PlayerId = currentState.PlayerId };
|
||||||
|
|
||||||
|
if (Vector3.Distance(baseState.Position, currentState.Position) > 0.01f)
|
||||||
|
{
|
||||||
|
delta.Flags |= DeltaFlags.Position;
|
||||||
|
delta.Position = currentState.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Quaternion.Angle(baseState.Rotation, currentState.Rotation) > 0.1f)
|
||||||
|
{
|
||||||
|
delta.Flags |= DeltaFlags.Rotation;
|
||||||
|
delta.Rotation = currentState.Rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseState.Health != currentState.Health)
|
||||||
|
{
|
||||||
|
delta.Flags |= DeltaFlags.Health;
|
||||||
|
delta.Health = currentState.Health;
|
||||||
|
}
|
||||||
|
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 客户端预测与回滚
|
||||||
|
|
||||||
|
客户端立即响应玩家输入,然后在收到服务器确认后进行校正。
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 输入命令
|
||||||
|
public struct PlayerInputCommand
|
||||||
|
{
|
||||||
|
public uint Tick { get; set; }
|
||||||
|
public long Timestamp { get; set; }
|
||||||
|
public Vector2 MoveDirection { get; set; }
|
||||||
|
public Vector2 LookDirection { get; set; }
|
||||||
|
public InputFlags Flags { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum InputFlags
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Jump = 1 << 0,
|
||||||
|
Attack = 1 << 1,
|
||||||
|
Interact = 1 << 2,
|
||||||
|
Reload = 1 << 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端预测系统
|
||||||
|
public class ClientPredictionSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
private PredictionModel _prediction;
|
||||||
|
private ClientGameStateModel _gameState;
|
||||||
|
private readonly Queue<PlayerInputCommand> _pendingInputs = new();
|
||||||
|
private uint _lastProcessedServerTick;
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
_prediction = this.GetModel<PredictionModel>();
|
||||||
|
_gameState = this.GetModel<ClientGameStateModel>();
|
||||||
|
|
||||||
|
this.RegisterEvent<PlayerInputEvent>(OnPlayerInput);
|
||||||
|
this.RegisterEvent<ServerStateReceivedEvent>(OnServerStateReceived);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerInput(PlayerInputEvent e)
|
||||||
|
{
|
||||||
|
var input = new PlayerInputCommand
|
||||||
|
{
|
||||||
|
Tick = _prediction.CurrentTick,
|
||||||
|
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||||
|
MoveDirection = e.MoveDirection,
|
||||||
|
LookDirection = e.LookDirection,
|
||||||
|
Flags = e.Flags
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存输入用于重放
|
||||||
|
_pendingInputs.Enqueue(input);
|
||||||
|
|
||||||
|
// 立即应用预测
|
||||||
|
ApplyInput(input);
|
||||||
|
|
||||||
|
// 发送到服务器
|
||||||
|
SendInputToServer(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnServerStateReceived(ServerStateReceivedEvent e)
|
||||||
|
{
|
||||||
|
_lastProcessedServerTick = e.Snapshot.Tick;
|
||||||
|
|
||||||
|
// 应用服务器状态
|
||||||
|
ApplyServerState(e.Snapshot);
|
||||||
|
|
||||||
|
// 移除已确认的输入
|
||||||
|
while (_pendingInputs.Count > 0 && _pendingInputs.Peek().Tick <= e.Snapshot.Tick)
|
||||||
|
{
|
||||||
|
_pendingInputs.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重放未确认的输入
|
||||||
|
ReplayPendingInputs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyInput(PlayerInputCommand input)
|
||||||
|
{
|
||||||
|
var player = _gameState.LocalPlayer;
|
||||||
|
|
||||||
|
// 应用移动
|
||||||
|
var movement = input.MoveDirection * player.Speed * Time.DeltaTime;
|
||||||
|
player.Position += new Vector3(movement.X, 0, movement.Y);
|
||||||
|
|
||||||
|
// 应用旋转
|
||||||
|
if (input.LookDirection != Vector2.Zero)
|
||||||
|
{
|
||||||
|
player.Rotation = Quaternion.LookRotation(
|
||||||
|
new Vector3(input.LookDirection.X, 0, input.LookDirection.Y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用动作
|
||||||
|
if ((input.Flags & InputFlags.Jump) != 0 && player.IsGrounded)
|
||||||
|
{
|
||||||
|
player.Velocity = new Vector3(player.Velocity.X, player.JumpForce, player.Velocity.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReplayPendingInputs()
|
||||||
|
{
|
||||||
|
// 从服务器状态开始重放所有未确认的输入
|
||||||
|
var savedState = SavePlayerState();
|
||||||
|
|
||||||
|
foreach (var input in _pendingInputs)
|
||||||
|
{
|
||||||
|
ApplyInput(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 架构设计
|
||||||
|
|
||||||
|
### 1. 分离逻辑层
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 共享游戏逻辑 (客户端和服务器都使用)
|
||||||
|
public class SharedGameLogic
|
||||||
|
{
|
||||||
|
public static void ProcessMovement(PlayerState player, Vector2 moveDirection, float deltaTime)
|
||||||
|
{
|
||||||
|
var movement = moveDirection.Normalized() * player.Speed * deltaTime;
|
||||||
|
player.Position += new Vector3(movement.X, 0, movement.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CanJump(PlayerState player)
|
||||||
|
{
|
||||||
|
return player.IsGrounded && !player.IsStunned;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CalculateDamage(int attackPower, int defense, float criticalChance)
|
||||||
|
{
|
||||||
|
var baseDamage = Math.Max(1, attackPower - defense);
|
||||||
|
var isCritical = Random.Shared.NextDouble() < criticalChance;
|
||||||
|
return isCritical ? baseDamage * 2 : baseDamage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务器权威逻辑
|
||||||
|
public class ServerGameLogicSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
private ServerGameStateModel _gameState;
|
||||||
|
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
_gameState = this.GetModel<ServerGameStateModel>();
|
||||||
|
|
||||||
|
this.RegisterEvent<PlayerInputReceivedEvent>(OnPlayerInputReceived);
|
||||||
|
this.RegisterEvent<AttackRequestEvent>(OnAttackRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerInputReceived(PlayerInputReceivedEvent e)
|
||||||
|
{
|
||||||
|
var player = _gameState.GetPlayer(e.PlayerId);
|
||||||
|
|
||||||
|
// 验证输入
|
||||||
|
if (!ValidateInput(e.Input))
|
||||||
|
{
|
||||||
|
SendInputRejection(e.PlayerId, "Invalid input");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用共享逻辑
|
||||||
|
SharedGameLogic.ProcessMovement(player, e.Input.MoveDirection, Time.DeltaTime);
|
||||||
|
|
||||||
|
// 服务器端验证
|
||||||
|
if ((e.Input.Flags & InputFlags.Jump) != 0)
|
||||||
|
{
|
||||||
|
if (SharedGameLogic.CanJump(player))
|
||||||
|
{
|
||||||
|
player.Velocity = new Vector3(player.Velocity.X, player.JumpForce, player.Velocity.Z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAttackRequest(AttackRequestEvent e)
|
||||||
|
{
|
||||||
|
var attacker = _gameState.GetPlayer(e.AttackerId);
|
||||||
|
var target = _gameState.GetPlayer(e.TargetId);
|
||||||
|
|
||||||
|
// 服务器端验证
|
||||||
|
if (!CanAttack(attacker, target))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算伤害
|
||||||
|
var damage = SharedGameLogic.CalculateDamage(
|
||||||
|
attacker.AttackPower,
|
||||||
|
target.Defense,
|
||||||
|
attacker.CriticalChance
|
||||||
|
);
|
||||||
|
|
||||||
|
// 应用伤害
|
||||||
|
target.Health = Math.Max(0, target.Health - damage);
|
||||||
|
|
||||||
|
// 广播事件
|
||||||
|
this.SendEvent(new PlayerDamagedEvent
|
||||||
|
{
|
||||||
|
AttackerId = e.AttackerId,
|
||||||
|
TargetId = e.TargetId,
|
||||||
|
Damage = damage,
|
||||||
|
RemainingHealth = target.Health
|
||||||
|
});
|
||||||
|
|
||||||
|
if (target.Health == 0)
|
||||||
|
{
|
||||||
|
this.SendEvent(new PlayerDiedEvent
|
||||||
|
{
|
||||||
|
PlayerId = e.TargetId,
|
||||||
|
KillerId = e.AttackerId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端表现逻辑
|
||||||
|
public class ClientPresentationSystem : AbstractSystem
|
||||||
|
{
|
||||||
|
protected override void OnInit()
|
||||||
|
{
|
||||||
|
this.RegisterEvent<PlayerDamagedEvent>(OnPlayerDamaged);
|
||||||
|
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerDamaged(PlayerDamagedEvent e)
|
||||||
|
{
|
||||||
|
// 播放受击特效
|
||||||
|
PlayDamageEffect(e.TargetId, e.Damage);
|
||||||
|
|
||||||
|
// 播放受击音效
|
||||||
|
PlayDamageSound(e.TargetId);
|
||||||
|
|
||||||
|
// 更新 UI
|
||||||
|
UpdateHealthBar(e.TargetId, e.RemainingHealth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerDied(PlayerDiedEvent e)
|
||||||
|
{
|
||||||
|
// 播放死亡动画
|
||||||
|
PlayDeathAnimation(e.PlayerId);
|
||||||
|
|
||||||
|
// 播放死亡音效
|
||||||
|
PlayDeathSound(e.PlayerId);
|
||||||
|
|
||||||
|
// 显示击杀提示
|
||||||
|
ShowKillFeed(e.KillerId, e.PlayerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
1359
docs/zh-CN/best-practices/performance.md
Normal file
1359
docs/zh-CN/best-practices/performance.md
Normal file
File diff suppressed because it is too large
Load Diff
839
docs/zh-CN/contributing.md
Normal file
839
docs/zh-CN/contributing.md
Normal file
@ -0,0 +1,839 @@
|
|||||||
|
# 贡献指南
|
||||||
|
|
||||||
|
欢迎为 GFramework 贡献代码!本指南将帮助你了解如何参与项目开发。
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
GFramework 是一个开源的游戏开发框架,我们欢迎所有形式的贡献:
|
||||||
|
|
||||||
|
- 报告 Bug 和提出功能建议
|
||||||
|
- 提交代码修复和新功能
|
||||||
|
- 改进文档和示例
|
||||||
|
- 参与讨论和代码审查
|
||||||
|
|
||||||
|
## 行为准则
|
||||||
|
|
||||||
|
### 社区规范
|
||||||
|
|
||||||
|
我们致力于为所有贡献者提供友好、安全和包容的环境。参与本项目时,请遵守以下准则:
|
||||||
|
|
||||||
|
- **尊重他人**:尊重不同的观点和经验
|
||||||
|
- **建设性沟通**:提供有建设性的反馈,避免人身攻击
|
||||||
|
- **协作精神**:帮助新贡献者融入社区
|
||||||
|
- **专业态度**:保持专业和礼貌的交流方式
|
||||||
|
|
||||||
|
### 不可接受的行为
|
||||||
|
|
||||||
|
- 使用性别化语言或图像
|
||||||
|
- 人身攻击或侮辱性评论
|
||||||
|
- 骚扰行为(公开或私下)
|
||||||
|
- 未经许可发布他人的私人信息
|
||||||
|
- 其他不道德或不专业的行为
|
||||||
|
|
||||||
|
## 如何贡献
|
||||||
|
|
||||||
|
### 报告问题
|
||||||
|
|
||||||
|
发现 Bug 或有功能建议时,请通过 GitHub Issues 提交:
|
||||||
|
|
||||||
|
1. **搜索现有 Issue**:避免重复提交
|
||||||
|
2. **使用清晰的标题**:简洁描述问题
|
||||||
|
3. **提供详细信息**:
|
||||||
|
- Bug 报告:复现步骤、预期行为、实际行为、环境信息
|
||||||
|
- 功能建议:使用场景、预期效果、可能的实现方案
|
||||||
|
|
||||||
|
**Bug 报告模板**:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**描述**
|
||||||
|
简要描述 Bug
|
||||||
|
|
||||||
|
**复现步骤**
|
||||||
|
1. 执行操作 A
|
||||||
|
2. 执行操作 B
|
||||||
|
3. 观察到错误
|
||||||
|
|
||||||
|
**预期行为**
|
||||||
|
应该发生什么
|
||||||
|
|
||||||
|
**实际行为**
|
||||||
|
实际发生了什么
|
||||||
|
|
||||||
|
**环境信息**
|
||||||
|
- GFramework 版本:
|
||||||
|
- .NET 版本:
|
||||||
|
- 操作系统:
|
||||||
|
- Godot 版本(如适用):
|
||||||
|
|
||||||
|
**附加信息**
|
||||||
|
日志、截图等
|
||||||
|
```
|
||||||
|
|
||||||
|
### 提交 Pull Request
|
||||||
|
|
||||||
|
#### 基本流程
|
||||||
|
|
||||||
|
1. **Fork 仓库**:在 GitHub 上 Fork 本项目
|
||||||
|
2. **克隆到本地**:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/your-username/GFramework.git
|
||||||
|
cd GFramework
|
||||||
|
```
|
||||||
|
3. **创建特性分支**:
|
||||||
|
```bash
|
||||||
|
git checkout -b feature/your-feature-name
|
||||||
|
# 或
|
||||||
|
git checkout -b fix/your-bug-fix
|
||||||
|
```
|
||||||
|
4. **进行开发**:编写代码、添加测试、更新文档
|
||||||
|
5. **提交更改**:遵循提交规范(见下文)
|
||||||
|
6. **推送分支**:
|
||||||
|
```bash
|
||||||
|
git push origin feature/your-feature-name
|
||||||
|
```
|
||||||
|
7. **创建 PR**:在 GitHub 上创建 Pull Request
|
||||||
|
|
||||||
|
#### PR 要求
|
||||||
|
|
||||||
|
- **清晰的标题**:简洁描述变更内容
|
||||||
|
- **详细的描述**:
|
||||||
|
- 变更的背景和动机
|
||||||
|
- 实现方案说明
|
||||||
|
- 测试验证结果
|
||||||
|
- 相关 Issue 链接(如有)
|
||||||
|
- **代码质量**:通过所有 CI 检查
|
||||||
|
- **测试覆盖**:为新功能添加测试
|
||||||
|
- **文档更新**:更新相关文档
|
||||||
|
|
||||||
|
### 改进文档
|
||||||
|
|
||||||
|
文档改进同样重要:
|
||||||
|
|
||||||
|
- **修正错误**:拼写、语法、技术错误
|
||||||
|
- **补充示例**:添加代码示例和使用场景
|
||||||
|
- **完善说明**:改进不清晰的描述
|
||||||
|
- **翻译工作**:帮助翻译文档(如需要)
|
||||||
|
|
||||||
|
文档位于 `docs/` 目录,使用 Markdown 格式编写。
|
||||||
|
|
||||||
|
## 开发环境设置
|
||||||
|
|
||||||
|
### 前置要求
|
||||||
|
|
||||||
|
- **.NET SDK**:8.0、9.0 或 10.0
|
||||||
|
- **Git**:版本控制工具
|
||||||
|
- **IDE**(推荐):
|
||||||
|
- Visual Studio 2022+
|
||||||
|
- JetBrains Rider
|
||||||
|
- Visual Studio Code + C# Dev Kit
|
||||||
|
|
||||||
|
### 克隆仓库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 克隆你 Fork 的仓库
|
||||||
|
git clone https://github.com/your-username/GFramework.git
|
||||||
|
cd GFramework
|
||||||
|
|
||||||
|
# 添加上游仓库
|
||||||
|
git remote add upstream https://github.com/GeWuYou/GFramework.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 恢复 NuGet 包
|
||||||
|
dotnet restore
|
||||||
|
|
||||||
|
# 恢复 .NET 本地工具
|
||||||
|
dotnet tool restore
|
||||||
|
```
|
||||||
|
|
||||||
|
### 构建项目
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建所有项目
|
||||||
|
dotnet build
|
||||||
|
|
||||||
|
# 构建特定配置
|
||||||
|
dotnet build -c Release
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行所有测试
|
||||||
|
dotnet test
|
||||||
|
|
||||||
|
# 运行特定测试项目
|
||||||
|
dotnet test GFramework.Core.Tests
|
||||||
|
dotnet test GFramework.SourceGenerators.Tests
|
||||||
|
|
||||||
|
# 生成测试覆盖率报告
|
||||||
|
dotnet test --collect:"XPlat Code Coverage"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 验证代码质量
|
||||||
|
|
||||||
|
项目使用 MegaLinter 进行代码质量检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 本地运行 MegaLinter(需要 Docker)
|
||||||
|
docker run --rm -v $(pwd):/tmp/lint oxsecurity/megalinter:v9
|
||||||
|
|
||||||
|
# 或使用 CI 流程验证
|
||||||
|
git push origin your-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
## 代码规范
|
||||||
|
|
||||||
|
### 命名规范
|
||||||
|
|
||||||
|
遵循 C# 标准命名约定:
|
||||||
|
|
||||||
|
- **类、接口、方法**:PascalCase
|
||||||
|
```csharp
|
||||||
|
public class PlayerController { }
|
||||||
|
public interface IEventBus { }
|
||||||
|
public void ProcessInput() { }
|
||||||
|
```
|
||||||
|
|
||||||
|
- **私有字段**:_camelCase(下划线前缀)
|
||||||
|
```csharp
|
||||||
|
private int _health;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **参数、局部变量**:camelCase
|
||||||
|
```csharp
|
||||||
|
public void SetHealth(int newHealth)
|
||||||
|
{
|
||||||
|
var oldHealth = _health;
|
||||||
|
_health = newHealth;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **常量**:PascalCase
|
||||||
|
```csharp
|
||||||
|
public const int MaxPlayers = 4;
|
||||||
|
private const string DefaultName = "Player";
|
||||||
|
```
|
||||||
|
|
||||||
|
- **接口**:I 前缀
|
||||||
|
```csharp
|
||||||
|
public interface IArchitecture { }
|
||||||
|
public interface ICommand<TInput> { }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 代码风格
|
||||||
|
|
||||||
|
- **缩进**:4 个空格(不使用 Tab)
|
||||||
|
- **大括号**:Allman 风格(独占一行)
|
||||||
|
```csharp
|
||||||
|
if (condition)
|
||||||
|
{
|
||||||
|
DoSomething();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **using 指令**:文件顶部,按字母顺序排列
|
||||||
|
```csharp
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GFramework.Core.Abstractions;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **空行**:
|
||||||
|
- 命名空间后空一行
|
||||||
|
- 类成员之间空一行
|
||||||
|
- 逻辑块之间适当空行
|
||||||
|
|
||||||
|
- **行长度**:建议不超过 120 字符
|
||||||
|
|
||||||
|
### 注释规范
|
||||||
|
|
||||||
|
#### XML 文档注释
|
||||||
|
|
||||||
|
所有公共 API 必须包含 XML 文档注释:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
/// <summary>
|
||||||
|
/// 架构基类,提供系统、模型、工具等组件的注册与管理功能。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TModel">模型类型</typeparam>
|
||||||
|
/// <param name="configuration">架构配置</param>
|
||||||
|
/// <returns>注册的模型实例</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">当 model 为 null 时抛出</exception>
|
||||||
|
public TModel RegisterModel<TModel>(TModel model) where TModel : IModel
|
||||||
|
{
|
||||||
|
// 实现代码
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 代码注释
|
||||||
|
|
||||||
|
- **何时添加注释**:
|
||||||
|
- 复杂的算法逻辑
|
||||||
|
- 非显而易见的设计决策
|
||||||
|
- 临时解决方案(使用 TODO 或 HACK 标记)
|
||||||
|
- 性能关键代码的优化说明
|
||||||
|
|
||||||
|
- **注释风格**:
|
||||||
|
```csharp
|
||||||
|
// 单行注释使用双斜杠
|
||||||
|
|
||||||
|
// 多行注释可以使用多个单行注释
|
||||||
|
// 每行都以双斜杠开始
|
||||||
|
|
||||||
|
/* 或使用块注释
|
||||||
|
* 适用于较长的说明
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
- **避免无用注释**:
|
||||||
|
```csharp
|
||||||
|
// 不好:注释重复代码内容
|
||||||
|
// 设置健康值为 100
|
||||||
|
health = 100;
|
||||||
|
|
||||||
|
// 好:解释为什么这样做
|
||||||
|
// 初始化时设置满血,避免首次战斗时的边界情况
|
||||||
|
health = MaxHealth;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设计原则
|
||||||
|
|
||||||
|
- **SOLID 原则**:遵循面向对象设计原则
|
||||||
|
- **依赖注入**:优先使用构造函数注入
|
||||||
|
- **接口隔离**:定义小而专注的接口
|
||||||
|
- **不可变性**:优先使用 `readonly` 和不可变类型
|
||||||
|
- **异步编程**:I/O 操作使用 `async`/`await`
|
||||||
|
|
||||||
|
## 提交规范
|
||||||
|
|
||||||
|
### Commit 消息格式
|
||||||
|
|
||||||
|
使用 Conventional Commits 规范:
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Type(类型)
|
||||||
|
|
||||||
|
- **feat**:新功能
|
||||||
|
- **fix**:Bug 修复
|
||||||
|
- **docs**:文档更新
|
||||||
|
- **style**:代码格式调整(不影响功能)
|
||||||
|
- **refactor**:重构(不是新功能也不是修复)
|
||||||
|
- **perf**:性能优化
|
||||||
|
- **test**:添加或修改测试
|
||||||
|
- **chore**:构建过程或辅助工具的变动
|
||||||
|
- **ci**:CI 配置文件和脚本的变动
|
||||||
|
|
||||||
|
#### Scope(范围)
|
||||||
|
|
||||||
|
指明变更影响的模块:
|
||||||
|
|
||||||
|
- `core`:GFramework.Core
|
||||||
|
- `game`:GFramework.Game
|
||||||
|
- `godot`:GFramework.Godot
|
||||||
|
- `generators`:源码生成器
|
||||||
|
- `docs`:文档
|
||||||
|
- `tests`:测试
|
||||||
|
|
||||||
|
#### Subject(主题)
|
||||||
|
|
||||||
|
- 使用祈使句,现在时态:"add" 而不是 "added" 或 "adds"
|
||||||
|
- 首字母小写
|
||||||
|
- 结尾不加句号
|
||||||
|
- 限制在 50 字符以内
|
||||||
|
|
||||||
|
#### Body(正文)
|
||||||
|
|
||||||
|
- 详细描述变更的动机和实现细节
|
||||||
|
- 与主题空一行
|
||||||
|
- 每行不超过 72 字符
|
||||||
|
|
||||||
|
#### Footer(页脚)
|
||||||
|
|
||||||
|
- 关联 Issue:`Closes #123`
|
||||||
|
- 破坏性变更:`BREAKING CHANGE: 描述`
|
||||||
|
|
||||||
|
#### 示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 简单提交
|
||||||
|
git commit -m "feat(core): add event priority support"
|
||||||
|
|
||||||
|
# 详细提交
|
||||||
|
git commit -m "fix(godot): resolve scene loading race condition
|
||||||
|
|
||||||
|
修复了在快速切换场景时可能出现的资源加载竞态条件。
|
||||||
|
通过引入场景加载锁机制,确保同一时间只有一个场景在加载。
|
||||||
|
|
||||||
|
Closes #456"
|
||||||
|
|
||||||
|
# 破坏性变更
|
||||||
|
git commit -m "refactor(core): change IArchitecture interface
|
||||||
|
|
||||||
|
BREAKING CHANGE: IArchitecture.Init() 现在返回 Task 而不是 void。
|
||||||
|
所有继承 Architecture 的类需要更新为异步初始化。
|
||||||
|
|
||||||
|
Migration guide: 将 Init() 改为 async Task Init()
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分支策略
|
||||||
|
|
||||||
|
- **main**:主分支,保持稳定
|
||||||
|
- **feature/***:新功能分支
|
||||||
|
- `feature/event-priority`
|
||||||
|
- `feature/godot-ui-system`
|
||||||
|
- **fix/***:Bug 修复分支
|
||||||
|
- `fix/memory-leak`
|
||||||
|
- `fix/null-reference`
|
||||||
|
- **docs/***:文档更新分支
|
||||||
|
- `docs/api-reference`
|
||||||
|
- `docs/tutorial-update`
|
||||||
|
- **refactor/***:重构分支
|
||||||
|
- `refactor/logging-system`
|
||||||
|
|
||||||
|
#### 分支命名规范
|
||||||
|
|
||||||
|
- 使用小写字母和连字符
|
||||||
|
- 简洁描述分支目的
|
||||||
|
- 避免使用个人名称
|
||||||
|
|
||||||
|
## 测试要求
|
||||||
|
|
||||||
|
### 单元测试
|
||||||
|
|
||||||
|
所有新功能和 Bug 修复都应包含单元测试:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Tests.events;
|
||||||
|
|
||||||
|
public class EventBusTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Subscribe_ShouldReceiveEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var eventBus = new EventBus();
|
||||||
|
var received = false;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
eventBus.Subscribe<TestEvent>(e => received = true);
|
||||||
|
eventBus.Publish(new TestEvent());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(received);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(1)]
|
||||||
|
[InlineData(10)]
|
||||||
|
[InlineData(100)]
|
||||||
|
public void Subscribe_MultipleEvents_ShouldReceiveAll(int count)
|
||||||
|
{
|
||||||
|
// 测试实现
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试组织
|
||||||
|
|
||||||
|
- **测试项目**:`*.Tests` 后缀
|
||||||
|
- **测试类**:`*Tests` 后缀,与被测试类对应
|
||||||
|
- **测试方法**:`MethodName_Scenario_ExpectedResult` 格式
|
||||||
|
- **测试数据**:使用 `[Theory]` 和 `[InlineData]` 进行参数化测试
|
||||||
|
|
||||||
|
### 测试覆盖率
|
||||||
|
|
||||||
|
- **目标**:新代码覆盖率 > 80%
|
||||||
|
- **关键路径**:核心功能覆盖率 > 90%
|
||||||
|
- **边界情况**:测试异常情况和边界值
|
||||||
|
|
||||||
|
### 集成测试
|
||||||
|
|
||||||
|
对于涉及多个组件交互的功能,添加集成测试:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class ArchitectureIntegrationTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Architecture_FullLifecycle_ShouldWork()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var architecture = new TestArchitecture();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await architecture.InitAsync();
|
||||||
|
var result = architecture.GetModel<TestModel>();
|
||||||
|
await architecture.DestroyAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能测试
|
||||||
|
|
||||||
|
对性能敏感的代码,添加基准测试:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class EventBusBenchmarks
|
||||||
|
{
|
||||||
|
private EventBus _eventBus;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_eventBus = new EventBus();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Publish_1000Events()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 1000; i++)
|
||||||
|
{
|
||||||
|
_eventBus.Publish(new TestEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文档要求
|
||||||
|
|
||||||
|
### XML 注释
|
||||||
|
|
||||||
|
所有公共 API 必须包含完整的 XML 文档注释:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
/// <summary>
|
||||||
|
/// 事件总线接口,提供事件的发布和订阅功能。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 事件总线使用观察者模式实现,支持类型安全的事件分发。
|
||||||
|
/// 所有订阅都是弱引用,避免内存泄漏。
|
||||||
|
/// </remarks>
|
||||||
|
public interface IEventBus
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 订阅指定类型的事件。
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEvent">事件类型</typeparam>
|
||||||
|
/// <param name="handler">事件处理器</param>
|
||||||
|
/// <returns>取消订阅的句柄</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">当 handler 为 null 时抛出</exception>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// var unregister = eventBus.Subscribe&lt;PlayerDiedEvent&gt;(e =&gt;
|
||||||
|
/// {
|
||||||
|
/// Console.WriteLine($"Player {e.PlayerId} died");
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // 取消订阅
|
||||||
|
/// unregister.Dispose();
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
IUnRegister Subscribe<TEvent>(Action<TEvent> handler) where TEvent : IEvent;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Markdown 文档
|
||||||
|
|
||||||
|
#### 文档结构
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# 标题
|
||||||
|
|
||||||
|
简要介绍模块功能和用途。
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
解释关键概念和术语。
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
提供最简单的使用示例。
|
||||||
|
|
||||||
|
## 详细用法
|
||||||
|
|
||||||
|
### 功能 A
|
||||||
|
|
||||||
|
详细说明和代码示例。
|
||||||
|
|
||||||
|
### 功能 B
|
||||||
|
|
||||||
|
详细说明和代码示例。
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
推荐的使用模式和注意事项。
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
FAQ 列表。
|
||||||
|
|
||||||
|
## 相关资源
|
||||||
|
|
||||||
|
链接到相关文档和示例。
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 代码示例
|
||||||
|
|
||||||
|
- **完整性**:示例代码应该可以直接运行
|
||||||
|
- **注释**:关键步骤添加注释说明
|
||||||
|
- **格式化**:使用正确的语法高亮
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 创建架构实例
|
||||||
|
var architecture = new GameArchitecture();
|
||||||
|
|
||||||
|
// 初始化架构
|
||||||
|
await architecture.InitAsync();
|
||||||
|
|
||||||
|
// 注册模型
|
||||||
|
var playerModel = architecture.GetModel<PlayerModel>();
|
||||||
|
|
||||||
|
// 发送命令
|
||||||
|
await architecture.SendCommandAsync(new AttackCommand
|
||||||
|
{
|
||||||
|
TargetId = enemyId
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 图表
|
||||||
|
|
||||||
|
使用 Mermaid 或 ASCII 图表说明复杂概念:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Controller] --> B[Command]
|
||||||
|
B --> C[System]
|
||||||
|
C --> D[Model]
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## PR 流程
|
||||||
|
|
||||||
|
### 创建 PR
|
||||||
|
|
||||||
|
1. **确保分支最新**:
|
||||||
|
```bash
|
||||||
|
git fetch upstream
|
||||||
|
git rebase upstream/main
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **推送到 Fork**:
|
||||||
|
```bash
|
||||||
|
git push origin feature/your-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **创建 PR**:
|
||||||
|
- 在 GitHub 上点击 "New Pull Request"
|
||||||
|
- 选择 base: `main` ← compare: `your-branch`
|
||||||
|
- 填写 PR 模板
|
||||||
|
|
||||||
|
### PR 模板
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 变更说明
|
||||||
|
|
||||||
|
简要描述本 PR 的变更内容。
|
||||||
|
|
||||||
|
## 变更类型
|
||||||
|
|
||||||
|
- [ ] Bug 修复
|
||||||
|
- [ ] 新功能
|
||||||
|
- [ ] 破坏性变更
|
||||||
|
- [ ] 文档更新
|
||||||
|
- [ ] 性能优化
|
||||||
|
- [ ] 代码重构
|
||||||
|
|
||||||
|
## 相关 Issue
|
||||||
|
|
||||||
|
Closes #123
|
||||||
|
|
||||||
|
## 测试
|
||||||
|
|
||||||
|
描述如何测试这些变更:
|
||||||
|
|
||||||
|
- [ ] 添加了单元测试
|
||||||
|
- [ ] 添加了集成测试
|
||||||
|
- [ ] 手动测试通过
|
||||||
|
|
||||||
|
## 检查清单
|
||||||
|
|
||||||
|
- [ ] 代码遵循项目规范
|
||||||
|
- [ ] 添加了必要的注释
|
||||||
|
- [ ] 更新了相关文档
|
||||||
|
- [ ] 所有测试通过
|
||||||
|
- [ ] 没有引入新的警告
|
||||||
|
|
||||||
|
## 截图(如适用)
|
||||||
|
|
||||||
|
添加截图或 GIF 展示变更效果。
|
||||||
|
|
||||||
|
## 附加说明
|
||||||
|
|
||||||
|
其他需要说明的内容。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 代码审查
|
||||||
|
|
||||||
|
PR 提交后,维护者会进行代码审查:
|
||||||
|
|
||||||
|
- **响应反馈**:及时回复审查意见
|
||||||
|
- **修改代码**:根据建议进行调整
|
||||||
|
- **讨论方案**:对有争议的地方进行讨论
|
||||||
|
- **保持耐心**:审查可能需要时间
|
||||||
|
|
||||||
|
#### 审查关注点
|
||||||
|
|
||||||
|
- **功能正确性**:代码是否实现了预期功能
|
||||||
|
- **代码质量**:是否遵循项目规范
|
||||||
|
- **测试覆盖**:是否有足够的测试
|
||||||
|
- **性能影响**:是否有性能问题
|
||||||
|
- **向后兼容**:是否破坏现有 API
|
||||||
|
|
||||||
|
### 合并流程
|
||||||
|
|
||||||
|
1. **通过 CI 检查**:所有自动化测试通过
|
||||||
|
2. **代码审查通过**:至少一位维护者批准
|
||||||
|
3. **解决冲突**:如有冲突需先解决
|
||||||
|
4. **合并方式**:
|
||||||
|
- 功能分支:Squash and merge
|
||||||
|
- 修复分支:Merge commit
|
||||||
|
- 文档更新:Squash and merge
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 如何同步上游更新?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 获取上游更新
|
||||||
|
git fetch upstream
|
||||||
|
|
||||||
|
# 合并到本地 main
|
||||||
|
git checkout main
|
||||||
|
git merge upstream/main
|
||||||
|
|
||||||
|
# 更新你的 Fork
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# 更新特性分支
|
||||||
|
git checkout feature/your-feature
|
||||||
|
git rebase main
|
||||||
|
```
|
||||||
|
|
||||||
|
### 如何解决合并冲突?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 拉取最新代码
|
||||||
|
git fetch upstream
|
||||||
|
git rebase upstream/main
|
||||||
|
|
||||||
|
# 如果有冲突,手动解决后
|
||||||
|
git add .
|
||||||
|
git rebase --continue
|
||||||
|
|
||||||
|
# 强制推送(因为 rebase 改变了历史)
|
||||||
|
git push origin feature/your-feature --force-with-lease
|
||||||
|
```
|
||||||
|
|
||||||
|
### 提交了错误的代码怎么办?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 修改最后一次提交
|
||||||
|
git add .
|
||||||
|
git commit --amend
|
||||||
|
|
||||||
|
# 或者撤销最后一次提交
|
||||||
|
git reset --soft HEAD~1
|
||||||
|
# 修改后重新提交
|
||||||
|
git add .
|
||||||
|
git commit -m "fix: correct implementation"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 如何运行特定的测试?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行单个测试类
|
||||||
|
dotnet test --filter "FullyQualifiedName~EventBusTests"
|
||||||
|
|
||||||
|
# 运行单个测试方法
|
||||||
|
dotnet test --filter "FullyQualifiedName~EventBusTests.Subscribe_ShouldReceiveEvent"
|
||||||
|
|
||||||
|
# 运行特定类别的测试
|
||||||
|
dotnet test --filter "Category=Integration"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 如何生成文档?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 VitePress(如果还没安装)
|
||||||
|
cd docs
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 本地预览文档
|
||||||
|
npm run docs:dev
|
||||||
|
|
||||||
|
# 构建文档
|
||||||
|
npm run docs:build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 代码审查需要多长时间?
|
||||||
|
|
||||||
|
- **简单修复**:通常 1-3 天
|
||||||
|
- **新功能**:可能需要 1-2 周
|
||||||
|
- **大型重构**:可能需要更长时间
|
||||||
|
|
||||||
|
请耐心等待,维护者会尽快审查。
|
||||||
|
|
||||||
|
### 我的 PR 被拒绝了怎么办?
|
||||||
|
|
||||||
|
不要气馁!被拒绝的原因可能是:
|
||||||
|
|
||||||
|
- 不符合项目方向
|
||||||
|
- 需要更多讨论
|
||||||
|
- 实现方式需要调整
|
||||||
|
|
||||||
|
你可以:
|
||||||
|
|
||||||
|
- 在 Issue 中讨论方案
|
||||||
|
- 根据反馈调整实现
|
||||||
|
- 寻求维护者的建议
|
||||||
|
|
||||||
|
### 如何成为维护者?
|
||||||
|
|
||||||
|
持续贡献高质量的代码和文档,积极参与社区讨论,帮助其他贡献者。维护者会邀请活跃且负责任的贡献者加入维护团队。
|
||||||
|
|
||||||
|
## 获取帮助
|
||||||
|
|
||||||
|
如果你在贡献过程中遇到问题:
|
||||||
|
|
||||||
|
- **GitHub Issues**:提问或报告问题
|
||||||
|
- **GitHub Discussions**:参与讨论
|
||||||
|
- **代码注释**:查看现有代码的注释和文档
|
||||||
|
|
||||||
|
## 致谢
|
||||||
|
|
||||||
|
感谢所有为 GFramework 做出贡献的开发者!你们的努力让这个项目变得更好。
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
通过向本项目提交代码,你同意你的贡献将在 Apache License 2.0 下发布。
|
||||||
950
docs/zh-CN/migration-guide.md
Normal file
950
docs/zh-CN/migration-guide.md
Normal file
@ -0,0 +1,950 @@
|
|||||||
|
# 版本迁移指南
|
||||||
|
|
||||||
|
本文档提供 GFramework 不同版本之间的迁移指导,帮助开发者平滑升级到新版本。
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
### 迁移指南的使用
|
||||||
|
|
||||||
|
本迁移指南旨在帮助开发者:
|
||||||
|
|
||||||
|
- **了解版本间的重大变更**:明确不同版本之间的 API 变化和行为差异
|
||||||
|
- **规划迁移路径**:根据项目实际情况选择合适的迁移策略
|
||||||
|
- **减少迁移风险**:通过详细的步骤说明和代码示例降低升级风险
|
||||||
|
- **快速定位问题**:提供常见问题的解决方案和回滚策略
|
||||||
|
|
||||||
|
### 阅读建议
|
||||||
|
|
||||||
|
1. **确认当前版本**:查看项目中使用的 GFramework 版本号
|
||||||
|
2. **查看目标版本**:确定要升级到的目标版本
|
||||||
|
3. **阅读相关章节**:重点关注涉及的版本迁移章节
|
||||||
|
4. **测试验证**:在测试环境中完成迁移并充分测试
|
||||||
|
5. **逐步升级**:对于跨多个大版本的升级,建议分步进行
|
||||||
|
|
||||||
|
## 版本兼容性
|
||||||
|
|
||||||
|
### 版本号说明
|
||||||
|
|
||||||
|
GFramework 遵循 [语义化版本](https://semver.org/lang/zh-CN/) 规范:
|
||||||
|
|
||||||
|
```
|
||||||
|
主版本号.次版本号.修订号 (MAJOR.MINOR.PATCH)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **主版本号(MAJOR)**:不兼容的 API 变更
|
||||||
|
- **次版本号(MINOR)**:向后兼容的功能新增
|
||||||
|
- **修订号(PATCH)**:向后兼容的问题修正
|
||||||
|
|
||||||
|
### 兼容性矩阵
|
||||||
|
|
||||||
|
| 源版本 | 目标版本 | 兼容性 | 迁移难度 | 说明 |
|
||||||
|
|-------|-------|---------|------|---------------|
|
||||||
|
| 0.0.x | 0.0.y | ✅ 完全兼容 | 低 | 修订版本,直接升级 |
|
||||||
|
| 0.0.x | 1.0.0 | ⚠️ 部分兼容 | 中 | 需要代码调整 |
|
||||||
|
| 0.x.x | 1.x.x | ❌ 不兼容 | 高 | 重大变更,需要重构 |
|
||||||
|
| 1.x.x | 1.y.y | ✅ 向后兼容 | 低 | 次版本升级,可能有废弃警告 |
|
||||||
|
| 1.x.x | 2.0.0 | ❌ 不兼容 | 高 | 重大变更,需要重构 |
|
||||||
|
|
||||||
|
### .NET 版本支持
|
||||||
|
|
||||||
|
| GFramework 版本 | .NET 8.0 | .NET 9.0 | .NET 10.0 |
|
||||||
|
|---------------|----------|----------|-----------|
|
||||||
|
| 0.0.x | ✅ | ✅ | ✅ |
|
||||||
|
| 1.0.x | ✅ | ✅ | ✅ |
|
||||||
|
| 2.0.x | ❌ | ✅ | ✅ |
|
||||||
|
|
||||||
|
### Godot 版本支持
|
||||||
|
|
||||||
|
| GFramework 版本 | Godot 4.3 | Godot 4.4 | Godot 4.5 | Godot 4.6+ |
|
||||||
|
|---------------|-----------|-----------|-----------|------------|
|
||||||
|
| 0.0.x | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| 1.0.x | ❌ | ✅ | ✅ | ✅ |
|
||||||
|
| 2.0.x | ❌ | ❌ | ✅ | ✅ |
|
||||||
|
|
||||||
|
## 从 0.x 迁移到 1.x
|
||||||
|
|
||||||
|
### 重大变更概述
|
||||||
|
|
||||||
|
1.0 版本是 GFramework 的第一个稳定版本,引入了多项重大变更以提升框架的稳定性、性能和可维护性。
|
||||||
|
|
||||||
|
#### 架构层面变更
|
||||||
|
|
||||||
|
- **架构初始化方式变更**:统一使用异步初始化
|
||||||
|
- **生命周期阶段调整**:简化阶段流程,移除冗余阶段
|
||||||
|
- **IOC 容器增强**:支持作用域和生命周期管理
|
||||||
|
- **模块系统重构**:引入新的模块注册机制
|
||||||
|
|
||||||
|
#### API 变更
|
||||||
|
|
||||||
|
- **命名空间调整**:部分类型移动到新的命名空间
|
||||||
|
- **接口签名变更**:部分接口方法签名调整
|
||||||
|
- **废弃 API 移除**:移除 0.x 中标记为废弃的 API
|
||||||
|
- **泛型约束调整**:部分泛型方法增加或调整约束
|
||||||
|
|
||||||
|
#### 行为变更
|
||||||
|
|
||||||
|
- **事件传播机制**:优化事件传播逻辑
|
||||||
|
- **协程调度策略**:改进协程调度算法
|
||||||
|
- **资源管理策略**:引入新的资源释放策略
|
||||||
|
|
||||||
|
### 迁移前准备
|
||||||
|
|
||||||
|
#### 1. 备份项目
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建项目备份
|
||||||
|
git checkout -b backup-before-migration
|
||||||
|
git push origin backup-before-migration
|
||||||
|
|
||||||
|
# 或使用文件系统备份
|
||||||
|
cp -r YourProject YourProject-backup
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 检查当前版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看当前使用的 GFramework 版本
|
||||||
|
dotnet list package | grep GFramework
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 更新依赖工具
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 更新 .NET SDK
|
||||||
|
dotnet --version
|
||||||
|
|
||||||
|
# 更新 NuGet 客户端
|
||||||
|
dotnet nuget --version
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 运行现有测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 确保所有测试通过
|
||||||
|
dotnet test
|
||||||
|
|
||||||
|
# 记录测试结果作为基准
|
||||||
|
dotnet test --logger "trx;LogFileName=baseline-tests.trx"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 迁移步骤
|
||||||
|
|
||||||
|
#### 步骤 1:更新 NuGet 包
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 更新核心包
|
||||||
|
dotnet add package GeWuYou.GFramework.Core --version 1.0.0
|
||||||
|
dotnet add package GeWuYou.GFramework.Core.Abstractions --version 1.0.0
|
||||||
|
|
||||||
|
# 更新游戏扩展包
|
||||||
|
dotnet add package GeWuYou.GFramework.Game --version 1.0.0
|
||||||
|
dotnet add package GeWuYou.GFramework.Game.Abstractions --version 1.0.0
|
||||||
|
|
||||||
|
# 更新 Godot 集成包(如果使用)
|
||||||
|
dotnet add package GeWuYou.GFramework.Godot --version 1.0.0
|
||||||
|
|
||||||
|
# 更新源码生成器
|
||||||
|
dotnet add package GeWuYou.GFramework.SourceGenerators --version 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤 2:更新命名空间引用
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core;
|
||||||
|
using GFramework.Core.Architecture;
|
||||||
|
using GFramework.Core.Events;
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using GFramework.Core.Abstractions.Architecture;
|
||||||
|
using GFramework.Core.Abstractions.Events;
|
||||||
|
using GFramework.Core.Architecture;
|
||||||
|
using GFramework.Core.Events;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤 3:更新架构初始化代码
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
RegisterModel(new PlayerModel());
|
||||||
|
RegisterSystem(new GameplaySystem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步初始化
|
||||||
|
var architecture = new GameArchitecture();
|
||||||
|
architecture.Initialize();
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GameArchitecture : Architecture
|
||||||
|
{
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
RegisterModel(new PlayerModel());
|
||||||
|
RegisterSystem(new GameplaySystem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推荐使用异步初始化
|
||||||
|
var architecture = new GameArchitecture();
|
||||||
|
await architecture.InitializeAsync();
|
||||||
|
|
||||||
|
// 或者使用同步初始化(不推荐)
|
||||||
|
// architecture.Initialize();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤 4:更新事件注册代码
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 注册事件
|
||||||
|
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
|
||||||
|
|
||||||
|
// 发送事件
|
||||||
|
this.SendEvent(new PlayerDiedEvent());
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 注册事件(API 保持兼容)
|
||||||
|
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
|
||||||
|
|
||||||
|
// 发送事件(API 保持兼容)
|
||||||
|
this.SendEvent(new PlayerDiedEvent());
|
||||||
|
|
||||||
|
// 新增:带优先级的事件注册
|
||||||
|
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied, priority: 100);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤 5:更新命令和查询代码
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MovePlayerCommand : AbstractCommand
|
||||||
|
{
|
||||||
|
public Vector2 Direction { get; set; }
|
||||||
|
|
||||||
|
protected override void OnDo()
|
||||||
|
{
|
||||||
|
// 执行逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送命令
|
||||||
|
this.SendCommand(new MovePlayerCommand { Direction = direction });
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 命令 API 保持兼容
|
||||||
|
public class MovePlayerCommand : AbstractCommand
|
||||||
|
{
|
||||||
|
public Vector2 Direction { get; set; }
|
||||||
|
|
||||||
|
protected override void OnDo()
|
||||||
|
{
|
||||||
|
// 执行逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送命令(API 保持兼容)
|
||||||
|
this.SendCommand(new MovePlayerCommand { Direction = direction });
|
||||||
|
|
||||||
|
// 新增:异步命令支持
|
||||||
|
public class LoadDataCommand : AbstractAsyncCommand
|
||||||
|
{
|
||||||
|
protected override async Task OnDoAsync()
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤 6:更新 IOC 容器使用
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 注册服务
|
||||||
|
RegisterUtility(new StorageUtility());
|
||||||
|
|
||||||
|
// 获取服务
|
||||||
|
var storage = this.GetUtility<StorageUtility>();
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 注册服务(API 保持兼容)
|
||||||
|
RegisterUtility(new StorageUtility());
|
||||||
|
|
||||||
|
// 获取服务(API 保持兼容)
|
||||||
|
var storage = this.GetUtility<StorageUtility>();
|
||||||
|
|
||||||
|
// 新增:按优先级获取服务
|
||||||
|
var storages = this.GetUtilities<IStorageUtility>();
|
||||||
|
var primaryStorage = storages.FirstOrDefault();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤 7:更新协程代码
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 启动协程
|
||||||
|
var handle = CoroutineHelper.Start(MyCoroutine());
|
||||||
|
|
||||||
|
// 等待协程
|
||||||
|
yield return new WaitForCoroutine(handle);
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 启动协程(API 保持兼容)
|
||||||
|
var handle = CoroutineHelper.Start(MyCoroutine());
|
||||||
|
|
||||||
|
// 等待协程(API 保持兼容)
|
||||||
|
yield return new WaitForCoroutine(handle);
|
||||||
|
|
||||||
|
// 新增:协程分组和优先级
|
||||||
|
var handle = CoroutineHelper.Start(
|
||||||
|
MyCoroutine(),
|
||||||
|
group: "gameplay",
|
||||||
|
priority: CoroutinePriority.High
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### API 变更详解
|
||||||
|
|
||||||
|
#### 废弃的 API
|
||||||
|
|
||||||
|
以下 API 在 1.0 版本中已被移除:
|
||||||
|
|
||||||
|
##### 1. 同步命令查询扩展(已废弃)
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 这些方法在 1.0 中已移除
|
||||||
|
this.SendCommandSync(command);
|
||||||
|
this.SendQuerySync(query);
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用标准方法
|
||||||
|
this.SendCommand(command);
|
||||||
|
this.SendQuery(query);
|
||||||
|
|
||||||
|
// 或使用异步方法
|
||||||
|
await this.SendCommandAsync(command);
|
||||||
|
await this.SendQueryAsync(query);
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 2. 旧版事件 API(已废弃)
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 旧版事件注册方式
|
||||||
|
EventBus.Register<MyEvent>(handler);
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用新的事件注册方式
|
||||||
|
this.RegisterEvent<MyEvent>(handler);
|
||||||
|
|
||||||
|
// 或使用事件总线
|
||||||
|
this.GetEventBus().Register<MyEvent>(handler);
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 3. 直接访问 IOC 容器(已限制)
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 直接访问容器
|
||||||
|
var container = architecture.Container;
|
||||||
|
container.Register<IService, ServiceImpl>();
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用架构提供的注册方法
|
||||||
|
architecture.RegisterUtility<IService>(new ServiceImpl());
|
||||||
|
|
||||||
|
// 或在 Init 方法中注册
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
RegisterUtility<IService>(new ServiceImpl());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 新增的 API
|
||||||
|
|
||||||
|
##### 1. 优先级支持
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 事件优先级
|
||||||
|
this.RegisterEvent<MyEvent>(handler, priority: 100);
|
||||||
|
|
||||||
|
// 服务优先级
|
||||||
|
RegisterUtility<IService>(service, priority: 10);
|
||||||
|
|
||||||
|
// 协程优先级
|
||||||
|
CoroutineHelper.Start(routine, priority: CoroutinePriority.High);
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 2. 异步初始化增强
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 异步初始化架构
|
||||||
|
await architecture.InitializeAsync();
|
||||||
|
|
||||||
|
// 等待架构就绪
|
||||||
|
await architecture.WaitUntilReadyAsync();
|
||||||
|
|
||||||
|
// 异步初始化组件
|
||||||
|
public class MyModel : AbstractModel, IAsyncInitializable
|
||||||
|
{
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
await LoadDataAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 3. 事件过滤和统计
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 事件过滤
|
||||||
|
this.RegisterEvent<MyEvent>(handler, filter: e => e.IsValid);
|
||||||
|
|
||||||
|
// 事件统计
|
||||||
|
var stats = eventBus.GetStatistics();
|
||||||
|
Console.WriteLine($"Total events: {stats.TotalEventsSent}");
|
||||||
|
```
|
||||||
|
|
||||||
|
##### 4. 协程分组管理
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 创建协程组
|
||||||
|
var handle = CoroutineHelper.Start(
|
||||||
|
routine,
|
||||||
|
group: "ui-animations"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 暂停协程组
|
||||||
|
CoroutineHelper.PauseGroup("ui-animations");
|
||||||
|
|
||||||
|
// 恢复协程组
|
||||||
|
CoroutineHelper.ResumeGroup("ui-animations");
|
||||||
|
|
||||||
|
// 停止协程组
|
||||||
|
CoroutineHelper.StopGroup("ui-animations");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置变更
|
||||||
|
|
||||||
|
#### 架构配置
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var architecture = new GameArchitecture();
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用配置对象
|
||||||
|
var config = new ArchitectureConfiguration
|
||||||
|
{
|
||||||
|
ArchitectureProperties = new ArchitectureProperties
|
||||||
|
{
|
||||||
|
StrictPhaseValidation = true,
|
||||||
|
AllowLateRegistration = false
|
||||||
|
},
|
||||||
|
LoggerProperties = new LoggerProperties
|
||||||
|
{
|
||||||
|
MinimumLevel = LogLevel.Information
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var architecture = new GameArchitecture(configuration: config);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 日志配置
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用默认日志
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 配置日志系统
|
||||||
|
var logConfig = new LoggingConfiguration
|
||||||
|
{
|
||||||
|
MinimumLevel = LogLevel.Debug,
|
||||||
|
Appenders = new List<ILogAppender>
|
||||||
|
{
|
||||||
|
new ConsoleAppender(),
|
||||||
|
new FileAppender("logs/game.log")
|
||||||
|
},
|
||||||
|
Filters = new List<ILogFilter>
|
||||||
|
{
|
||||||
|
new LogLevelFilter(LogLevel.Warning),
|
||||||
|
new NamespaceFilter("GFramework.*")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 依赖变更
|
||||||
|
|
||||||
|
#### NuGet 包更新
|
||||||
|
|
||||||
|
| 包名 | 0.x 版本 | 1.x 版本 | 变更说明 |
|
||||||
|
|------------------------------------------|--------|--------|----------|
|
||||||
|
| Microsoft.Extensions.DependencyInjection | 8.0.0 | 10.0.3 | 升级到最新版本 |
|
||||||
|
| Arch | 1.x | 2.1.0 | ECS 框架升级 |
|
||||||
|
| Arch.System | 1.0.x | 1.1.0 | 系统组件升级 |
|
||||||
|
|
||||||
|
#### 包拆分
|
||||||
|
|
||||||
|
1.0 版本对包结构进行了优化:
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<PackageReference Include="GeWuYou.GFramework" Version="0.0.200" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- 推荐按需引用 -->
|
||||||
|
<PackageReference Include="GeWuYou.GFramework.Core" Version="1.0.0" />
|
||||||
|
<PackageReference Include="GeWuYou.GFramework.Core.Abstractions" Version="1.0.0" />
|
||||||
|
<PackageReference Include="GeWuYou.GFramework.Game" Version="1.0.0" />
|
||||||
|
<PackageReference Include="GeWuYou.GFramework.Godot" Version="1.0.0" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### 代码迁移工具
|
||||||
|
|
||||||
|
#### 自动化迁移工具
|
||||||
|
|
||||||
|
GFramework 提供了迁移工具来自动化部分迁移工作:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装迁移工具
|
||||||
|
dotnet tool install -g GFramework.MigrationTool
|
||||||
|
|
||||||
|
# 运行迁移分析
|
||||||
|
gframework-migrate analyze --project YourProject.csproj
|
||||||
|
|
||||||
|
# 执行自动迁移
|
||||||
|
gframework-migrate apply --project YourProject.csproj --target-version 1.0.0
|
||||||
|
|
||||||
|
# 生成迁移报告
|
||||||
|
gframework-migrate report --output migration-report.html
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 手动迁移检查清单
|
||||||
|
|
||||||
|
使用以下清单确保完整迁移:
|
||||||
|
|
||||||
|
- [ ] 更新所有 NuGet 包到 1.0.0
|
||||||
|
- [ ] 更新命名空间引用
|
||||||
|
- [ ] 替换废弃的 API
|
||||||
|
- [ ] 更新架构初始化代码
|
||||||
|
- [ ] 更新配置代码
|
||||||
|
- [ ] 运行所有单元测试
|
||||||
|
- [ ] 运行集成测试
|
||||||
|
- [ ] 执行性能测试
|
||||||
|
- [ ] 更新文档和注释
|
||||||
|
- [ ] 代码审查
|
||||||
|
|
||||||
|
### 测试迁移
|
||||||
|
|
||||||
|
#### 单元测试更新
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Test]
|
||||||
|
public void TestArchitectureInit()
|
||||||
|
{
|
||||||
|
var architecture = new TestArchitecture();
|
||||||
|
architecture.Initialize();
|
||||||
|
|
||||||
|
Assert.That(architecture.CurrentPhase, Is.EqualTo(ArchitecturePhase.Ready));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Test]
|
||||||
|
public async Task TestArchitectureInit()
|
||||||
|
{
|
||||||
|
var architecture = new TestArchitecture();
|
||||||
|
await architecture.InitializeAsync();
|
||||||
|
|
||||||
|
Assert.That(architecture.CurrentPhase, Is.EqualTo(ArchitecturePhase.Ready));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 集成测试更新
|
||||||
|
|
||||||
|
**0.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Test]
|
||||||
|
public void TestGameFlow()
|
||||||
|
{
|
||||||
|
var game = new GameArchitecture();
|
||||||
|
game.Initialize();
|
||||||
|
|
||||||
|
game.SendCommand(new StartGameCommand());
|
||||||
|
var score = game.SendQuery(new GetScoreQuery());
|
||||||
|
|
||||||
|
Assert.That(score, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.x 版本:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[Test]
|
||||||
|
public async Task TestGameFlow()
|
||||||
|
{
|
||||||
|
var game = new GameArchitecture();
|
||||||
|
await game.InitializeAsync();
|
||||||
|
|
||||||
|
await game.SendCommandAsync(new StartGameCommand());
|
||||||
|
var score = await game.SendQueryAsync(new GetScoreQuery());
|
||||||
|
|
||||||
|
Assert.That(score, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 编译错误
|
||||||
|
|
||||||
|
#### 问题 1:命名空间找不到
|
||||||
|
|
||||||
|
**错误信息:**
|
||||||
|
|
||||||
|
```
|
||||||
|
error CS0246: The type or namespace name 'IArchitecture' could not be found
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 添加正确的命名空间引用
|
||||||
|
using GFramework.Core.Abstractions.Architecture;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 问题 2:方法签名不匹配
|
||||||
|
|
||||||
|
**错误信息:**
|
||||||
|
|
||||||
|
```
|
||||||
|
error CS1501: No overload for method 'RegisterEvent' takes 1 arguments
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 0.x 版本
|
||||||
|
this.RegisterEvent<MyEvent>(handler);
|
||||||
|
|
||||||
|
// 1.x 版本(兼容)
|
||||||
|
this.RegisterEvent<MyEvent>(handler);
|
||||||
|
|
||||||
|
// 1.x 版本(带优先级)
|
||||||
|
this.RegisterEvent<MyEvent>(handler, priority: 100);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 问题 3:泛型约束不满足
|
||||||
|
|
||||||
|
**错误信息:**
|
||||||
|
|
||||||
|
```
|
||||||
|
error CS0311: The type 'MyType' cannot be used as type parameter 'T'
|
||||||
|
in the generic type or method. There is no implicit reference conversion
|
||||||
|
from 'MyType' to 'GFramework.Core.Abstractions.IModel'.
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 确保类型实现了正确的接口
|
||||||
|
public class MyModel : AbstractModel, IModel
|
||||||
|
{
|
||||||
|
// 实现
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行时错误
|
||||||
|
|
||||||
|
#### 问题 1:架构未初始化
|
||||||
|
|
||||||
|
**错误信息:**
|
||||||
|
|
||||||
|
```
|
||||||
|
InvalidOperationException: Architecture is not initialized
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 确保在使用前初始化架构
|
||||||
|
var architecture = new GameArchitecture();
|
||||||
|
await architecture.InitializeAsync();
|
||||||
|
await architecture.WaitUntilReadyAsync();
|
||||||
|
|
||||||
|
// 然后再使用
|
||||||
|
this.SendCommand(new MyCommand());
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 问题 2:服务未注册
|
||||||
|
|
||||||
|
**错误信息:**
|
||||||
|
|
||||||
|
```
|
||||||
|
InvalidOperationException: Service of type 'IMyService' is not registered
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 在 Init 方法中注册服务
|
||||||
|
protected override void Init()
|
||||||
|
{
|
||||||
|
RegisterUtility<IMyService>(new MyServiceImpl());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 问题 3:事件处理器未触发
|
||||||
|
|
||||||
|
**问题描述:**
|
||||||
|
事件发送后,注册的处理器没有被调用。
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 确保事件处理器正确注册
|
||||||
|
var unregister = this.RegisterEvent<MyEvent>(OnMyEvent);
|
||||||
|
|
||||||
|
// 确保在对象销毁时注销
|
||||||
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
unregister?.UnRegister();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查事件类型是否匹配
|
||||||
|
this.SendEvent(new MyEvent()); // 确保类型完全一致
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能问题
|
||||||
|
|
||||||
|
#### 问题 1:初始化时间过长
|
||||||
|
|
||||||
|
**问题描述:**
|
||||||
|
架构初始化耗时明显增加。
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用异步初始化
|
||||||
|
await architecture.InitializeAsync();
|
||||||
|
|
||||||
|
// 对于耗时的初始化操作,使用异步方法
|
||||||
|
public class MyModel : AbstractModel, IAsyncInitializable
|
||||||
|
{
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
// 异步加载数据
|
||||||
|
await LoadDataAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 问题 2:事件处理性能下降
|
||||||
|
|
||||||
|
**问题描述:**
|
||||||
|
事件处理速度变慢。
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 使用事件过滤减少不必要的处理
|
||||||
|
this.RegisterEvent<MyEvent>(
|
||||||
|
handler,
|
||||||
|
filter: e => e.ShouldProcess
|
||||||
|
);
|
||||||
|
|
||||||
|
// 使用优先级控制处理顺序
|
||||||
|
this.RegisterEvent<MyEvent>(
|
||||||
|
criticalHandler,
|
||||||
|
priority: 100
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 兼容性问题
|
||||||
|
|
||||||
|
#### 问题 1:Godot 版本不兼容
|
||||||
|
|
||||||
|
**问题描述:**
|
||||||
|
升级后在 Godot 4.3 中无法运行。
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# GFramework 1.0 要求 Godot 4.4+
|
||||||
|
# 升级 Godot 到 4.4 或更高版本
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 问题 2:.NET 版本不兼容
|
||||||
|
|
||||||
|
**问题描述:**
|
||||||
|
项目使用 .NET 7.0,无法使用 GFramework 1.0。
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- 升级项目到 .NET 8.0 或更高版本 -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 回滚方案
|
||||||
|
|
||||||
|
如果迁移过程中遇到无法解决的问题,可以按以下步骤回滚:
|
||||||
|
|
||||||
|
### 步骤 1:恢复包版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 回滚到 0.x 版本
|
||||||
|
dotnet add package GeWuYou.GFramework.Core --version 0.0.200
|
||||||
|
dotnet add package GeWuYou.GFramework.Core.Abstractions --version 0.0.200
|
||||||
|
dotnet add package GeWuYou.GFramework.Game --version 0.0.200
|
||||||
|
dotnet add package GeWuYou.GFramework.Godot --version 0.0.200
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 2:恢复代码
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 从备份分支恢复
|
||||||
|
git checkout backup-before-migration
|
||||||
|
|
||||||
|
# 或从文件系统备份恢复
|
||||||
|
rm -rf YourProject
|
||||||
|
cp -r YourProject-backup YourProject
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 3:验证回滚
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 清理构建缓存
|
||||||
|
dotnet clean
|
||||||
|
rm -rf bin obj
|
||||||
|
|
||||||
|
# 重新构建
|
||||||
|
dotnet build
|
||||||
|
|
||||||
|
# 运行测试
|
||||||
|
dotnet test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 4:记录问题
|
||||||
|
|
||||||
|
创建问题报告,包含:
|
||||||
|
|
||||||
|
- 遇到的具体错误
|
||||||
|
- 错误发生的环境信息
|
||||||
|
- 复现步骤
|
||||||
|
- 相关代码片段
|
||||||
|
|
||||||
|
提交到 [GitHub Issues](https://github.com/GeWuYou/GFramework/issues)。
|
||||||
|
|
||||||
|
## 获取帮助
|
||||||
|
|
||||||
|
### 官方资源
|
||||||
|
|
||||||
|
- **文档中心**:[https://gewuyou.github.io/GFramework/](https://gewuyou.github.io/GFramework/)
|
||||||
|
- **GitHub 仓库**:[https://github.com/GeWuYou/GFramework](https://github.com/GeWuYou/GFramework)
|
||||||
|
- **问题追踪**:[https://github.com/GeWuYou/GFramework/issues](https://github.com/GeWuYou/GFramework/issues)
|
||||||
|
- **讨论区**:[https://github.com/GeWuYou/GFramework/discussions](https://github.com/GeWuYou/GFramework/discussions)
|
||||||
|
|
||||||
|
### 社区支持
|
||||||
|
|
||||||
|
- 在 GitHub Discussions 中提问
|
||||||
|
- 查看已有的 Issues 和 Pull Requests
|
||||||
|
- 参考示例项目和教程
|
||||||
|
|
||||||
|
### 商业支持
|
||||||
|
|
||||||
|
如需专业的迁移支持服务,请联系项目维护团队。
|
||||||
|
|
||||||
|
## 附录
|
||||||
|
|
||||||
|
### A. 完整的 API 对照表
|
||||||
|
|
||||||
|
| 0.x API | 1.x API | 说明 |
|
||||||
|
|-----------------------------|----------------------------------------|------------|
|
||||||
|
| `architecture.Initialize()` | `await architecture.InitializeAsync()` | 推荐使用异步初始化 |
|
||||||
|
| `this.SendCommandSync()` | `this.SendCommand()` | 移除 Sync 后缀 |
|
||||||
|
| `this.SendQuerySync()` | `this.SendQuery()` | 移除 Sync 后缀 |
|
||||||
|
| `EventBus.Register()` | `this.RegisterEvent()` | 使用扩展方法 |
|
||||||
|
| `Container.Register()` | `RegisterUtility()` | 使用架构方法 |
|
||||||
|
|
||||||
|
### B. 迁移时间估算
|
||||||
|
|
||||||
|
| 项目规模 | 预估时间 | 说明 |
|
||||||
|
|---------------|-------|--------------|
|
||||||
|
| 小型(<10k 行) | 1-2 天 | 主要是包更新和测试 |
|
||||||
|
| 中型(10k-50k 行) | 3-5 天 | 需要代码审查和重构 |
|
||||||
|
| 大型(>50k 行) | 1-2 周 | 需要分模块迁移和充分测试 |
|
||||||
|
|
||||||
|
### C. 相关资源
|
||||||
|
|
||||||
|
- [架构设计文档](./core/architecture.md)
|
||||||
|
- [事件系统文档](./core/events.md)
|
||||||
|
- [命令查询文档](./core/command.md)
|
||||||
|
- [协程系统文档](./core/coroutine.md)
|
||||||
|
- [最佳实践](./best-practices/architecture-patterns.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**:1.0.0
|
||||||
|
**最后更新**:2026-03-07
|
||||||
|
**许可证**:Apache 2.0
|
||||||
File diff suppressed because it is too large
Load Diff
1641
docs/zh-CN/tutorials/large-project-organization.md
Normal file
1641
docs/zh-CN/tutorials/large-project-organization.md
Normal file
File diff suppressed because it is too large
Load Diff
1147
docs/zh-CN/tutorials/unit-testing.md
Normal file
1147
docs/zh-CN/tutorials/unit-testing.md
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user