腳本化程式設計
腳本化程式設計的最大好處就是簡單靈活,另外就是熱更新,這在網遊中廣泛被采用,在網遊中,通常采用引擎(c/c++)+腳本(lua/python)的架構,那種sdk性質的代碼放在引擎中,這些代碼在遊戲上線後通常很穩定很少被修改,而真正遊戲邏輯的制作就都在腳本層中進行。這樣有兩個好處:1.腳本層的bug基本不會導緻程式的crash,因為是沙盒的。2.對于運作的代碼,可以友善的采用熱更新修複bug。
c與lua的互動
而如果想在腳本層編寫邏輯代碼,一個最重要的就是需要将引擎層即c++中的對象、函數、全局變量等等暴露給腳本層來通路,當然有時也要能讓c++層能夠友善的通路腳本層的全局變量。最近稍微仔細的探索了一下c++與lua之間互動的知識,稍作總結,分享給需要的人。
在c中可以寫代碼通路調用lua(函數、變量..),同時也可以在c中寫代碼讓所lua裡面能通路c。c和lua的互動通常無外乎就以下這三種情況
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuEzNxgzX3QjM3YzMyYzMx8CX0AzLcNDMzEDMy8CXzRWYvxGc19CX0Vmbu4GZzNmL51mLn1Wavw1LcpDc0RHaiojIsJye.png)
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寫的,對于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