- 将标题从"架构模式最佳实践"改为"架构设计模式指南" - 添加全面的架构设计模式介绍和概述 - 新增MVC模式详细说明,包括概念、GFramework实现示例和最佳实践 - 新增MVVM模式详细说明,包括概念、GFramework实现示例和最佳实践 - 新增命令模式详细说明,包括概念、实现示例和撤销功能支持 - 新增查询模式详细说明,包括CQRS概念和复杂查询示例 - 新增事件驱动模式详细说明,包括事件定义和监听实现 - 新增依赖注入模式详细说明,包括构造函数注入示例 - 新增服务定位器模式详细说明,包括与依赖注入对比 - 新增对象池模式详细说明,包括通用对象池实现 - 新增状态模式详细说明,包括异步状态和状态机系统 - 补充模式选择与组合建议,针对小型、中型、大型项目提供不同方案 - 更新代码示例中的泛型语法格式,统一使用尖括号表示法
43 KiB
故障排除与调试
本指南帮助你诊断和解决 GFramework 使用中的常见问题。
如何使用本指南
- 快速查找:使用浏览器的搜索功能(Ctrl+F / Cmd+F)查找错误信息或关键词
- 分类浏览:根据问题类型(安装、架构、事件等)查看对应章节
- 排查清单:每个问题都提供了详细的排查步骤和解决方案
- 代码示例:所有解决方案都包含可运行的代码示例
安装问题
NuGet 包安装失败
问题:无法安装 GFramework NuGet 包。
错误信息:
NU1101: Unable to find package GFramework.Core
解决方案:
- 检查包源配置:
# 查看当前包源
dotnet nuget list source
# 添加 NuGet.org 源
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
- 清理 NuGet 缓存:
dotnet nuget locals all --clear
- 手动指定版本:
dotnet add package GFramework.Core --version 1.0.0
依赖冲突
问题:安装 GFramework 后出现依赖版本冲突。
错误信息:
NU1107: Version conflict detected for Microsoft.Extensions.DependencyInjection
解决方案:
- 检查项目文件:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<!-- 确保使用兼容的版本 -->
<PackageReference Include="GFramework.Core" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
</Project>
- 统一依赖版本:
<ItemGroup>
<!-- 在 Directory.Build.props 中统一管理版本 -->
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
.NET 版本不兼容
问题:项目无法编译,提示 .NET 版本不兼容。
错误信息:
error NETSDK1045: The current .NET SDK does not support targeting .NET 8.0
解决方案:
- 检查 .NET SDK 版本:
dotnet --version
- 安装正确的 .NET SDK:
- GFramework 需要 .NET 8.0 或更高版本
- 下载地址:https://dotnet.microsoft.com/download
- 更新项目目标框架:
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
架构问题
架构初始化失败
问题:架构初始化时抛出异常。
错误信息:
InvalidOperationException: Architecture is already initialized
原因:重复调用 Initialize() 方法。
解决方案:
// ❌ 错误:重复初始化
var arch = new GameArchitecture();
arch.Initialize();
arch.Initialize(); // 抛出异常
// ✅ 正确:只初始化一次
var arch = new GameArchitecture();
if (!arch.IsInitialized)
{
arch.Initialize();
}
// ✅ 更好:使用单例模式
public class GameArchitecture : Architecture<GameArchitecture>
{
protected override void Init()
{
// 注册组件
}
}
// 使用
var arch = GameArchitecture.Interface;
服务未注册
问题:尝试获取未注册的服务。
错误信息:
InvalidOperationException: No service for type 'IPlayerService' has been registered
解决方案:
// ❌ 错误:未注册服务
public class GameArchitecture : Architecture
{
protected override void Init()
{
// 忘记注册 IPlayerService
}
}
var service = arch.GetService<IPlayerService>(); // 抛出异常
// ✅ 正确:先注册服务
public class GameArchitecture : Architecture
{
protected override void Init()
{
// 注册服务
RegisterService<IPlayerService, PlayerService>();
}
}
// ✅ 使用 IOC 容器注册
protected override void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IPlayerService, PlayerService>();
services.AddTransient<IGameService, GameService>();
}
组件注册顺序错误
问题:组件初始化时依赖的其他组件尚未注册。
错误信息:
NullReferenceException: Object reference not set to an instance of an object
解决方案:
// ❌ 错误:SystemB 依赖 ModelA,但 ModelA 后注册
protected override void Init()
{
RegisterSystem(new SystemB()); // SystemB.OnInit() 中访问 ModelA 失败
RegisterModel(new ModelA());
}
// ✅ 正确:先注册依赖项
protected override void Init()
{
// 1. 先注册 Model
RegisterModel(new ModelA());
// 2. 再注册 System
RegisterSystem(new SystemB());
// 3. 最后注册 Utility
RegisterUtility<IConfigUtility>(new ConfigUtility());
}
// ✅ 更好:使用延迟初始化
public class SystemB : AbstractSystem
{
private ModelA _modelA;
protected override void OnInit()
{
// 在 OnInit 中获取依赖,此时所有组件已注册
_modelA = this.GetModel<ModelA>();
}
}
异步初始化问题
问题:异步架构初始化未正确等待。
错误信息:
InvalidOperationException: Architecture not fully initialized
解决方案:
// ❌ 错误:未等待异步初始化完成
var arch = new AsyncGameArchitecture();
arch.InitializeAsync(); // 未等待
var model = arch.GetModel<PlayerModel>(); // 可能失败
// ✅ 正确:等待异步初始化
var arch = new AsyncGameArchitecture();
await arch.InitializeAsync();
var model = arch.GetModel<PlayerModel>(); // 安全
// ✅ 在 Godot 中使用
public partial class GameRoot : Node
{
private AsyncGameArchitecture _architecture;
public override async void _Ready()
{
_architecture = new AsyncGameArchitecture();
await _architecture.InitializeAsync();
// 现在可以安全使用架构
var model = _architecture.GetModel<PlayerModel>();
}
}
常见错误
1. 组件未注册错误
错误信息:KeyNotFoundException: 未找到类型为 'XXX' 的组件
原因:尝试获取未注册的组件。
解决方案:
// ❌ 错误:未注册 PlayerModel
var arch = new GameArchitecture();
arch.Initialize();
var model = arch.GetModel<PlayerModel>(); // 抛出异常
// ✅ 正确:先注册再获取
public class GameArchitecture : Architecture
{
protected override void Init()
{
RegisterModel(new PlayerModel()); // 注册模型
}
}
2. 事件监听器未触发
问题:注册了事件监听器但没有被调用。
原因:
- 事件类型不匹配
- 监听器在事件发送前注销
- 事件发送时使用了错误的类型
- 事件传播被停止
解决方案:
// ❌ 错误:事件类型不匹配
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
this.SendEvent(new PlayerAttackedEvent()); // 不会触发
// ✅ 正确:事件类型匹配
this.RegisterEvent<PlayerAttackedEvent>(OnPlayerAttacked);
this.SendEvent(new PlayerAttackedEvent()); // 正确触发
// ❌ 错误:过早注销
var unregister = this.RegisterEvent<GameEvent>(OnGameEvent);
unregister.UnRegister(); // 立即注销
this.SendEvent(new GameEvent()); // 不会触发
// ✅ 正确:在适当时机注销
private IUnRegister _eventUnregister;
public void Initialize()
{
_eventUnregister = this.RegisterEvent<GameEvent>(OnGameEvent);
}
public void Cleanup()
{
_eventUnregister?.UnRegister();
}
// ❌ 错误:事件传播被停止
this.RegisterEvent<GameEvent>(e =>
{
e.StopPropagation(); // 停止传播
});
this.RegisterEvent<GameEvent>(OnGameEvent); // 不会被调用
// ✅ 正确:使用优先级控制执行顺序
this.RegisterEvent<GameEvent>(OnGameEventFirst, priority: 100);
this.RegisterEvent<GameEvent>(OnGameEventSecond, priority: 50);
3. 内存泄漏
问题:应用内存持续增长。
原因:
- 未注销事件监听器
- 未注销属性监听器
- 未销毁 Architecture
- 循环引用
解决方案:
// ✅ 正确:使用 UnRegisterList 管理注销
private IUnRegisterList _unregisterList = new UnRegisterList();
public void Initialize()
{
this.RegisterEvent<Event1>(OnEvent1)
.AddToUnregisterList(_unregisterList);
model.Property.Register(OnPropertyChanged)
.AddToUnregisterList(_unregisterList);
}
public void Cleanup()
{
_unregisterList.UnRegisterAll();
}
// ✅ 销毁架构
architecture.Destroy();
// ✅ 使用 EventListenerScope 自动管理生命周期
public partial class Player : Node
{
private EventListenerScope _eventScope;
public override void _Ready()
{
_eventScope = new EventListenerScope(this.GetArchitecture());
_eventScope.Register<PlayerDiedEvent>(OnPlayerDied);
_eventScope.Register<PlayerAttackedEvent>(OnPlayerAttacked);
}
public override void _ExitTree()
{
_eventScope.Dispose(); // 自动注销所有监听器
}
}
// ✅ 在 Godot 中使用 UnRegisterOnFree
public partial class GameController : Node
{
public override void _Ready()
{
this.RegisterEvent<GameEvent>(OnGameEvent)
.UnRegisterOnFree(this); // 节点释放时自动注销
}
}
4. 循环依赖
问题:两个系统相互依赖导致死循环。
原因:系统间直接调用而不是通过事件通信。
解决方案:
// ❌ 错误:直接调用导致循环依赖
public class SystemA : AbstractSystem
{
private void OnEvent(EventA e)
{
var systemB = this.GetSystem<SystemB>();
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<EventB>(OnEventB);
}
}
// ✅ 使用 Command 模式
public class SystemA : AbstractSystem
{
private void OnEvent(EventA e)
{
this.SendCommand(new ProcessDataCommand());
}
}
// ✅ 使用 Query 模式获取数据
public class SystemA : AbstractSystem
{
private void ProcessData()
{
var data = this.SendQuery(new GetPlayerDataQuery());
// 处理数据
}
}
事件系统问题
事件未触发
问题:发送事件后没有任何监听器响应。
排查步骤:
- 检查事件类型是否正确:
// 确保发送和监听的是同一个事件类型
public struct PlayerDiedEvent : IEvent { }
// 注册
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
// 发送
this.SendEvent(new PlayerDiedEvent()); // 类型必须完全匹配
- 检查是否在架构初始化后注册:
// ❌ 错误:在架构初始化前注册
var arch = new GameArchitecture();
arch.RegisterEvent<GameEvent>(OnGameEvent); // 可能失败
// ✅ 正确:在架构初始化后注册
var arch = new GameArchitecture();
arch.Initialize();
arch.RegisterEvent<GameEvent>(OnGameEvent);
- 检查事件总线是否正确注册:
protected override void Init()
{
// 确保注册了事件总线
RegisterSystem(new EventBusModule());
}
事件执行顺序错误
问题:多个监听器的执行顺序不符合预期。
解决方案:
// ✅ 使用优先级控制执行顺序(数值越大优先级越高)
this.RegisterEvent<GameEvent>(OnGameEventFirst, priority: 100);
this.RegisterEvent<GameEvent>(OnGameEventSecond, priority: 50);
this.RegisterEvent<GameEvent>(OnGameEventLast, priority: 0);
// ✅ 使用事件链
public struct FirstEvent : IEvent { }
public struct SecondEvent : IEvent { }
public struct ThirdEvent : IEvent { }
this.RegisterEvent<FirstEvent>(e =>
{
// 处理第一个事件
this.SendEvent(new SecondEvent()); // 触发下一个事件
});
this.RegisterEvent<SecondEvent>(e =>
{
// 处理第二个事件
this.SendEvent(new ThirdEvent()); // 触发下一个事件
});
事件传播控制
问题:需要在某些条件下停止事件传播。
解决方案:
// ✅ 使用 StopPropagation
this.RegisterEvent<DamageEvent>(e =>
{
if (IsInvincible)
{
e.StopPropagation(); // 停止传播,后续监听器不会执行
return;
}
ApplyDamage(e.Amount);
}, priority: 100); // 高优先级先执行
// ✅ 使用条件过滤
this.RegisterEvent<DamageEvent>(e =>
{
if (e.Target != this)
return; // 不是针对自己的伤害,忽略
ApplyDamage(e.Amount);
});
协程问题
协程不执行
问题:启动协程后没有任何效果。
原因:
- 未正确启动协程调度器
- 协程方法没有返回
IEnumerator - 忘记调用
yield return
解决方案:
// ❌ 错误:未启动协程调度器
public partial class GameRoot : Node
{
public override void _Ready()
{
StartCoroutine(MyCoroutine()); // 不会执行
}
private IEnumerator MyCoroutine()
{
yield return new WaitForSeconds(1);
}
}
// ✅ 正确:在 Godot 中使用协程
public partial class GameRoot : Node
{
private CoroutineScheduler _scheduler;
public override void _Ready()
{
_scheduler = new CoroutineScheduler(new GodotTimeSource(this));
_scheduler.StartCoroutine(MyCoroutine());
}
public override void _Process(double delta)
{
_scheduler?.Update((float)delta);
}
private IEnumerator MyCoroutine()
{
yield return new WaitForSeconds(1);
GD.Print("Coroutine executed!");
}
}
// ❌ 错误:方法签名错误
private void MyCoroutine() // 应该返回 IEnumerator
{
// 不会作为协程执行
}
// ✅ 正确:返回 IEnumerator
private IEnumerator MyCoroutine()
{
yield return new WaitForSeconds(1);
}
// ❌ 错误:忘记 yield return
private IEnumerator MyCoroutine()
{
new WaitForSeconds(1); // 缺少 yield return
GD.Print("This executes immediately!");
}
// ✅ 正确:使用 yield return
private IEnumerator MyCoroutine()
{
yield return new WaitForSeconds(1);
GD.Print("This executes after 1 second!");
}
协程死锁
问题:协程永远不会完成。
原因:
- 等待条件永远不满足
- 循环等待
- 等待已停止的协程
解决方案:
// ❌ 错误:等待条件永远不满足
private IEnumerator WaitForever()
{
yield return new WaitUntil(() => false); // 永远等待
}
// ✅ 正确:添加超时机制
private IEnumerator WaitWithTimeout()
{
float timeout = 5f;
float elapsed = 0f;
while (!condition && elapsed < timeout)
{
elapsed += Time.DeltaTime;
yield return null;
}
if (elapsed >= timeout)
{
GD.PrintErr("Timeout!");
}
}
// ✅ 使用 WaitForEventWithTimeout
private IEnumerator WaitForEventSafely()
{
yield return new WaitForEventWithTimeout<GameEvent>(
this.GetArchitecture(),
timeout: 5f
);
}
// ❌ 错误:循环等待
private IEnumerator CoroutineA()
{
yield return StartCoroutine(CoroutineB());
}
private IEnumerator CoroutineB()
{
yield return StartCoroutine(CoroutineA()); // 循环等待
}
// ✅ 正确:避免循环依赖
private IEnumerator CoroutineA()
{
yield return new WaitForSeconds(1);
this.SendEvent(new EventA());
}
private IEnumerator CoroutineB()
{
yield return new WaitForEvent<EventA>(this.GetArchitecture());
// 继续执行
}
协程提前停止
问题:协程在完成前被意外停止。
解决方案:
// ✅ 保存协程句柄
private CoroutineHandle _coroutineHandle;
public void StartMyCoroutine()
{
_coroutineHandle = _scheduler.StartCoroutine(MyCoroutine());
}
public void StopMyCoroutine()
{
if (_coroutineHandle != null && _coroutineHandle.IsRunning)
{
_coroutineHandle.Stop();
}
}
// ✅ 检查协程状态
private IEnumerator MonitorCoroutine()
{
var handle = _scheduler.StartCoroutine(LongRunningCoroutine());
while (handle.IsRunning)
{
yield return null;
}
GD.Print($"Coroutine completed with state: {handle.State}");
}
// ✅ 使用 try-finally 确保清理
private IEnumerator CoroutineWithCleanup()
{
try
{
yield return new WaitForSeconds(1);
// 执行操作
}
finally
{
// 清理资源
Cleanup();
}
}
资源管理问题
资源加载失败
问题:无法加载资源文件。
错误信息:
FileNotFoundException: Could not find file 'res://assets/player.png'
解决方案:
// ❌ 错误:路径错误
var texture = ResourceLoader.Load<Texture2D>("assets/player.png"); // 缺少 res://
// ✅ 正确:使用完整路径
var texture = ResourceLoader.Load<Texture2D>("res://assets/player.png");
// ✅ 检查资源是否存在
if (ResourceLoader.Exists("res://assets/player.png"))
{
var texture = ResourceLoader.Load<Texture2D>("res://assets/player.png");
}
else
{
GD.PrintErr("Resource not found!");
}
// ✅ 使用资源管理器
public class GameArchitecture : Architecture
{
protected override void Init()
{
RegisterSystem(new ResourceManager());
}
}
// 加载资源
var handle = this.GetSystem<ResourceManager>()
.LoadAsync<Texture2D>("res://assets/player.png");
await handle.Task;
if (handle.IsValid)
{
var texture = handle.Resource;
}
else
{
GD.PrintErr($"Failed to load resource: {handle.Error}");
}
资源内存泄漏
问题:加载的资源未释放,导致内存持续增长。
解决方案:
// ❌ 错误:未释放资源
public void LoadTexture()
{
var texture = ResourceLoader.Load<Texture2D>("res://assets/player.png");
// 使用 texture
// 忘记释放
}
// ✅ 正确:使用资源句柄自动管理
private IResourceHandle<Texture2D> _textureHandle;
public void LoadTexture()
{
_textureHandle = resourceManager.Load<Texture2D>("res://assets/player.png");
}
public void Cleanup()
{
_textureHandle?.Release(); // 释放资源
}
// ✅ 使用自动释放策略
var handle = resourceManager.Load<Texture2D>(
"res://assets/player.png",
new AutoReleaseStrategy(timeToLive: 60f) // 60秒后自动释放
);
// ✅ 使用 using 语句
public async Task LoadAndUseResource()
{
using var handle = resourceManager.Load<Texture2D>("res://assets/player.png");
await handle.Task;
// 使用资源
var texture = handle.Resource;
// 离开作用域时自动释放
}
资源加载超时
问题:大型资源加载时间过长。
解决方案:
// ✅ 使用异步加载
private async Task LoadLargeResourceAsync()
{
var handle = resourceManager.LoadAsync<PackedScene>("res://scenes/large_scene.tscn");
// 显示加载进度
while (!handle.IsDone)
{
GD.Print($"Loading: {handle.Progress * 100}%");
await Task.Delay(100);
}
if (handle.IsValid)
{
var scene = handle.Resource;
}
}
// ✅ 使用协程加载
private IEnumerator LoadLargeResourceCoroutine()
{
var handle = resourceManager.LoadAsync<PackedScene>("res://scenes/large_scene.tscn");
yield return new WaitForProgress(handle);
if (handle.IsValid)
{
var scene = handle.Resource;
}
}
// ✅ 预加载资源
public override void _Ready()
{
// 在游戏开始时预加载常用资源
PreloadResources();
}
private async void PreloadResources()
{
var resources = new[]
{
"res://assets/player.png",
"res://assets/enemy.png",
"res://sounds/bgm.ogg"
};
foreach (var path in resources)
{
await resourceManager.LoadAsync<Resource>(path).Task;
}
GD.Print("All resources preloaded!");
}
Godot 集成问题
场景加载失败
问题:无法加载或实例化场景。
错误信息:
NullReferenceException: Object reference not set to an instance of an object
解决方案:
// ❌ 错误:路径错误或场景未导出
var scene = GD.Load<PackedScene>("scenes/player.tscn"); // 缺少 res://
// ✅ 正确:使用完整路径
var scene = GD.Load<PackedScene>("res://scenes/player.tscn");
var instance = scene.Instantiate();
// ✅ 检查场景是否存在
if (ResourceLoader.Exists("res://scenes/player.tscn"))
{
var scene = GD.Load<PackedScene>("res://scenes/player.tscn");
var instance = scene.Instantiate<Player>();
AddChild(instance);
}
else
{
GD.PrintErr("Scene not found!");
}
// ✅ 使用场景路由器
public class GameSceneRouter : SceneRouterBase
{
protected override void RegisterScenes()
{
Register<MainMenuScene>("MainMenu", "res://scenes/main_menu.tscn");
Register<GameScene>("Game", "res://scenes/game.tscn");
}
}
// 导航到场景
await sceneRouter.NavigateToAsync("Game");
节点查找失败
问题:使用 GetNode() 查找节点返回 null。
错误信息:
NullReferenceException: Object reference not set to an instance of an object
解决方案:
// ❌ 错误:路径错误
var player = GetNode<Player>("Player"); // 节点可能在子节点中
// ✅ 正确:使用完整路径
var player = GetNode<Player>("Level/Player");
// ✅ 使用 % 符号访问唯一节点(Godot 4.0+)
var player = GetNode<Player>("%Player");
// ✅ 检查节点是否存在
if (HasNode("Player"))
{
var player = GetNode<Player>("Player");
}
else
{
GD.PrintErr("Player node not found!");
}
// ✅ 使用 NodePath 缓存
[Export] private NodePath _playerPath;
private Player _player;
public override void _Ready()
{
if (_playerPath != null)
{
_player = GetNode<Player>(_playerPath);
}
}
信号连接失败
问题:信号未正确连接或触发。
解决方案:
// ❌ 错误:信号名称错误
button.Connect("press", Callable.From(OnButtonPressed)); // 应该是 "pressed"
// ✅ 正确:使用正确的信号名称
button.Connect("pressed", Callable.From(OnButtonPressed));
// ✅ 使用类型安全的信号连接
button.Pressed += OnButtonPressed;
// ✅ 使用 GFramework 的流式 API
button.OnPressed()
.Subscribe(OnButtonPressed)
.AddToUnregisterList(_unregisterList);
// ✅ 检查信号是否存在
if (button.HasSignal("pressed"))
{
button.Connect("pressed", Callable.From(OnButtonPressed));
}
// ✅ 自动注销信号
public partial class GameController : Node
{
private IUnRegisterList _unregisterList = new UnRegisterList();
public override void _Ready()
{
var button = GetNode<Button>("Button");
button.OnPressed()
.Subscribe(OnButtonPressed)
.AddToUnregisterList(_unregisterList);
}
public override void _ExitTree()
{
_unregisterList.UnRegisterAll();
}
private void OnButtonPressed()
{
GD.Print("Button pressed!");
}
}
架构上下文丢失
问题:在 Godot 节点中无法访问架构。
错误信息:
InvalidOperationException: Architecture context not found
解决方案:
// ❌ 错误:未设置架构锚点
public partial class Player : Node
{
public override void _Ready()
{
var model = this.GetModel<PlayerModel>(); // 失败:无架构上下文
}
}
// ✅ 正确:在根节点设置架构锚点
public partial class GameRoot : Node
{
private GameArchitecture _architecture;
public override void _Ready()
{
_architecture = new GameArchitecture();
_architecture.Initialize();
// 设置架构锚点
var anchor = new ArchitectureAnchor();
anchor.SetArchitecture(_architecture);
AddChild(anchor);
}
}
// 现在子节点可以访问架构
public partial class Player : Node
{
public override void _Ready()
{
var model = this.GetModel<PlayerModel>(); // 成功
}
}
// ✅ 使用 Godot 模块
public class GameArchitecture : AbstractArchitecture
{
protected override void Init()
{
// 注册 Godot 模块
this.RegisterGodotModule<PlayerModule>();
this.RegisterGodotModule<EnemyModule>();
}
}
UI 页面导航问题
问题:UI 页面无法正确显示或切换。
解决方案:
// ❌ 错误:未注册 UI 页面
var uiRouter = this.GetSystem<IUiRouter>();
await uiRouter.PushAsync("MainMenu"); // 失败:页面未注册
// ✅ 正确:先注册 UI 页面
public class GameArchitecture : Architecture
{
protected override void Init()
{
var uiRegistry = new GodotUiRegistry();
uiRegistry.Register("MainMenu", "res://ui/main_menu.tscn", UiLayer.Page);
uiRegistry.Register("Settings", "res://ui/settings.tscn", UiLayer.Modal);
RegisterUtility<IGodotUiRegistry>(uiRegistry);
RegisterSystem(new UiRouter(uiRegistry));
}
}
// 导航到页面
var uiRouter = this.GetSystem<IUiRouter>();
await uiRouter.PushAsync("MainMenu");
// ✅ 使用转场效果
await uiRouter.PushAsync("Settings", new UiTransitionOptions
{
TransitionType = UiTransitionType.Fade,
Duration = 0.3f
});
// ✅ 处理导航失败
try
{
await uiRouter.PushAsync("NonExistentPage");
}
catch (InvalidOperationException ex)
{
GD.PrintErr($"Navigation failed: {ex.Message}");
}
性能问题
事件处理缓慢
问题:事件处理耗时过长,导致游戏卡顿。
诊断:
// 测量事件处理时间
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
arch.SendEvent(new HeavyEvent());
stopwatch.Stop();
GD.Print($"Event processing time: {stopwatch.ElapsedMilliseconds}ms");
// 使用性能分析器
public class PerformanceProfiler : AbstractSystem
{
protected override void OnInit()
{
this.RegisterEvent<IEvent>(e =>
{
var sw = Stopwatch.StartNew();
// 事件处理
sw.Stop();
if (sw.ElapsedMilliseconds > 16) // 超过一帧
{
GD.PrintErr($"Slow event: {e.GetType().Name} took {sw.ElapsedMilliseconds}ms");
}
});
}
}
优化:
// ❌ 低效:在事件处理中进行复杂计算
private void OnEvent(HeavyEvent e)
{
for (int i = 0; i < 1000000; i++)
{
// 复杂计算
}
}
// ✅ 高效:异步处理
private async void OnEvent(HeavyEvent e)
{
await Task.Run(() =>
{
for (int i = 0; i < 1000000; i++)
{
// 复杂计算
}
});
}
// ✅ 使用协程分帧处理
private void OnEvent(HeavyEvent e)
{
this.StartCoroutine(ProcessHeavyEventCoroutine(e));
}
private IEnumerator ProcessHeavyEventCoroutine(HeavyEvent e)
{
const int batchSize = 1000;
for (int i = 0; i < 1000000; i += batchSize)
{
// 处理一批数据
for (int j = 0; j < batchSize && i + j < 1000000; j++)
{
// 计算
}
yield return null; // 下一帧继续
}
}
频繁的组件访问
问题:每帧都调用 GetModel、GetSystem 等方法导致性能下降。
优化:
// ❌ 低效:每帧访问
public override void _Process(double delta)
{
var model = this.GetModel<PlayerModel>(); // 每帧调用
model.Health.Value -= 1;
}
// ✅ 高效:缓存引用
private PlayerModel _playerModel;
public override void _Ready()
{
_playerModel = this.GetModel<PlayerModel>(); // 只调用一次
}
public override void _Process(double delta)
{
_playerModel.Health.Value -= 1;
}
// ✅ 使用延迟初始化
private PlayerModel _playerModel;
private PlayerModel PlayerModel => _playerModel ??= this.GetModel<PlayerModel>();
public override void _Process(double delta)
{
PlayerModel.Health.Value -= 1;
}
内存占用过高
问题:游戏运行时内存持续增长。
诊断:
// 监控内存使用
public class MemoryMonitor : Node
{
public override void _Process(double delta)
{
var memoryUsed = GC.GetTotalMemory(false) / 1024 / 1024;
GD.Print($"Memory used: {memoryUsed} MB");
if (memoryUsed > 500) // 超过 500MB
{
GD.PrintErr("High memory usage detected!");
}
}
}
优化:
// ✅ 使用对象池
public class BulletPool : AbstractNodePoolSystem<Bullet>
{
protected override Bullet CreateInstance()
{
var scene = GD.Load<PackedScene>("res://entities/bullet.tscn");
return scene.Instantiate<Bullet>();
}
protected override void OnGet(Bullet bullet)
{
bullet.Show();
bullet.ProcessMode = ProcessModeEnum.Inherit;
}
protected override void OnRelease(Bullet bullet)
{
bullet.Hide();
bullet.ProcessMode = ProcessModeEnum.Disabled;
}
}
// 使用对象池
var bullet = bulletPool.Get();
// 使用完毕后归还
bulletPool.Release(bullet);
// ✅ 及时释放资源
public override void _ExitTree()
{
// 注销事件监听器
_unregisterList.UnRegisterAll();
// 释放资源句柄
_textureHandle?.Release();
// 停止协程
_coroutineHandle?.Stop();
// 销毁架构(如果是根节点)
_architecture?.Destroy();
}
// ✅ 定期执行垃圾回收
private float _gcTimer = 0f;
private const float GC_INTERVAL = 60f; // 每60秒
public override void _Process(double delta)
{
_gcTimer += (float)delta;
if (_gcTimer >= GC_INTERVAL)
{
_gcTimer = 0f;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
协程性能问题
问题:大量协程导致性能下降。
优化:
// ❌ 低效:创建大量短期协程
for (int i = 0; i < 1000; i++)
{
StartCoroutine(ShortCoroutine());
}
// ✅ 高效:合并协程
StartCoroutine(BatchCoroutine(1000));
private IEnumerator BatchCoroutine(int count)
{
for (int i = 0; i < count; i++)
{
// 处理
if (i % 10 == 0)
yield return null; // 每10次操作暂停一次
}
}
// ✅ 使用协程池
private readonly Queue<IEnumerator> _coroutineQueue = new();
private IEnumerator CoroutinePoolRunner()
{
while (true)
{
if (_coroutineQueue.Count > 0)
{
var coroutine = _coroutineQueue.Dequeue();
yield return coroutine;
}
else
{
yield return null;
}
}
}
// 添加到队列而不是立即启动
_coroutineQueue.Enqueue(MyCoroutine());
调试技巧
1. 启用日志系统
// 使用 GFramework 日志系统
public class GameArchitecture : Architecture
{
protected override void Init()
{
// 注册日志工厂
RegisterUtility<ILoggerFactory>(new GodotLoggerFactory());
}
}
// 在组件中使用日志
public class PlayerSystem : AbstractSystem
{
private ILogger _logger;
protected override void OnInit()
{
_logger = this.GetUtility<ILoggerFactory>().CreateLogger<PlayerSystem>();
_logger.LogInfo("PlayerSystem initialized");
}
private void OnPlayerDied(PlayerDiedEvent e)
{
_logger.LogWarning($"Player died at position {e.Position}");
}
}
// 配置日志级别
var loggingConfig = new LoggingConfiguration
{
MinimumLevel = LogLevel.Debug,
Appenders = new[]
{
new ConsoleAppender(),
new FileAppender("logs/game.log")
}
};
2. 使用断点调试
// 在关键位置添加断点
public override void _Ready()
{
var model = this.GetModel<PlayerModel>();
// 在这里设置断点,检查 model 的值
GD.Print($"Player health: {model.Health.Value}");
}
// 使用条件断点
private void OnDamage(DamageEvent e)
{
// 只在伤害大于50时中断
if (e.Amount > 50)
{
GD.Print("High damage detected!"); // 在这里设置断点
}
}
// 使用 GD.PushWarning 和 GD.PushError
if (player == null)
{
GD.PushError("Player is null!"); // 在输出面板显示错误
return;
}
3. 追踪事件流
// 创建事件追踪系统
public class EventTracer : AbstractSystem
{
private ILogger _logger;
protected override void OnInit()
{
_logger = this.GetUtility<ILoggerFactory>().CreateLogger<EventTracer>();
// 使用反射监听所有事件(仅用于调试)
this.RegisterEvent<PlayerDiedEvent>(e =>
_logger.LogDebug($"Event: PlayerDiedEvent"));
this.RegisterEvent<PlayerAttackedEvent>(e =>
_logger.LogDebug($"Event: PlayerAttackedEvent - Damage: {e.Damage}"));
}
}
// 在架构中启用事件追踪
#if DEBUG
protected override void Init()
{
RegisterSystem(new EventTracer());
}
#endif
4. 性能分析
// 使用 Godot 性能监视器
public override void _Process(double delta)
{
// 在编辑器中查看性能统计
Performance.GetMonitor(Performance.Monitor.TimeFps);
Performance.GetMonitor(Performance.Monitor.MemoryStatic);
}
// 自定义性能计数器
public class PerformanceCounter
{
private readonly Dictionary<string, Stopwatch> _timers = new();
public void StartTimer(string name)
{
if (!_timers.ContainsKey(name))
_timers[name] = new Stopwatch();
_timers[name].Restart();
}
public void StopTimer(string name)
{
if (_timers.TryGetValue(name, out var timer))
{
timer.Stop();
GD.Print($"{name}: {timer.ElapsedMilliseconds}ms");
}
}
}
// 使用
var counter = new PerformanceCounter();
counter.StartTimer("EventProcessing");
arch.SendEvent(new GameEvent());
counter.StopTimer("EventProcessing");
5. 单元测试调试
[Test]
public void DebugPlayerDamage()
{
var arch = new TestArchitecture();
arch.Initialize();
var player = arch.GetModel<PlayerModel>();
// 打印初始状态
GD.Print($"Initial Health: {player.Health.Value}");
// 发送伤害事件
arch.SendEvent(new DamageEvent { Amount = 10 });
// 打印最终状态
GD.Print($"Final Health: {player.Health.Value}");
// 验证
Assert.AreEqual(90, player.Health.Value);
}
常见错误信息
NU1101: Unable to find package
错误类型:NuGet 包安装错误
完整错误信息:
NU1101: Unable to find package GFramework.Core. No packages exist with this id in source(s): nuget.org
原因:
- 包源配置错误
- 包名拼写错误
- 网络连接问题
解决方案:
# 1. 检查包源
dotnet nuget list source
# 2. 添加 NuGet.org 源
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
# 3. 清理缓存
dotnet nuget locals all --clear
# 4. 重新安装
dotnet restore
KeyNotFoundException: 未找到类型
错误类型:架构组件未注册
完整错误信息:
System.Collections.Generic.KeyNotFoundException: 未找到类型为 'PlayerModel' 的组件
原因:尝试获取未注册的组件
解决方案:
// 在架构中注册组件
public class GameArchitecture : Architecture
{
protected override void Init()
{
RegisterModel(new PlayerModel());
RegisterSystem(new PlayerSystem());
RegisterUtility<IConfigUtility>(new ConfigUtility());
}
}
InvalidOperationException: Architecture is already initialized
错误类型:重复初始化
完整错误信息:
System.InvalidOperationException: Architecture is already initialized
原因:多次调用 Initialize() 方法
解决方案:
// 检查初始化状态
if (!architecture.IsInitialized)
{
architecture.Initialize();
}
// 或使用单例模式
public class GameArchitecture : Architecture<GameArchitecture>
{
// 自动处理单例
}
NullReferenceException: Object reference not set
错误类型:空引用异常
常见场景:
- 节点未找到:
// ❌ 错误
var player = GetNode<Player>("Player"); // 返回 null
player.Health = 100; // 抛出异常
// ✅ 正确
if (HasNode("Player"))
{
var player = GetNode<Player>("Player");
player.Health = 100;
}
- 组件未注册:
// ❌ 错误
var model = this.GetModel<PlayerModel>(); // 返回 null
model.Health.Value = 100; // 抛出异常
// ✅ 正确:先注册
protected override void Init()
{
RegisterModel(new PlayerModel());
}
- 架构上下文丢失:
// ❌ 错误:未设置架构锚点
var model = this.GetModel<PlayerModel>(); // 抛出异常
// ✅ 正确:设置架构锚点
var anchor = new ArchitectureAnchor();
anchor.SetArchitecture(architecture);
AddChild(anchor);
InvalidCastException: Unable to cast object
错误类型:类型转换错误
完整错误信息:
System.InvalidCastException: Unable to cast object of type 'Node' to type 'Player'
原因:节点类型不匹配
解决方案:
// ❌ 错误:强制转换
var player = (Player)GetNode("Player"); // 如果不是 Player 类型会抛出异常
// ✅ 正确:使用泛型方法
var player = GetNode<Player>("Player");
// ✅ 使用 as 操作符
var player = GetNode("Player") as Player;
if (player != null)
{
player.Health = 100;
}
// ✅ 使用 is 模式匹配
if (GetNode("Player") is Player player)
{
player.Health = 100;
}
ArgumentException: An item with the same key has already been added
错误类型:重复注册
完整错误信息:
System.ArgumentException: An item with the same key has already been added. Key: PlayerModel
原因:重复注册同一类型的组件
解决方案:
// ❌ 错误:重复注册
protected override void Init()
{
RegisterModel(new PlayerModel());
RegisterModel(new PlayerModel()); // 重复注册
}
// ✅ 正确:只注册一次
protected override void Init()
{
RegisterModel(new PlayerModel());
}
// ✅ 如果需要多个实例,使用不同的键
protected override void Init()
{
RegisterModel(new PlayerModel(), "Player1");
RegisterModel(new PlayerModel(), "Player2");
}
TimeoutException: The operation has timed out
错误类型:操作超时
常见场景:
- 资源加载超时:
// ✅ 增加超时时间
var handle = resourceManager.LoadAsync<Texture2D>(
"res://assets/large_texture.png",
timeout: 30f // 30秒超时
);
- 事件等待超时:
// ✅ 使用带超时的等待
yield return new WaitForEventWithTimeout<GameEvent>(
architecture,
timeout: 5f
);
StackOverflowException: Operation caused a stack overflow
错误类型:栈溢出
原因:
- 无限递归
- 循环依赖
解决方案:
// ❌ 错误:无限递归
private void ProcessData()
{
ProcessData(); // 无限递归
}
// ✅ 正确:添加终止条件
private void ProcessData(int depth = 0)
{
if (depth > 100)
return;
ProcessData(depth + 1);
}
// ❌ 错误:循环依赖
public class SystemA : AbstractSystem
{
protected override void OnInit()
{
this.GetSystem<SystemB>().Initialize();
}
}
public class SystemB : AbstractSystem
{
protected override void OnInit()
{
this.GetSystem<SystemA>().Initialize(); // 循环依赖
}
}
// ✅ 正确:使用事件解耦
public class SystemA : AbstractSystem
{
protected override void OnInit()
{
this.SendEvent(new InitializeSystemBEvent());
}
}
ObjectDisposedException: Cannot access a disposed object
错误类型:访问已释放的对象
完整错误信息:
System.ObjectDisposedException: Cannot access a disposed object. Object name: 'Architecture'
原因:在架构销毁后继续使用
解决方案:
// ✅ 检查对象状态
if (architecture != null && !architecture.IsDisposed)
{
var model = architecture.GetModel<PlayerModel>();
}
// ✅ 在销毁前清理引用
public override void _ExitTree()
{
_unregisterList.UnRegisterAll();
_architecture = null; // 清理引用
}
// ✅ 使用弱引用
private WeakReference<GameArchitecture> _architectureRef;
public void UseArchitecture()
{
if (_architectureRef.TryGetTarget(out var arch))
{
var model = arch.GetModel<PlayerModel>();
}
}
FileNotFoundException: Could not find file
错误类型:文件未找到
完整错误信息:
System.IO.FileNotFoundException: Could not find file 'res://assets/player.png'
原因:
- 文件路径错误
- 文件不存在
- 文件未导入到项目
解决方案:
// ✅ 检查文件是否存在
if (ResourceLoader.Exists("res://assets/player.png"))
{
var texture = ResourceLoader.Load<Texture2D>("res://assets/player.png");
}
else
{
GD.PrintErr("File not found!");
}
// ✅ 使用正确的路径格式
// Godot 使用 res:// 协议
var texture = ResourceLoader.Load<Texture2D>("res://assets/player.png");
// ✅ 检查文件是否在 .import 文件中
// 确保文件已被 Godot 导入
NotImplementedException: The method is not implemented
错误类型:方法未实现
完整错误信息:
System.NotImplementedException: The method or operation is not implemented
原因:抽象方法或接口方法未实现
解决方案:
// ❌ 错误:未实现抽象方法
public class MySystem : AbstractSystem
{
// 缺少 OnInit 实现
}
// ✅ 正确:实现所有抽象方法
public class MySystem : AbstractSystem
{
protected override void OnInit()
{
// 实现初始化逻辑
}
}
// ✅ 实现接口方法
public class MyLoader : IResourceLoader
{
public async Task<T> LoadAsync<T>(string path) where T : class
{
// 实现加载逻辑
await Task.Delay(100);
return default;
}
}
常见问题排查清单
- 所有组件都已注册?
- 事件类型是否匹配?
- 是否正确注销了监听器?
- Architecture 是否已初始化?
- 是否有循环依赖?
- 内存使用是否持续增长?
- 事件处理是否过于复杂?
- 是否缓存了频繁访问的组件?
- 资源是否正确释放?
- 协程是否正确启动和停止?
- Godot 节点路径是否正确?
- 信号连接是否成功?
获取帮助
如果问题仍未解决:
- 查看 Core 文档 了解更多细节
- 查看 架构组件 了解架构设计
- 查看 Godot 集成 了解 Godot 特定问题
- 在 GitHub Issues 提交问题
- 查看 教程 中的示例代码
- 查看 常见问题 获取快速答案
提示:在提交 Issue 时,请提供:
- 错误信息和完整的堆栈跟踪
- 最小化的可复现代码示例
- 你的环境信息(.NET 版本、Godot 版本、操作系统等)
- 已尝试的解决方案
- 相关的配置文件(如有)