GFramework/docs/zh-CN/troubleshooting.md
GeWuYou 1c2e68fc5a style(docs): 统一文档中IoC容器术语格式并优化架构生命周期代码
- 将文档中的"IOC"统一更正为"IoC"格式
- 重构ArchitectureLifecycle类构造函数使用主构造函数语法
- 移除私有字段前缀下划线,直接使用参数名称
- 修复配置访问权限问题,移除字段访问前缀下划线
- 调整组件注册方法泛型约束,提升类型安全
- 优化日志记录器访问方式,移除字段访问前缀下划线
- 重构销毁逻辑,分离组件清理和服务模块销毁流程
- 提取清理组件逻辑到独立方法,提升代码可读性
- 更新阶段转换和钩子通知中的对象引用方式
2026-03-17 12:50:43 +08:00

43 KiB
Raw Permalink Blame History

故障排除与调试

本指南帮助你诊断和解决 GFramework 使用中的常见问题。

如何使用本指南

  1. 快速查找使用浏览器的搜索功能Ctrl+F / Cmd+F查找错误信息或关键词
  2. 分类浏览:根据问题类型(安装、架构、事件等)查看对应章节
  3. 排查清单:每个问题都提供了详细的排查步骤和解决方案
  4. 代码示例:所有解决方案都包含可运行的代码示例

安装问题

NuGet 包安装失败

问题:无法安装 GFramework NuGet 包。

错误信息

NU1101: Unable to find package GFramework.Core

解决方案

  1. 检查包源配置
# 查看当前包源
dotnet nuget list source

# 添加 NuGet.org 源
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
  1. 清理 NuGet 缓存
dotnet nuget locals all --clear
  1. 手动指定版本
dotnet add package GFramework.Core --version 1.0.0

依赖冲突

问题:安装 GFramework 后出现依赖版本冲突。

错误信息

NU1107: Version conflict detected for Microsoft.Extensions.DependencyInjection

解决方案

  1. 检查项目文件
<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>
  1. 统一依赖版本
<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 版本
dotnet --version
  1. 安装正确的 .NET SDK
  1. 更新项目目标框架
<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&lt;GameArchitecture&gt;
{
    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&lt;IPlayerService&gt;(); // 抛出异常

// ✅ 正确:先注册服务
public class GameArchitecture : Architecture
{
    protected override void Init()
    {
        // 注册服务
        RegisterService&lt;IPlayerService, PlayerService&gt;();
    }
}

// ✅ 使用 IoC 容器注册
protected override void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton&lt;IPlayerService, PlayerService&gt;();
    services.AddTransient&lt;IGameService, GameService&gt;();
}

组件注册顺序错误

问题:组件初始化时依赖的其他组件尚未注册。

错误信息

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&lt;IConfigUtility&gt;(new ConfigUtility());
}

// ✅ 更好:使用延迟初始化
public class SystemB : AbstractSystem
{
    private ModelA _modelA;

    protected override void OnInit()
    {
        // 在 OnInit 中获取依赖,此时所有组件已注册
        _modelA = this.GetModel&lt;ModelA&gt;();
    }
}

异步初始化问题

问题:异步架构初始化未正确等待。

错误信息

InvalidOperationException: Architecture not fully initialized

解决方案

// ❌ 错误:未等待异步初始化完成
var arch = new AsyncGameArchitecture();
arch.InitializeAsync(); // 未等待
var model = arch.GetModel&lt;PlayerModel&gt;(); // 可能失败

// ✅ 正确:等待异步初始化
var arch = new AsyncGameArchitecture();
await arch.InitializeAsync();
var model = arch.GetModel&lt;PlayerModel&gt;(); // 安全

// ✅ 在 Godot 中使用
public partial class GameRoot : Node
{
    private AsyncGameArchitecture _architecture;

    public override async void _Ready()
    {
        _architecture = new AsyncGameArchitecture();
        await _architecture.InitializeAsync();

        // 现在可以安全使用架构
        var model = _architecture.GetModel&lt;PlayerModel&gt;();
    }
}

常见错误

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&lt;PlayerDiedEvent&gt;(OnPlayerDied);
this.SendEvent(new PlayerAttackedEvent()); // 不会触发

// ✅ 正确:事件类型匹配
this.RegisterEvent&lt;PlayerAttackedEvent&gt;(OnPlayerAttacked);
this.SendEvent(new PlayerAttackedEvent()); // 正确触发

// ❌ 错误:过早注销
var unregister = this.RegisterEvent&lt;GameEvent&gt;(OnGameEvent);
unregister.UnRegister(); // 立即注销
this.SendEvent(new GameEvent()); // 不会触发

