Unity3D游戏开发之Lua与游戏的不解之缘(转载中)

来源:互联网 发布:java与 编辑:程序博客网 时间:2024/05/20 23:05

大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。在前一篇文章《Unity3D游戏开发之Lua与游戏的不解

之缘(上)》中,博主带领大家初步探索了Lua语言与游戏开发领域之间的紧密联系,今天让我们来继续将Lua语言进行到底吧!通过前面的学习,我们知道设计Lua语言的目的是为了将Lua嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua语言本身没有像其它语言提供丰富的类库,因此Lua语言必须依赖于其它语言来完成功能上的扩展(可是正是在功能上牺牲才换来了Lua精简而稳定的核心)。如果我们要深入了解Lua语言的话,就必须要了解Lua语言与其它语言的交互接口,因为这将是我们使用Lua语言的基础。那么,今天就让博主来带领大家一起学习Lua语言与其它语言的交互吧!


      一、Lua堆栈

    如果我们想要理解Lua语言与其它语言交互的实质,我们首先就要理解Lua堆栈。简单来说,Lua语言之所以能和C/C++进行交互,主要是因为存在这样一个无处不在的虚拟栈。栈的特点是先进后出,在Lua语言中,Lua堆栈是一种索引可以是正数或者负数的结构,并规定正数1永远表示栈底,负数-1永远表示栈顶。换句话说呢,在不知道栈大小的情况下,我们可以通过索引-1取得栈底元素、通过索引1取得栈顶元素。下面呢,我们通过一个实例来加深我们对于这段话的理解:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2.   
  3. extern "C" {  
  4. #include "lua.h"  
  5. #include "lualib.h"  
  6. #include "lauxlib.h"  
  7. }  
  8.   
  9. using namespace std;  
  10.   
  11. int main()  
  12. {  
  13.     //创建Lua环境  
  14.     lua_State* L=lua_open();  
  15.     //打开Lua标准库,常用的标准库有luaopen_base、luaopen_package、luaopen_table、luaopen_io、  
  16.     //luaopen_os、luaopen_string、luaopen_math、luaopen_debug  
  17.     luaL_openlibs(L);  
  18.     //压入一个数字20  
  19.     lua_pushnumber(L,20);  
  20.     //压入一个数字15  
  21.     lua_pushnumber(L,15);  
  22.     //压入一个字符串Lua  
  23.     lua_pushstring(L,"Lua");  
  24.     //压入一个字符串C  
  25.     lua_pushstring(L,"C");  
  26.     //获取栈元素个数  
  27.     int n=lua_gettop(L);  
  28.     //遍历栈中每个元素  
  29.     for(int i=1;i<=n;i++)  
  30.     {  
  31.         cout << lua_tostring(L ,i) << endl;  
  32.     }  
  33.     return 0;  
  34. }  

在上面的这段代码中,我们可以可以看到我们首先创建了一个lua_State类型的变量L,我们可以将它理解成一个Lua运行环境的上下文(Context),这里我们在Lua堆栈中压入了四个元素:20、15、"Lua"、"C"然后将其输出,如果大家理解了Lua堆栈中的索引,那么最终输出的结果应该是:20、15、"Lua"、"C",因为索引1始终指向栈底,最先入栈的元素会处于栈底。因此当我们按照递增的索引顺序来输出栈中的元素的话,实际上是自下而上输出,这样我们就能得到这样的结果了。

       好了,如果这段代码没有什么问题的话,接下来我们来讲解Lua为C/C++提供的接口,它们均被定义在lua.h文件中。Lua提供的C/C++接口大部分与栈操作有关,因此深入理解Lua堆栈是学习Lua语言的重点和难点。通过数据结构的知识,我们可以知道栈有出栈和入栈两种基本操作,Lua提供的C API中入栈可以通过push系列的方法来实现,如下图所示:


而出栈或者说查询的方法则可以通过to系列的方法来实现,如下图:


