# 第20讲 | 脚本语言在游戏开发中有哪些应用? 上一次,我们谈到了如何在游戏中嵌入脚本语言,我们用的语言是Lua。Lua语言具有轻量级、速度快的特点,而且API的调用也非常方便和直观。现在,我们仍然拿Lua脚本,试着把它应用在我们开发的游戏中。 我们使用C语言来对Lua脚本的绑定做一次深入的剖析,然后来看一下,在游戏开发中绑定了脚本语言后,脚本语言能做些什么事情。 首先,我们要明白一点,事实上**任何模块都可以使用脚本语言编写**。当然在游戏开发的过程中,需要分工明确,如果不分工的话,效率可能会比较低。 在需要某些效率要求非常高的情况下,一般是用C、C++或ASM语言,将底层模块搭建好,然后将一些逻辑部分分出来,给脚本语言处理。比如我们熟知的服务器端,可以使用C/C++来编写服务器端的IOCP或者epoll处理;而接收、发送、逻辑处理等等,都可以使用绑定脚本的方式编写。 我们在编写的过程中,需要对C/C++的语言和代码有个了解,我们需要先考虑这个函数。 ``` int test_func(lua_State *L) { return 0; } ``` 这只是一个空的C函数,在这个函数里面,我们看到它的传入参数是lua\_State,接受一个指针L。随后,这个函数返回一个0。 lua\_State是Lua虚拟机的对象指针,也就是我们需要把前面new出来的一个虚拟机传进去,才可以保证在这个函数里面,使用的是一致的虚拟机。 这个函数的作用是,**只要注册到了Lua虚拟机里面,它就是lua的一个函数,其中在lua函数中,传入的参数由函数内部决定**。 比如我可以这么写: ``` int test_func(lua_State *L) { const char *p1 = lua_tostring(L, 1); const char *p2 = lua_tostring(L, 2); // .... do something lua_pushstring(L, "something"); return 1; } ``` 这里面,lua\_tosting 就是这个函数的传入参数,传入的是一个字符串的参数;第二个参数也是字符串参数,其中 lua\_tosting 的第二个参数1或者2,表明的是在Lua虚拟机的堆栈中从栈底到栈顶开始计数,一般先压入的参数在第一个,后压入的在第二个,以此类推。返回1的意思是,这个函数会返回一个参数,这个参数就是我们前面 lua\_pushstring 后压入的这个内容something,这就是返回的参数。 那么这个函数究竟怎么注册成为Lua函数呢?我们来看这段代码。 ``` lua_register(L, "test", &test_func); ``` lua\_register函数的功能是,注册C函数到Lua虚拟机。其中L是虚拟机指针。这个在前面的代码都有说到,而第二个参数test就是注册在Lua虚拟机中的函数名,所以这个函数名叫test。第三个参数是函数指针,我们把test\_func这个函数传入到lua\_register函数中。这样,一个函数就注册好了。 那么,如果我们在游戏中有许多许多的函数需要注册到Lua中,那么这种写法是不是太慢了,有没有一种快捷的写法来支持注册等操作呢? 如果你没有C/C++的语言基础,或者C/C++语言基础比较薄弱,下面的内容可能需要花一点时间消化,我也会竭尽所能解释清楚代码的意思,但如果你已经是个C/C++程序员,那么下面的代码对你来说应该不会太难。 我们需要使用lua\_register,我们先看它里面有什么参数。第一个是**字符串**,也就是**char\*;**第二个是**函数指针**,也就是**int (_)(lua\_State_)** 这种形式的。 那么,我们需要定义一个struct结构,这个结构可以这么写: ``` #define _max 256 typedef struct _ph_func { char ph_name[_max]; int (*ph_p_func)(lua_State*); } ph_func; ``` 我们定义了一个struct结构,这个结构的名字叫\_ph\_func,名字叫什么并没有关系,但是最开始有一个typedef,这说明在这个结构声明完后,接下来最后一行ph\_func就是替代最初定义的那个\_ph\_func的名字,替代的结果是,**ph\_func 等同于struct \_ph\_func**,这在很多C语言的代码里面经常能见到。 接下来,我们看到char ph\_name\[\_max\]。其中\_max的值为256。我相信你应该能理解这句话。第二个变量就是我们所看到的函数指针,其中ph\_p\_func是函数指针,其中函数指针指向的内容目前暂时还没有确定,我们将在后续初始化这个结构变量的时候进行赋值。 我们来仔细看一下这两段宏的内容。 ``` #define func_reg(fname) #fname, &ph_##fname #define func_lua(fname) int ph_##fname(lua_State* L) ``` 其中func\_reg是在给前面那个结构体初始化赋值的时候使用的,因为我们知道,如果我们需要给这个结构体赋值,看起来的代码是这样: ``` ph_func pobj = {"test", &test_func}; ``` 那么由于我们有大量的函数需要注册,所以我们将之拆分为宏,其中#fname的意思是,将fname变为字符串,而ph\_##fname的意思是使用##字符,将前后内容连接起来。 通过这个宏,比如我们输入一个a赋值给 fname,那么#fname就变成字符串"a",通过 ph\_##fname,结果就是ph\_a。 接下来的代码,是方便在代码中编写一个一个lua注册函数用的,所以很明显,和上述的宏一样,我们只需要输入a,那么这个函数就变成了 int ph\_a(lua\_State\* L); 定义好了这两个宏,我们怎么来应用呢? ``` func_lua(test_func); ph_func p_funcs[] = { { func_reg(test_func) }, }; func_lua(test_func) { const char *p1 = lua_tostring(L, 1); const char *p2 = lua_tostring(L, 2); // .... do something lua_pushstring(L, "something"); return 1; } void register_func(lua_State* L) { int i; for(i=0; i