LeanCLR Godot
浅尝一下 LeanCLR 跑 Godot 4 脚本。 (目前是娱乐向,请勿用于生产环境,建议等官方绑定)
原版不带Mono Godot加载GDExtension,C# 代码交给 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.exe,Godot 4.6.3。
已知补丁
thirdparty/leanclr 子模块
src/runtime/build_config.h:LEANCLR_FATAL_ON_RAISE_NOT_IMPLEMENTED_ERROR设为0(见上文)。
src/leanclr_script.cpp
godot-cpp 4.5 把 gdextension_interface_script_instance_create3 从 gdextension_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.txt(Web 构建)
仓库里已经为 Web 构建持久化地打了两个补丁,都在 if(EMSCRIPTEN) 块(或紧邻它的 option 块)里:
-
强制单线程(Web 当前不支持多线程,且
-sSIDE_MODULE + pthreads在 emcc 里是实验性的):if(EMSCRIPTEN) set(GODOTCPP_THREADS OFF CACHE BOOL "godot-cpp threading support" FORCE) endif() -
降到
-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_godottarget 在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.cmake。leanclr.gdextension已经配了web.release.wasm32 = "res://bin/Release/leanclr_godot.wasm",构建产物会自动落到project/bin/Release/libleanclr_godot.wasm。要跑起来用编辑器导出一份 Web 即可(或godot --headless --export-release "Web" output.html配project/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。