这两部分是学习Lua语言一定要去了解的内容,因为以后如果需要我们将Lua整合到其它项目中这些内容,这些东西可以说是原理性、核心性的东西。好了,下面我们利用这里的API对一个示例代码进行改造,这里加入了对栈中元素类型的判断:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2.   
  3. extern "C" {  
  4. #include "lua.h"  
  5. #include "lualib.h"  
  6. #include "lauxlib.h"  
  7. }  
  8.   
  9. using namespace std;  
  10.   
  11. int main()  
  12. {  
  13.     //创建Lua环境  
  14.     lua_State* L=lua_open();  
  15.     //打开Lua标准库,常用的标准库有luaopen_base、luaopen_package、luaopen_table、luaopen_io、  
  16.     //luaopen_os、luaopen_string、luaopen_math、luaopen_debug  
  17.     luaL_openlibs(L);  
  18.     //压入一个数字20  
  19.     lua_pushnumber(L,20);  
  20.     //压入一个字符串15  
  21.     lua_pushnumber(L,15);  
  22.     //压入一个字符串Lua  
  23.     lua_pushstring(L,"Lua");  
  24.     //压入一个字符串C  
  25.     lua_pushstring(L,"C");  
  26.     //获取栈中元素个数  
  27.     int n=lua_gettop(L);  
  28.     //遍历栈中每个元素  
  29.     for(int i=1;i<=n;i++)  
  30.     {  
  31.         //类型判断  
  32.         switch(lua_type(L,i))  
  33.        {  
  34.           case LUA_TSTRING:  
  35.             cout << "This value's type is string" << endl;  
  36.           break;  
  37.           case LUA_TNUMBER:  
  38.             cout << "This value's type is number" << endl;  
  39.           break;  
  40.         }  
  41.         //输出值  
  42.         cout << lua_tostring(L ,i) << endl;  
  43.     }  
  44.   
  45.     //释放Lua  
  46.     lua_close(L);  
  47. }  

    二、Lua与C++交互

   Lua与C++的交互从宿主语言的选择划分上可以分为C++调用Lua和Lua调用C++两中类型:

   1、C++调用Lua

    使用C++调用Lua时我们可以直接利用C++中的Lua环境来直接Lua脚本,例如我们在外部定义了一个lua脚本文件,我们现在需要使用C++来访问这个脚本该怎么做呢?在这里我们可以使用luaL_loadfile()、luaL_dofile()这两个方法个方法来实现,其区别是前者仅加载脚本文件而后者会在加载的同时调用脚本文件。我们一起来看下面的代码:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2.   
  3. using namespace std;  
  4.   
  5. #include <iostream>  
  6.   
  7. extern "C" {  
  8. #include "lua.h"  
  9. #include "lualib.h"  
  10. #include "lauxlib.h"  
  11. }  
  12.   
  13. using namespace std;  
  14.   
  15. int main()  
  16. {  
  17.     //创建Lua环境  
  18.     lua_State* L=luaL_newstate();  
  19.     //打开Lua标准库,常用的标准库有luaopen_base、luaopen_package、luaopen_table、luaopen_io、  
  20.     //luaopen_os、luaopen_string、luaopen_math、luaopen_debug  
  21.     luaL_openlibs(L);  
  22.   
  23.     //下面的代码可以用luaL_dofile()来代替  
  24.     //加载Lua脚本  
  25.     luaL_loadfile(L,"script.lua");  
  26.     //运行Lua脚本  
  27.     lua_pcall(L,0,0,0);  
  28.   
  29.     //将变量arg1压入栈顶  
  30.     lua_getglobal(L,"arg1");  
  31.     //将变量arg2压入栈顶  
  32.     lua_getglobal(L,"arg2");  
  33.   
  34.     //读取arg1、arg2的值  
  35.     int arg1=lua_tonumber(L,-1);  
  36.     int arg2=lua_tonumber(L,-2);  
  37.   
  38.     //输出Lua脚本中的两个变量  
  39.     cout <<"arg1="<<arg1<<endl;  
  40.     cout <<"arg2="<<arg2<<endl;  
  41.   
  42.     //将函数printf压入栈顶  
  43.     lua_getglobal(L,"printf");  
  44.     //调用printf()方法  
  45.     lua_pcall(L,0,0,0);  
  46.   
  47.     //将函数sum压入栈顶  
  48.     lua_getglobal(L,"sum");  
  49.     //传入参数  
  50.     lua_pushinteger(L,15);  
  51.     lua_pushinteger(L,25);  
  52.     //调用printf()方法  
  53.     lua_pcall(L,2,1,0);//这里有2个参数、1个返回值  
  54.     //输出求和结果  
  55.     cout <<"sum="<<lua_tonumber(L,-1)<<endl;  
  56.   
  57.     //将表table压入栈顶  
  58.     lua_getglobal(L,"table");  
  59.     //获取表  
  60.     lua_gettable(L,-1);  
  61.     //输出表中第一个元素  
  62.     cout <<"table.a="<<lua_tonumber(L,-2)<<endl;  
  63.   
  64. }  
