Godot引擎集成Python扩展开发指南:从原理到实践

发布时间:2026/8/3 13:20:48

Godot引擎集成Python扩展开发指南:从原理到实践 1. 项目概述为什么要在Godot里集成Python如果你是一个从Python生态转战游戏开发的开发者或者你的游戏项目需要用到一些强大的Python库比如数据分析、机器学习、科学计算或者某些特定的网络协议库那么直接在Godot里调用Python代码会是一个极具吸引力的想法。Godot内置的GDScript虽然易学易用但在处理复杂算法、对接现有Python生态时就显得有些力不从心。这时为Godot开发一个Python扩展模块就成了一条打通两个世界的“高速公路”。这个项目的核心目标不是简单地用Python脚本替换GDScript而是创建一个能被Godot引擎原生识别、像其他GDScript或C#脚本一样可以附加到节点上、拥有完整生命周期回调的自定义模块。这意味着你可以在Godot编辑器中直接创建继承自PythonScript的脚本在_ready()、_process()里写Python代码并且能安全、高效地与Godot的节点树、信号、资源系统进行交互。我最初尝试这个方向是因为需要在一个模拟经营游戏里集成一个轻量级的机器学习模型来做NPC的行为预测。用纯GDScript重写模型推理既不现实效率也低。通过构建Python扩展我成功地将训练好的TensorFlow Lite模型封装成了一个Godot节点游戏运行时直接调用性能和数据流都处理得非常优雅。下面我就把从零开始构建一个可用、可靠的Godot Python扩展模块的全过程包括我踩过的坑和总结的最佳实践详细分享给你。2. 环境准备与工具链选型在动手写代码之前搭好一个稳固的开发环境是成功的一半。Godot的扩展开发主要依赖C和其自身的构建系统SCons而Python扩展则需要额外考虑Python的嵌入Embedding或扩展Extending问题。2.1 核心工具与版本锁定版本兼容性是第一个要跨过的坎。不同版本的Godot引擎其模块API可能有细微差别Python的C API也在不断演进。Godot版本我强烈建议使用Godot 4.x的稳定版本如4.2.1。4.x版本相比3.x在模块构建系统和GDExtension新的扩展方式上有了长足进步文档也更完善。本项目基于传统的“模块(Module)”方式它在4.x中依然被支持且对于深度集成更为合适。确保你从官网下载的是源码版本而非预编译的编辑器。Python版本选择Python 3.8到3.11之间的64位版本。太老的版本如3.6可能缺少某些API太新的版本如3.12可能与Godot的构建脚本存在未知兼容性问题。关键点务必使用“Development”安装选项。在Windows安装时勾选“Install for all users”和“Add Python to PATH”最重要的是要勾选“Download debug binaries”和“Install debug binaries”吗不最重要的是勾选“py launcher”和“Install for all users”吗不对对于开发扩展最重要的是在安装时或安装后确保你有python3x_d.lib调试库和python3x.lib发布库以及对应的头文件include文件夹。在Linux/macOS上通常需要安装python3-dev或python3-devel包。编译工具链WindowsVisual Studio 2019或2022并安装“使用C的桌面开发”工作负载。MSVC编译器是必须的。LinuxGCC或Clang以及scons构建工具。通过包管理器安装build-essential。macOSXcode Command Line Tools以及scons。注意绝对不要使用Anaconda或Miniconda环境中的Python来编译。这些发行版可能修改了库的链接方式和路径极易导致链接错误。使用从python.org下载的标准CPython发行版。2.2 项目目录结构规划清晰的目录结构能让后续的开发和维护事半功倍。假设你的Godot源码解压在了D:\godot-engine-4.2.1-stable我建议在它旁边创建一个专门的工作区。D:\godot-dev\ ├── godot-engine-4.2.1-stable\ # Godot源码 │ ├── modules\ # 官方和其他第三方模块 │ └── ... └── godot-python-module\ # 我们的Python扩展项目 ├── SCsub # SCons构建脚本 ├── config.py # 模块配置可选 ├── register_types.cpp # 类型注册入口 ├── register_types.h ├── py_script.cpp # 核心PythonScript类实现 ├── py_script.h ├── godot_python.h # 与Python C API交互的封装 ├── godot_python.cpp └── test_project\ # 用于测试的Godot项目 └── ...我们将把godot-python-module这个文件夹整个复制或软链接到Godot源码的modules目录下。这样Godot的SCons构建系统就能自动发现并编译它。3. 核心原理Godot模块与Python C API的桥梁要理解如何开发首先得明白Godot模块是如何工作的以及Python如何被嵌入到C应用中。3.1 Godot模块系统浅析Godot引擎是高度模块化的。每个模块都是一个独立的代码库在引擎启动时被动态加载或静态链接。一个标准的Godot模块需要提供几个关键函数initialize_module_name_module模块初始化时调用用于向Godot的核心类DB注册本模块提供的所有类。uninitialize_module_name_module模块卸载时调用用于清理资源。一系列继承自Godot核心类如Object,RefCounted,Node的C类。我们的目标就是创建一个名为PythonScript的类它继承自Godot的Script类。这样它就能被Godot的脚本语言系统管理可以被设置给任何Object派生类主要是Node。3.2 Python的嵌入Embedding模式我们不是在用C扩展Python而是将Python解释器嵌入到Godot这个C应用程序中。这意味着在Godot启动时或模块初始化时我们需要调用Py_Initialize()来启动Python解释器。我们可以用C API如PyRun_SimpleString,PyObject_CallObject来执行Python代码、调用Python函数、获取Python对象的值。我们需要管理Python对象与GodotVariant类型之间的转换。这是整个扩展中最复杂也最核心的部分。在Godot关闭时需要调用Py_Finalize()来安全关闭解释器。这种模式下Python代码运行在同一个进程内避免了进程间通信的开销但也要非常小心内存管理和全局解释器锁GIL的问题。3.3 数据类型转换Variant vs PyObjectGodot使用Variant作为所有动态类型的通用容器整数、浮点数、字符串、数组、字典、对象引用等。Python则使用PyObject*。让两者互通就需要一个转换层。Godot - Python当从GDScript调用一个Python脚本的方法并传入参数时我们需要将Variant参数转换为对应的Python对象。例如int-PyLong_FromLongfloat-PyFloat_FromDoubleString-PyUnicode_FromStringArray- 遍历并递归转换生成PythonlistDictionary- 遍历并递归转换生成PythondictObject*(Godot对象) - 这是一个难点。通常我们需要将其包装成一个自定义的Python类型这个类型内部持有一个Godot对象的弱引用或安全句柄。Python - Godot当Python方法返回一个值或者我们需要读取Python对象的属性给Godot时需要进行反向转换。PyLong-Variant(int)PyFloat-Variant(float)PyUnicode-Variant(String)PyList- 遍历生成GodotArrayPyDict- 遍历生成GodotDictionary自定义的Godot对象包装器 - 解包出内部的Object*转换为Variant实现一个健壮且完整的转换层是扩展稳定性的基石。初期可以只实现基本类型后续再逐步扩展。4. 分步实现从空模块到可运行脚本让我们从零开始一步步构建出这个扩展模块。4.1 第一步创建模块骨架在godot-python-module目录下首先创建register_types.h和register_types.cpp。这是模块的入口。register_types.h#ifndef PYTHON_MODULE_REGISTER_TYPES_H #define PYTHON_MODULE_REGISTER_TYPES_H void initialize_python_module_module(); void uninitialize_python_module_module(); #endif // PYTHON_MODULE_REGISTER_TYPES_Hregister_types.cpp#include register_types.h #include py_script.h // 我们即将创建的核心类 #include gdextension_interface.h #include godot_cpp/core/class_db.hpp #include godot_cpp/core/defs.hpp #include godot_cpp/godot.hpp using namespace godot; void initialize_python_module_module() { // 在这里注册我们模块提供的所有类 ClassDB::register_classPythonScript(); // 未来还可以注册其他类比如PythonNode等 GD.Print(Python module initialized.); } void uninitialize_python_module_module() { // 进行必要的清理工作例如释放Python解释器 GD.Print(Python module uninitialized.); } extern C { // GDExtension要求的入口点 GDExtensionBool GDE_EXPORT python_module_library_init( GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); init_obj.register_initializer(initialize_python_module_module); init_obj.register_terminator(uninitialize_python_module_module); // 设置初始化级别通常为MODULE init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_MODULE); return init_obj.init(); } }4.2 第二步定义PythonScript核心类创建py_script.h和py_script.cpp。这个类继承自Script是扩展的心脏。py_script.h#ifndef PY_SCRIPT_H #define PY_SCRIPT_H #include godot_cpp/classes/script.hpp #include godot_cpp/variant/variant.hpp #include godot_cpp/core/binder_common.hpp namespace godot { class PythonScript : public Script { GDCLASS(PythonScript, Script) private: String source_code; // 存储Python源代码 String script_path; // 脚本文件路径 // 这里将来会存储编译后的PyCodeObject指针或模块对象 protected: static void _bind_methods(); public: PythonScript(); ~PythonScript(); // 重写Script类的关键虚函数 virtual bool can_instantiate() const override; virtual RefScript get_base_script() const override; virtual StringName get_global_name() const override; virtual bool inherits_script(const RefScript p_script) const override; virtual StringName get_instance_base_type() const override; virtual ScriptLanguage *get_language() const override; // 加载和设置源代码 void set_source_code(const String p_code); String get_source_code() const; // 一个简单的测试方法用于验证Python执行 Variant execute_string(const String p_code); }; } // namespace godot #endif // PY_SCRIPT_Hpy_script.cpp (部分关键实现)#include py_script.h #include godot_python.h // 我们将在这里封装Python C API调用 #include godot_cpp/classes/global_constants.hpp #include godot_cpp/core/error_macros.hpp namespace godot { void PythonScript::_bind_methods() { ClassDB::bind_method(D_METHOD(set_source_code, code), PythonScript::set_source_code); ClassDB::bind_method(D_METHOD(get_source_code), PythonScript::get_source_code); ClassDB::bind_method(D_METHOD(execute_string, code), PythonScript::execute_string); ADD_PROPERTY(PropertyInfo(Variant::STRING, source_code), set_source_code, get_source_code); } PythonScript::PythonScript() { // 初始化成员变量 source_code ; } PythonScript::~PythonScript() { // 清理可能存在的Python资源 } bool PythonScript::can_instantiate() const { // 暂时返回true表示可以附加到节点 return true; } StringName PythonScript::get_instance_base_type() const { // 这个脚本默认可以附加到任何Object上但通常我们限定为Node // 返回“Node”会更准确 return Node; } void PythonScript::set_source_code(const String p_code) { if (source_code p_code) { return; } source_code p_code; // TODO: 这里应该触发Python代码的编译或重新加载 } String PythonScript::get_source_code() const { return source_code; } Variant PythonScript::execute_string(const String p_code) { // 这是我们的第一个突破口直接执行一段Python字符串并返回结果 // 调用我们封装好的Python工具函数 return GodotPython::execute_string(p_code); } // 其他必须重写的虚函数可以先返回默认值或空值 RefScript PythonScript::get_base_script() const { return RefScript(); } StringName PythonScript::get_global_name() const { return StringName(); } bool PythonScript::inherits_script(const RefScript p_script) const { return false; } ScriptLanguage *PythonScript::get_language() const { // 我们需要一个自定义的ScriptLanguage实例初期可以先返回nullptr后期实现 return nullptr; } } // namespace godot4.3 第三步封装Python C API交互层创建godot_python.h和godot_python.cpp。这个文件负责所有与Python解释器的直接交互管理GIL并进行类型转换。godot_python.h#ifndef GODOT_PYTHON_H #define GODOT_PYTHON_H #include godot_cpp/variant/variant.hpp #include godot_cpp/core/error_macros.hpp #include Python.h namespace godot { class GodotPython { public: // 初始化Python解释器应在模块初始化时调用 static bool initialize(); // 关闭Python解释器应在模块卸载时调用 static void finalize(); // 执行一段Python代码字符串并返回结果转换为Variant static Variant execute_string(const String p_code); // 工具函数Variant 转 PyObject* static PyObject* variant_to_pyobj(const Variant p_var); // 工具函数PyObject* 转 Variant static Variant pyobj_to_variant(PyObject *p_obj); private: static bool is_initialized; // 获取全局解释器锁(GIL)确保线程安全 static void ensure_gil(); static void release_gil(); }; } // namespace godot #endif // GODOT_PYTHON_Hgodot_python.cpp (核心实现)#include godot_python.h namespace godot { bool GodotPython::is_initialized false; bool GodotPython::initialize() { if (is_initialized) { return true; } Py_Initialize(); if (!Py_IsInitialized()) { ERR_FAIL_V_MSG(false, Failed to initialize Python interpreter.); } is_initialized true; GD.Print(Python interpreter initialized successfully.); return true; } void GodotPython::finalize() { if (is_initialized) { Py_Finalize(); is_initialized false; GD.Print(Python interpreter finalized.); } } Variant GodotPython::execute_string(const String p_code) { if (!is_initialized) { ERR_FAIL_V_MSG(Variant(), Python interpreter not initialized.); } // 确保我们持有GIL PyGILState_STATE gstate PyGILState_Ensure(); Variant ret; PyObject *main_module PyImport_AddModule(__main__); PyObject *global_dict PyModule_GetDict(main_module); PyObject *local_dict PyDict_New(); // 将Godot的print函数暴露给Python可选但非常有用 // 这里需要先实现一个C函数包装godot::print_line略复杂先跳过。 // 编译并执行代码 PyObject *py_code_obj Py_CompileString(p_code.utf8().get_data(), godot, Py_file_input); if (py_code_obj) { PyObject *temp PyEval_EvalCode(py_code_obj, global_dict, local_dict); Py_DECREF(py_code_obj); if (temp) { // 执行成功但通常模块级代码返回None // 我们可以尝试获取最后一个表达式的值这很复杂。 // 简单起见我们返回一个表示执行成功的布尔值。 ret true; Py_DECREF(temp); } else { // 执行出错获取错误信息 PyObject *type, *value, *traceback; PyErr_Fetch(type, value, traceback); if (value) { PyObject *str_obj PyObject_Str(value); if (str_obj) { const char *err_msg PyUnicode_AsUTF8(str_obj); ERR_PRINT(String(Python execution error: ) err_msg); Py_DECREF(str_obj); } } PyErr_Restore(type, value, traceback); PyErr_Print(); // 打印到标准错误 ret false; } } else { PyErr_Print(); ERR_PRINT(Failed to compile Python code.); ret false; } Py_DECREF(local_dict); PyGILState_Release(gstate); return ret; } // 以下是类型转换的简化实现仅示例基本类型 PyObject* GodotPython::variant_to_pyobj(const Variant p_var) { switch (p_var.get_type()) { case Variant::NIL: Py_RETURN_NONE; case Variant::BOOL: return PyBool_FromLong((int64_t)p_var); case Variant::INT: return PyLong_FromLong((int64_t)p_var); case Variant::FLOAT: return PyFloat_FromDouble((double)p_var); case Variant::STRING: { String s p_var; return PyUnicode_FromString(s.utf8().get_data()); } // 数组、字典等复杂类型需要递归处理此处省略... default: // 对于无法直接转换的类型返回None或抛出错 WARN_PRINT(String(Unsupported Variant type for Python conversion: ) Variant::get_type_name(p_var.get_type())); Py_RETURN_NONE; } } Variant GodotPython::pyobj_to_variant(PyObject *p_obj) { if (p_obj nullptr || p_obj Py_None) { return Variant(); } if (PyBool_Check(p_obj)) { return Variant(p_obj Py_True); } if (PyLong_Check(p_obj)) { return Variant((int64_t)PyLong_AsLongLong(p_obj)); } if (PyFloat_Check(p_obj)) { return Variant(PyFloat_AsDouble(p_obj)); } if (PyUnicode_Check(p_obj)) { const char *c_str PyUnicode_AsUTF8(p_obj); return Variant(String(c_str)); } // 处理列表、元组、字典等... 此处省略 WARN_PRINT(Unsupported Python object type for Variant conversion.); return Variant(); } } // namespace godot4.4 第四步编写SCons构建脚本创建SCsub文件告诉SCons如何编译我们的模块。# SCsub Import(env) # 添加模块源码 srcs [ register_types.cpp, py_script.cpp, godot_python.cpp, ] # 添加包含路径 # 假设Python安装在标准位置这里需要根据你的系统调整 # Windows示例路径可能需要修改 python_prefix ARGUMENTS.get(python_prefix, C:/Python311) python_include python_prefix /include python_libs python_prefix /libs # 将模块添加到构建环境 env_thirdparty env.Clone() env_thirdparty.add_source_files(env.modules_sources, srcs) # 添加Python头文件路径 env_thirdparty.Append(CPPPATH[python_include]) env_thirdparty.Append(LIBPATH[python_libs]) # 链接Python库 # 注意库名debug版本可能叫python311_d python_lib_name python311 if env[target] debug: python_lib_name _d # 检查你的Python安装是否有调试库 env_thirdparty.Append(LIBS[python_lib_name])4.5 第五步集成到Godot并编译将整个godot-python-module文件夹复制到Godot源码的modules目录下。打开终端或VS Developer Command Prompt导航到Godot源码根目录。执行SCons编译命令。关键点需要指定python_prefix参数。Windows (VS2022):scons platformwindows targeteditor dev_buildyes -j8 python_prefixC:/Python311Linux:scons platformlinux targeteditor dev_buildyes -j8 python_prefix/usrmacOS:scons platformmacos targeteditor dev_buildyes -j8 python_prefix/usr/local/Frameworks/Python.framework/Versions/3.11dev_buildyes会启用更多调试信息便于排查问题。-j8表示使用8个线程并行编译根据你的CPU核心数调整。编译成功后会在bin目录下生成godot.platform.tools.target如godot.windows.tools.editor.exe。这就是集成了我们Python模块的Godot编辑器。4.6 第六步在Godot编辑器中测试运行编译好的自定义Godot编辑器。创建一个新项目或打开测试项目。在资源管理器里右键 -创建新资源。如果你在register_types.cpp中正确注册了PythonScript你应该能在列表里找到它可能叫PythonScript或Python Script。创建一个PythonScript资源选中它在检查器面板里找到source_code属性输入一段简单的Python代码比如print(Hello from Python inside Godot!) for i in range(5): print(fCounting: {i})创建一个简单的Node在其检查器面板的Script属性中选择你刚创建的PythonScript资源。运行场景。如果一切顺利你暂时还看不到输出因为我们还没有将Python的print重定向到Godot的输出面板。但是如果代码有语法错误我们的execute_string方法会通过ERR_PRINT将错误信息打印到Godot编辑器底部“输出”面板的“错误”页签。实操心得第一次编译十有八九会失败最常见的问题是找不到Python头文件或库。仔细检查SCsub中的python_prefix路径是否正确以及python_lib_name是否与你的Python版本匹配python311, python310等。在Windows上确保链接的是python311.lib而不是python3.lib。5. 进阶实现让Python脚本“活”起来上面的步骤只是让Godot认识了一个可以执行字符串的PythonScript类。一个真正可用的脚本模块还需要实现以下关键功能5.1 实现ScriptLanguage子类要让Godot将.py文件识别为一种脚本语言并支持在编辑器中创建、编辑、继承必须创建一个继承自ScriptLanguage的类例如PythonLanguage。这是一个庞大的工程需要实现数十个虚函数包括get_name(): 返回语言名称如“Python”。init()/finish(): 语言初始化和清理。get_template(): 提供新脚本的模板代码。validate(): 验证脚本语法。complete_code(): 代码补全。find_function(): 查找函数定义。最重要的是get_global_class_name()和can_inherit_from_file()这关系到资源继承系统。对于初期原型可以暂时不完整实现ScriptLanguage而是让PythonScript的get_language()返回nullptr。但这会限制功能例如无法通过“创建脚本”菜单直接创建.py文件。5.2 实现脚本实例与方法调用PythonScript继承自Script它需要管理脚本实例。当脚本被附加到一个Godot对象如Node时应该编译源代码将Python源代码字符串编译成PyCodeObject或者更常见的是将其作为一个模块加载使用PyImport_ExecCodeModule。创建脚本实例在Python端这个“实例”可以是一个模块字典或者一个自定义类的对象。我们需要在Godot的ScriptInstance对象和Python对象之间建立映射。处理方法调用当GDScript或引擎调用该脚本实例的某个方法如_ready时PythonScript需要找到对应的Python函数通过属性查找getattr并使用转换后的参数调用它最后将返回值转换回Variant。处理属性访问类似地需要拦截对脚本属性的获取get和设置set操作并将其映射到Python对象的属性上。这涉及到实现ScriptInstance类。一个简化的流程是在PythonScript中为每个附加它的Godot对象创建一个对应的、弱引用的Python字典作为命名空间。当调用该对象的方法时就在那个字典的上下文中执行相应的Python函数。5.3 信号与属性导出Godot强大的信号Signal和导出属性Export系统也需要对接。信号可以在Python脚本中定义Godot信号吗一种思路是在PythonScript的_bind_methods中预定义一些信号然后提供Python函数来emit它们。更高级的做法是允许在Python代码中使用装饰器或特定语法来声明信号然后在编译/加载时动态地添加到Godot类中。导出属性同样困难。需要在Python源码中解析特殊注释如# export var speed: float然后在Godot端通过_get_property_list等方法动态添加这些属性。这需要实现一个Python源码解析器可以是简单的基于正则表达式也可以集成ast模块进行静态分析。6. 常见问题、调试技巧与性能优化在开发过程中你肯定会遇到各种奇怪的问题。以下是我总结的一些常见坑点和解决思路。6.1 编译与链接问题问题现象可能原因解决方案fatal error C1083: Cannot open include file: Python.hSCons找不到Python头文件。检查SCsub中python_include路径。确保路径中存在Python.h。在Windows上路径通常是C:\Python311\include。LNK2019: unresolved external symbol __imp__Py_Initialize链接器找不到Python库。检查SCsub中python_libs路径和python_lib_name。确保链接的是正确的库文件如python311.lib。在Windows上Debug构建可能需要链接python311_d.lib。undefined reference toPy_Initialize (Linux/macOS)类似Windows的链接错误。确保LIBS列表里包含了正确的Python库名如-lpython3.11。使用pkg-config --libs python3.11来获取正确的链接参数并整合到SCsub中。编译成功但编辑器启动崩溃Python运行库版本不匹配或路径错误。确保Godot编辑器运行时能找到对应的Python DLLWindows或SOLinux。可以将Python安装目录添加到系统PATH或者将必要的DLL复制到Godot编辑器同级目录。调试技巧在godot_python.cpp的initialize()函数开始和结束处添加GD.Print。如果连开始的信息都没打印说明模块根本没被加载检查register_types.cpp的初始化函数是否被正确调用。如果打印了开始但没打印成功说明Py_Initialize()失败了可能是Python环境损坏。6.2 运行时崩溃与内存错误这是嵌入Python时最头疼的问题多与引用计数和GIL有关。引用计数错误Python使用引用计数管理内存。Py_INCREF和Py_DECREF必须成对出现。一个常见错误是将Python函数返回的“借用引用”borrowed reference当作“新引用”new reference来处理导致过早DECREF或漏掉INCREF。黄金法则除了明确说明返回新引用的API如PyLong_FromLong其他返回PyObject*的API如PyDict_GetItemString通常都是借用引用如果你需要长期持有它必须Py_INCREF它。全局解释器锁GILPython解释器不是线程安全的。任何调用Python C API的代码如果可能从非创建Python解释器的线程调用必须先获取GILPyGILState_Ensure并在调用结束后释放PyGILState_Release。Godot的部分回调如_process可能发生在其他线程如果不加锁100%会导致随机崩溃。Godot对象生命周期Python中持有对Godot对象的引用时必须使用Ref或弱引用。如果Godot对象先于Python包装器被销毁而Python代码还试图访问它就会访问野指针导致崩溃。建议在Python端使用Godot提供的WeakRef对象。6.3 性能优化考量避免频繁的C/Python边界穿越每次调用Python函数、获取/设置属性都有开销。对于在_process中每帧都要调用的逻辑应尽量在C侧完成或者将批量操作打包在一次Python调用中。缓存Python对象不要每次调用都通过PyObject_GetAttrString查找函数。在脚本初始化时就将常用的函数对象如_ready,_process查找出来并缓存为PyObject*。使用PyPy或C扩展CPython在数值计算上较慢。如果扩展的核心是密集计算可以考虑将计算核心用C/C写成Godot模块Python只做胶水。使用numpy等已高度优化的C扩展库。确保你的嵌入环境能正确导入这些库。理论上可以嵌入PyPy但集成复杂度极高不推荐。6.4 让Python的print输出到Godot编辑器这是一个提升开发体验的重要功能。我们需要重定向Python的sys.stdout和sys.stderr。// 在godot_python.cpp中新增一个函数 static PyObject* godot_print(PyObject* self, PyObject* args) { const char* msg nullptr; if (!PyArg_ParseTuple(args, s, msg)) { return nullptr; } // 使用Godot的打印函数可以带颜色等信息 Godot::print(msg); // 也可以打印到编辑器输出面板 // UtilityFunctions::print(msg); Py_RETURN_NONE; } static PyMethodDef GodotPythonMethods[] { {print, godot_print, METH_VARARGS, Print to Godots output.}, {nullptr, nullptr, 0, nullptr} }; static struct PyModuleDef GodotPythonModule { PyModuleDef_HEAD_INIT, godot_output, nullptr, -1, GodotPythonMethods }; PyMODINIT_FUNC PyInit_godot_output(void) { return PyModule_Create(GodotPythonModule); } // 在initialize()函数中初始化解释器后添加 PyImport_AppendInittab(godot_output, PyInit_godot_output); // 然后执行一段Python代码将我们的print替换sys.stdout const char* redirect_code import sys\n import godot_output\n class GodotWriter:\n def write(self, msg):\n godot_output.print(msg)\n def flush(self):\n pass\n sys.stdout GodotWriter()\n sys.stderr GodotWriter()\n; PyRun_SimpleString(redirect_code);这样Python脚本中的print(“Hello”)就会显示在Godot编辑器的“输出”面板中了。开发Godot的Python扩展是一个深入引擎内部和Python解释器的旅程充满了挑战但一旦打通它将为你的游戏开发打开一扇新的大门。从最简单的字符串执行开始逐步实现类型转换、方法调用、信号连接最终形成一个功能完备的脚本系统。这个过程需要耐心调试和对两个系统内存模型的深刻理解。我建议从一个非常具体、有限的目标开始比如“在Godot中调用一个Python函数计算斐波那契数列”成功后再逐步扩展而不是试图一开始就构建一个完整的脚本语言集成。

相关新闻