- 为什么要在游戏中使用脚本语言?
要解释这个问题首先我们先来了解一下脚本语言的特性:
- 学习门槛低,快速上手
- 开发成本低,可维护性强
- 动态语言,灵活性高
相对于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上获得。