C++ 對象的Lua腳本化
腳本化編程
腳本化編程的最大好處就是簡單靈活,另外就是熱更新,這在網遊中廣泛被采用,在網遊中,通常采用引擎(c/C++)+腳本(lua/python)的架構,那種SDK性質的代碼放在引擎中,這些代碼在遊戲上線後通常很穩定很少被修改,而真正遊戲邏輯的製作就都在腳本層中進行。這樣有兩個好處:1.腳本層的bug基本不會導致程序的crash,因為是沙盒的。2.對於運行的代碼,可以方便的采用熱更新修複bug。
C與lua的交互
而如果想在腳本層編寫邏輯代碼,一個最重要的就是需要將引擎層即C++中的對象、函數、全局變量等等暴露給腳本層來訪問,當然有時也要能讓C++層能夠方便的訪問腳本層的全局變量。最近稍微仔細的探索了一下C++與Lua之間交互的知識,稍作總結,分享給需要的人。
在C中可以寫代碼訪問調用lua(函數、變量..),同時也可以在C中寫代碼讓所lua裏麵能訪問c。c和lua的交互通常無外乎就以下這三種情況
1.這種情況C是啟動程序,然後調用lua裏的變量
2.這種情況C被編成庫程序,啟動程序是lua(通常是你的lua解釋器lua.exe),在lua中可以使用c的變量
3.這種情況C是啟動程序,然後調用lua裏的變量,然後調用的lua裏麵又能訪問c的變量。
無論以上哪種,情況,做到C和lua交互的代碼都是在c中寫的,這些代碼被稱為lua的C API。我們要利用C API做三件事:
1.執行某個LUA腳本
2.獲取某個LUA的全局變量
2.將C變量或函數注冊成指定的規格,使得在LUA中可以調用注冊的那些C變量和函數。
如果能完成者三件事,那麼就可以完成上圖的所有這些途徑。
將C API的常用功能和交互的API以圖形化的方法繪製出來:
上圖中C和lua通過一個虛擬的棧(全局的還有函數內部局部的)來實現互相的訪問。
其中
1.執行某個LUA腳本
2.獲取某個LUA的全局變量
2.將C變量或函數注冊成指定的規格,使得在LUA中可以調用注冊的那些C變量和函數。
這三點都可以在上圖中找到相關的實現方法,但是這些方法都是為C寫的,對於C++的引擎來說,我們通常想實現一種在腳本層的麵向對象的訪問,通俗來說,比如C++層有個類C,C有個借口 Get(),我們希望在腳本層可以方便的寫出c= newClassC(),c:Get()這樣的代碼,這就是本文探討的C++對象的LUa腳本化,這在C++引擎的腳本化編程中非常重要。
C++ 對象的Lua腳本化
假設有一個類MyCClass 我在C++層實現了它的一些方法,如SetI(int)、 GetI()等,我想將這個類腳本化給Lua層能麵向對象的訪問。
下麵寫下我的一些簡要實現
--New :
在C++中定義函數int MyCClass::NewMyCClass( lua_State* L ) ,這個函數在lua中可以使用c=NewMyCClass()來生成一個MyCClass的對象
NewMyCClass 具體實現是:
int MyCClass::NewMyCClass( lua_State* L )
{
//創建usrdata實例並傳到棧中
size_t bytes=sizeof(MyCClass);
MyCClass* c=(MyCClass*)lua_newuserdata(L,bytes);
//為usrdata創建元表以實現麵向對象的方法,這裏給c這個對象加入一個元表,原表中對GetI這個index賦予MyCClass::GetI這個函數
int r=luaL_newmetatable(L,"MyCClassMeta");
lua_pushvalue(L,-1);//copy --index table
lua_pushcfunction(L,MyCClass::GetI);
lua_setfield(L,-2,"GetI");//reg func
lua_setfield(L,-2,"__index");//set __index table
lua_setmetatable(L,-2);//-2?
return 1;
}
當然在程序開始處要注冊這個NewMyCClass
lua_register(L,"NewMyCClass",MyCClass::NewMyCClass);
--成員函數GetI:可以在lua層 c:GetI()
int MyCClass::GetI( lua_State* L )
{
MyCClass* c=(MyCClass*)lua_touserdata(L,1);
int r=c->GetI()
lua_pushinteger(L,r);
return 1;
}
--成員函數SetI 可以在lua層 c:SetI(100)
int MyCClass::SetI( lua_State* L )
{
MyCClass* c=(MyCClass*)(lua_touserdata(L,1));
int p1=luaL_checkint(L,2);
int r=c->SetI(p1)
return 0;
}
這樣一個非常簡單的在lua層麵向對象的訪問C++的實現就好了。
優化
1.完整的版本在元表的賦值中可能還有加入MyCClass::Del以實現c:Del()來刪除 但是有些架構所有的析構操作可能都在引擎層完成,並不暴露給腳本層,(甚至new操作也是)
2.上麵的代碼對於在c++中寫的每個類都要相應的寫出來一遍這些函數,如對於MyCClass::SetI(int i)你就要寫一個供lua調用版本的static int MyCClass::SetI( lua_State* L ),因為給lua調用函數永遠是這種類型,且需要是全局的。在實際框架的搭建中,我們通常采用宏函數的方法,如定義一個宏 REG_CLASS_FUNC(class,class_func)來按一定規則自動產生這個lua調用版的函數。相應的也可以通過定義一個宏函數REG_CLASS來自動注冊一個類的lua_register(L,"NewMyCClass",MyCClass::NewMyCClass)給lua
最後更新:2017-04-03 22:30:57