a = {} -- construct a global table a
i = 3
i, a[i] = i+1, 20
print (i, a[i], a[3])
结果是 4 nil 20
。因为,左边先解释得到地址,后计算值,因此不会影响 a[4]
2、函数定义
function factorial(n)
local x = 1
for i = 2, n do
x = x * i
return x
print(factorial(3))
这段代码展示了它的函数定义、局部变量定义、控制语句等。具体有哪些控制结构,参见手册!
建议课后继续阅读 lua 菜鸟教程
3、Unity 插件技术与 Lua 插件安装
插件是扩展 Unity 能力的利器。AR,或你编写的优化算法都必须通过插件的方式供 Unity 使用。
3.1 Unity 插件(plugin)
Unity 支持两种插件,官网文档:
Managed plugin: 是使用 Visual Studio 等工具创建的托管 .NET 程序集。它们仅包含 .NET 代码,这意味着它们无法访问 .NET 库不支持的任何功能。但是,Unity使用的标准 .NET 工具可以访问托管代码来编译脚本。因此,托管插件代码和 Unity 脚本代码之间的使用差异很小,除了插件是在 Unity 外部编译,且可能没有源代码。
Native: 是特定机器硬件与操作系统平台的本机代码库。他们可以访问操作系统调用和第三方代码库等功能,否则 Unity 将无法使用这些功能。但是,Unity 的工具无法以托管库的方式访问这些库。常指 C/C++ 类 C 语言编译的动态库(.dll, .so)
3.2 Lua 动态库编译
如果你有 苹果 Mac 或 Linux PC, 可参照 SLua 的 make。 强力建议你自己 make 各个平台的库。
3.3 Lua 插件安装
操作 14-02,插件部署练习:
SLua 已编译的版本是 Lua 5.1.5,
1、下载 SLua 解压。主要目的是免去各平台下编译的烦恼,以及一些可供学习的原代码
2、创建一个新项目 lua
3、在 Assets 下创建目录 Plugins
注意 Plugins 大小写!
注意:各平台插件原生代码默认目录 Plugin Inspector Setting!
4、加载平台本地动态库
将编译好的动态库(dll,so),从 SLua 对应目录拖入 Plugins。例如开发平台 64 位 window,则拖入 x64。
未来,你编译哪个平台,就加载哪个平台库。
5、加载 lua 与 c# 的接口库
在 Plugins 目录下建立目录 managed;
从 Slua -> Plugins -> Slua_Managed 中拖 LuaDLL.cs 和 LuaDLLWrapper.cs 进入 managed。导入 c 语言库,让 Unity 访问。
6、OK! 运行,不应该出现任何错误。
不出错非常重要!不闪退就好啊。下面我们进一步验证安装。
4、Lua 虚拟机 与 Lua 栈
4.1 Lua 应用程序接口
Lua 既可以作为独立应用,但多数情况下会嵌入其他应用,如游戏、Nginx 或 其他应用,方便动态扩展业务功能,提升应用的灵活性。
Lua 手册 ! 的最后给出了 lua 语言标准库 和 与应用程序接口 C API。
语言标准库包括: basic, math, string, io, coroutine, os, debug, package
C API 包括: C API, auxiliary library
环境变量与常数
4.2 Lua 虚拟机
Lua 虚拟机 即一个 Lua 脚本程序的执行环境 或 沙箱。它由 lua_newstate
创建。
每个线程 只能有一个 Lua 虚拟机。因此,仅有且只有第一个 Lua 虚拟机在 程序主线程 中,可以访问 Unity 的游戏对象。
Unity 不支持除主线程以外的线程访问游戏对象,或与渲染相关的任何对象。当然,值类型、JsonUtility 通常是线程安全的
不同虚拟机之间是相互隔离的。因此,一个游戏一般仅需要一个 lua 虚拟机实例。
4.2 Lua 栈
每个虚拟机都有一个与应用程序交换数据的栈。这个栈默认有 20 个元素。应用程序必须保证栈的安全、正确使用。
Lua 每个 api 都有一个标志,[-o, +p, x] 表示 [ 出栈参数个数,入栈结果个数,是否返回异常] 。例如::
lua_getglobal [-0, +1, e]
int lua_getglobal (lua_State *L, const char *name);
Pushes onto the stack the value of the global name. Returns the type of that value
分别表示调用该 API ,没有元素出栈,有一个结果元素入栈,会有异常。
lua_tointeger [-0, +0, –]
lua_Integer lua_tointeger (lua_State *L, int index);
Equivalent to lua_tointegerx with isnum equal to NULL.
api 中 index 参数表示数据在栈中位置,index 为负数,如 -1 表示栈顶,-2,-3 类推;
index 为正数,表示位置, 如 1 是栈底,2,3 类推;
lua_gettop 返回当前栈的高度;0 表示 空栈。
5、 应用(host)与 Lua 虚拟机互操作入门
这部分讲述如何利用虚拟机的栈,实现 应用程序 与 lua 之间数据交换和函数调用。
5.1 访问 Lua 虚拟机的全局变量
操作 14-03,读写 lua 变量练习:
using SLua;
using System;
public class my : MonoBehaviour {
// Use this for initialization
void Start () {
IntPtr _L = LuaDLL.luaL_newstate (); // create a lua VM
LuaDLL.luaL_openlibs(_L); // open lua libs
LuaDLL.lua_dostring (_L,"num = 3"); // Excute a simple lua Script
LuaDLL.lua_getglobal (_L, "num"); // push the lua var named ‘num’ on the stack
int i = LuaDLL.lua_tointeger (_L, -1); // read return value on the top of stack
LuaDLL.lua_pop(_L,1) ; // pop, your must maintain the stack carefully!!!
Debug.logger.Log(i);
//this.transform.position = new Vector3 (0, i, 0);
LuaDLL.lua_close (_L);
这个程序非常简单,第一步创建虚拟机;然后执行简单赋值,然后把变量结果通过栈传给应用;关闭虚拟机。
错误操作特容易产生闪退,请及时保存程序!!
解释以下代码?栈有错误吗?
LuaDLL.lua_pushinteger (_L, 2);
LuaDLL.lua_setglobal (_L, "a");
LuaDLL.lua_pushinteger (_L, 3);
LuaDLL.lua_setglobal (_L, "b");
LuaDLL.lua_dostring (_L,"c = a+b");
LuaDLL.lua_getglobal (_L, "c");
Debug.logger.Log( LuaDLL.lua_tointeger (_L, -1) );
LuaDLL.lua_pop(_L,1) ;
lua_setglobal
,请翻译手册描述,并解释 API 栈行为!
5.2 相互调用函数
操作 14-04,相互调用函数练习:
该段代码第一部分是 HOST 调用 Luau 虚拟机中的 foo (x,y)
,然后,Lua 程序调用 HOST 的 dofile("luafile.lua")
函数
using UnityEngine;
using System.Collections;
using System;
using UnityEngine.UI;
using SLua; //using LuaInterface; // lua DLL C# 封装
public class Lua_func : MonoBehaviour {
public GameObject text;
// Use this for initialization
void Start () {
Text txt = text.GetComponent<Text> ();
// Create Lua State
IntPtr _L = LuaDLL.luaL_newstate ();
LuaDLL.luaL_openlibs(_L);
// define lua fuction
String func = @"
function foo (x,y)
return x+y
LuaDLL.lua_dostring (_L,func);
// call lua fuction
LuaDLL.lua_getglobal (_L, "foo"); // push the lua function var on the stack
LuaDLL.lua_pushnumber (_L, 6); // push paramters on the stack
LuaDLL.lua_pushnumber (_L, 3);
// http://www.lua.org/manual/5.1/manual.html#lua_call
LuaDLL.lua_call (_L, 2, 1); //call foo with two parameter on Stack. [-(nargs+1), +nresults, e]
// get result
int i = LuaDLL.lua_tointeger (_L, -1);
LuaDLL.lua_pop(_L,1) ;
txt.text = i.ToString();
// define cFunction in Lua machine,and then call it
LuaDLL.lua_pushcfunction (_L, dofile); // set LuaCSFunction
LuaDLL.lua_setglobal (_L, "dofile"); // assign to "dofile"
LuaDLL.lua_dostring (_L, "dofile(\"tail.lua\")"); //call dofile(_L) in c#
LuaDLL.lua_close (_L);
// lua dofile("luafile.lua") callback handler, must static
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
internal static int dofile(IntPtr L)
int n = LuaDLL.lua_gettop (L); // number of arguments
Debug.logger.Log (n);
string fileName = LuaDLL.lua_tostring(L, 1); // index +/positive
Debug.logger.Log (fileName);
return 0; // number of results
创建 Lua_func.cs 程序
在场景中放置 text UI 游戏对象
挂入代码,将 text 对象拖入插槽
应用程序调用 Lua 定义的函数非常简单,将函数 *参数的值 压入栈底,压入参数,然后 lua_call, 然后从栈顶取回 返回值 即可!
从 Lua 调用 c# 的函数就不那么简单了。
设置函数变量
调用 C 函数
Lua 官方手册 给出了 CFunction 的通讯协议
typedef int (*lua_CFunction) (lua_State *L);
协议规定,Lua 函数参数按左到右,正方向压入栈。即 C 语言从 栈的 1,2,3 … 位置读参数,API lua_gettop(L) 是参数的个数。 返回结果给 Lua 按接收顺序压入堆栈,返回值是参数的个数。
以下是 C 的函数,请翻译成 C# 并调用该函数
static int foo (lua_State *L) {
int n = lua_gettop(L); /* number of arguments */
lua_Number sum = 0;
int i;
for (i = 1; i <= n; i++) {
if (!lua_isnumber(L, i)) {
lua_pushstring(L, "incorrect argument");
lua_error(L);
sum += lua_tonumber(L, i);
lua_pushnumber(L, sum/n); /* first result */
lua_pushnumber(L, sum); /* second result */
return 2; /* number of results */
如果 Lua 回调函数中存在异步加载、或继续调用 Lua 虚拟机? 晕了,难以想象!
为什么必须使用 MonoPInvokeCallbackAttribute 呢?自己搜索!
5.3 复杂数据的交换
简单数据交换很方便,但是 C# 有 命名空间、类、结构、数字、字典等。Lua 有 函数 与万能的 table,它们是如何建立对应关系的呢?
数据交换,必须了解 lua_[push|to|is][*] 系列 API 函数。 例如: lua_pushnumber 将 c 语言 double 值压入栈成为 lua 认识的 number。 Lua 常见类型值:boolean,number,string,nil, none, cfunction, function, … 都可以处理。
1、Lua 表
如果你熟悉 javascript , lua 表和 javascript 表概念是没有区别的。javascript 每个实例有一个根表 window
而 Lua 是 _G
罢了。
例如: 在 javascript 中 window.x = 3
、x = 3
与 window["x"]=3
都是同样语义。
表就是一个 key-value 对集合的数据结构(字典)。表用 key 索引访问,索引一般是字符串或整数。虽然可以号称任意对象做索引,似乎也没有太多的必要。
操作 14-04,复杂数据练习:
point = { x = 10, y = 20 } -- Create new table
print(point["x"]) -- Prints 10
print(point.x) -- Has exactly the same meaning as line above. The easier-to-read
-- dot notation is just syntactic sugar.
其中,{...}
表示表,是一个字典。dot 符号称为“语法糖”,让人类读起来舒服一些,易于理解一些。
让案例再复杂点:
point = { x = 10, y = 20 }
point[1] = "string1"
point[2] = "string2"
point[5] = "string5"
point[-1] = nil
point[point] = { x = 30, y = 40 }
print(point[2],point["x"],#point,point[3],point,point[point].x)
string2 10 2 nil table: 0x15f1f10 30
其中,# 操作符是计算数组的大小。 为什么不是 5 是 2 呢 ?
在 lua 执行器运行以下代码:
point = { x = 10, y = 20 }
point[1] = "string1"
point[2] = "string2"
point[5] = "string5"
point[-1] = nil
point[point] = { x = 30, y = 40 }
print(point[2],point["x"],#point,point[3],point,point[point].x)
for i,v in pairs(point) do
if (type(i) ~= "table") then print ("pair:"..i.." = "..v) end
for i,v in ipairs(point) do
if (type(i) ~= "table") then print ("ipair:"..i.." = "..v) end
其中 ..
表示字符 cat 操作;pairs 是内置迭代函数。 pairs 迭代表中所有元素对, ipair 迭代从 1 开始连续的整数 key。 #
表示有 1,2 两个元素。
其实,这样赋值:
point = {"string1","string2",nil,x = 10, y = 20,nil,"string5" }
输出也一样。
在 Lua demo 有一个 dump 全局变量的 lua 案例程序 globals, lua 代码如下:
-- globals.lua
-- show all global variables
local seen={}
function dump(t,i)
seen[t]=true
local s={}
local n=0
for k in pairs(t) do
n=n+1 s[n]=k
table.sort(s)
for k,v in ipairs(s) do
print(i,v)
v=t[v]
if type(v)=="table" and not seen[v] then
dump(v,i.."\t")
dump(_G,"")
2、结构(记录)、数组的交换
Lua 应用程序 API 提供以下函数满足交换的要求。因为栈就只有 20 个单元,不可能把所有数据压上。数据处理过程如下:
假设栈内 index 位置元素是一个表(指针指向表), 常用 API 有 lua_getglobal, lua_createtable;判断 lua_istable
读表的方法 lua_getTable, 写表就是 lua_settable
下边的代码就是我们读一个 points 数组到 c#
void Start () {
// Create Lua State
IntPtr _L = LuaDLL.luaL_newstate ();
LuaDLL.luaL_openlibs(_L);
//read vector[]
String luaStr = @"_g = { {x=1,y=2},{x=3,y=4},{x=5,y=6} }";
LuaDLL.lua_dostring (_L, luaStr);
LuaDLL.lua_getglobal (_L, "_g");
int index = LuaDLL.lua_gettop (_L); // index of the table on the stack
Debug.logger.Log ("index = " + index.ToString ());
for (int i = 0; i < 3; i++) {
LuaDLL.lua_pushinteger (_L, i+1); // idx of array _g
LuaDLL.lua_gettable (_L, index); // get _g[idx] [-1, +1, e] on the top
int vind = LuaDLL.lua_gettop (_L); // index of the sub table on the stack
LuaDLL.lua_pushstring (_L, "x");
LuaDLL.lua_gettable (_L, vind);
v2 [i].x = (float)LuaDLL.lua_tonumber (_L, -1);
LuaDLL.lua_pushstring (_L, "y");
LuaDLL.lua_gettable (_L, vind);
v2 [i].y = (float)LuaDLL.lua_tonumber (_L, -1);
Debug.logger.Log ("vec y = " + v2[i].y.ToString ());
LuaDLL.lua_pop (_L, 3); // why pop(3)?
LuaDLL.lua_pop (_L, 1);
LuaDLL.lua_close (_L);
写一段程序,将一个 Vecter3 写到 Lua 中。
5.4 批量注册宿主的函数
1、c# 委托与 c 函数指针
Lua 只有 C 接口,那么 C# 的安全的“指针”,如何转为 c 的函数指针呢? 让我们分析 luaDLL.cs 的函数 lua_pushcfunction 的原代码:
public static void lua_pushcfunction(IntPtr luaState, LuaCSFunction function)
IntPtr fn = Marshal.GetFunctionPointerForDelegate(function);
lua_pushcclosure(luaState, fn, 0);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern void lua_pushcclosure(IntPtr l, IntPtr f, int nup);
Marshal 类解包得到非托管的函数指针。MSDN 描述是“提供了一个方法集合,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法” [。 可见,当我们享受托管带来的编程便利时,似乎忘记了底层关键技术,然而,要让 Lua 与 Unity 无缝交互,你需要知道语言的执行机理、编译技术、复杂的指针转换 …
Marshal 实现的托管对象 与 IntPtr 转换:
委托 与 函数指针
GetFunctionPointerForDelegate
GetDelegateForFunctionPointer
结构值(值类型) 与 非托管内存(* byte[])
StructureToPtr
PtrToStructure
SizeOf
// Create Lua State
IntPtr _L = LuaDLL.luaL_newstate ();
LuaDLL.luaL_openlibs(_L);
lua_register.RegisterLib(_L,"my_math", mathlib);
LuaDLL.lua_dostring (_L, @"
a = my_math.my_cos(1)
LuaDLL.lua_getglobal (_L, "a"); // push the lua var named ‘num’ on the stack
double ff = LuaDLL.lua_tonumber (_L, -1); // read return value on the top of stack
print( "watch output = " + ff );
LuaDLL.lua_pop(_L,1) ; // pop, your must maintain the stack carefully!!!
// Close Lua State
LuaDLL.lua_close (_L);
public static void RegisterLib (IntPtr luaState, string libName, LuaL_Reg[] lib) {
LuaDLL.lua_newtable (luaState); // Creates a new empty table and pushes it onto the stack. It is equivalent to lua_createtable(L, 0, 0).
int tableIdx = LuaDLL.lua_gettop(luaState);
foreach (LuaL_Reg reg in mathlib){
LuaDLL.lua_pushstring (luaState, reg.funcName); // set k
LuaDLL.lua_pushcfunction (luaState, reg.func); // set v = LuaCSFunction
LuaDLL.lua_settable(luaState, tableIdx); // index of table on the stack
LuaDLL.lua_setglobal (luaState, libName); // set to table
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
public static int my_math_sin (IntPtr L) {
LuaDLL.lua_pushnumber(L, Math.Sin(LuaDLL.luaL_checknumber(L, 1)));
return 1;
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
public static int my_math_cos (IntPtr L) {
LuaDLL.lua_pushnumber(L, Math.Cos(LuaDLL.luaL_checknumber(L, 1)));
return 1;
public static LuaL_Reg[] mathlib = {
new LuaL_Reg ( "my_cos", my_math_cos ),
new LuaL_Reg ( "my_sin", my_math_sin ),
public struct LuaL_Reg
public string funcName;
public LuaCSFunction func;
public LuaL_Reg(string name, LuaCSFunction f ) {
funcName = name;
func = f;
程序要点:
RegisterLib 创建一个表作为命名空间,将 C# 函数作为该表的成员函数
编写合格的静态函数,处理 lua 函数使用的调用栈,如 my_math_sin
6、lua 面向对象编程与互操作
Lua 没有类的概念,但可以使用 MetaTable 的特性实现面向对象编程。 它类似 JavaScript 的 ProtoType 机制,加上一些语法糖来方便表示面向对象的概念。
由于 Lua 和 C# 都是带有自动垃圾回收机制的语言,仔细管理对象的生命周期。这几乎是 C# 与 Lua 对象交互编程的噩梦,不小心就会导致非法引用或内存泄漏。 C# 是安全的托管语言, 这导致与 Lua 互操作的复杂性。如值类型(如结构)与引用类型(如对象)指针获取的差异。
Lua 中给个值可以有一个元表。 元表就是一个普通的Lua表,它定义了原始值某些特定操作行为。 例如,当非数字值进行加法操作,如果元表中有一个 __add
的函数,则会调用该函数。
如果访问值类型没有的字段或函数,则会触发事件,交给对应元表执行。
元函数:元表 __XXX
形式命名的函数成员是元函数。常用元函数包括:
__index
当访问值中不存在的成员时调用;
__newindex
当设置值中不存在的成员时调用;
__add
当操作值的 +
操作时调用;
其他运算,详见官方手册 metetable
使用 getmetatable
函数可以获得一个值的元表;
使用 setmetatable
函数你智能改变表类型的元表。 在 lua 中你不能改变其他类型的元表,除非你使用 C API。
除了 表 和 userdata 具有单独的元表(尽管多个表和userdata可以共享元表),所有其他类型的值每种类型共享一个 metatable; 也就是说,所有数字只有一个 metatable,用于处理 toString 等操作。
对于一个对象(值),我们可以定义它的方法(成员函数)在元表中。利用语法糖 :
定义方法,function table:func(args)
它等价于 function table.func(self, args)
。 使用该方法时 object:func(args)
实际调用是 ` object.func(object, args)`
6.2 lua 面向对象的实现方法
操作 14-05,lua 对象练习:
创建一个 vector 对象(值),它有元表 Vector,具体 Lua 代码是:
local Vector = {}
Vector.__index = Vector
function Vector:new(x, y, z) -- The constructor
return setmetatable({x = x, y = y, z = z}, Vector)
function Vector:magnitude() -- Another method
-- Reference the implicit object using self
return math.sqrt(self.x^2 + self.y^2 + self.z^2) --self is the vector not Vector
local vec = Vector:new(0, 1, 0) -- Create a vector
print(vec:magnitude()) -- Call a method (output: 1)
print(vec.x) -- Access a member variable (output: 0)
vector 和 Vector 都是普通表! ,当多个值的元表共享 Vector 时, Vector 就产生了类定义的效果。
修改以上代码,说明错误的原因。
注释第二句 --Vector.__index = Vector
,有错误吗? why?
在后面添加以下代码,会有期望的结果吗? 如何改?
local x = vec.new(3,4,0)
print(x.magnitude())
6.3 访问 unity 中的游戏对象
这里我们以游戏对象的访问,研究 lua 如何与面向对象语言互操作。以下代码展示了用 Lua 的 userdata 类型值指定一个 metatable,实现 lua 中操纵游戏面向的方法,具体 C# 代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Runtime.InteropServices;
using SLua;
public class AccessGameObject : MonoBehaviour {
// Use this for initialization
void Start () {
IntPtr _L = LuaDLL.luaL_newstate ();
LuaDLL.luaL_openlibs(_L);
RegisterGameObject (_L);
LuaDLL.lua_dostring (_L, @"
a = GameObject.Find('Cube')
-- GameObject.__index = GameObject
a:Move(3,4,0)
b = a.name
LuaDLL.lua_getglobal (_L, "b"); // push the lua var on the stack
print(" watch trieved attribution name = "+ LuaDLL.lua_tostring (_L, -1)); // read return value
LuaDLL.lua_pop(_L,1) ;
LuaDLL.lua_close (_L);
public static void RegisterGameObject (IntPtr luaState) {
LuaDLL.luaL_newmetatable (luaState, "GameObject"); //.Only metatable can be set to userdate
int tableIdx = LuaDLL.lua_gettop(luaState);
foreach (LuaL_Reg reg in gameObjectReg){
LuaDLL.lua_pushstring (luaState, reg.funcName); // set k
LuaDLL.lua_pushcfunction (luaState, reg.func); // set v = LuaCSFunction
LuaDLL.lua_settable(luaState, tableIdx); // index of table on the stack
LuaDLL.lua_setglobal (luaState, "GameObject"); // set to table
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
public static int gameObjectFind (IntPtr L) {
String name = LuaDLL.lua_tostring (L, -1);
print (name);
GameObject o = GameObject.Find (name);
if (o == null) {
LuaDLL.lua_pushnil (L);
return 1;
// https://stackoverflow.com/questions/17339928/c-sharp-how-to-convert-object-to-intptr-and-back
GCHandle handle = GCHandle.Alloc(o);
IntPtr parameter = (IntPtr) handle;
// set metatable for userdate
LuaDLL.lua_pushlightuserdata(L, parameter);
LuaDLL.luaL_getmetatable(L, "GameObject");
LuaDLL.lua_setmetatable (L, -2);
print ("o=" + o);
print ("g=" + parameter);
//handle.Free(); //free it before Lua release it, but we don't known!
return 1;
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
public static int gameObjectMove (IntPtr L) {
print (LuaDLL.lua_gettop (L));
float z = (float)LuaDLL.lua_tonumber (L, -1);
float y = (float)LuaDLL.lua_tonumber (L, -2);
float x = (float)LuaDLL.lua_tonumber (L, -3);
IntPtr parameter = LuaDLL.lua_touserdata (L, -4);
GCHandle handle = (GCHandle) parameter; // convert to original object
GameObject o = handle.Target as GameObject;
o.transform.position = new Vector3 (x, y, z);
return 0; // non return value
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
public static int GetValue (IntPtr L) {
string fname = LuaDLL.lua_tostring (L, -1); // stack 2
IntPtr parameter = LuaDLL.lua_touserdata (L, -2); // stack 1
print ("fname = " + fname);
LuaDLL.lua_getmetatable (L, -2); // meta stack 3
LuaDLL.lua_pushstring (L, fname); // meta stack 4
LuaDLL.lua_rawget (L, -2);
if (!LuaDLL.lua_isnil (L,-1)) { // get from meta
LuaDLL.lua_remove (L, -2); // remove metatable index
return 1;
// not found in meta
LuaDLL.lua_pop(L,2);
GCHandle handle = (GCHandle) parameter; // convert to original object
GameObject o = handle.Target as GameObject;
if (fname == "name") {
LuaDLL.lua_pushstring (L, o.name);
return 1;
LuaDLL.lua_pushnil (L);
return 1;
public static LuaL_Reg[] gameObjectReg = {
new LuaL_Reg ( "Find", gameObjectFind ),
new LuaL_Reg ( "Move", gameObjectMove ),
new LuaL_Reg ( "__index", GetValue ),
//new LuaL_Reg ( "__newindex", SetValue ),
public struct LuaL_Reg
public string funcName;
public LuaCSFunction func;
public LuaL_Reg(string name, LuaCSFunction f ) {
funcName = name;
func = f;
程序要点:
RegisterGameObject 创建一个元表对象
非表值的元表必须使用 luaL_newmetatable 创建元表
添加该类的成员函数或你自定义的函数的静态封装, 如 gameObjectMove
编写 Lua 对象生产函数
这里的案例是 gameObjectFind
第一步:处理参数,从左至右入栈
第二步:生产或使用已有游戏对象,转换 Object 到 Intptr
这里有一个潜在内存泄露点,GCHandle.Alloc 没有对应的 Free(关键是不能 Free)
注意,如果一个对象以 alloc 是否需要继续 alloc ???
第三步:将句柄入栈为 userdata 并添加元表
第四步:将对象返回 Lua
编写 Lua 对象成员函数
这里的案例 gameObjectMove
请先了解 :
语法糖的含义,因此栈底一定是 Lua 调用对象
第一步:处理参数,从左至右入栈
第二步:将 Lua 调用对象转为对应的 GameObject 实例
第三步:执行操作,返回参数
编写元表的 __index
和 __newindex
这里的案例 GetValue,它实现了 对象属性访问 与 成员函数的调用
如果没有 __index
元函数,Lua 对象将找不到它元表中的方法
第一步:处理参数,分别是 索引值、调用对象
索引值可能是字串、整数、或任意对象的值,一般只有前两种
调用对象只有可能是 userdata 或 table
第二步:取调用对象的元表,检查是否有该成员。如存在,直接返回该成员值
第三步:如果是该对象属性,取值并返回,否则返回 nil 或设置 LuaError
编写元表的 __gc
当 lua 垃圾收集启动,释放该对象时使用。 函数形式:function gc_event (udata)
Free 为该 Lua 对象分配的 Handle。 如何编程?
注意:并没有销毁对象!
其实编写上述程序非常困难,且 bug 很多,栈错误、栈中数据处理不当,都会导致游戏退出。不得不说,c# 托管对象节约了程序员很多时间,也不用太专注空间回收。好在这样的程序很有规律,可以用程序自动生成。
7、Lua 集成技术小结
以上描述了 Lua 与 C# 交互的主要关键技术。但这样编程太艰难了!
事实上,好早就有人做好了这一切。早期,luanet.dll 工具使用反射技术,实现了 C# 与 Lua 的无缝交互 。 以下是按时间组织的 Lua 与 Unity 交互的部分项目:
LuaInterface (2004~2009) http://luaforge.net/projects/luainterface/
Lua for Windows (2008~2010) http://luaforge.net/projects/luaforwindows/
NLua (2009~2014) http://nlua.org/ 或 https://github.com/NLua/NLua
CsToLua(2014.10~2015) https://github.com/topameng/CsToLua
ULua (2015~2016.3) http://ulua.org/ 或 https://github.com/topameng/tolua
SLua (2015~2016) https://github.com/pangweiwei/slua
如何选择开源项目?
活跃度 : 在 Github 上打开项目, 菜单 Insights -> Contributors 看 Commits
成熟度 : 分指数、release 数目
按上述标准,ULua 和 Slua 都是可选的!
如何阅读修改源代码?
从早期项目开始,如 CsToLua 作为起点,代码会简单一些。
然后,适当阅读一些博客
本文仅讲述了脚本集成技术的基础,离实战有较多距离,但可以帮助你走好未来的路。
8、作业与练习
1、检查你的学习成果
阅读以下博客
Unity3d中使用Lua
这个程序有没有 bug,如有请指出并解决它!
用好lua+unity,让性能飞起来——lua与c#交互篇
阅读这样的文章是不错的,但它的观点都正确吗?保持批判性思维,以自己的实践为基础讨论问题。人云我云,道听途说,是做工程的大忌!
个人认为,让 lua GC 释放对象指针的 GCHanle 成本小,内存比较可控。 因此文中观点 2 在 Unity 中不一定成立,因为设计合理的游戏程序几乎不销毁游戏对象。
你都赞成作者3-4的观点吗?请阅读以下 lua 访问 C# 结构类型 的交互程序(Vector3),指出感觉性能被掏空的要点
// Use this for initialization
void Start () {
IntPtr _L = LuaDLL.luaL_newstate ();
LuaDLL.luaL_openlibs(_L);
RegisterVector3 (_L);
LuaDLL.lua_dostring (_L, @"
local v = Vector3.New()
v.x = 3
v.y = 4
v1 = v.normalized
collectgarbage()
b = v1.x
LuaDLL.lua_getglobal (_L, "b"); // push the lua var on the stack
print(" watch trieved attribution name = "+ LuaDLL.lua_tostring (_L, -1)); // read return value
LuaDLL.lua_pop(_L,1) ;
LuaDLL.lua_close (_L);
public static void RegisterVector3 (IntPtr luaState) {
LuaDLL.luaL_newmetatable (luaState, "Vector3"); //.Only metatable can be set to userdate
int tableIdx = LuaDLL.lua_gettop(luaState);
foreach (LuaL_Reg reg in gameObjectReg){
LuaDLL.lua_pushstring (luaState, reg.funcName); // set k
LuaDLL.lua_pushcfunction (luaState, reg.func); // set v = LuaCSFunction
LuaDLL.lua_settable(luaState, tableIdx); // index of table on the stack
LuaDLL.lua_setglobal (luaState, "Vector3"); // set to table
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
public static int untiy_Vector3_New (IntPtr L) {
//there are some bug here for checking parameters
Vector3 v = new Vector3();
// https://msdn.microsoft.com/en-us/library/4ca6d5z7(v=vs.110).aspx
// Initialize unmanged memory to hold the struct.
IntPtr pnt = Marshal.AllocHGlobal(Marshal.SizeOf(v));
// Copy the struct to unmanaged memory.
Marshal.StructureToPtr(v, pnt, false);
// set metatable for userdate
LuaDLL.lua_pushlightuserdata(L, pnt);
LuaDLL.luaL_getmetatable(L, "Vector3");
LuaDLL.lua_setmetatable (L, -2);
print ("v=" + v);
print ("pnt=" + pnt);
//free pnt before Lua release it, but we don't known!
return 1;
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
public static int GetValue (IntPtr L) {
string fname = LuaDLL.lua_tostring (L, -1); // stack 2
IntPtr pnt = LuaDLL.lua_touserdata (L, -2); // stack 1
print ("fname = " + fname);
LuaDLL.lua_getmetatable (L, -2); // meta stack 3
LuaDLL.lua_pushstring (L, fname); // meta stack 4
LuaDLL.lua_rawget (L, -2);
if (!LuaDLL.lua_isnil (L,-1)) { // get from meta
LuaDLL.lua_remove (L, -2); // remove metatable index
return 1;
// not found in meta
LuaDLL.lua_pop(L,2);
// Create another Vector3 v.
// Set this Point to the value of the
// Point in unmanaged memory.
Vector3 v = (Vector3)Marshal.PtrToStructure(pnt, typeof(Vector3));
if (fname == "x") {
LuaDLL.lua_pushnumber (L, v.x);
return 1;
if (fname == "y") {
LuaDLL.lua_pushnumber (L, v.y);
return 1;
if (fname == "z") {
LuaDLL.lua_pushnumber (L, v.z);
return 1;
if (fname == "magnitude") {
LuaDLL.lua_pushnumber (L, v.magnitude);
return 1;
if (fname == "normalized") {
Vector3 v1 = v.normalized;
IntPtr p1 = Marshal.AllocHGlobal(Marshal.SizeOf(v1));
// Copy the struct to unmanaged memory.
Marshal.StructureToPtr(v1, p1, false);
// set metatable for userdate
LuaDLL.lua_pushlightuserdata(L, p1);
LuaDLL.luaL_getmetatable(L, "Vector3");
LuaDLL.lua_setmetatable (L, -2);
return 1;
LuaDLL.lua_pushnil (L);
return 1;
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
public static int SetValue (IntPtr L) {
//function(table, key, value)
string fname = LuaDLL.lua_tostring (L, -2); // stack 2
IntPtr pnt = LuaDLL.lua_touserdata (L, -3); // stack 1
print ("fname = " + fname);
// Create another Vector3 v.
// Set this Point to the value of the
// Point in unmanaged memory.
Vector3 v = (Vector3)Marshal.PtrToStructure(pnt, typeof(Vector3));
if (fname == "x") {
v.x = (float)LuaDLL.lua_tonumber (L, -1);
if (fname == "y") {
v.y = (float)LuaDLL.lua_tonumber (L, -1);
if (fname == "z") {
v.z = (float)LuaDLL.lua_tonumber (L, -1);
// Copy the struct to unmanaged memory.
Marshal.StructureToPtr(v, pnt, false);
return 0;
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
public static int ReleaseUdata (IntPtr L) {
IntPtr pnt = LuaDLL.lua_touserdata(L, -1); // get user data
print("release = " + pnt );
Marshal.FreeHGlobal(pnt);
return 0;
public static LuaL_Reg[] gameObjectReg = {
new LuaL_Reg ( "New", untiy_Vector3_New ),
new LuaL_Reg ( "__index", GetValue ),
new LuaL_Reg ( "__newindex", SetValue ),
new LuaL_Reg ( "__gc", ReleaseUdata ),
2、编程题
用搜索引擎搜 “luacomponent”,在不使用 Ulua 或 SLua 高层封装条件下,用 lua 代码完成游戏对象的简单运动,
例如: 在 5 秒内,一个 Cube 从 (0,0,0) 匀速移动到 (3,4,0) 。
要求: 编写一个简单 luaComponent C# 代码,在 update 中调用一个 lua 表的中 lua_update
函数, lua_update 会 调用 cube 对象的 SetPostion 方法。
课后参考阅读
Lua游戏AI编程入门 绝对是 NB 程序员必读!
Lua游戏开发实践指南 Lua 游戏入门指南