## 17.1 Sol2与C++交互 是一个Lua绑定库,用于将C++的类、函数、数据映射到Lua中,这样就可以在Lua中创建C++实例、访问C++实例、调用C++函数。 也对Lua数据进行封装提供一系列接口给C++,在C++中访问Lua数据也变得很方便。 作为一个全能的绑定库,你想要的它都有支持。 本小节通过对 提供的一系列特性,来学习介绍 与C++交互逻辑。 1.项目介绍 项目将之前章节的 - 架构提取出来,用作测试,并创建了 、 两个组件。 另外实现了Lua Component逻辑,并创建了 这个Lua组件。 这一小节的项目,其实就是引擎的基础架构,在这个基础上,集成 ,实现Lua Component。 2.特性介绍 介绍对类、函数、数据的绑定。 2.
CLion项目文件位于 samples/integrate_lua/integrate_lua/lua_call_cpp_sol2_component
sol2是一个Lua绑定库,用于将C++的类、函数、数据映射到Lua中,这样就可以在Lua中创建C++实例、访问C++实例、调用C++函数。
也对Lua数据进行封装提供一系列接口给C++,在C++中访问Lua数据也变得很方便。
Github:https://github.com/ThePhD/sol2 文档:https://sol2.readthedocs.io/en/latest/
sol2作为一个全能的绑定库,你想要的它都有支持。
本小节通过对sol2提供的一系列特性,来学习介绍sol2与C++交互逻辑。
项目将之前章节的GameObject-Component架构提取出来,用作测试,并创建了Animator、Camera两个组件。
另外实现了Lua Component逻辑,并创建了LoginScene 这个Lua组件。
这一小节的项目,其实就是引擎的基础架构,在这个基础上,集成sol2,实现Lua Component。

