[Unity3D]Unity3D游戏开发之Lua与游戏的不解之缘(中)

     

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei

转载请注明出处,本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanpei/article/details/39910099

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

       大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是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取得栈顶元素。下面呢,我们通过一个实例来加深我们对于这段话的理解:

#include <iostream>

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

using namespace std;

int main()
{
    //创建Lua环境
    lua_State* L=lua_open();
    //打开Lua标准库,常用的标准库有luaopen_base、luaopen_package、luaopen_table、luaopen_io、
    //luaopen_os、luaopen_string、luaopen_math、luaopen_debug
    luaL_openlibs(L);
    //压入一个数字20
    lua_pushnumber(L,20);
    //压入一个数字15
    lua_pushnumber(L,15);
    //压入一个字符串Lua
    lua_pushstring(L,"Lua");
    //压入一个字符串C
    lua_pushstring(L,"C");
    //获取栈元素个数
    int n=lua_gettop(L);
    //遍历栈中每个元素
    for(int i=1;i<=n;i++)
    {
        cout << lua_tostring(L ,i) << endl;
    }
    return 0;
}

在上面的这段代码中,我们可以可以看到我们首先创建了一个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系列的方法来实现,如下图所示:

[Unity3D]Unity3D游戏开发之Lua与游戏的不解之缘(中)_第1张图片

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

[Unity3D]Unity3D游戏开发之Lua与游戏的不解之缘(中)_第2张图片

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

#include <iostream>

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

using namespace std;

int main()
{
    //创建Lua环境
    lua_State* L=lua_open();
    //打开Lua标准库,常用的标准库有luaopen_base、luaopen_package、luaopen_table、luaopen_io、
    //luaopen_os、luaopen_string、luaopen_math、luaopen_debug
    luaL_openlibs(L);
    //压入一个数字20
    lua_pushnumber(L,20);
    //压入一个字符串15
    lua_pushnumber(L,15);
    //压入一个字符串Lua
    lua_pushstring(L,"Lua");
    //压入一个字符串C
    lua_pushstring(L,"C");
    //获取栈中元素个数
    int n=lua_gettop(L);
    //遍历栈中每个元素
    for(int i=1;i<=n;i++)
    {
        //类型判断
        switch(lua_type(L,i))
       {
          case LUA_TSTRING:
            cout << "This value's type is string" << endl;
          break;
          case LUA_TNUMBER:
            cout << "This value's type is number" << endl;
          break;
        }
        //输出值
        cout << lua_tostring(L ,i) << endl;
    }

    //释放Lua
    lua_close(L);
}

    二、Lua与C++交互

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

   1、C++调用Lua

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

#include <iostream>

using namespace std;

#include <iostream>

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

using namespace std;

int main()
{
    //创建Lua环境
    lua_State* L=luaL_newstate();
    //打开Lua标准库,常用的标准库有luaopen_base、luaopen_package、luaopen_table、luaopen_io、
    //luaopen_os、luaopen_string、luaopen_math、luaopen_debug
    luaL_openlibs(L);

    //下面的代码可以用luaL_dofile()来代替
    //加载Lua脚本
    luaL_loadfile(L,"script.lua");
    //运行Lua脚本
    lua_pcall(L,0,0,0);

    //将变量arg1压入栈顶
    lua_getglobal(L,"arg1");
    //将变量arg2压入栈顶
    lua_getglobal(L,"arg2");

    //读取arg1、arg2的值
    int arg1=lua_tonumber(L,-1);
    int arg2=lua_tonumber(L,-2);

    //输出Lua脚本中的两个变量
    cout <<"arg1="<<arg1<<endl;
    cout <<"arg2="<<arg2<<endl;

    //将函数printf压入栈顶
    lua_getglobal(L,"printf");
    //调用printf()方法
    lua_pcall(L,0,0,0);

    //将函数sum压入栈顶
    lua_getglobal(L,"sum");
    //传入参数
    lua_pushinteger(L,15);
    lua_pushinteger(L,25);
    //调用printf()方法
    lua_pcall(L,2,1,0);//这里有2个参数、1个返回值
    //输出求和结果
    cout <<"sum="<<lua_tonumber(L,-1)<<endl;

    //将表table压入栈顶
    lua_getglobal(L,"table");
    //获取表
    lua_gettable(L,-1);
    //输出表中第一个元素
    cout <<"table.a="<<lua_tonumber(L,-2)<<endl;

}

在这段代码中我们调用了一个外部的文件script.lua。这是一个Lua脚本文件,在调试阶段,我们需要将其放置在和C++项目源文件同级的目录下,而在正式运行阶段,我们只需要将其和最终的可执行文件放在同一个目录下就好了。下面是脚本代码:

--在Lua中定义两个变量
arg1=15
arg2=20

--在Lua中定义一个表
table=
{
    a=25,
    b=30
}

--在Lua中定义一个求和的方法
function sum(a,b)
  return a+b
end

--在Lua中定义一个输出的方法
function printf()
  print("This is a function declared in Lua")
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,表示要返回的值的数目。

