HEngine
  • 引擎编译
  • 未来工作
  • 使用文档
    • 代码规范
    • Shader规范
    • config 配置
  • Python脚本系统
    • 技术分析
    • 使用api
  • Cpp脚本系统
    • 技术分析
  • 技术文档 & 心得体会
    • 数学
      • 坐标系、UV与深度范围
      • 行主序 与 列主序
    • 路径管理
    • 构建系统
    • 自定义资源格式
    • UI
      • undo/redo
      • 分辨率处理
    • ECS 系统
    • 插件系统
    • 数学库
    • Ref Counted
    • 音频
    • ChatGPT
    • Mesh、Vertex 等组织关系
    • 编辑器热更新方案
    • Profile
    • NLP
    • RenderDoc
    • CodeGen与反射系统
    • 安装包
    • 物理引擎
      • PhysX集成
      • Bullet集成
    • 动画
  • 图形后端
    • 坐标系差异
    • Feature差异
    • RHI 封装
    • Shader与Constant Buffer
    • DX12
    • Vulkan
    • Render Graph
    • 渲染整体架构
  • 图形Feature
    • 毛发
    • 鼠标拾取
    • 实例化渲染
  • Shader
    • Shader 交叉编译
    • Shader Toy
    • Shader 热更新
    • PBR
  • 打包
    • 打包
  • Bug 记录 & 解决过程
    • 闪退记录汇总
    • 导入图片显示混乱
    • D3D下glfw+imgui失效
  • 其他资料
    • 总结集合
Powered by GitBook
On this page
  • import 路径
  • 代码绑定
  • Stub
  • 接口设计
  • HEngine模块设计(前面代码绑定的补充)
  • 脚本设计
  • pybind11
  • 模板函数
  • 根据字符串获取component
  • Component自动绑定
  1. Python脚本系统

技术分析

Previousconfig 配置Next使用api

Last updated 2 years ago

import 路径

最后测试,我们要用 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 里面再去包含这个头文件。

Stub

项目使用 pybind11,想要生成 stub 代码提示出来,可以参考:

https://github.com/sizmailov/pybind11-stubgen

https://github.com/robotpy/robotpy-build/issues/1#issuecomment-663871072

接口设计

HEngine模块设计(前面代码绑定的补充)

我们希望把所有功能全部封装在HEngine模块中,例如 Input 等都是作为其子模块存在,并且为了不用dll的形式把C++编写的模块给python调用,我们使用 PYBIND11_EMBEDDED_MODULE 直接给 python 获取模块,而 Input 等则在其中定义子模块(Pybind11 不允许两个模块同时存在,我们一个模块必须全部定义完),因此我改成了在别的头文件使用函数,在HEngine模块的声明中调用此函数,如:

// in CoreBinding.h
PYBIND11_EMBEDDED_MODULE(HEngine, HEngineModule)
{
    py::class_<Timestep>(HEngineModule, "Timestep")
        .def(py::init<float>())
        .def("GetSeconds", &Timestep::GetSeconds);

    InputBinding(HEngineModule);
    LogBinding(HEngineModule);
}

上面的代码在 CoreBinding.h 中,这个头文件又会 include 其他的头文件如 InputBinding.h,而具体的 InputBinding 函数则为:

// in InputBinding.h
static void InputBinding(py::module_& HEngineModule)
{
    py::module_ InputModule = HEngineModule.def_submodule("Input");
    InputModule.def("IsKeyPressed", &Input::IsKeyPressed);
    ...
}

脚本设计

# in MoveRight.py
import numpy as np
import HEngine

class Props():
    step = 5.0
    ignore__ = 111
    handsome = "hbh"

class MoveRight():
    def __init__(self) -> None:
        pass

    def Start():
        return
    
    def Update(ts):
        return

    def Stop():
        return

    def UpdateInEditor(ts):
        return

Props 全局类里面定义的变量会暴露在编辑器中,可以在编辑器设置值,并最终序列化保存到文件中(Entity的PythonScriptComponent里面),反序列化加载的时候再设定回来。

类名 MoveRight 必须和文件名一致,这是模仿了unity(mono)的设计,这样我们就可以在引擎层根据文件名(这会作为路径索引保存在 PythonScriptComponent 中)去获取到类名,从而实例化这个类出来,并调用其方法。

Start 和 Stop 函数分别在游戏开始和结束时调用一次,Update 每帧调用一次,特别地我设计了一个 UpdateInEditor 接口,PythonScriptComponent 有一个 static 的 UseEditor 的 bool 变量来作为是否开启这个函数的开关,方便我们直接在编辑器下调试。

pybind11

模板函数

因为模板的两阶段编译,我们必须实例化出来才能传给python,可以如下这样使用重载:

根据字符串获取component

返回类型是各个component,而使用模板作为返回值,我们无法推断模板参数是什么(注意我们是要根据字符串获取这个返回值,例如 GetComponent<Transform>,他的返回值就应该是一个 TransformComponent。

因此可以用 std::variant ,但是 variant 不能使用引用,这是因为引用必须要有初始化,而 variant 根本不知道怎么初始化!因此可以用指针来模拟,最终写了这样的代码:

py::class_<Entity>(HEngineModule, "Entity")
    .def(py::init())
    .def("GetComponent", [](Entity& entity, const std::string& compName) -> std::variant<std::reference_wrapper<TransformComponent>, std::reference_wrapper<PythonScriptComponent>> {
        if (compName == "Transform")
            return entity.GetComponent<TransformComponent>(); 
        else if (compName == "PythonScript")
            return entity.GetComponent<PythonScriptComponent>();
        return entity.GetComponent<TransformComponent>();
        });

Component自动绑定

https://github.com/hebohang/HEngineDev/commit/bdf54859d8ee7a17a67e9598dd6dec6ebfbd44ae

codegen出来所需的 PyBinding 方法,但是目前的问题是,要检测是否 codegen 需要根据类名判断,类名包含 Component 表示这是一个组件,因而生成对应方法。

当然这样子每个类都需要有此方法的声明,可以写到宏里面:

最后简单几句代码就能实现完全的所有的 Component 的绑定: