欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

如何用C++做游戏

程序员文章站 2022-03-08 23:05:16
上一节讲了一些基本的lua应用,或许你会说,还是很简单么。呵呵,恩,是的,本来lua就是为了让大家使用的方便快捷而设计的。如果设计的过为复杂,就不会有人使用了。 下面,我要强调一下,lua的栈的一些...

上一节讲了一些基本的lua应用,或许你会说,还是很简单么。呵呵,恩,是的,本来lua就是为了让大家使用的方便快捷而设计的。如果设计的过为复杂,就不会有人使用了。

下面,我要强调一下,lua的栈的一些概念,因为这个确实很重要,你会经常用到。熟练使用lua,最重要的就是要时刻知道什么时候栈里面的数据是什么顺序,都是什么。如果你能熟练知道这些,实际你已经是lua运用的高手了。

说 真的,第一次我接触栈的时候,没有把它想的很复杂,倒是看了网上很多的关于lua的文章让我对栈的理解云里雾里,什么元表,什么user,什么局部变量, 什么全局变量位移。说的那叫一个晕。本人脑子笨,理解不了这么多,也不知道为什么很多人喜欢把lua栈弄的七上八下,代码晦涩难懂。后来实在受不了了,去 lua网站下载了lua的文档,写的很清晰。lua的栈实际上几句话足以。

当你初始化一个栈的时候,它的栈底是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”);

请你告诉我输出是什么? 答案是

[test]ndata1  = 12, ndata2  = 11

呵呵,挺简单的吧,对了,其实就这么简单。网上其它的高阶运用,其实大部分都是对栈的位置进行调整。只要你抓住主要概念,看懂还是不难的。什么元表,什么变量,其实都一样,抓住核心,时刻知道栈里面的样子,就没有问题。

好了,回到我上一节的那个代码。

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里面有很多简化开发的工具,你可以去https://www.sourceforge.net/去找一下。它们能够帮助你简化c++对象与lua对象互转之间的代码。

这里说几个有名的,当然可能不全。

(lua tinker)如果你的在windows下,而且不考虑移植,那么我强烈推荐你去下载一个叫做lua tinker的小工具,整个工具非常简单,一个.h和一个.cpp。直接就可以引用到你的工程中,连独立编译都不用,这是一个韩国人写的lua与 c++接口转换的类,十分方便,代码简洁(居家旅行,必备良药)。

它是基于模板的,所以你可以很轻松的把你的c不支持的模板写法,虽然有人在 尝试把它修改到linux下编译,但据我所知,修改后效果较好的似乎还没有。不过如果你只是在 windows下,那就没什么可犹豫的,强烈推荐,你会喜欢它的。

(luabinder)相信用过boost库的朋友,或许对这个家伙很熟悉。它是一个很强大的linux下lua扩展包,帮你封装了很多lua的复 杂操作,主要解决了绑定c++对象和lua对象互动的关系,非常强大,不过嘛,对于freeeyes而言,还是不推荐,因为freeeyes很懒,不想为 了一个lua还要去编译一个庞大的boost库,当然,见仁见智,如果你的程序本身就已经加载了boost,那么就应该毫不犹豫的选择它。

(lua++)呵呵,这是我最喜欢,也是我一直用到现在的库,比较前两个而言,lua++的封装性没有那么好,很多东西还是需要一点代码的,不过之 所以我喜欢,是因为它是用c写的,可以在windows下和linux下轻松转换

还记得我昨天说过如何编译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 

#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对其修改。然后返回结果。