如何用C++作遊戲(2)

上一節講了一些基本的Lua應用,或許你會說,仍是很簡單麼。呵呵,恩,是的,原本Lua就是爲了讓你們使用的方便快捷而設計的。若是設計的過爲複雜,就不會有人使用了。linux

下面,我要強調一下,Lua的棧的一些概念,由於這個確實很重要,你會常常用到。熟練使用Lua,最重要的就是要時刻知道何時棧裏面的數據是什麼順序,都是什麼。若是你能熟練知道這些,實際你已是Lua運用的高手了。程序員

說 真的,第一次我接觸棧的時候,沒有把它想的很複雜,卻是看了網上不少的關於Lua的文章讓我對棧的理解雲裏霧裏,什麼元表,什麼User,什麼局部變量, 什麼全局變量位移。說的那叫一個暈。本人腦子笨,理解不了這麼多,也不知道爲何不少人喜歡把Lua棧弄的七上八下,代碼晦澀難懂。後來實在受不了了,去 Lua網站下載了Lua的文檔,寫的很清晰。Lua的棧實際上幾句話足以。windows

當你初始化一個棧的時候,它的棧底是1,而棧頂相對位置是-1,說形 象一些,你能夠把棧想象成一個環,有一個指針標記當前位置,若是-1,就是當前棧頂,若是是-2就是當前棧頂前面一個參數的位置。以此類推。安全

固然,你也可 以正序去取,這裏要注意,對於Lua的不少API,下標是從1開始的。這個和C++有些不一樣。並且,在棧的下標中,正數表示絕對棧底的下標,負數表示相對 棧頂的相對地址,這個必定要有清晰的概念,不然很容易看暈了。 讓咱們看一些例子,加深理解。多線程

lua_pushnumber(m_pState, 11); 
lua_pushnumber(m_pState, 12);

int nIn = lua_gettop(m_pState);  <–這裏加了一行, lua_gettop()這個API是告訴你目前棧裏元素的個數。 
若是僅僅是Push兩個參數,那麼nIn的數值是2,對。沒錯。那麼我們看看棧裏面是怎麼放的。我再加兩行代碼。

lua_pushnumber(m_pState, 11); 
lua_pushnumber(m_pState, 12);

int nIn = lua_gettop(m_pState)

int nData1 = lua_tonumber(m_pState, 1);     <–讀取棧底第一個絕對座標中的元素 
int nData2 = lua_tonumber(m_pState, 2);     <–讀取棧底第二個絕對座標中的元素 
printf(「[Test]nData1  = %d, nData2  = %d./n」);

若是是你,憑直覺,告訴我答案是什麼?函數

如今公佈答案,看看是否是和你想的同樣。工具

[Test]nData1  = 11, nData2  = 12

那麼,若是我把代碼換成網站

lua_pushnumber(m_pState, 11); 
lua_pushnumber(m_pState, 12);

int nIn = lua_gettop(m_pState)

int nData1 = lua_tonumber(m_pState, -1);     <–讀取棧頂第一個相對座標中的元素 
int nData2 = lua_tonumber(m_pState, -2);     <–讀取棧頂第二個相對座標中的元素 
printf(「[Test]nData1  = %d, nData2  = %d./n」);

請你告訴我輸出是什麼? 答案是lua

[Test]nData1  = 12, nData2  = 11

呵呵,挺簡單的吧,對了,其實就這麼簡單。網上其它的高階運用,其實大部分都是對棧的位置進行調整。只要你抓住主要概念,看懂仍是不難的。什麼元表,什麼變量,其實都同樣,抓住核心,時刻知道棧裏面的樣子,就沒有問題。spa

好了,回到我上一節的那個代碼。

bool CLuaFn::CallFileFn(const char* pFunctionName, int nParam1, int nParam2) 
{ 
        int nRet = 0; 
        if(NULL == m_pState) 
        { 
                printf(「[CLuaFn::CallFileFn]m_pState is NULL./n」); 
                return false; 
        }

        lua_getglobal(m_pState, pFunctionName);

        lua_pushnumber(m_pState, nParam1); 
        lua_pushnumber(m_pState, nParam2);

        int nIn = lua_gettop(m_pState); <–在這裏加一行。

        nRet = lua_pcall(m_pState, 2, 1, 0); 
        if (nRet != 0) 
        { 
                printf(「[CLuaFn::CallFileFn]call function(%s) error(%d)./n」, pFunctionName, nRet); 
                return false; 
        }

        if (lua_isnumber(m_pState, -1) == 1) 
        { 
                int nSum = lua_tonumber(m_pState, -1); 
                printf(「[CLuaFn::CallFileFn]Sum = %d./n」, nSum); 
        }

        int nOut = lua_gettop(m_pState); <–在這裏加一行。

        return true; 
}

nIn的答案是多少?或許你會說是2吧,呵呵,實際是3。或許你會問,爲何會多一個?其實我第一次看到這個數字,也很詫異。可是確實是3。由於你 調用的函數名稱佔據了一個堆棧的位置。其實,在獲取nIn那一刻,堆棧的樣子是這樣的(函數接口地址,參數1,參數2),函數名稱也是一個變量入棧的。

而 nOut輸出是1,lua_pcall()函數在調用成功以後,會自動的清空棧,而後把結果放入棧中。在獲取nOut的一刻,棧內是這幅摸樣(輸出參數 1)。

這裏就要再遷出一個更重要的概念了,Lua不是C++,對於C++程序員而言,一個函數會自動建立棧,當函數執行完畢後會自動清理 棧,Lua可不會給你這麼作,對於Lua而言,它沒有函數這個概念,一個棧對應一個lua_State指針,也就是說,你必須手動去清理你不用的棧,不然 會形成垃圾數據佔據你的內存。

不信?那麼我們來驗證一下,就拿昨天的代碼吧,你用for循環調用100萬次。看看nOut的輸出結果。。我相信,程序執行不到100萬次就會崩潰,而你的內存也會變的碩大無比。而nOut的輸出也會是這樣的 1,2,3,4,5,6。。。。。 緣由就是,Lua不會清除你之前棧內的數據,每調用一次都會給你生成一個新的棧元素插入其中。

那麼怎麼解決呢?呵呵,其實,若是不考慮多線程的話,在你的函數最後退出前加一句話,就能夠輕鬆解決這個問題。(Lua棧操做是非線程安全的!)

lua_settop(m_pState, -2);

這句話的意思是什麼?lua_settop()是設置棧頂的位置,我這麼寫,意思就是,棧頂指針目前在當前位置的-2的元素上。這樣,我就實現了對棧的清除。仔細想一下,是否是這個道理呢?

bool CLuaFn::CallFileFn(const char* pFunctionName, int nParam1, int nParam2) 
{ 
        int nRet = 0; 
        if(NULL == m_pState) 
        { 
                printf(「[CLuaFn::CallFileFn]m_pState is NULL./n」); 
                return false; 
        }

        lua_getglobal(m_pState, pFunctionName);

        lua_pushnumber(m_pState, nParam1); 
        lua_pushnumber(m_pState, nParam2);

        int nIn = lua_gettop(m_pState); <–在這裏加一行。

        nRet = lua_pcall(m_pState, 2, 1, 0); 
        if (nRet != 0) 
        { 
                printf(「[CLuaFn::CallFileFn]call function(%s) error(%d)./n」, pFunctionName, nRet); 
                return false; 
        }

        if (lua_isnumber(m_pState, -1) == 1) 
        { 
                int nSum = lua_tonumber(m_pState, -1); 
                printf(「[CLuaFn::CallFileFn]Sum = %d./n」, nSum); 
        }

        int nOut = lua_gettop(m_pState); <–在這裏加一行。 
        lua_settop(m_pState, -2);             <–清除不用的棧。

        return true; 
}

好了,再讓咱們運行100萬次,看看你的程序內存,看看你的程序還崩潰不? 若是你想打印 nOut的話,輸出會變成1,1,1,1,1。。。。