在这段代码中我们调用了一个外部的文件script.lua。这是一个Lua脚本文件,在调试阶段,我们需要将其放置在和C++项目源文件同级的目录下,而在正式运行阶段,我们只需要将其和最终的可执行文件放在同一个目录下就好了。下面是脚本代码:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. --在Lua中定义两个变量  
  2. arg1=15  
  3. arg2=20  
  4.   
  5. --在Lua中定义一个表  
  6. table=  
  7. {  
  8.     a=25,  
  9.     b=30  
  10. }  
  11.   
  12. --在Lua中定义一个求和的方法  
  13. function sum(a,b)  
  14.   return a+b  
  15. end  
  16.   
  17. --在Lua中定义一个输出的方法  
  18. function printf()  
  19.   print("This is a function declared in Lua")  
  20. end  
我们注意到在脚本文件中我们定义了一些变量和方法,在C++代码中我们首先用lua_getglobal()方法来讲Lua脚本中的变量或函数压入栈顶,这样我们就可以使用相关的to系列方法去获取它们,由于每次执行lua_getglobal()都是在栈顶,因为我们使用索引值-1来获取栈顶的元素。C++可以调用Lua中的方法,第一步和普通的变量相同,是将Lua中定义的方法压入栈顶,因为只有压入栈中,我们才能够使用这个方法,接下来,我们需要通过push系列的方法为栈中的方法传入参数,在完成参数传入后,我们可以使用一个lua_pcall()的方法来执行栈中的方法,它有四个参数,第一个参数是Lua环境状态Lua_State,第二个参数是要传入的参数个数,第三个参数是要返回的值的数目,第四个参数一般默认为0。由于Lua支持返回多个结果,因此,我们可以充分利用Lua的这一特点来返回多个值。执行该方法后,其结果会被压入栈顶,所以我们可以索引值-1来获取函数的结果。如果函数有多个返回值,则按照函数中定义的return 顺序,依次入栈,索引值-1代表最后一个返回值。好了,这就是C++调用Lua的具体实现了。

     2、Lua调用C++

     首先我们在C++中定义一个方法,该方法必须以Lua_State作为参数,返回值类型为int,表示要返回的值的数目。

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. static int AverageAndSum(lua_State *L)  
  2. {  
  3.     //返回栈中元素的个数  
  4.     int n = lua_gettop(L);  
  5.     //存储各元素之和  
  6.     double sum = 0;  
  7.     for (int i = 1; i <= n; i++)  
  8.     {  
  9.         //参数类型处理  
  10.         if (!lua_isnumber(L, i))  
  11.         {  
  12.             //传入错误信息  
  13.             lua_pushstring(L, "Incorrect argument to 'average'");  
  14.             lua_error(L);  
  15.         }  
  16.         sum += lua_tonumber(L, i);  
  17.     }  
  18.     //传入平均值  
  19.     lua_pushnumber(L, sum / n);  
  20.     //传入和  
  21.     lua_pushnumber(L, sum);  
  22.   
  23.     //返回值的个数,这里为2  
  24.     return 2;  
  25. }  
