- 为什么要在游戏中使用脚本语言?
要解释这个问题首先我们先来了解一下脚本语言的特性:
- 学习门槛低,快速上手
- 开发成本低,可维护性强
- 动态语言,灵活性高
相对于C/C++这类高复杂性、高风险的编译型语言来说,Lua脚本做为一种轻量级的动态语言,简单的语言特性,精简的核心和基础库,使得语言的学习门槛大大的降低,即使是没有任何游戏经验的人都能快速上手,开发游戏功能。实际上游戏设计是一种十分繁杂的工作,C/C++虽然给我们带来极大的高效性,但同时也不能忽视其复杂性,极易产生BUG,而且对于开发人员的要求非常高。从语言的的抽象层面来说C/C++的抽象低更加适合于底层逻辑的支持,而Lua脚本抽象层次高,更加适合游戏逻辑的实现。脚本语言运行在虚拟机之上,而虚拟机运行在游戏逻辑之上,作为一种解释型语言,我们可以随时修改并及时体现在游戏之中,快速完成开发。C/C++却做不到,对一个巨大的游戏工程,每次修改都需要重新编译,成本很高。设想一下,如果所有的功能都是使用C/C++实现的话,那么对开发人员来说简直是一场灾难。
- 如何在游戏中使用Lua脚本?
这里就不理论一大堆了,直接手把手教。
- 进入Lua官方网站下载Source源代码
- 在Visual Studio在新建一个解决方案名为Lua2Game
- 在Lua2Game解决方案下新建一个空项目,命名为LuaDll,将从Lua官网下载的源代码src中除luac.c文件之外的源代码拷贝到LuaDll工程,配置项目属性,常规->配置类型为静态库(lib)然后编译LuaDll项目。(luac.c是编译器,lua.c是解释器也就是lua虚拟机)
- 在Lua2Game解决方案下新建一个空项目,命名为Game,配置项目属性,常规->配置类型为应用程序(.exe), 这就是游戏demo。在项目属性中,链接器-> 输入->附加依赖项中加入../Debug/LuaDll.lib
- 在项目Game中实现脚本引擎CLuaScript(实现C/C++与Lua脚本的互相访问)
LuaScript.h
1 #ifndef __LUA_SCRIPT_H__
2 #define __LUA_SCRIPT_H__
3
4 #include "GameDef.h"
5
6 class CLuaScript
7 {
8 public:
9 CLuaScript();
10 ~CLuaScript();
11
12 public:
13 //实现C/C++对Lua脚本的调用
14 bool LoadScript(const char* szFileName); //实现lua脚本加载和编译
15 //调用Lua函数
16 bool CallFunction(char* cFuncName, int nResults, char* cFormat, va_list vlist);
17 bool CallFunction(const char* cFuncName, int nResults, char* cFormat, ...);
18
19 private:
20 void RegisterLuaLib(); //注册lua各种基础库
21 bool RegisterFunctions(TLua_Funcs Funcs[], int n);//将游戏接口注册到lua脚本
22
23 private:
24 lua_State* m_LuaState; //state 脚本和C\C++搞基就靠它了
25 bool m_IsLoadScript;
26 };
27
28
29 #endif
LuaScript.cpp
1 #include <iostream>
2 #include "LuaScript.h"
3
4 CLuaScript::CLuaScript()
5 {
6 m_LuaState = luaL_newstate();
7 if (!m_LuaState)
8 {
9 std::cout << "m_LuaState new state failed!" << std::endl;
10 return;
11 }
12 RegisterLuaLib();//注册lua标准库
13 RegisterFunctions(g_GameFunc, g_GetGameFuncSize());//注册c\c++脚本接口
14 m_IsLoadScript = false;
15 }
16
17 CLuaScript::~CLuaScript()
18 {
19 if (m_LuaState)
20 {
21 lua_close(m_LuaState);
22 m_LuaState = NULL;
23 }
24 m_IsLoadScript = false;
25 }
26
27 void CLuaScript::RegisterLuaLib()
28 {
29 if (!m_LuaState)
30 {
31 return;
32 }
33 luaL_openlibs(m_LuaState);
34 }
35
36 bool CLuaScript::RegisterFunctions(TLua_Funcs Funcs[], int n)
37 {
38 if (!m_LuaState)
39 {
40 return false;
41 }
42 for (int i = 0; i < n; i++)
43 lua_register(m_LuaState, Funcs[i].name, Funcs[i].func);
44 return true;
45 }
46
47 bool CLuaScript::LoadScript(const char* szFileName)
48 {
49 if (!szFileName || szFileName[0] == '\0')
50 {
51 std::cout << "Lua script file illegal!" << std::endl;
52 return false;
53 }
54 if (!m_LuaState)
55 return false;
56
57 m_IsLoadScript = (luaL_dofile(m_LuaState, szFileName) == LUA_OK);
58 if (!m_IsLoadScript)
59 {
60 std::cout << "<LUA_LOAD_ERROR>"<< lua_tostring(m_LuaState, -1) << std::endl;
61 lua_pop(m_LuaState, 1);
62 }
63 return m_IsLoadScript;
64 }
65
66 bool CLuaScript::CallFunction(char* cFuncName, int nResults, char* cFormat, va_list vlist)
67 {
68 if (!m_LuaState || !m_IsLoadScript)
69 return false;
70
71 double nNumber = 0;
72 int nInteger = 0;
73 char* cString = NULL;
74 void* pPoint = NULL;
75 int i = 0;
76 int nArgnum = 0;
77 lua_CFunction CFunc = NULL;
78
79 lua_getglobal(m_LuaState, cFuncName); //在堆栈中加入需要调用的函数名
80
81 while (cFormat[i] != '\0')
82 {
83 switch (cFormat[i])
84 {
85 case 'n'://输入的数据是double形 NUMBER,Lua来说是Double型
86 {
87 nNumber = va_arg(vlist, double);
88 lua_pushnumber(m_LuaState, nNumber);
89 nArgnum++;
90 }
91 break;
92
93 case 'd'://输入的数据为整形
94 {
95 nInteger = va_arg(vlist, int);
96 lua_pushinteger(m_LuaState, nInteger);
97 nArgnum++;
98 }
99 break;
100
101 case 's'://字符串型
102 {
103 cString = va_arg(vlist, char *);
104 lua_pushstring(m_LuaState, cString);
105 nArgnum++;
106 }
107 break;
108
109 case 'N'://NULL
110 {
111 lua_pushnil(m_LuaState);
112 nArgnum++;
113 }
114 break;
115
116 case 'f'://输入的是CFun形,即内部函数形
117 {
118 CFunc = va_arg(vlist, lua_CFunction);
119 lua_pushcfunction(m_LuaState, CFunc);
120 nArgnum++;
121 }
122 break;
123
124 case 'v'://输入的是堆栈中Index为nIndex的数据类型
125 {
126 nNumber = va_arg(vlist, int);
127 int nIndex1 = (int)nNumber;
128 lua_pushvalue(m_LuaState, nIndex1);
129 nArgnum++;
130 }
131 break;
132
133 }
134
135 i++;
136 }
137
138 int nRetcode = lua_pcall(m_LuaState, nArgnum, nResults, 0);
139
140 if (nRetcode != 0)
141 {
142 std::cout << "<LUA_CALL_FUNC_ERROR>" << lua_tostring(m_LuaState, -1) << std::endl;
143 lua_pop(m_LuaState, 1);
144 return false;
145 }
146
147 return true;
148 }
149
150
151 bool CLuaScript::CallFunction(const char* cFuncName, int nResults, char* cFormat, ...)
152 {
153 bool bResult = false;
154 va_list vlist;
155 va_start(vlist, cFormat);
156 bResult = CallFunction((char*)cFuncName, nResults, cFormat, vlist);
157 va_end(vlist);
158 return bResult;
159 }
6,定义用于实现定义给lua脚本的游戏接口
GameDef.h
1 #ifndef __GAME_DEF_H__
2 #define __GAME_DEF_H__
3
4 extern "C"{
5 #include "../../LuaDll/src/lua.h"
6 #include "../../LuaDll/src/lauxlib.h"
7 #include "../../LuaDll/src/lualib.h"
8 }
9
10
11 struct TLua_Funcs
12 {
13 const char *name;
14 lua_CFunction func;
15 };
16
17 extern TLua_Funcs g_GameFunc[];
18 extern int g_GetGameFuncSize();
19
20 #endif
GameDef.cpp
1 #include "GameDef.h"
2 #include <direct.h>
3 #include <iostream>
4 #include "Core.h"
5 using namespace std;
6
7
8 int LuaSayHello(lua_State* L)
9 {
10 cout << "Lua call c/c++:SayHello()" << endl;
11 cout << "Hello Everyone!" << endl;
12 if (lua_gettop(L) < 3)
13 return 0;
14 const char* szName = lua_tostring(L, 1);
15 int nParam1 = lua_tonumber(L, 2);
16 int nParam2 = lua_tonumber(L, 3);
17 cout << "My name is " << szName << endl;
18 lua_pushnumber(L, nParam1 / nParam2);
19 return 1;
20 }
21
22 int LuaStopGame(lua_State* L)
23 {
24 cout << "Lua call c/c++:StopGame()" << endl;
25 cout << "Game is over!" << endl;
26 g_Core.SetRunState(false);
27 return 0;
28 }
29
30 //脚本接口
31 TLua_Funcs g_GameFunc[] = {
32 { "SayHello", LuaSayHello },
33 { "StopGame", LuaStopGame },
34 };
35
36 int g_GetGameFuncSize()
37 {
38 return sizeof(g_GameFunc) / sizeof(TLua_Funcs);
39 }
7,模拟游戏主逻辑
Core.h
1 #ifndef __CORE_H__
2 #define __CORE_H__
3
4 #include "GameDef.h"
5 #include "LuaScript.h"
6
7 class CCore
8 {
9 public:
10 CCore();
11 ~CCore();
12
13 public:
14 bool Initialize();
15 void Uninitialize();
16 bool Breathe();
17 void SetRunState(bool bRunning);
18
19 private:
20 CLuaScript* m_Script;
21 bool m_bIsRuning;
22 };
23
24 extern CCore g_Core;
25
26 #endif
Core.cpp
1 #include "Core.h"
2 #include <time.h>
3 #include <iostream>
4 using namespace std;
5
6 CCore g_Core;
7
8 CCore::CCore()
9 {
10 m_Script = NULL;
11 m_bIsRuning = true;
12 }
13
14 CCore::~CCore()
15 {
16 if (m_Script)
17 {
18 delete m_Script;
19 m_Script = NULL;
20 }
21 }
22
23 bool CCore::Initialize()
24 {
25 //do something
26 return true;
27 }
28
29 void CCore::Uninitialize()
30 {
31 //do something
32 }
33
34 void CCore::SetRunState(bool bRunning)
35 {
36 m_bIsRuning = bRunning;
37 }
38
39 bool CCore::Breathe()
40 {
41 if (!m_bIsRuning)
42 return false;
43 static size_t c = 0;
44 size_t now = time(NULL);
45 if (now - c > 3)
46 {
47 c = now;
48 if (!m_Script)
49 {
50 m_Script = new CLuaScript;
51 }
52 if (m_Script)
53 {
54 //游戏调用lua脚本
55 m_Script->LoadScript("./../test.lua");
56 //调用脚本函数,请参看下面第9点test.lua脚本
57 m_Script->CallFunction("main", 1, "sdd", "luaer", c, c / 18);
58 }
59 else
60 {
61 std::cout << "new CLuaScript failed!" << std::endl;
62 m_bIsRuning = false;
63 }
64 }
65 return true;
66 }
8,最后是实现mian函数(也就是游戏的服务器)
1 #include <iostream>
2 #include "Core.h"
3 using namespace std;
4
5 int main(int argc, char* argv[])
6 {
7 if (!g_Core.Initialize())
8 {
9 g_Core.Uninitialize();
10 return 0;
11 }
12 std::cout << "-----------------Start game!!!-----------------" << std::endl;
13 while (1)
14 {
15 if (!g_Core.Breathe())
16 break;
17 }
18 std::cout << "-----------------Game over!!!-----------------" << std::endl;
19 g_Core.Uninitialize();
20
21 system("PAUSE");
22 return 1;
23 }
9,在工程目录下创建test.lua脚本给游戏调用
1 function main(szName, num1, num2)
2 print("main()", szName, num1, num2); --调用lua基础库函数
3 local nRet = SayHello(szName, num1, num2); --调用游戏接口并返回结果
4 print("nRet =", nRet);
5 local nRand = math.random(100);
6 print("nRand =", nRand)
7 if nRand > 80 then
8 StopGame(); --停止游戏
9 end
10 return 1;
11 end
运行结果:
1 -----------------Start game!!!-----------------
2 main() luaer 1410876602 78382033
3 Lua call c/c++:SayHello()
4 Hello Everyone!
5 My name is luaer
6 nRet = 18
7 nRand = 1
8 main() luaer 1410876606 78382033
9 Lua call c/c++:SayHello()
10 Hello Everyone!
11 My name is luaer
12 nRet = 18
13 nRand = 57
14 main() luaer 1410876610 78382033
15 Lua call c/c++:SayHello()
16 Hello Everyone!
17 My name is luaer
18 nRet = 18
19 nRand = 20
20 main() luaer 1410876614 78382034
21 Lua call c/c++:SayHello()
22 Hello Everyone!
23 My name is luaer
24 nRet = 18
25 nRand = 81
26 Lua call c/c++:StopGame()
27 Game is over!
28 -----------------Game over!!!-----------------
29 Press any key to continue . . .
Demo工程的完整版本可以通过github上获得。