mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
- 将文档中的"IOC"统一更正为"IoC"格式 - 重构ArchitectureLifecycle类构造函数使用主构造函数语法 - 移除私有字段前缀下划线,直接使用参数名称 - 修复配置访问权限问题,移除字段访问前缀下划线 - 调整组件注册方法泛型约束,提升类型安全 - 优化日志记录器访问方式,移除字段访问前缀下划线 - 重构销毁逻辑,分离组件清理和服务模块销毁流程 - 提取清理组件逻辑到独立方法,提升代码可读性 - 更新阶段转换和钩子通知中的对象引用方式
2066 lines
43 KiB
Markdown
2066 lines
43 KiB
Markdown
# 故障排除与调试
|
||
|
||
本指南帮助你诊断和解决 GFramework 使用中的常见问题。
|
||
|
||
## 如何使用本指南
|
||
|
||
1. **快速查找**:使用浏览器的搜索功能(Ctrl+F / Cmd+F)查找错误信息或关键词
|
||
2. **分类浏览**:根据问题类型(安装、架构、事件等)查看对应章节
|
||
3. **排查清单**:每个问题都提供了详细的排查步骤和解决方案
|
||
4. **代码示例**:所有解决方案都包含可运行的代码示例
|
||
|
||
## 安装问题
|
||
|
||
### NuGet 包安装失败
|
||
|
||
**问题**:无法安装 GFramework NuGet 包。
|
||
|
||
**错误信息**:
|
||
|
||
```
|
||
NU1101: Unable to find package GFramework.Core
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
1. **检查包源配置**:
|
||
|
||
```bash
|
||
# 查看当前包源
|
||
dotnet nuget list source
|
||
|
||
# 添加 NuGet.org 源
|
||
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
|
||
```
|
||
|
||
2. **清理 NuGet 缓存**:
|
||
|
||
```bash
|
||
dotnet nuget locals all --clear
|
||
```
|
||
|
||
3. **手动指定版本**:
|
||
|
||
```bash
|
||
dotnet add package GFramework.Core --version 1.0.0
|
||
```
|
||
|
||
### 依赖冲突
|
||
|
||
**问题**:安装 GFramework 后出现依赖版本冲突。
|
||
|
||
**错误信息**:
|
||
|
||
```
|
||
NU1107: Version conflict detected for Microsoft.Extensions.DependencyInjection
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
1. **检查项目文件**:
|
||
|
||
```xml
|
||
<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>
|
||
```
|
||
|
||
2. **统一依赖版本**:
|
||
|
||
```xml
|
||
<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
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
1. **检查 .NET SDK 版本**:
|
||
|
||
```bash
|
||
dotnet --version
|
||
```
|
||
|
||
2. **安装正确的 .NET SDK**:
|
||
|
||
- GFramework 需要 .NET 8.0 或更高版本
|
||
- 下载地址:https://dotnet.microsoft.com/download
|
||
|
||
3. **更新项目目标框架**:
|
||
|
||
```xml
|
||
<PropertyGroup>
|
||
<TargetFramework>net8.0</TargetFramework>
|
||
</PropertyGroup>
|
||
```
|
||
|
||
## 架构问题
|
||
|
||
### 架构初始化失败
|
||
|
||
**问题**:架构初始化时抛出异常。
|
||
|
||
**错误信息**:
|
||
|
||
```
|
||
InvalidOperationException: Architecture is already initialized
|
||
```
|
||
|
||
**原因**:重复调用 `Initialize()` 方法。
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:重复初始化
|
||
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
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:未注册服务
|
||
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
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误: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
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:未等待异步初始化完成
|
||
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' 的组件`
|
||
|
||
**原因**:尝试获取未注册的组件。
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:未注册 PlayerModel
|
||
var arch = new GameArchitecture();
|
||
arch.Initialize();
|
||
var model = arch.GetModel<PlayerModel>(); // 抛出异常
|
||
|
||
// ✅ 正确:先注册再获取
|
||
public class GameArchitecture : Architecture
|
||
{
|
||
protected override void Init()
|
||
{
|
||
RegisterModel(new PlayerModel()); // 注册模型
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 事件监听器未触发
|
||
|
||
**问题**:注册了事件监听器但没有被调用。
|
||
|
||
**原因**:
|
||
|
||
- 事件类型不匹配
|
||
- 监听器在事件发送前注销
|
||
- 事件发送时使用了错误的类型
|
||
- 事件传播被停止
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:事件类型不匹配
|
||
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
|
||
- 循环引用
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ✅ 正确:使用 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. 循环依赖
|
||
|
||
**问题**:两个系统相互依赖导致死循环。
|
||
|
||
**原因**:系统间直接调用而不是通过事件通信。
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:直接调用导致循环依赖
|
||
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());
|
||
// 处理数据
|
||
}
|
||
}
|
||
```
|
||
|
||
## 事件系统问题
|
||
|
||
### 事件未触发
|
||
|
||
**问题**:发送事件后没有任何监听器响应。
|
||
|
||
**排查步骤**:
|
||
|
||
1. **检查事件类型是否正确**:
|
||
|
||
```csharp
|
||
// 确保发送和监听的是同一个事件类型
|
||
public struct PlayerDiedEvent : IEvent { }
|
||
|
||
// 注册
|
||
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
|
||
|
||
// 发送
|
||
this.SendEvent(new PlayerDiedEvent()); // 类型必须完全匹配
|
||
```
|
||
|
||
2. **检查是否在架构初始化后注册**:
|
||
|
||
```csharp
|
||
// ❌ 错误:在架构初始化前注册
|
||
var arch = new GameArchitecture();
|
||
arch.RegisterEvent<GameEvent>(OnGameEvent); // 可能失败
|
||
|
||
// ✅ 正确:在架构初始化后注册
|
||
var arch = new GameArchitecture();
|
||
arch.Initialize();
|
||
arch.RegisterEvent<GameEvent>(OnGameEvent);
|
||
```
|
||
|
||
3. **检查事件总线是否正确注册**:
|
||
|
||
```csharp
|
||
protected override void Init()
|
||
{
|
||
// 确保注册了事件总线
|
||
RegisterSystem(new EventBusModule());
|
||
}
|
||
```
|
||
|
||
### 事件执行顺序错误
|
||
|
||
**问题**:多个监听器的执行顺序不符合预期。
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ✅ 使用优先级控制执行顺序(数值越大优先级越高)
|
||
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()); // 触发下一个事件
|
||
});
|
||
```
|
||
|
||
### 事件传播控制
|
||
|
||
**问题**:需要在某些条件下停止事件传播。
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ✅ 使用 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`
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:未启动协程调度器
|
||
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!");
|
||
}
|
||
```
|
||
|
||
### 协程死锁
|
||
|
||
**问题**:协程永远不会完成。
|
||
|
||
**原因**:
|
||
|
||
- 等待条件永远不满足
|
||
- 循环等待
|
||
- 等待已停止的协程
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:等待条件永远不满足
|
||
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());
|
||
// 继续执行
|
||
}
|
||
```
|
||
|
||
### 协程提前停止
|
||
|
||
**问题**:协程在完成前被意外停止。
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ✅ 保存协程句柄
|
||
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'
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:路径错误
|
||
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}");
|
||
}
|
||
```
|
||
|
||
### 资源内存泄漏
|
||
|
||
**问题**:加载的资源未释放,导致内存持续增长。
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:未释放资源
|
||
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;
|
||
|
||
// 离开作用域时自动释放
|
||
}
|
||
```
|
||
|
||
### 资源加载超时
|
||
|
||
**问题**:大型资源加载时间过长。
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ✅ 使用异步加载
|
||
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
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:路径错误或场景未导出
|
||
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
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:路径错误
|
||
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);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 信号连接失败
|
||
|
||
**问题**:信号未正确连接或触发。
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:信号名称错误
|
||
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
|
||
```
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:未设置架构锚点
|
||
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 页面无法正确显示或切换。
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:未注册 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}");
|
||
}
|
||
```
|
||
|
||
## 性能问题
|
||
|
||
### 事件处理缓慢
|
||
|
||
**问题**:事件处理耗时过长,导致游戏卡顿。
|
||
|
||
**诊断**:
|
||
|
||
```csharp
|
||
// 测量事件处理时间
|
||
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");
|
||
}
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
**优化**:
|
||
|
||
```csharp
|
||
// ❌ 低效:在事件处理中进行复杂计算
|
||
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` 等方法导致性能下降。
|
||
|
||
**优化**:
|
||
|
||
```csharp
|
||
// ❌ 低效:每帧访问
|
||
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;
|
||
}
|
||
```
|
||
|
||
### 内存占用过高
|
||
|
||
**问题**:游戏运行时内存持续增长。
|
||
|
||
**诊断**:
|
||
|
||
```csharp
|
||
// 监控内存使用
|
||
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!");
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**优化**:
|
||
|
||
```csharp
|
||
// ✅ 使用对象池
|
||
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();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 协程性能问题
|
||
|
||
**问题**:大量协程导致性能下降。
|
||
|
||
**优化**:
|
||
|
||
```csharp
|
||
// ❌ 低效:创建大量短期协程
|
||
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. 启用日志系统
|
||
|
||
```csharp
|
||
// 使用 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. 使用断点调试
|
||
|
||
```csharp
|
||
// 在关键位置添加断点
|
||
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. 追踪事件流
|
||
|
||
```csharp
|
||
// 创建事件追踪系统
|
||
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. 性能分析
|
||
|
||
```csharp
|
||
// 使用 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. 单元测试调试
|
||
|
||
```csharp
|
||
[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
|
||
```
|
||
|
||
**原因**:
|
||
|
||
- 包源配置错误
|
||
- 包名拼写错误
|
||
- 网络连接问题
|
||
|
||
**解决方案**:
|
||
|
||
```bash
|
||
# 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' 的组件
|
||
```
|
||
|
||
**原因**:尝试获取未注册的组件
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// 在架构中注册组件
|
||
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()` 方法
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// 检查初始化状态
|
||
if (!architecture.IsInitialized)
|
||
{
|
||
architecture.Initialize();
|
||
}
|
||
|
||
// 或使用单例模式
|
||
public class GameArchitecture : Architecture<GameArchitecture>
|
||
{
|
||
// 自动处理单例
|
||
}
|
||
```
|
||
|
||
### NullReferenceException: Object reference not set
|
||
|
||
**错误类型**:空引用异常
|
||
|
||
**常见场景**:
|
||
|
||
1. **节点未找到**:
|
||
|
||
```csharp
|
||
// ❌ 错误
|
||
var player = GetNode<Player>("Player"); // 返回 null
|
||
player.Health = 100; // 抛出异常
|
||
|
||
// ✅ 正确
|
||
if (HasNode("Player"))
|
||
{
|
||
var player = GetNode<Player>("Player");
|
||
player.Health = 100;
|
||
}
|
||
```
|
||
|
||
2. **组件未注册**:
|
||
|
||
```csharp
|
||
// ❌ 错误
|
||
var model = this.GetModel<PlayerModel>(); // 返回 null
|
||
model.Health.Value = 100; // 抛出异常
|
||
|
||
// ✅ 正确:先注册
|
||
protected override void Init()
|
||
{
|
||
RegisterModel(new PlayerModel());
|
||
}
|
||
```
|
||
|
||
3. **架构上下文丢失**:
|
||
|
||
```csharp
|
||
// ❌ 错误:未设置架构锚点
|
||
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'
|
||
```
|
||
|
||
**原因**:节点类型不匹配
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:强制转换
|
||
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
|
||
```
|
||
|
||
**原因**:重复注册同一类型的组件
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:重复注册
|
||
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
|
||
|
||
**错误类型**:操作超时
|
||
|
||
**常见场景**:
|
||
|
||
1. **资源加载超时**:
|
||
|
||
```csharp
|
||
// ✅ 增加超时时间
|
||
var handle = resourceManager.LoadAsync<Texture2D>(
|
||
"res://assets/large_texture.png",
|
||
timeout: 30f // 30秒超时
|
||
);
|
||
```
|
||
|
||
2. **事件等待超时**:
|
||
|
||
```csharp
|
||
// ✅ 使用带超时的等待
|
||
yield return new WaitForEventWithTimeout<GameEvent>(
|
||
architecture,
|
||
timeout: 5f
|
||
);
|
||
```
|
||
|
||
### StackOverflowException: Operation caused a stack overflow
|
||
|
||
**错误类型**:栈溢出
|
||
|
||
**原因**:
|
||
|
||
- 无限递归
|
||
- 循环依赖
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:无限递归
|
||
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'
|
||
```
|
||
|
||
**原因**:在架构销毁后继续使用
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ✅ 检查对象状态
|
||
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'
|
||
```
|
||
|
||
**原因**:
|
||
|
||
- 文件路径错误
|
||
- 文件不存在
|
||
- 文件未导入到项目
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ✅ 检查文件是否存在
|
||
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
|
||
```
|
||
|
||
**原因**:抽象方法或接口方法未实现
|
||
|
||
**解决方案**:
|
||
|
||
```csharp
|
||
// ❌ 错误:未实现抽象方法
|
||
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 节点路径是否正确?
|
||
- [ ] 信号连接是否成功?
|
||
|
||
## 获取帮助
|
||
|
||
如果问题仍未解决:
|
||
|
||
1. 查看 [Core 文档](/zh-CN/core/) 了解更多细节
|
||
2. 查看 [架构组件](/zh-CN/core/architecture) 了解架构设计
|
||
3. 查看 [Godot 集成](/zh-CN/godot/) 了解 Godot 特定问题
|
||
4. 在 [GitHub Issues](https://github.com/GeWuYou/GFramework/issues) 提交问题
|
||
5. 查看 [教程](/zh-CN/tutorials/) 中的示例代码
|
||
6. 查看 [常见问题](/zh-CN/faq) 获取快速答案
|
||
|
||
---
|
||
|
||
**提示**:在提交 Issue 时,请提供:
|
||
|
||
- 错误信息和完整的堆栈跟踪
|
||
- 最小化的可复现代码示例
|
||
- 你的环境信息(.NET 版本、Godot 版本、操作系统等)
|
||
- 已尝试的解决方案
|
||
- 相关的配置文件(如有)
|