// ✅ 正确:在适当时机注销
private IUnRegister _eventUnregister;

public void Initialize()
{
    _eventUnregister = this.RegisterEvent&lt;GameEvent&gt;(OnGameEvent);
}

public void Cleanup()
{
    _eventUnregister?.UnRegister();
}

// ❌ 错误:事件传播被停止
this.RegisterEvent&lt;GameEvent&gt;(e =>
{
    e.StopPropagation(); // 停止传播
});

this.RegisterEvent&lt;GameEvent&gt;(OnGameEvent); // 不会被调用

// ✅ 正确:使用优先级控制执行顺序
this.RegisterEvent&lt;GameEvent&gt;(OnGameEventFirst, priority: 100);
this.RegisterEvent&lt;GameEvent&gt;(OnGameEventSecond, priority: 50);

3. 内存泄漏

问题:应用内存持续增长。

原因

  • 未注销事件监听器
  • 未注销属性监听器
  • 未销毁 Architecture
  • 循环引用

解决方案

// ✅ 正确:使用 UnRegisterList 管理注销
private IUnRegisterList _unregisterList = new UnRegisterList();

public void Initialize()
{
    this.RegisterEvent&lt;Event1&gt;(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&lt;PlayerDiedEvent&gt;(OnPlayerDied);
        _eventScope.Register&lt;PlayerAttackedEvent&gt;(OnPlayerAttacked);
    }

    public override void _ExitTree()
    {
        _eventScope.Dispose(); // 自动注销所有监听器
    }
}

// ✅ 在 Godot 中使用 UnRegisterOnFree
public partial class GameController : Node
{
    public override void _Ready()
    {
        this.RegisterEvent&lt;GameEvent&gt;(OnGameEvent)
            .UnRegisterOnFree(this); // 节点释放时自动注销
    }
}

4. 循环依赖

问题:两个系统相互依赖导致死循环。

原因:系统间直接调用而不是通过事件通信。

解决方案

