xLua是Unity3D下Lua编程解决方案,自2016年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能、易用性、扩展性而广受好评。现在,腾讯已经将xLua开源到GitHub。
2016年12月末,xLua刚刚实现新的突破:全平台支持用Lua修复C#代码bug。
目前Unity下的Lua热更新方案大多都是要求要热更新的部分一开始就要用Lua语言实现,不足之处在于:
xLua热补丁技术支持在运行时把一个C#实现(函数,操作符,属性,事件,或者整个类)替换成Lua实现,意味着你可以:
xLua插件下载地址:https://github.com/Tencent/xLua
创建工程并导入xLua插件
通过xLua插件运行lua程序
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class MyHelloWorld : MonoBehaviour {
void Start () {
// 创建lua环境
LuaEnv luaenv = new LuaEnv();
// 运行Lua代码
luaenv.DoString("print('Hello World')");
// 关闭Lua环境
luaenv.Dispose();
}
}
可以看到,输出了打印,前缀有Lua的标识表示这是由Lua中的方法执行的
反过来,也可以使用lua调用C#中的程序
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class MyHelloWorld : MonoBehaviour {
void Start () {
// 创建lua环境
LuaEnv luaenv = new LuaEnv();
// 运行Lua代码
//luaenv.DoString("print('Hello World')");
luaenv.DoString("CS.UnityEngine.Debug.Log('Hello World')");
// 关闭Lua环境
luaenv.Dispose();
}
}
这个时候,打印前就没有Lua标识符了,表示这是由C#中代码执行的
上面是C#和Lua之间的简单调用,但是在实际工作中,我们不可能这么写。我们的做法是写好Lua文件后,在C#中加载这个文件,然后使用其中的函数功能。
首先我们创建好一个Lua文件,然后在C#中加载后使用
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class MyHello : MonoBehaviour {
void Start () {
TextAsset t = Resources.Load("helloworld.lua");
LuaEnv luaenv = new LuaEnv();
luaenv.DoString(t.ToString());
luaenv.Dispose();
}
}
注意:在加载的时候,我们使用的是TextAsset文本格式,它默认识别的后缀为.txt,所以我们上面创建的lua文件后缀不是.lua,但是为了让我们方便的看出它是一个lua文件,所以取名的时候使用.lua.txt。
除了上面的加载方法外,更常用的方法是使用require加载
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class MyHello : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'helloworld'");
luaenv.Dispose();
}
}
require实际上是调一个个的loader去加载,有一个成功则不再往下尝试,全部失败则报文件找不到。目前Lua除了原生的loader外,还添加了从Resources加载的loader,需要注意的是Resources只支持有限的后缀,放在Resources下的lua文件需要加上.txt后缀。
我们发现上面的lua文件都是放在Resources文件夹下,因为原生的loader会在这个下面去加载。在我们的项目中,可能我们的lua文件放在自定义的文件夹下,这个时候就需要我们自定义loader,在xLua加自定义loader是很简单的,只涉及到一个接口:
public delegate byte[] CustomLoader(ref string filepath);
public void LuaEnv.AddLoader(CustomLoader loader)
通过AddLoader可以注册个回调,该回调参数是字符串,lua代码里头调用require时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把filepath修改为真实路径传出。该回调返回值是一个byte数组,如果为空表示该loader找不到,否则则为lua文件的内容。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using System.IO;
public class CreateNewLoader : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
// 自定义loader
luaenv.AddLoader(MyLoader);
luaenv.DoString("require 'newloaderText'");
luaenv.Dispose();
}
private byte[] MyLoader(ref string filePath)
{
string absPath = Application.streamingAssetsPath + "/" + filePath + ".lua.txt";
return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(absPath));
}
}
上面代码中我们定义的lua文件为“newloaderText.lua”,该文件位于“StreamingAssets”文件夹下,该文件夹与Assets文件夹同级,所以在后面设置路径的时候使用系统自带的函数“Application.streamingAssetsPath”可以找到该文件夹。当然,我们也可以自定义文件夹的位置,后面的路径改一下就行。
上面的执行过程,注册回调后,调用require的时候,将“newloaderText”传递给回调函数"MyLoader",在此回调函数中我们加载到指定文件然后传回来使用。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 获取lua中的全局变量
int num = luaenv.Global.Get("num");
string name = luaenv.Global.Get("name");
bool isPause = luaenv.Global.Get("isPause");
Debug.Log("num:" + num);
Debug.Log("name:" + name);
Debug.Log("isPause:" + isPause);
luaenv.Dispose();
}
}
使用函数LuaEnv.Global就能访问,其中,luaenv.Global.Get
注意:lua的table中的字段名和C#的class中的字段名要一一对应(名字也要相同),否则取不到值。此种方式为值拷贝,修改class的字段值不会同步到table,反过来也不会。使用此种方式,不能访问lua的函数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 获取lua中的全局table
Person p = luaenv.Global.Get("Person");
Debug.Log("name:" + p.name);
Debug.Log("age:" + p.age);
luaenv.Dispose();
}
class Person
{
public string name;
public int age;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 获取lua中的全局table(映射到interface)
Person_1 p1 = luaenv.Global.Get("Person");
Debug.Log("name:" + p1.name);
Debug.Log("age:" + p1.age);
p1.eat("apple");
luaenv.Dispose();
}
[CSharpCallLua]
interface Person_1
{
string name { get;set;}
int age { get; set; }
void eat(string str);
}
}
注意:在lua中定义函数的时候,第一个参数是arg,需要写上,名字随意取都行,这里写的self。在C#中定义接口的时候,要加上标签[CSharpCallLua]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 获取lua中的全局table(通过Dictionary)
Dictionary dict = luaenv.Global.Get>("Person");
foreach(string key in dict.Keys)
{
print("key:" + key + " value:" + dict[key]);
}
luaenv.Dispose();
}
}
注意:映射到Dictionary<>的时候,只映射了Lua中键值对的形式,普通的值没有映射过来
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 获取lua中的全局table(通过List)
List
注意:映射到List<>的时候,只映射了Lua中值的形式,键值对的形式没有映射过来
映射到LuaTable类:这种方式不常用,也不建议使用
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 访问lua中的全局函数(映射到delegate)
Add add = luaenv.Global.Get("add");
int res1 = 0; int res2 = 0;
int res = add(3, 4, out res1, out res2);
print("res:" + res);
print("res1:" + res1);
print("res2:" + res2);
add = null;
luaenv.Dispose();
}
[CSharpCallLua]
delegate int Add(int a, int b, out int res1, out int res2);
}
注意:使用delegate需要添加特性[CSharpCallLua],如果lua中函数返回多值,在C#中只能接收一个值,其它值从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数。
在C#这样new一个对象:
var newGameObj = new UnityEngine.GameObject();
对应到Lua是这样:
local newGameObj = CS.UnityEngine.GameObject()
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class LuaCallCSharp : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaCallCS'");
luaenv.Dispose();
}
}
如果需要经常访问的类,可以先用局部变量引用后访问,除了减少敲代码的时间,还能提高性能
读成员属性
testobj.DMF
写成员属性
testobj.DMF = 1024