接下来我们在C++中使用lua_register()方法完成对该方法的注册

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. lua_register(L, "AverageAndSum", AverageAndSum);  
这样我们就可以在Lua环境中使用这个方法啦,前提是定义必须在执行代码之前完成,我们在Lua脚本文件下加入对该方法的调用:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. --在Lua中调用C++中定义并且注册的方法  
  2. average,sum=AverageAndSum(20,52,75,14)  
  3. print("Average=".average)  
  4. print("Sum=".sum)  
如果我们需要在C++中查看该方法调用的结果,那么这个在C++中调用Lua是一样的。好了,C++和Lua的交互终于讲完了,被这块的代码纠结了好几天,这下总算是搞明白了。当然这只是对原理的一种学习和理解啦,如果希望更好的使用Lua调用C++,建议了解这几个项目:

LuaPlus、LuaBind。这样相信大家对于C++中的方法如何在Lua中绑定会有更好的认识吧!



    三、Lua与C#交互

   既然我们已经知道了C++是怎样和Lua完成交互的,理论上我们可以通过编写dll的方式将前面完成的工作继续在C#中运行,可是这样做我们需要花费大量时间在三种语言之间纠结,因为这样会增加调试的难度。之前有个做coco2dx的朋友抱怨要在C++、JavaScript、Lua之间来回跑,我当时没觉得有什么,因为我最困难的时候就是C#和Java项目混合的情形,如今我算是深有体会了啊,这算是报应吗?哈哈,好了,不说这个了,好在C#与Lua的交互目方面前已经有了较好的解决方案,在开源社区我们可以找到很多的支持在C#中调用Lua的工具库,博主这里向大家推荐的是LuaInterface这个开源项目,这个开源项目我找到了两个地址:

1、https://github.com/Jakosa/LuaInterface

2、http://code.google.com/p/luainterface

博主个人感觉这应该是同一个项目,因为两个项目的源代码是一样的,不过从Github上下载的项目在使用的时候会报错,估计是我电脑里的Lua版本和它项目里所用的Lua的版本不一致造成的吧。下面的这个项目是可以使用的,博主这里写了一个简单的示例:

[csharp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //------------------------------------------------------------------------------  
  2. // <summary>  
  3. //     这是一个用以演示LuaInterface的简单程序,通过LuaInterface我们可以实现在C#与Lua的  
  4. //     的相互通信。Lua是一个轻巧而高效的语言,它可以和任何语言混合使用。Lua语言最初并不是  
  5. //     为游戏开发而诞生,却是因为游戏开发而成名。目前,在世界上有大量的游戏使用了Lua作为它  
  6. //     的脚本语言。如图Unity使用了C#作为它的语言,Lua在游戏开发领域发挥着不可忽视的重要作  
  7. //     用。使用LuaInterface的方法如下:  
  8. //     1.C#  
  9. //     注册Lua中可调用方法:  
  10. //    mLua.RegisterFunction(Lua调用方法名, 类, 类.GetMethod(C#方法名));  
  11. //    注:C#不要使用方法级泛型,即 void Fun<T>(string str);,如果使用,系统自动判定T为第一个参数的类型。  
  12. //     加载Lua代码  
  13. //     mLua.DoString(Lua代码);  
  14. //    mLua.DoFile(Lua文件绝对路径);  
  15. //     调用Lua方法  
  16. //     mLua.GetFunction(Lua方法).Call(参数);  注:此处参数不要传递dynamic类型的类,否则Lua中无法获取属性值  
  17. //     2.Lua  
  18. //     调用C#方法时需要先注册注册后按照Lua方法处理  
  19. // </summary>  
  20. //------------------------------------------------------------------------------  
  21. using System;  
  22. using LuaInterface;  
  23. namespace LuaExample  
  24. {  
  25.     public class LuaScript  
  26.     {  
  27.         //定义LuaFile属性以便于从外部调用一个Lua脚本  
  28.         private string mLuaFile;  
  29.         public string LuaFile {  
  30.             get {  
  31.                 return mLuaFile;  
  32.             }  
  33.             set {  
  34.                 mLuaFile = value;  
  35.             }  
  36.         }  
  37.   
  38.         //Lua虚拟机  
  39.         private Lua mLua;  
  40.   
  41.         //构造函数  
  42.         public LuaScript ()  
  43.         {  
  44.             //初始化Lua虚拟机  
  45.             mLua=new Lua();  
  46.             //注册Printf方法  
  47.             mLua.RegisterFunction("Printf",this,this.GetType().GetMethod("Printf"));  
  48.         }  
  49.   
  50.         //定义一个C#方法供Lua使用  
  51.         public void Printf(string str)  
  52.         {  
  53.             Console.WriteLine("This Method is Invoked by Lua:" + str);  
  54.         }  
  55.   
  56.         //在C#中调用Lua方法  
  57.         public void DoFile()  
  58.         {  
  59.             if(mLuaFile!="")  
  60.                 //执行Lua脚本中的代码  
  61.                 mLua.DoFile(mLuaFile);  
  62.         }  
  63.   
  64.         //在C#中调用Lau方法  
  65.         public void DoString()  
  66.         {  
  67.             //以字符串形式定义的Lua脚本  
  68.             string mFuncString="function Add(a,b) io.write(a+b) end";  
  69.             //在Lua中定义该方法  
  70.             mLua.DoString(mFuncString);  
  71.             //调用该方法  
  72.             mLua.GetFunction("Add").Call(4,8);  
  73.         }  
  74.   
  75.         //在Lua中调用C#脚本  
  76.         public void Invoke()  
  77.         {  
  78.             //调用注册的Printf方法  
  79.             mLua.GetFunction("Printf").Call("Hello Lua");  
  80.         }  
  81.     }  
  82. }  
接下来我们编写一个主类来调用这个类:

[csharp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. using System;  
  2. using LuaInterface;  
  3.   
  4. namespace LuaExample  
  5. {  
  6.     class MainClass  
  7.     {  
  8.         public static void Main (string[] args)  
  9.         {  
  10.             //实例化LuaSxript  
  11.             LuaScript mLua=new LuaScript();  
  12.             //设置LuaFile  
  13.             mLua.LuaFile="D:\\test.lua";  
  14.             //调用字符串中定义的Lua方法  
  15.             mLua.DoString();  
  16.             //为美观考虑增加一个空行  
  17.             Console.WriteLine();  
  18.             //执行Lua文件中定义的脚本  
  19.             mLua.DoFile();  
  20.             //调用C#中定义的方法  
  21.             mLua.Invoke();  
  22.         }  
  23.     }  
  24. }  
好了,C#与Lua的交互解决了,更多的内容期待着大家自行到该项目源代码中去寻找。好了,先这样吧!

    四、Lua与Java交互

    和C#类似的一点是在Java中我们可以使用JNI来调用C++代码,因此理论上Lua和Java应该是可以通过JNI来交互的,这块博主目前没有展开研究。这里只给大家推荐以下工具库:

1、LuaJava

2、luaj


    五、结语

    好吧,好了,好几天的时间来研究Lua语言的API,总算感觉是收获多一点吧。因为C++方面研究的东西不是很多,所以像编译C++项目、配置C++环境、引用C++库和头文件这些问题以前都不大会,这次竟然一下子都学会了,博主推荐大家使用CodeBlocks这个C/C++开发环境,它内置的gcc编译器我觉得还不错啦,而且它跨平台啊,以后工作了说不定会在Linux和Mac下做开发,选择一个跨平台的编辑器或者是IDE,对于我们来说未尝不是一件好事啊,因为学习新东西总是要花一定成本的。好了,今天的内容就是这样啦,希望大家喜欢啊,嘻嘻,突然觉得这篇文章好长啊。


每日箴言:别总因为迁就别人就委屈自己,这个世界没几个人值得你总弯腰。弯腰的时间久了,只会让人习惯于你的低姿态,你的不重要。


0 0