// ❌ 错误:直接调用导致循环依赖
public class SystemA : AbstractSystem
{
    private void OnEvent(EventA e)
    {
        var systemB = this.GetSystem&lt;SystemB&gt;();
        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&lt;EventB&gt;(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. 检查事件类型是否正确
// 确保发送和监听的是同一个事件类型
public struct PlayerDiedEvent : IEvent { }

// 注册
this.RegisterEvent&lt;PlayerDiedEvent&gt;(OnPlayerDied);

// 发送
this.SendEvent(new PlayerDiedEvent()); // 类型必须完全匹配
  1. 检查是否在架构初始化后注册
// ❌ 错误:在架构初始化前注册
var arch = new GameArchitecture();
arch.RegisterEvent&lt;GameEvent&gt;(OnGameEvent); // 可能失败

// ✅ 正确:在架构初始化后注册
var arch = new GameArchitecture();
arch.Initialize();
arch.RegisterEvent&lt;GameEvent&gt;(OnGameEvent);
  1. 检查事件总线是否正确注册
protected override void Init()
{
    // 确保注册了事件总线
    RegisterSystem(new EventBusModule());
}

事件执行顺序错误

问题:多个监听器的执行顺序不符合预期。

解决方案

// ✅ 使用优先级控制执行顺序(数值越大优先级越高)
this.RegisterEvent&lt;GameEvent&gt;(OnGameEventFirst, priority: 100);
this.RegisterEvent&lt;GameEvent&gt;(OnGameEventSecond, priority: 50);
this.RegisterEvent&lt;GameEvent&gt;(OnGameEventLast, priority: 0);

// ✅ 使用事件链
public struct FirstEvent : IEvent { }
public struct SecondEvent : IEvent { }
public struct ThirdEvent : IEvent { }

this.RegisterEvent&lt;FirstEvent&gt;(e =>
{
    // 处理第一个事件
    this.SendEvent(new SecondEvent()); // 触发下一个事件
});

this.RegisterEvent&lt;SecondEvent&gt;(e =>
{
    // 处理第二个事件
    this.SendEvent(new ThirdEvent()); // 触发下一个事件
});

事件传播控制

问题:需要在某些条件下停止事件传播。

解决方案

// ✅ 使用 StopPropagation
this.RegisterEvent&lt;DamageEvent&gt;(e =>
{
    if (IsInvincible)
    {
        e.StopPropagation(); // 停止传播,后续监听器不会执行
        return;
    }

    ApplyDamage(e.Amount);
}, priority: 100); // 高优先级先执行

// ✅ 使用条件过滤
this.RegisterEvent&lt;DamageEvent&gt;(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 &lt; timeout)
    {
        elapsed += Time.DeltaTime;
        yield return null;
    }

    if (elapsed &gt;= timeout)
    {
        GD.PrintErr("Timeout!");
    }
}

// ✅ 使用 WaitForEventWithTimeout
private IEnumerator WaitForEventSafely()
{
    yield return new WaitForEventWithTimeout&lt;GameEvent&gt;(
        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&lt;EventA&gt;(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&lt;Texture2D&gt;("assets/player.png"); // 缺少 res://

// ✅ 正确:使用完整路径
var texture = ResourceLoader.Load&lt;Texture2D&gt;("res://assets/player.png");

// ✅ 检查资源是否存在
if (ResourceLoader.Exists("res://assets/player.png"))
{
    var texture = ResourceLoader.Load&lt;Texture2D&gt;("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&lt;ResourceManager&gt;()
    .LoadAsync&lt;Texture2D&gt;("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&lt;Texture2D&gt;("res://assets/player.png");
    // 使用 texture
    // 忘记释放
}

// ✅ 正确:使用资源句柄自动管理
private IResourceHandle&lt;Texture2D&gt; _textureHandle;

public void LoadTexture()
{
    _textureHandle = resourceManager.Load&lt;Texture2D&gt;("res://assets/player.png");
}

public void Cleanup()
{
    _textureHandle?.Release(); // 释放资源
}

// ✅ 使用自动释放策略
var handle = resourceManager.Load&lt;Texture2D&gt;(
    "res://assets/player.png",
    new AutoReleaseStrategy(timeToLive: 60f) // 60秒后自动释放
);

// ✅ 使用 using 语句
public async Task LoadAndUseResource()
{
    using var handle = resourceManager.Load&lt;Texture2D&gt;("res://assets/player.png");
    await handle.Task;

    // 使用资源
    var texture = handle.Resource;

    // 离开作用域时自动释放
}

资源加载超时

问题:大型资源加载时间过长。

解决方案

// ✅ 使用异步加载
private async Task LoadLargeResourceAsync()
{
    var handle = resourceManager.LoadAsync&lt;PackedScene&gt;("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&lt;PackedScene&gt;("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&lt;Resource&gt;(path).Task;
    }

    GD.Print("All resources preloaded!");
}

Godot 集成问题

场景加载失败

问题:无法加载或实例化场景。

错误信息

NullReferenceException: Object reference not set to an instance of an object

解决方案

// ❌ 错误:路径错误或场景未导出
var scene = GD.Load&lt;PackedScene&gt;("scenes/player.tscn"); // 缺少 res://

// ✅ 正确:使用完整路径
var scene = GD.Load&lt;PackedScene&gt;("res://scenes/player.tscn");
var instance = scene.Instantiate();

// ✅ 检查场景是否存在
if (ResourceLoader.Exists("res://scenes/player.tscn"))
{
    var scene = GD.Load&lt;PackedScene&gt;("res://scenes/player.tscn");
    var instance = scene.Instantiate&lt;Player&gt;();
    AddChild(instance);
}
else
{
    GD.PrintErr("Scene not found!");
}

// ✅ 使用场景路由器
public class GameSceneRouter : SceneRouterBase
{
    protected override void RegisterScenes()
    {
        Register&lt;MainMenuScene&gt;("MainMenu", "res://scenes/main_menu.tscn");
        Register&lt;GameScene&gt;("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&lt;Player&gt;("Player"); // 节点可能在子节点中

// ✅ 正确:使用完整路径
var player = GetNode&lt;Player&gt;("Level/Player");

// ✅ 使用 % 符号访问唯一节点Godot 4.0+
var player = GetNode&lt;Player&gt;("%Player");

// ✅ 检查节点是否存在
if (HasNode("Player"))
{
    var player = GetNode&lt;Player&gt;("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&lt;Player&gt;(_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&lt;Button&gt;("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&lt;PlayerModel&gt;(); // 失败:无架构上下文
    }
}

// ✅ 正确:在根节点设置架构锚点
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&lt;PlayerModel&gt;(); // 成功
    }
}

// ✅ 使用 Godot 模块
public class GameArchitecture : AbstractArchitecture
{
    protected override void Init()
    {
        // 注册 Godot 模块
        this.RegisterGodotModule&lt;PlayerModule&gt;();
        this.RegisterGodotModule&lt;EnemyModule&gt;();
    }
}

UI 页面导航问题

问题UI 页面无法正确显示或切换。

解决方案

// ❌ 错误:未注册 UI 页面
var uiRouter = this.GetSystem&lt;IUiRouter&gt;();
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&lt;IGodotUiRegistry&gt;(uiRegistry);
        RegisterSystem(new UiRouter(uiRegistry));
    }
}

// 导航到页面
var uiRouter = this.GetSystem&lt;IUiRouter&gt;();
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&lt;IEvent&gt;(e =>
        {
            var sw = Stopwatch.StartNew();
            // 事件处理
            sw.Stop();
            if (sw.ElapsedMilliseconds &gt; 16) // 超过一帧
            {
                GD.PrintErr($"Slow event: {e.GetType().Name} took {sw.ElapsedMilliseconds}ms");
            }
        });
    }
}

优化

// ❌ 低效:在事件处理中进行复杂计算
private void OnEvent(HeavyEvent e)
{
    for (int i = 0; i &lt; 1000000; i++)
    {
        // 复杂计算
    }
}

// ✅ 高效:异步处理
private async void OnEvent(HeavyEvent e)
{
    await Task.Run(() =>
    {
        for (int i = 0; i &lt; 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 &lt; 1000000; i += batchSize)
    {
        // 处理一批数据
        for (int j = 0; j &lt; batchSize && i + j &lt; 1000000; j++)
        {
            // 计算
        }
        yield return null; // 下一帧继续
    }
}

频繁的组件访问

问题:每帧都调用 GetModelGetSystem 等方法导致性能下降。

优化

// ❌ 低效:每帧访问
public override void _Process(double delta)
{
    var model = this.GetModel&lt;PlayerModel&gt;(); // 每帧调用
    model.Health.Value -= 1;
}

// ✅ 高效:缓存引用
private PlayerModel _playerModel;

public override void _Ready()
{
    _playerModel = this.GetModel&lt;PlayerModel&gt;(); // 只调用一次
}

public override void _Process(double delta)
{
    _playerModel.Health.Value -= 1;
}

// ✅ 使用延迟初始化
private PlayerModel _playerModel;
private PlayerModel PlayerModel =&gt; _playerModel ??= this.GetModel&lt;PlayerModel&gt;();

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 &gt; 500) // 超过 500MB
        {
            GD.PrintErr("High memory usage detected!");
        }
    }
}

优化

// ✅ 使用对象池
public class BulletPool : AbstractNodePoolSystem&lt;Bullet&gt;
{
    protected override Bullet CreateInstance()
    {
        var scene = GD.Load&lt;PackedScene&gt;("res://entities/bullet.tscn");
        return scene.Instantiate&lt;Bullet&gt;();
    }

    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 &gt;= GC_INTERVAL)
    {
        _gcTimer = 0f;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

协程性能问题

问题:大量协程导致性能下降。

优化

// ❌ 低效:创建大量短期协程
for (int i = 0; i &lt; 1000; i++)
{
    StartCoroutine(ShortCoroutine());
}

// ✅ 高效:合并协程
StartCoroutine(BatchCoroutine(1000));

private IEnumerator BatchCoroutine(int count)
{
    for (int i = 0; i &lt; count; i++)
    {
        // 处理
        if (i % 10 == 0)
            yield return null; // 每10次操作暂停一次
    }
}

// ✅ 使用协程池
private readonly Queue&lt;IEnumerator&gt; _coroutineQueue = new();

private IEnumerator CoroutinePoolRunner()
{
    while (true)
    {
        if (_coroutineQueue.Count &gt; 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&lt;ILoggerFactory&gt;(new GodotLoggerFactory());
    }
}

// 在组件中使用日志
public class PlayerSystem : AbstractSystem
{
    private ILogger _logger;

    protected override void OnInit()
    {
        _logger = this.GetUtility&lt;ILoggerFactory&gt;().CreateLogger&lt;PlayerSystem&gt;();
        _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&lt;PlayerModel&gt;();
    // 在这里设置断点,检查 model 的值
    GD.Print($"Player health: {model.Health.Value}");
}

// 使用条件断点
private void OnDamage(DamageEvent e)
{
    // 只在伤害大于50时中断
    if (e.Amount &gt; 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&lt;ILoggerFactory&gt;().CreateLogger&lt;EventTracer&gt;();

        // 使用反射监听所有事件(仅用于调试)
        this.RegisterEvent&lt;PlayerDiedEvent&gt;(e =&gt;
            _logger.LogDebug($"Event: PlayerDiedEvent"));
        this.RegisterEvent&lt;PlayerAttackedEvent&gt;(e =&gt;
            _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&lt;string, Stopwatch&gt; _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&lt;PlayerModel&gt;();

    // 打印初始状态
    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&lt;IConfigUtility&gt;(new ConfigUtility());
    }
}

InvalidOperationException: Architecture is already initialized

错误类型:重复初始化

完整错误信息

System.InvalidOperationException: Architecture is already initialized

原因:多次调用 Initialize() 方法

解决方案

// 检查初始化状态
if (!architecture.IsInitialized)
{
    architecture.Initialize();
}

// 或使用单例模式
public class GameArchitecture : Architecture&lt;GameArchitecture&gt;
{
    // 自动处理单例
}

NullReferenceException: Object reference not set

错误类型:空引用异常

常见场景

  1. 节点未找到
// ❌ 错误
var player = GetNode&lt;Player&gt;("Player"); // 返回 null
player.Health = 100; // 抛出异常

// ✅ 正确
if (HasNode("Player"))
{
    var player = GetNode&lt;Player&gt;("Player");
    player.Health = 100;
}
  1. 组件未注册
// ❌ 错误
var model = this.GetModel&lt;PlayerModel&gt;(); // 返回 null
model.Health.Value = 100; // 抛出异常

// ✅ 正确:先注册
protected override void Init()
{
    RegisterModel(new PlayerModel());
}
  1. 架构上下文丢失
// ❌ 错误:未设置架构锚点
var model = this.GetModel&lt;PlayerModel&gt;(); // 抛出异常

// ✅ 正确:设置架构锚点
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&lt;Player&gt;("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

错误类型:操作超时

常见场景

  1. 资源加载超时
// ✅ 增加超时时间
var handle = resourceManager.LoadAsync&lt;Texture2D&gt;(
    "res://assets/large_texture.png",
    timeout: 30f // 30秒超时
);
  1. 事件等待超时
// ✅ 使用带超时的等待
yield return new WaitForEventWithTimeout&lt;GameEvent&gt;(
    architecture,
    timeout: 5f
);

StackOverflowException: Operation caused a stack overflow

错误类型:栈溢出

原因

  • 无限递归
  • 循环依赖

解决方案

// ❌ 错误:无限递归
private void ProcessData()
{
    ProcessData(); // 无限递归
}

// ✅ 正确:添加终止条件
private void ProcessData(int depth = 0)
{
    if (depth &gt; 100)
        return;

    ProcessData(depth + 1);
}

// ❌ 错误:循环依赖
public class SystemA : AbstractSystem
{
    protected override void OnInit()
    {
        this.GetSystem&lt;SystemB&gt;().Initialize();
    }
}

public class SystemB : AbstractSystem
{
    protected override void OnInit()
    {
        this.GetSystem&lt;SystemA&gt;().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&lt;PlayerModel&gt;();
}

// ✅ 在销毁前清理引用
public override void _ExitTree()
{
    _unregisterList.UnRegisterAll();
    _architecture = null; // 清理引用
}

// ✅ 使用弱引用
private WeakReference&lt;GameArchitecture&gt; _architectureRef;

public void UseArchitecture()
{
    if (_architectureRef.TryGetTarget(out var arch))
    {
        var model = arch.GetModel&lt;PlayerModel&gt;();
    }
}

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&lt;Texture2D&gt;("res://assets/player.png");
}
else
{
    GD.PrintErr("File not found!");
}

// ✅ 使用正确的路径格式
// Godot 使用 res:// 协议
var texture = ResourceLoader.Load&lt;Texture2D&gt;("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&lt;T&gt; LoadAsync&lt;T&gt;(string path) where T : class
    {
        // 实现加载逻辑
        await Task.Delay(100);
        return default;
    }
}

常见问题排查清单

  • 所有组件都已注册?
  • 事件类型是否匹配?
  • 是否正确注销了监听器?
  • Architecture 是否已初始化?
  • 是否有循环依赖?
  • 内存使用是否持续增长?
  • 事件处理是否过于复杂?
  • 是否缓存了频繁访问的组件?
  • 资源是否正确释放?
  • 协程是否正确启动和停止?
  • Godot 节点路径是否正确?
  • 信号连接是否成功?

获取帮助

如果问题仍未解决:

  1. 查看 Core 文档 了解更多细节
  2. 查看 架构组件 了解架构设计
  3. 查看 Godot 集成 了解 Godot 特定问题
  4. GitHub Issues 提交问题
  5. 查看 教程 中的示例代码
  6. 查看 常见问题 获取快速答案

提示:在提交 Issue 时,请提供:

  • 错误信息和完整的堆栈跟踪
  • 最小化的可复现代码示例
  • 你的环境信息(.NET 版本、Godot 版本、操作系统等)
  • 已尝试的解决方案
  • 相关的配置文件(如有)