static int AverageAndSum(lua_State *L)
{
	//返回栈中元素的个数
	int n = lua_gettop(L);
	//存储各元素之和
	double sum = 0;
	for (int i = 1; i <= n; i++)
	{
	    //参数类型处理
		if (!lua_isnumber(L, i))
		{
		    //传入错误信息
			lua_pushstring(L, "Incorrect argument to 'average'");
			lua_error(L);
		}
		sum += lua_tonumber(L, i);
	}
	//传入平均值
	lua_pushnumber(L, sum / n);
	//传入和
	lua_pushnumber(L, sum);

	//返回值的个数,这里为2
	return 2;
}
接下来我们在C++中使用lua_register()方法完成对该方法的注册

  lua_register(L, "AverageAndSum", AverageAndSum);
这样我们就可以在Lua环境中使用这个方法啦,前提是定义必须在执行代码之前完成,我们在Lua脚本文件下加入对该方法的调用:

--在Lua中调用C++中定义并且注册的方法
average,sum=AverageAndSum(20,52,75,14)
print("Average=".average)
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的版本不一致造成的吧。下面的这个项目是可以使用的,博主这里写了一个简单的示例:

//------------------------------------------------------------------------------
// <summary>
//     这是一个用以演示LuaInterface的简单程序,通过LuaInterface我们可以实现在C#与Lua的
//     的相互通信。Lua是一个轻巧而高效的语言,它可以和任何语言混合使用。Lua语言最初并不是
//     为游戏开发而诞生,却是因为游戏开发而成名。目前,在世界上有大量的游戏使用了Lua作为它
//     的脚本语言。如图Unity使用了C#作为它的语言,Lua在游戏开发领域发挥着不可忽视的重要作
//     用。使用LuaInterface的方法如下:
//     1.C#
//     注册Lua中可调用方法:
//    mLua.RegisterFunction(Lua调用方法名, 类, 类.GetMethod(C#方法名));
//    注:C#不要使用方法级泛型,即 void Fun<T>(string str);,如果使用,系统自动判定T为第一个参数的类型。
//	   加载Lua代码
//	   mLua.DoString(Lua代码);
//    mLua.DoFile(Lua文件绝对路径);
//     调用Lua方法
//	   mLua.GetFunction(Lua方法).Call(参数);  注:此处参数不要传递dynamic类型的类,否则Lua中无法获取属性值
//     2.Lua
//     调用C#方法时需要先注册注册后按照Lua方法处理
// </summary>
//------------------------------------------------------------------------------
using System;
using LuaInterface;
namespace LuaExample
{
	public class LuaScript
	{
		//定义LuaFile属性以便于从外部调用一个Lua脚本
		private string mLuaFile;
		public string LuaFile {
			get {
				return mLuaFile;
			}
			set {
				mLuaFile = value;
			}
		}

		//Lua虚拟机
		private Lua mLua;

		//构造函数
		public LuaScript ()
		{
			//初始化Lua虚拟机
			mLua=new Lua();
			//注册Printf方法
			mLua.RegisterFunction("Printf",this,this.GetType().GetMethod("Printf"));
		}

		//定义一个C#方法供Lua使用
		public void Printf(string str)
		{
			Console.WriteLine("This Method is Invoked by Lua:" + str);
		}

		//在C#中调用Lua方法
		public void DoFile()
		{
			if(mLuaFile!="")
				//执行Lua脚本中的代码
				mLua.DoFile(mLuaFile);
		}

		//在C#中调用Lau方法
		public void DoString()
		{
			//以字符串形式定义的Lua脚本
			string mFuncString="function Add(a,b) io.write(a+b) end";
			//在Lua中定义该方法
			mLua.DoString(mFuncString);
			//调用该方法
			mLua.GetFunction("Add").Call(4,8);
		}

		//在Lua中调用C#脚本
		public void Invoke()
		{
			//调用注册的Printf方法
			mLua.GetFunction("Printf").Call("Hello Lua");
		}
	}
}
接下来我们编写一个主类来调用这个类:

using System;
using LuaInterface;

namespace LuaExample
{
	class MainClass
	{
		public static void Main (string[] args)
		{
			//实例化LuaSxript
			LuaScript mLua=new LuaScript();
			//设置LuaFile
			mLua.LuaFile="D:\\test.lua";
			//调用字符串中定义的Lua方法
			mLua.DoString();
			//为美观考虑增加一个空行
			Console.WriteLine();
			//执行Lua文件中定义的脚本
			mLua.DoFile();
			//调用C#中定义的方法
			mLua.Invoke();
		}
	}
}
好了,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,对于我们来说未尝不是一件好事啊,因为学习新东西总是要花一定成本的。好了,今天的内容就是这样啦,希望大家喜欢啊,嘻嘻,突然觉得这篇文章好长啊。


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


[Unity3D]Unity3D游戏开发之Lua与游戏的不解之缘(中)_第3张图片


-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei

转载请注明出处,本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanpei/article/details/39910099

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------



你可能感兴趣的:(技术,开源项目,技术人,游戏开发,开源社区)