介绍对类、函数、数据的绑定。
下面是绑定GameObject的代码:
//绑定 GameObject { sol_state.new_usertype<GameObject>("GameObject",sol::call_constructor,sol::constructors<GameObject()>(), "AddComponent", &GameObject::AddComponentFromLua, "GetComponent",&GameObject::GetComponentFromLua, sol::meta_function::equal_to,&GameObject::operator== ); }
上述代码执行后,就可以在lua中编写下面的代码:
---创建GameObject实例 local game_object=GameObject() --调用GameObject函数 local component=game_object:AddComponent("Camera")
sol2提供两种构造函数方式,直接带() ,或者使用.new()
默认就是.new()
sol_state.new_usertype<Player>("Player",sol::constructors<Player(),Player(int)>());
这样注册后,在lua中就可以调用.new()实例化cpp对象。
local player=Player.new()
为了和实例化cpp对象代码保持一致,实例化lua对象也通过.new() 函数来做。
setmetatable(LoginScene,{["new"]=function(table,param) local instance=setmetatable({},{__index=table}) return instance end})
主动指定构造函数为sol::call_constructor,使用__call metafunction。
sol_state.new_usertype<Player>("Player",sol::call_constructor,sol::constructors<Player(),Player(int)>())
这样注册后,在lua中就可以直接用()实例化cpp对象。
local player=Player()
为了和实例化cpp对象代码保持一致,实例化lua对象通过__call函数来做。
setmetatable(LoginScene,{["__call"]=function(table,param) local instance=setmetatable({},{__index=table}) return instance end})
对于有继承关系的类,则需要先绑定基类,然后再绑定子类。
参考Camera的注册代码:
//绑定 Component { sol_state.new_usertype<Component>("Component",sol::call_constructor,sol::constructors<Component()>(), "Awake",&Component::Awake, "Update",&Component::Update, "game_object",&Component::game_object, "set_game_object",&Component::set_game_object ); } //绑定 Camera { sol_state.new_usertype<Camera>("Camera",sol::call_constructor,sol::constructors<Camera()>(), sol::base_classes,sol::bases<Component>(), "position",&Camera::position, "set_position",&Camera::set_position ); }
子类中是不用绑定基类函数的。
如果需要大量的操作符重载,那可以参考glm::vec3的绑定过程,代码如下:
auto glm_ns_table = sol_state["glm"].get_or_create<sol::table>(); glm_ns_table.new_usertype<glm::vec3>("vec3",sol::call_constructor,sol::constructors<glm::vec3(const float&, const float&, const float&)>(), "x", &glm::vec3::x, "y", &glm::vec3::y, "z", &glm::vec3::z, "r", &glm::vec3::r, "g", &glm::vec3::g, "b", &glm::vec3::b, sol::meta_function::to_string,[] (const glm::vec3* vec) -> std::string {return glm::to_string(*vec);}, sol::meta_function::addition,[] (const glm::vec3* vec_a,const glm::vec3* vec_b) {return (*vec_a)+(*vec_b);}, sol::meta_function::subtraction,[] (const glm::vec3* vec_a,const glm::vec3* vec_b) {return (*vec_a)-(*vec_b);}, sol::meta_function::multiplication,[] (const glm::vec3* vec,const float a) {return (*vec)*a;}, sol::meta_function::division,[] (const glm::vec3* vec,const float a) {return (*vec)/a;}, sol::meta_function::unary_minus,[] (const glm::vec3* vec) {return (*vec)*-1;}, sol::meta_function::equal_to,[] (const glm::vec3* vec_a,const glm::vec3* vec_b) {return (*vec_a)==(*vec_b);} );
上述代码中,以sol::meta_function::为前缀的,就是Lua提供的操作符重载元方法,只要对其赋值即可实现操作符重载。
上述代码执行后,就可以在lua中编写下面的代码:
print("glm.vec3(4,5,6)+glm.vec3(4,5,6): " .. tostring(glm.vec3(4,5,6)+glm.vec3(4,5,6))) print("glm.vec3(4,5,6)-glm.vec3(4,5,6): " .. tostring(glm.vec3(4,5,6)-glm.vec3(4,5,6))) print("glm.vec3(4,5,6)*3: " .. tostring(glm.vec3(4,5,6)*3)) print("glm.vec3(4,5,6)/3: " .. tostring(glm.vec3(4,5,6)/3))
简单的直接绑定,例如:
GameObject* game_object_; void CompareGameObject(GameObject* a,GameObject* b){ std::cout<<"CompareGameObject a==b: "<<(a==b)<<std::endl; }
对应的绑定代码如下:
//绑定普通函数 { sol_state.set_function("CompareGameObject", &CompareGameObject); }
上述代码执行后,就可以在lua中编写下面的代码:
print("----------- simple function ------------") local go=GameObject() CompareGameObject(go,go)
对付复杂函数的绑定,需要函数重载的,可以参考对 glm 提供的API的绑定。
auto glm_ns_table = sol_state["glm"].get_or_create<sol::table>(); glm_ns_table.set_function("rotate",sol::overload([] (const glm::mat4* m,const float f,const glm::vec3* v) {return glm::rotate(*m,f,*v);})); glm_ns_table.set_function("radians",sol::overload([] (const float f) {return glm::radians(f);})); glm_ns_table.set_function("to_string",sol::overload( [] (const glm::mat4* m) {return glm::to_string((*m));}, [] (const glm::vec3* v) {return glm::to_string((*v));} ));
绑定常量直接设置到lua即可,下面代码用table模拟了NameSpace。
const int const_value=12; auto test_ns_table = sol_state["Test"].get_or_create<sol::table>(); test_ns_table.set("const_value",const_value);
有以下枚举:
typedef enum KeyAction{ UP=0, DOWN=1, REPEAT=2 }KeyAction;
下面函数返回了枚举中的一项:
KeyAction GetKeyActionUp(){ return KeyAction::UP; }
如果想在lua中调用GetKeyActionUp,那么需要注册这个函数,以及返回的枚举。
下面代码注册枚举类型到Lua:
sol_state.new_enum<KeyAction,true>("KeyAction",{ {"UP",KeyAction::UP}, {"DOWN",KeyAction::DOWN} });
然后注册函数:
sol_state.set_function("GetKeyActionUp", &GetKeyActionUp);
上述代码执行后,就可以在lua中编写下面的代码:
print(GetKeyActionUp())
上述代码执行后,就可以在lua中编写下面的代码:
print(GetKeyActionUp()) print(KeyAction.UP) print(GetKeyActionUp()==KeyAction.UP)
以调用main.lua中的main()为例:
//调用lua main() sol::protected_function main_function=sol_state["main"]; result=main_function(); if(result.valid()== false){ sol::error err = result; std::cerr << "----- RUN LUA ERROR ----" << std::endl; std::cerr << err.what() << std::endl; std::cerr << "------------------------" << std::endl; }
以Lua组件LoginScene为例:
--file:example/login_scene.lua line:1 LoginScene={ game_object_ } function LoginScene:game_object() return self.game_object_ end function LoginScene:set_game_object(game_object) self.game_object_=game_object end function LoginScene:Awake() print("LoginScene Awake") end function LoginScene:Update() print("LoginScene Update") end setmetatable(LoginScene,{["__call"]=function(table,param) local instance=setmetatable({},{__index=table}) return instance end})
在GameObject::AddComponentFromLua根据传入的组件名,创建组件实例并添加,然后调用组件成员函数。
//file:source/game_object.h line:80 /// 根据传入的组件名,创建组件实例 /// \param component_type_name /// \return sol::table AddComponentFromLua(std::string component_type_name) { sol::protected_function component_type_construct_function=sol_state[component_type_name];//对c++的class注册为table,并实现了__call,所以可以直接带括号。 auto result=component_type_construct_function(); if(result.valid()== false){ sol::error err = result; std::cerr << "----- RUN LUA ERROR ----" << std::endl; std::cerr <<"AddComponentFromLua call type construct error,type:"<<component_type_name<< std::endl; std::cerr << err.what() << std::endl; std::cerr << "------------------------" << std::endl; } sol::table new_table=result; result=new_table["set_game_object"](new_table,this); if(result.valid()== false){ sol::error err = result; std::cerr << "----- RUN LUA ERROR ----" << std::endl; std::cerr <<"AddComponentFromLua call set_game_object error,type:"<<component_type_name<< std::endl; std::cerr << err.what() << std::endl; std::cerr << "------------------------" << std::endl; } ...... return new_table; }
sol_state[component_type_name]以组件名获取到全局Table后,直接以括号调用就可以创建实例对象new_table。
然后以new_table["set_game_object"]就可以获取到成员函数set_game_object的引用。
然后直接调用函数即可。
记得要传入self,即要传入new_table。