天天看點

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的互動通常無外乎就以下這三種情況

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腳本化

上圖中c和lua通過一個虛拟的棧(全局的還有函數内部局部的)來實作互相的通路。

其中

這三點都可以在上圖中找到相關的實作方法,但是這些方法都是為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);

--成員函數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

繼續閱讀