如何用C++做游戏(1)

现在,越来越多的C++服务器和客户端融入了脚本的支持,尤其在网游领域,脚本语言已经渗透到了方方面面,比如你可以在你的客户端增加一个脚本,这个脚本将会帮你在界面上显示新的数据,亦或帮你完成某些任务,亦或帮你查看别的玩家或者NPC的状态。。。如此等等。

但是我觉得,其实脚本语言与(C++)的结合,远远比你在游戏中看到的特效要来的迅猛。它可以运用到方方面面的领域,比如你最常见的应用领域。

比如,你 可以用文本编辑器,写一个脚本语言,然后用你的程序加载一下,就会产生出很绚丽的界面。亦或一两句文本语言,就会让你的程序发送数据给服务器,是不是很酷 呢?

Lua脚本

Lua语言,想必不少程序员都听过,据我所知,由于《魔兽世界》里面对它的加载,它一下子变成了很多游戏开发者竞相研究的对象,至于这个巴西创造者么,我不过多介绍,网上有很多关于lua的教材和例子,说真的,对于当年的我而言,几乎看不懂,当时很郁闷,感觉Lua复杂的要命,有些惧怕,后来沉下心来一点点研究,觉得其实还是蛮简洁的。只是网上的资料或许偏向于某些功能,导致了逻辑和代码的复杂。后来总结,其实学习一种脚本语言,完全可以抱着放松的心态一 点点的研究,反而效果会更好。

我们来看看怎么写一个简单的lua程序吧。

建立一个文件,起名Sample.lua
里面添加这样的代码。

function func_Add(x, y) 
   return x+y; 
end

这是一个标准的lua语法,一个函数,实现简单的a+b操作,并返回操作结果。
保存退出。
多一句嘴,在Lua里面,是可以支持多数据返回的。
比如你这么写:

function func_Add(x, y) 
   return x+y, x-y; 
end

意思是返回第一个参数是相加的结果,第二个是相减的结果,也是可以的。在lua里面没有类型的概念。当然,在C++接受这样的返回值的时候,也很简单,请往下看。

好了,材料齐备了,咱们来看看C++程序怎么调用它。
首先,建立一个类,负责加载这个lua文件,并执行函数操作,我们姑且叫做CLuaFn
要加载这个lua文件,按照正常的思路,我们应该先加载,然后再调用不同的函数。恩,对了,咱们就这么做。

extern “C” 
{ 
        #include “lua.h” 
        #include “lualib.h” 
        #include “lauxlib.h” 
};

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文件对应一个。 
}

恩,头文件就这么多,看看,一点也不复杂吧,看了cpp我想你会更高兴,因为代码一样很少。我一个个函数给你们介绍。

void CLuaFn::Init() 
{ 
        if(NULL == m_pState) 
        { 
                m_pState = lua_open(); 
                luaL_openlibs(m_pState); 
        } 
}

初始化函数,标准代码,没啥好说的,lua_open()是返回给你一个lua对象指针,luaL_openlibs()是一个好东西,在 lua4,初始化要做一大堆的代码,比如加载lua的string库,io库,math库等等等等,代码洋洋洒洒一大堆,其实都是不必要的,因为这些库你 基本都需要用到,除了练习你的打字能力别的意义不大,因为代码写法都是固定的。于是在5以后,Lua的创造者修改了很多,这就是其一,一句话帮你加载了所 有你可能用到的Lua基本库。

void CLuaFn::Close() 
{ 
        if(NULL != m_pState) 
        { 
                lua_close(m_pState); 
                m_pState = NULL; 
        } 
}

顾名思义,我用完了,关闭我的Lua对象并释放资源。呵呵,标准写法,没啥好说的。

bool CLuaFn:: LoadLuaFile(const char* pFileName) 
{ 
        int nRet = 0; 
        if(NULL == m_pState) 
        { 
                printf(“[CLuaFn:: LoadLuaFile]m_pState is NULL./n”); 
                return false; 
        }

        nRet = luaL_dofile(m_pState, pFileName); 
        if (nRet != 0) 
        { 
                printf(“[CLuaFn:: LoadLuaFile]luaL_loadfile(%s) is file(%d)(%s)./n”, pFileName, nRet, lua_tostring(m_pState, -1)); 
                return false; 
        }

        return true; 
}

加载一个Lua文件

这里我要详细的说一下,因为Lua是脚本语言,加载lua文件本身的时候才会编译。

所 以,推荐大家在加载文件的时候尽量放在程序的初始化中,因为当你执行luaL_dofile()函数的时候,Lua会启用语法分析器,去分析你的脚本语法 是否符合Lua规则,如果你胡乱的传一个文件过去,Lua就会告诉你文件语法错误,无法加载。如果你的Lua脚本很大,函数很多,语法分析器会比较耗时, 所以,加载的时候,尽量放在合适的地方,而且,对于一个Lua文件而言,反复加载luaL_dofile()除了会使你的CPU变热没有任何意义。