最後說一句,lua_tonumber()或lua_tostring()還有之後咱們要用到的lua_touserdata()必定要將數據徹底取出後保存到你的別的變量中去,不然會由於清棧操做,致使你的程序異常,切記!

呵呵,說了這麼多,主要是讓你們如何寫一個嚴謹的Lua程序,不要運行沒兩下就崩潰了。好了,基礎棧的知識先說到這裏,之後還有一些技巧的運用,到時候會給你們展現。

下面說一下,Lua的工具。(爲何要說這個呢?呵呵,由於咱們下一步要用到其中的一個幫助咱們的開發。)

呵呵,其實,Lua裏面有不少簡化開發的工具,你能夠去http://www.sourceforge.net/去找一下。它們可以幫助你簡化C++對象與Lua對象互轉之間的代碼。

這裏說幾個有名的,固然可能不全。

(lua tinker)若是你的系統在windows下,並且不考慮移植,那麼我強烈推薦你去下載一個叫作lua tinker的小工具,整個工具很是簡單,一個.h和一個.cpp。直接就能夠引用到你的工程中,連獨立編譯都不用,這是一個韓國人寫的Lua與 C++接口轉換的類,十分方便,代碼簡潔(居家旅行,必備良藥)。

它是基於模板的,因此你能夠很輕鬆的把你的C對象綁定到Lua中。代碼較長,呵呵, 有興趣的朋友能夠給我留言索要lua tinker的例子。就不貼在這裏了。不過我我的不推薦這個東西,由於它在Linux下是編譯不過去的。它使用了一種g不支持的模板寫法,雖然有人在 嘗試把它修改到Linux下編譯,但據我所知,修改後效果較好的彷佛尚未。不過若是你只是在 windows下,那就沒什麼可猶豫的,強烈推薦,你會喜歡它的。

(Luabinder)相信用過Boost庫的朋友,或許對這個傢伙很熟悉。它是一個很強大的Linux下Lua擴展包,幫你封裝了不少Lua的復 雜操做,主要解決了綁定C++對象和Lua對象互動的關係,很是強大,不過嘛,對於freeeyes而言,仍是不推薦,由於freeeyes很懶,不想爲 了一個Lua還要去編譯一個龐大的boost庫,固然,見仁見智,若是你的程序自己就已經加載了boost,那麼就應該堅決果斷的選擇它。

(lua++)呵呵,這是我最喜歡,也是我一直用到如今的庫,比較前兩個而言,lua++的封裝性沒有那麼好,不少東西仍是須要一點代碼的,不過之 因此我喜歡,是由於它是用C寫的,能夠在windows下和linux下輕鬆轉換

還記得我昨天說過如何編譯Lua麼,如今請你再作一遍,不一樣的是,請把lua的程序包中的src/lib中的全部h和cpp,還有 include下的那個.h拷貝到你上次創建的lua工程中。而後所有添加到你的靜態連接庫工程中去,從新編譯。會生成一個新的lua.lib,這個 lua就自動包含了lua的功能。最後記得把tolua++.h放在你的Include文件夾下。 行了,咱們把上次CLuaFn類稍微改一下。

extern 「C」 
{ 
        #include 「lua.h」 
        #include 「lualib.h」 
        #include 「lauxlib.h」 
        #include 「tolua++」   //這裏加一行 
};

class CLuaFn 
{ 
public: 
        CLuaFn(void); 
        ~CLuaFn(void);

        void Init();            //初始化Lua對象指針參數 
        void Close();         //關閉Lua對象指針

        bool LoadLuaFile(const char* pFileName);                              //加載指定的Lua文件 
        bool CallFileFn(const char* pFunctionName, int nParam1, int nParam2);        //執行指定Lua文件中的函數

private: 
        lua_State* m_pState;   //這個是Lua的State對象指針,你能夠一個lua文件對應一個。 
};

行了,這樣咱們就能用Lua++下的功能了。

你們看到了 bool CallFileFn(const char* pFunctionName, int nParam1, int nParam2);這個函數的運用。演示了真麼調用Lua函數。

下面,我改一下,這個函數。爲何?仍是由於freeeyes很懶,我可不想每有一個函數,我都要寫一個C++函數去調用,太累!我要寫一個通用的!支持任意函數調用的接口!

因而我建立了兩個類。支持任意參數的輸入和輸出,並打包送給lua去執行,說幹就幹。

#ifndef _PARAMDATA_H 
#define _PARAMDATA_H

#include <vector>

#define MAX_PARAM_200 200

using namespace std;

struct _ParamData 
{ 
public: 
        void* m_pParam; 
        char  m_szType[MAX_PARAM_200]; 
        int   m_TypeLen;

public: 
        _ParamData() 
        { 
                m_pParam    = NULL; 
                m_szType[0] = ‘/0′; 
                m_TypeLen   = 0; 
        };

        _ParamData(void* pParam, const char* szType, int nTypeLen) 
        { 
                SetParam(pParam, szType, nTypeLen); 
        }

        ~_ParamData() {};

        void SetParam(void* pParam, const char* szType, int nTypeLen) 
        { 
                m_pParam = pParam; 
                sprintf(m_szType, 「%s」, szType); 
                m_TypeLen = nTypeLen; 
        };

        bool SetData(void* pParam, int nLen) 
        { 
                if(m_TypeLen < nLen) 
                { 
                        return false; 
                }

                if(nLen > 0) 
                { 
                        memcpy(m_pParam, pParam, nLen); 
                } 
                else 
                { 
                        memcpy(m_pParam, pParam, m_TypeLen); 
                } 
                return true; 
        }

        void* GetParam() 
        { 
                return m_pParam; 
        }

        const char* GetType() 
        { 
                return m_szType; 
        }

        bool CompareType(const char* pType) 
        { 
                if(0 == strcmp(m_szType, pType)) 
                { 
                        return true; 
                } 
                else 
                { 
                        return false; 
                } 
        } 
};

class CParamGroup 
{ 
public: 
        CParamGroup() {}; 
        ~CParamGroup() 
        { 
                Close(); 
        };

        void Init() 
        { 
                m_vecParamData.clear(); 
        };

        void Close() 
        { 
                for(int i = 0; i < (int)m_vecParamData.size(); i++) 
                { 
                        _ParamData* pParamData = m_vecParamData; 
                        delete pParamData; 
                        pParamData = NULL; 
                } 
                m_vecParamData.clear(); 
        };

        void Push(_ParamData* pParam) 
        { 
                if(pParam != NULL) 
                { 
                        m_vecParamData.push_back(pParam); 
                } 
        };

        _ParamData* GetParam(int nIndex) 
        { 
                if(nIndex < (int)m_vecParamData.size()) 
                { 
                        return m_vecParamData[nIndex]; 
                } 
                else 
                { 
                        return NULL; 
                } 
        };

        int GetCount() 
        { 
                return (int)m_vecParamData.size(); 
        }

private: 
        typedef vector<_ParamData*> vecParamData; 
        vecParamData m_vecParamData; 
};

#endif

#endif

我建立了兩個類,把Lua要用到的類型,數據都封裝起來了。這樣,我只須要這麼改寫這個函數。 bool CallFileFn(const char* pFunctionName, CParamGroup& ParamIn, CParamGroup& ParamOut); 它就能按照不一樣的參數自動給我調用,嘿嘿,懶到家吧!

其實這兩個類很簡單,_ParamData是參數類,把你要用到的參數放入到這個對象中去,標明類型的大小,類型名稱,內存塊。而CParamGroup負責將不少不少的_ParamData打包在一塊兒,放在vector裏面。

好了,讓咱們看看CallFileFn函數裏面我怎麼改的。

bool CLuaFn::CallFileFn(const char* pFunctionName, CParamGroup& ParamIn, CParamGroup& ParamOut) 
{ 
        int nRet = 0; 
        int i    = 0; 
        if(NULL == m_pState) 
        { 
                printf(「[CLuaFn::CallFileFn]m_pState is NULL./n」); 
                return false; 
        }

        lua_getglobal(m_pState, pFunctionName);

        //加載輸入參數 
        for(i = 0; i < ParamIn.GetCount(); i++) 
        { 
                PushLuaData(m_pState, ParamIn.GetParam(i)); 
        }

        nRet = lua_pcall(m_pState, ParamIn.GetCount(), ParamOut.GetCount(), 0); 
        if (nRet != 0) 
        { 
                printf(「[CLuaFn::CallFileFn]call function(%s) error(%s)./n」, pFunctionName, lua_tostring(m_pState, -1)); 
                return false; 
        }

        //得到輸出參數 
        int nPos = 0; 
        for(i = ParamOut.GetCount() – 1; i >= 0; i–) 
        { 
                nPos–; 
                PopLuaData(m_pState, ParamOut.GetParam(i), nPos); 
        }

        int nCount = lua_gettop(m_pState); 
        lua_settop(m_pState, -1-ParamOut.GetCount());

        return true; 
}

別的沒變,加了兩個循環,由於考慮lua是能夠支持多結果返回的,因此我也作了一個循環接受參數。

lua_settop(m_pState, -1-ParamOut.GetCount());這句話是否是有些意思,恩,是的,我這裏作了一個小技巧,由於我不知道返回參數有幾個,因此我會根據返回參數的個數從新設置棧頂。這樣作能夠返回任意數量的棧並且清除乾淨。

或許細心的你已經發現,裏面多了兩個函數。恩,是的。來看看這兩個函數在幹什麼。

bool CLuaFn::PushLuaData(lua_State* pState, _ParamData* pParam) 
{ 
        if(pParam == NULL) 
        { 
                return false; 
        }

        if(pParam->CompareType(「string」)) 
        { 
                lua_pushstring(m_pState, (char* )pParam->GetParam()); 
                return true; 
        }

        if(pParam->CompareType(「int」)) 
        { 
                int* nData = (int* )pParam->GetParam(); 
                lua_pushnumber(m_pState, *nData); 
                return true; 
        } 
        else 
        { 
                void* pVoid = pParam->GetParam(); 
                tolua_pushusertype(m_pState, pVoid, pParam->GetType()); 
                return true; 
        } 
}

參數入棧操做,呵呵,或許你會問tolua_pushusertype(m_pState, pVoid, pParam->GetType());這句話,你可能有些看不懂,不要緊,我會在下一講詳細的解釋Lua++的一些API的用法。

如今大概和你說 一下,這句話的意思就是,把一個C++對象傳輸給Lua函數。 再看看,下面一個。

bool CLuaFn:: PopLuaData(lua_State* pState, _ParamData* pParam, int nIndex) 
{ 
        if(pParam == NULL) 
        { 
                return false; 
        }

        if(pParam->CompareType(「string」)) 
        { 
                if (lua_isstring(m_pState, nIndex) == 1) 
                { 
                        const char* pData = (const char*)lua_tostring(m_pState, nIndex); 
                        pParam->SetData((void* )pData, (int)strlen(pData)); 
                } 
                return true; 
        }

        if(pParam->CompareType(「int」)) 
        { 
                if (lua_isnumber(m_pState, nIndex) == 1) 
                { 
                        int nData = (int)lua_tonumber(m_pState, nIndex); 
                        pParam->SetData(&nData, sizeof(int)); 
                } 
                return true; 
        } 
        else 
        { 
                pParam->SetData(tolua_tousertype(m_pState, nIndex, NULL), -1); 
                return true; 
        } 
}

彈出一個參數並賦值。pParam->SetData(tolua_tousertype(m_pState, nIndex, NULL), -1);這句話一樣,我在下一講中詳細介紹。

好了,咱們又進了一步,咱們能夠用這個函數綁定任意一個Lua函數格式。而代碼不用多寫,懶蛋的目的達到了。

這一講主要是介紹了一些基本知識,或許有點多餘,可是我以爲是必要的,在下一講中,我講開始詳細介紹如何綁定一個C++對象給Lua,並讓Lua對其修改。而後返回結果。

小夥伴們,還請持續關注更新,更多幹貨和資料請直接聯繫我,也能夠加羣710520381,邀請碼:柳貓,歡迎你們共同討論

相關文章
相關標籤/搜索