Files
Workspace/Godot

ShrinkSDK for Godot 4.7 C#

Godot 适配层使用共享 ShrinkSDK 运行时和 MSBuild/Cecil CodeGen。桌面基线为 Godot 4.7.2 Mono / .NET 8。Android 消费工程使用 .NET 9,并由工程提供对应平台的运行与导出验证;Web 和 iOS 不在本次支持范围。

直接运行 Workspace 示例

使用 Godot 4.7.2 Mono 打开:

Godot/Samples/ShrinkSDK.Godot.Sample/project.godot

首次打开前可以先完成导入和编译:

$godot = Join-Path $env:GODOT_HOME "godot_console.exe"
& $godot --headless --editor --path .\Godot\Samples\ShrinkSDK.Godot.Sample --quit
dotnet build .\Godot\Samples\ShrinkSDK.Godot.Sample\ShrinkSDK.Godot.Sample.csproj
& $godot --headless --path .\Godot\Samples\ShrinkSDK.Godot.Sample

GODOT_HOME 指向 Godot Mono 安装目录。成功运行时会输出 SHRINK_GODOT_SMOKE_PASS。

在现有 Godot C# 项目中安装

项目需要:

  • Godot 4.7.2 Mono;
  • .NET 8 SDK;
  • .csproj 使用 Godot.NET.Sdk/4.7.2;
  • 项目级 NuGet.Config 保留既有源并加入 ShrinkSDK feed。

NuGet.Config 的最小增量如下:

<configuration>
  <packageSources>
    <add key="ShrinkSDK" value="https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json" />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
  </packageSources>
</configuration>

安装 Godot 宿主和所需模块:

dotnet add package ShrinkSDK.Godot --version 0.2.1
dotnet add package ShrinkSDK.EntJoy.Godot --version 0.1.1
dotnet add package ShrinkSDK.EventBus --version 2.3.1
dotnet add package ShrinkSDK.Command --version 0.4.3
dotnet add package ShrinkSDK.Network --version 0.4.3
dotnet add package ShrinkSDK.DataSaver --version 2.5.0
dotnet restore
dotnet build

只有实际使用的模块需要安装。版本以 ShrinkSDK NuGet 源中的已发布版本为准,并保留命令中的固定版本号,不要改用浮动版本。

EventBus 调试器(可选)

ShrinkSDK.EventBus.Godot 0.1.1 提供 Godot 编辑器中的事件订阅与分发调试窗口。安装 NuGet 包后,将包内 contentFiles/any/any/addons/shrink_event_bus 复制到项目的 addons/shrink_event_bus,再在 Project > Project Settings > Plugins 启用 ShrinkEventBus。它依赖公开的 EventBus.ObserveDiagnostics,订阅快照列出静态 EventBus 管理的 Bus,分发记录也可观察独立 Host;Release 构建不包含采集实现。

主线程 Bus 可以使用显式延后发布:

var result = EventBus.PostDeferred(bus, new PlayerReadyEvent());
if (!result.Accepted)
    GD.PushWarning($"deferred post rejected: {result.Failure}");

延后事件在下一次宿主主线程泵送时执行。Inline、DedicatedThread、TaskPool 或没有 IShrinkDeferredMainThreadDispatcher 的宿主会返回 DeferredUnsupported,不会静默改成同步分发。

EntJoy ECS(可选)

ShrinkSDK.EntJoy.Godot 将 EntJoy 的 headless ECS/JobSystem 接入 Godot 节点生命周期。主场景根节点改为继承 ShrinkEntJoyHost 后,可从 EntJoyWorld 使用 EntityManager、QueryBuilder 和 EntJoy Job API;宿主会在 _Ready 初始化共享 JobSystem,在每帧 _Process 交换事件缓冲并提交待处理 Job,在 _ExitTree 释放 World。详见 Godot/Packages/ShrinkSDK.EntJoy.Godot/README.md。

Installer 插件

ShrinkSDK.Godot.Installer 的 nupkg 内包含完整的 addons/shrinksdk。取得 0.2.0 包后,将包内:

contentFiles/any/any/addons/shrinksdk

复制到 Godot 项目的:

addons/shrinksdk

然后在 Godot 中打开 Project > Project Settings > Plugins,启用 ShrinkSDK Installer。左侧 ShrinkSDK 面板可以:

  • 查看项目直接安装的 ShrinkSDK NuGet 包及版本;
  • 安装、升级或移除模块;
  • 保留项目已有 NuGet 源和无关 PackageReference;
  • 执行 dotnet restore/build;
  • 查看 CodeGen 最后一次织入结果和生成的注册数量。