或许你对

printf(“[CLuaFn:: LoadLuaFile]luaL_loadfile(%s) is file(%d)(%s)./n”, pFileName, nRet, lua_tostring(m_pState, -1))

这句话很感兴趣,这个在干什么?这里我先说

lua_tostring(m_pState, -1)

这是在干什么,还记得我说的Lua是基于栈传输数据的么?那么,如果报错,我怎么知道错误是什么?luaL_dofile标准返回一个int,我总 不能到lua.h里面遍历这个nRet 是啥意思吧,恩,Lua创造者早就为你想好了,只不过你需要稍微动一下你的脑筋。Lua的创造者在语法分析器分析你的语法的时候,发现错误,会有一段文字 告诉你是什么错误,它会把这个字符串放在栈顶。那么,怎么取得栈顶的字符串呢?lua_tostring(m_pState, -1)就可以,-1代表的是当前栈的位置是相对栈顶。当然,你也可以看看栈里面还有一些什么其他古怪的数据,你可以用1,2,3(这些是绝对位置,而-1 是相对位置)去尝试,呵呵。不过,相信你得到的也很难看懂,因为一个Lua对象执行的时候,会用很多次栈进行数据交换,而你看到的,有可能是交换中的数 据。那么,话说回来,这句话的意思就是”[CLuaFn:: LoadLuaFile]luaL_loadfile(文件名) is file(错误编号)(错误具体描述文字)./n”

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);

        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); 
        }

        return true; 
}

这个函数是,传入函数名称和参数,去你的Lua文件中去执行。

lua_getglobal(m_pState, pFunctionName);

这个函数是验证你的Lua函数是否在你当前加载的Lua文件中,并把指针指向这个函数位置。

lua_pushnumber(m_pState, nParam1);   <—对应你的x参数 
lua_pushnumber(m_pState, nParam2);   <—对应你的y参数

这就是著名的压栈操作了,把你的参数压入Lua的数据栈。供Lua语法器去获得你的数据。
lua_pushnumber()是一个压入数字,lua_pushstring()是压入一个字符串。。。

那么你会问,如果我有一个自己的类型,一个类指针或者别的什么,我怎么压入?别着急,方法当然是有的,呵呵,不过你先看看如果简单的如何做,在下几讲中,我会告诉你更强大的Lua压栈艺术。
这里需要注意的是,压栈的顺序,对,简单说,就是从左到右的参数,左边的先进栈,右边的最后进栈。

nRet = lua_pcall(m_pState, 2, 1, 0);
这句话的意思是,执行这个函数,2是输入参数的个数,1是输出参数的个数。当然,如果你把Lua函数改成
return x+y, x-y;
代码需要改成nRet = lua_pcall(m_pState, 2, 2, 0);
明白了吧,呵呵,很简单吧。
当然,如果函数执行失败,会触发nRet,我这里偷了个懒,如果你想得到为什么错了?可以用lua_tostring(m_pState, -1)去栈顶找,明白?是不是有点感觉了?

lua_isnumber(m_pState, -1)

这句话是判定栈顶的元素是不是数字。因为如果执行成功,栈顶就应该是你的数据返回值。

int nSum = lua_tonumber(m_pState, -1); 
printf(“[CLuaFn::CallFileFn]Sum = %d./n”, nSum);

这个nSum就是返回的结果。
当然,你会问,如果 return x+y, x-y;我该怎么办?

int nSum = lua_tonumber(m_pState, -1); 
int nSub = lua_tonumber(m_pState, -2);

搞定,看见没。按照压栈顺序。呵呵,是不是又有感觉了,对,栈就是数据交互的核心。对Lua的理解程度和运用技巧,其实就是对栈的灵活运用和操作。
好了。你的第一个Lua程序大功告成!竟然不是Hello world,呵呵。
好了,我们看看Main函数怎么写吧,相信大家都会写。

#include “LuaFn.h”

int _tmain(int argc, _TCHAR* argv[]) 
{ 
        CLuaFn LuaFn;

        //LuaFn.InitClass();

        LuaFn.LoadLuaFile(“Sample.lua”); 
        LuaFn.CallFileFn(“func_Add”, 11, 12); 
        getchar();

        return 0; 
}

行了,Build一下,看看,是不是你要的结果?如果是,贺喜你,你已经迈出了Lua的第一步。
洋洋洒洒写了一个小时,喝口水吧,呵呵, 下一讲,我将强化这个LuaFn类,让它给我做更多的事情。呵呵,最后,我会让你打到,用Lua文件直接画出一个Windows窗体来。并在上面画出各种 按钮,列表,以及复选框。是不是感觉很酷?用文本去创造一个程序?很激动吧,恩,确实,Lua能给你做到。只要你有耐心看下去。。。

小伙伴们,还请持续关注更新,更多干货和资料请直接联系我,也可以加群710520381,邀请码:柳猫,欢迎大家共同讨论

柳猫
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容