技术分析
Last updated
Last updated
最后测试,我们要用 dll 的形式而非现存的解释器,需要经过以下操作:
cmake这样设置就不会用解释器了,但是需要我们使用(我们使用的是python3.11版本):python311.lib python311.dll 添加python的include路径以让pybind可以include LIb/encodings(放在和exe同一路径)
这是因为,pybind会首先添加搜索路径exe的路径,而py的搜索路径为sys.path,sys是解释器内包含的。
而py初始化需要encodings模块,所以我们和exe放一起以便py找得到。encodings里面是各种py的内建类。
我们本来想参考这个库:
https://github.com/trevday/eternal2d-engine
但是使用 PYBIND11_EMBEDDED_MODULE 单独在 cpp 中,调用py的时候却不能 import,并没有生效。
所以最后改成头文件的形式,PythonScriptSystem 里面再去包含这个头文件。
项目使用 pybind11,想要生成 stub 代码提示出来,可以参考:
https://github.com/sizmailov/pybind11-stubgen
https://github.com/robotpy/robotpy-build/issues/1#issuecomment-663871072
我们希望把所有功能全部封装在HEngine模块中,例如 Input 等都是作为其子模块存在,并且为了不用dll的形式把C++编写的模块给python调用,我们使用 PYBIND11_EMBEDDED_MODULE 直接给 python 获取模块,而 Input 等则在其中定义子模块(Pybind11 不允许两个模块同时存在,我们一个模块必须全部定义完),因此我改成了在别的头文件使用函数,在HEngine模块的声明中调用此函数,如:
上面的代码在 CoreBinding.h 中,这个头文件又会 include 其他的头文件如 InputBinding.h,而具体的 InputBinding 函数则为:
Props 全局类里面定义的变量会暴露在编辑器中,可以在编辑器设置值,并最终序列化保存到文件中(Entity的PythonScriptComponent里面),反序列化加载的时候再设定回来。
类名 MoveRight 必须和文件名一致,这是模仿了unity(mono)的设计,这样我们就可以在引擎层根据文件名(这会作为路径索引保存在 PythonScriptComponent 中)去获取到类名,从而实例化这个类出来,并调用其方法。
Start 和 Stop 函数分别在游戏开始和结束时调用一次,Update 每帧调用一次,特别地我设计了一个 UpdateInEditor 接口,PythonScriptComponent 有一个 static 的 UseEditor 的 bool 变量来作为是否开启这个函数的开关,方便我们直接在编辑器下调试。
因为模板的两阶段编译,我们必须实例化出来才能传给python,可以如下这样使用重载:
返回类型是各个component,而使用模板作为返回值,我们无法推断模板参数是什么(注意我们是要根据字符串获取这个返回值,例如 GetComponent<Transform>,他的返回值就应该是一个 TransformComponent。
因此可以用 std::variant ,但是 variant 不能使用引用,这是因为引用必须要有初始化,而 variant 根本不知道怎么初始化!因此可以用指针来模拟,最终写了这样的代码:
https://github.com/hebohang/HEngineDev/commit/bdf54859d8ee7a17a67e9598dd6dec6ebfbd44ae
codegen出来所需的 PyBinding 方法,但是目前的问题是,要检测是否 codegen 需要根据类名判断,类名包含 Component 表示这是一个组件,因而生成对应方法。
当然这样子每个类都需要有此方法的声明,可以写到宏里面:
最后简单几句代码就能实现完全的所有的 Component 的绑定: