Files
2026-06-07 22:55:56 +08:00

6.5 KiB
Raw Permalink Blame History

LeanCLR Godot

浅尝一下 LeanCLR 跑 Godot 4 脚本。 (目前是娱乐向,请勿用于生产环境,建议等官方绑定)

原版不带Mono Godot加载GDExtensionC# 代码交给 LeanCLR 解释执行。现在 demo 里可以把 .cs 直接挂到节点上,也可以在游戏窗口里的 CodeEdit 改 C#,点运行后编译一个新 assembly,然后不重启切过去。

已完成:

  • Godot API 绑定尽量全量生成,热更代码后面会直接用
  • C# 脚本资源能在编辑器里加载、保存、挂节点
  • 运行时热重载保留字段状态,接近 Python/Lua 那种 reload 手感
  • demo 是一个简单 Flappy Bird,用来测输入、Process、状态迁移和重载

编译

拉子模块:

git submodule update --init --recursive

LeanCLR 默认会把所有未实现的 icall 视为致命错误并 abort。本项目对接的 Godot API 只是一小部分,所以必须先把这个开关关掉,否则跑 demo 时第一碰到未实现接口就崩。修改 thirdparty/leanclr/src/runtime/build_config.h

-#define LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 1
+#define LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 0

这个 patch 在子模块里,git submodule update 之后需要重新打。

编 native 扩展:

cmake -S . -B build-master
cmake --build build-master --config Debug --target leanclr_godot

第一次 cmake -S . -B build-master 会通过 FetchContent 拉 godot-cpp 4.5,但此时 src/generated/godot_api.generated.cpp 还不存在,配置会失败。所以顺序是:

cmake -S . -B build-master
python3 tools/binding_generator/generate_bindings.py --api build-master/_deps/godot-cpp-src/gdextension/extension_api.json
cmake -S . -B build-master   # 再配一次,让 add_library 看到生成的 cpp
cmake --build build-master --config Debug --target leanclr_godot

GODOT_CPP_BRANCH 默认是 4.5,仓库里已经为 4.5 打过两个补丁(见下方「已知补丁」)。

编 demo 的 C#

dotnet msbuild managed/GodotSharpCompat/GodotSharpCompat.csproj -p:Configuration=Debug
dotnet msbuild project/Game.csproj -p:Configuration=Debug

dotnet msbuild 只能用单横线参数(-p:...),双横线(/p:...)会报 MSB1008: 只能指定一个项目。 Windows 上要用 python3,别用 python(会被 Microsoft Store 别名劫持弹出安装器)。

运行

"/path/to/Godot" --path project

/path/to/Godot 换成你机器上实际的 Godot 可执行文件路径。这台机器是 D:/Projects/godot-build/build/godot.exeGodot 4.6.3。

已知补丁

thirdparty/leanclr 子模块

  • src/runtime/build_config.hLEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR 设为 0(见上文)。

src/leanclr_script.cpp

godot-cpp 4.5 把 gdextension_interface_script_instance_create3gdextension_interface:: 命名空间搬到了 godot::internal:: 里,且原来的 <godot_cpp/core/gdextension_interface_loader.hpp> 头文件已经不存在。要把:

#include <godot_cpp/core/gdextension_interface_loader.hpp>
// ...
void* script_instance = gdextension_interface::script_instance_create3(&script_instance_info(), instance);

改成:

#include <godot_cpp/godot.hpp>
// ...
void* script_instance = ::godot::internal::gdextension_interface_script_instance_create3(&script_instance_info(), instance);

tools/binding_generator/generate_bindings.py

godot-cpp 4.5 把 ClassDB 整体重命名成了 ClassDBSingleton 单例包装。生成器原本的 CPP_HEADER_NAME_OVERRIDES["ClassDB"] = "class_db_singleton" 只重命名了头文件路径,C++ 类名仍是 ClassDB,编译会爆 get_class_static 不存在。补丁:新增 SINGLETON_CLASS_CPP_NAMES = {"ClassDB": "ClassDBSingleton"},让生成出的 C++ 走 ClassDBSingleton::get_singleton()->method(...)C# 侧 ClassDB 的方法全部变成 static

CMakeLists.txtWeb 构建)

仓库里已经为 Web 构建持久化地打了两个补丁,都在 if(EMSCRIPTEN) 块(或紧邻它的 option 块)里:

  1. 强制单线程Web 当前不支持多线程,且 -sSIDE_MODULE + pthreads 在 emcc 里是实验性的):

    if(EMSCRIPTEN)
        set(GODOTCPP_THREADS OFF CACHE BOOL "godot-cpp threading support" FORCE)
    endif()
    
  2. 降到 -O1:自动生成的 godot_api.generated.cpp 里有几千个 invoker 函数,-O3 下编译器会把参数拆包全部内联,单个函数会塞进 11 万+ 局部变量,浏览器加载时会报 Uncaught (in promise) CompileError: WebAssembly.instantiate(): Compiling function #N failed: local count too large(V8 单函数局部变量上限 50,000)。leanclr_godot target 在 if(EMSCRIPTEN) 块里被显式加了 target_compile_options(leanclr_godot PRIVATE -O1)

Web 构建方法:

# 需要 Emscripten 3.1+、Ninja 都在 PATH 上
rm -rf build-web
cmake -S . -B build-web -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE="<emsdk>/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
  -DCMAKE_BUILD_TYPE=Release
cmake --build build-web --target leanclr_godot

这台机器 Emscripten 在 D:/Projects/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmakeleanclr.gdextension 已经配了 web.release.wasm32 = "res://bin/Release/leanclr_godot.wasm",构建产物会自动落到 project/bin/Release/libleanclr_godot.wasm。要跑起来用编辑器导出一份 Web 即可(或 godot --headless --export-release "Web" output.htmlproject/export_presets.cfg)。

注意事项:

  • -O1 让 wasm 从 -O3 时的 26 MB 涨到 10 MB 左右(Release 去掉符号),是必要的代价。
  • leanclr_wasm_libcxx_shim.cpp 提供 Emscripten libc++ 缺的 std::__1::__hash_memory 符号,Web 链接时会自动包含。
  • em++: warning: ignoring unsupported linker flag: -soname 是无害的,-soname 是 ELF 链接器标志,emcc 不识别但 wasm 产物仍然正确。

几个常看的文件

src/ : GDExtension 和 bridge。

managed/GodotSharpCompat/ : 给 C# 用的 Godot API 外观。Generated/ 子目录是绑定生成器的产物,.gitignore 已忽略。

project/runtime_hot_reload_demo.tscn : 现在的主 demo。

project/scripts/HotReloadSmoke.cs : Flappy 的 C# 脚本。

project/scripts/RuntimeCSharpEditor.gd : 游戏里的 C# 编辑器窗口。

project/leanclr/live_reload.txt : 热重载 marker。写 Game 就回到默认 Game.dll,写别的 assembly 名就切到那个 dll。