已安装版本高于面板目录版本时会显示 Newer 并禁用更新按钮,不会执行降级。

在 Workspace 中生成 Installer 包:

dotnet build .\Assets\Modules\ShrinkInstaller\Godot~\ShrinkSDK.Godot.Installer.csproj -c Release
dotnet pack .\Assets\Modules\ShrinkInstaller\Godot~\ShrinkSDK.Godot.Installer.csproj -c Release --no-build -o .\Godot\Artifacts

宿主节点

主场景根节点继承 ShrinkGodotHost,并在 _Ready 中先调用 base._Ready():

using Cysharp.Threading.Tasks;
using ShrinkSDK.Godot;

public partial class GameRoot : ShrinkGodotHost
{
    public override void _Ready()
    {
        base._Ready();
        BootAsync().Forget();
    }

    private async UniTaskVoid BootAsync()
    {
        RequireWoven(GetType().Assembly);
        await StartAppAsync();
    }
}

宿主负责日志、主线程派发、时间、user:// 持久化路径、截图、应用生命周期和网络 dispatch queue。Runtime 属性暴露可组合的 ShrinkGodotRuntime,供不直接继承宿主节点的入口复用。使用 ShrinkNetworkService 时,通过 AddNetworkService(service) 交给宿主逐帧泵送,释放前调用 RemoveNetworkService(service)。

ShutdownAppAsync() 只停止 App,保留运行时继续泵送派发和网络队列;ShutdownAsync() 会先停止 App,再关闭整个运行时。退出流程需要等待 App 的 inverse effect 完成时使用后者。

组合式宿主

Godot 4.7.2 项目优先让常驻入口继承 Node,持有一个 ShrinkGodotRuntime:在 _Ready 中 Initialize(this),在 _Process(delta) 中 Tick(delta),在 _Notification(what) 中 Notify(what)。入口设置 ProcessMode = ProcessModeEnum.Always,暂停时继续处理基础服务。

启动前必须初始化;退出时先保存业务状态并关闭服务组合,再等待 ShutdownAsync(),最后调用 SceneTree.Quit()。正常退出由项目显式协调,强制终止不保证清理。网络注册与逐帧驱动在所有者线程调用,后台任务可以使用 TryPost / TryPostDeferred。

受限沙箱中的设置存储可使用 ShrinkGodotStorage.CreateRenameOnly(rootPath),通过 ShrinkDataSaverAppModuleConfig.RuntimeConfig.Storage 注入,并调用 ShrinkBasicComposition.CreateEntries(services, network: null, dataSaver: config)。轮转中断从备份恢复,孤立临时文件不会自动成为正式文件。

运行 WorldDemo.tscn 可验证两个实际世界节点的候选恢复、失败保留原世界、提交替换与订阅释放,以及暂停派发和正常退出。成功标记为 SHRINK_GODOT_WORLD_PASS。

CodeGen 织入

EventBus、Command、Network 和 App 的特性注册由 ShrinkSDK.CodeGen 在构建时生成。项目正常引用这些模块后,buildTransitive 会默认设置:

<ShrinkCodeGenEnabled>true</ShrinkCodeGenEnabled>

执行:

dotnet build

日志应出现类似:

[ShrinkSDK.CodeGen] ... woven: instance=1, static=1, registry=2

运行时可以在启动阶段验证程序集:

ShrinkGodotHost.RequireWoven(GetType().Assembly);

缺少织入时应修复 NuGet 引用或构建配置,不要增加运行时反射扫描兜底。只有明确采用手工注册的普通 .NET 宿主才设置 <ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>。

特性声明示例

using ShrinkCommand;
using ShrinkEventBus;
using ShrinkNetwork;

public readonly struct PlayerReadyEvent : IShrinkEvent { }

[ShrinkEventSubscriber]
public sealed class PlayerReadySubscriber
{
    [ShrinkSubscribe]
    private void OnPlayerReady(PlayerReadyEvent value) { }
}

[ShrinkCommandSubscriber]
public static class GameCommands
{
    [ShrinkCommand("game/status")]
    public static void Status() { }
}

[ShrinkNetworkMessage(41001, "game/player-ready")]
public sealed class PlayerReadyMessage : IShrinkNetworkMessage { }

实例订阅者使用 EventBus.Attach(instance) 绑定;静态订阅、Command、Network 和 App registry 由织入代码自动注册。