XLua基础
Xlua是腾讯研发的一款Lua开源插件,为Unity、 .Net、 Mono等C#环境增加Lua脚本编程的能力,借助xLua,这些Lua代码可以方便的和C#相互调用,在游戏中,该技术多用于热更新。可以在GitHub上搜索XLua进行下载,如果网速太慢,也可以在gitee上下载
C#执行Lua脚本
我们在学习每一个课程的时候,最先接触的都是“Hello world”,在XLua中,如何执行该语句呢?将下载的Xlua项目打开,并且创建一个新的脚本,引入XLua命名空间,并挂载到游戏物体。
//声明一个Lua虚拟机
public static LuaEnv lua;
private void Start()
{
//对虚拟机进行实例化
lua = new LuaEnv();
//用C#的UnityEngine.Debug.Log打印日志
lua.DoString("CS.UnityEngine.Debug.Log('Hello world')");
lua.Dispose();
}
此时,运行项目,可以看到在控制台中打印出了 Hello world
Tips💁♂:一个LuaEnv实例对应Lua虚拟机,出于开销的考虑,建议全局唯一。
通过Lua.DoString
函数,我们可以在Lua语言中快速执行C#代码,但是当Lua脚本内容较多时,将不是很方便执行,这时可以将脚本写在Lua文件中,并C#中执行.
首先在Unity中创建Resources
文件夹,并在里面创建一个txt文本文件,命名为"Demo1.lua.txt"(其要求的是一个文本文档,文件名中的.lua并没有其他含义,仅仅是起标识作用),在文本中加入CS.UnityEngine.Debug.Log("Hello world,load file")
,接下来加载该文件
private void Start()
{
//对虚拟机进行实例化
lua = new LuaEnv();
//方式一:通过TextAsset加载
TextAsset asset = Resources.Load<TextAsset>("LuaDemo1.lua");
lua.DoString(asset.text);
//方式二:通过Require加载(常用),只能使用Resources与内置的路径的lua文件,不能自定义路径
lua.DoString("require 'LuaDemo1'");
//方式三:自定义Loader加载,使用Require加载有不能自定义路径等缺点,通过自定义Loader则可以解决上述问题
lua.AddLoader(DemoLoader);
lua.DoString("require 'LuaDemo1'");
}
//自定义Loader函数,执行DoString时会自动调用该函数,里面可以实现自己的逻辑
private byte[] DemoLoader(ref string filepath)
{
string path = filepath + ".lua";
TextAsset asset = Resources.Load<TextAsset>(path);
return asset.bytes;
}
获取Lua中的变量
首先我们修改lua文本文档LuaDemo1
中的内容,定义下面两个变量
x = 123;
y = "456"
接下来在C#中获取x和y的值,Global是Lua中的一个全局表,里面包含了所有的全局变量
lua.DoString("require 'LuaDemo1'");
//获取Lua中的值
int x = lua.Global.Get<int>("x");
string y = lua.Global.Get<string>("y");
Debug.Log(x + " , " + y); //打印 123 , 456
//修改Lua中的值
lua.Global.Set("x", 321);
Debug.Log(lua.Global.Get<int>("x")); //打印 321
获取Lua中的Table
如果Lua脚本中包含Table,又该怎么调用呢?我们在Lua文本中添加Table,如下所示
x = 123;
y = "456"
--定义person表
person =
{
name = "slayer",
age = 8,
"only string",
999,
speak = function()
print("this is a function in Person")
end
}
--通过.的方式为person表添加函数
function person.sayName(self)
print(self.name)
end
--通过:的方式为person表添加函数 等价于 person.getAge(self)
function person:getAge()
print(self.age)
end
C#若要获取Lua中Table值,可以通过类或者接口建立映射,然后获取其值,下面展示如何使用接口获取
private void Start()
{
lua.DoString("require 'LuaDemo1'");
//上面person表中,对于"only string",999这样单独的值,可以通过List建立映射
List<object> list = new List<object>();
list = lua.Global.Get<List<object>>("person");
foreach (var val in list)
{
Debug.Log(val); //分别打印"only string",999
}
IPersonal person = lua.Global.Get<IPersonal>("person");
Debug.Log(person.name); //输出 "slayer"
Debug.Log("Sum:" + person.getSum(12, 34)); //输出 46
}
//定义IPersonal接口,注意这里必须使用 CSharpCallLua 标签
//并且里面的属性和方法要和Lua脚本中的person表对应
[CSharpCallLua]
public interface IPersonal
{
string name { get; set; }
string age { get; set; }
void sayName();
int getSum(int num1, int num2);
}
获取Lua中的函数
我们先在Lua脚本中定义两个函数,如下所示:
function m_Print(msg)
print(msg)
end
function Add(a,b)
return a + b
end
要在C#中调用这两个函数,需要使用委托,对于没有返回值的,可以使用C#自带的Action,有返回值则使用Func,当然也可以自定义委托
lua.DoString("require 'LuaDemo1'");
//通过Action获取Lua中的映射,并执行该函数
Action<string> mPrint = lua.Global.Get<Action<string>>("m_Print");
mPrint("Hello world");
Func<int, int, int> add = lua.Global.Get<Func<int, int, int>>("Add");
int ans = add(1, 2);
Debug.Log("Add ans:" + ans); //输出 3
错误信息:InvalidCastException: This type must add to CSharpCallLua:XXXX
执行上述代码时,可能会出现上述错误,该错误表面当前使用的类型没有加上CSharpCallLua
标签,此时我们需要将该标签添加到对应的地方,正常情况下错误将会消失.但有时候,明明添加了该标签,还是提示这个错误,这时通过下面三个步骤一般都可以解决.
-
方式一:在Unity的Player Setting中将API Compatibility Level设置为 .NET 4.X
-
方式二:点击XLua -> Clear Generated Code -> Generated Code
-
方式三:在Assets文件夹中,找到XLua -> Examples,找到ExampleGenConfig.cs文件,在CSharpCallLua中按照格式添加你要调用的方法
再不行就只能换一个版本重试了.
XLua调用C#静态方法
在上一节中我们学习了怎么通过C#调用XLua打印“Hello world”,那么XLua如何调用C#呢?一行代码轻松搞定
首先创建一个txt文件保存XLua脚本内容,我们命名为“LuaDemo2.lua.txt”
,里面的内容如下
--调用C#中Debug.Log函数
--Lua通过CS来访问C#的代码,后面的UnityEngine为命名空间
--CS.UnityEngine.Debug.Log("Hello world")
--在写代码时经常需要调用CS.UnityEngine里面的函数,为了方便我们可以定义一个变量将其保存起来
--使用local关键字定义一个局部变量 下面代码实现的效果与上面相同
local cu = CS.UnityEngine
cs.Debug.Log("Hello world")
--调用其他方法、字段也与之类似,例如
--print(cu.Time.deltaTime)
要执行这行代码,我们创建一个C#脚本,并执行Lua文件
lua.DoString("require 'LuaDemo2'"); //打印Hello world
XLua访问自定义的C#类
在C#中定义一个名为People的类,内容如下:
public class People
{
public string name;
public int age;
public void Show()
{
Debug.Log(name + ":" + age);
}
}
接下来在XLua中对其进行访问
--找到People类并对其进行实例化,等价于:
--local people = CS.People
--people = people()
local people = CS.People()
people.name = "taylor"
people.age = 23
people:Show() --打印 taylor:23
XLua查找游戏对象
在unity场景中创建一个Cube,怎么在Lua中修改它的名字呢?
local cug = CS.UnityEngine.GameObject
local cube = cug.Find("Cube")
cube.name = "Change"
调用脚本后可以看到,场景中的Cube
成功改名为Change
XLua实现Unity内置函数,如Awake,Update...
先在Lua脚本中定义一个start函数,函数名称随意
function luaStart()
print("this is lua start")
end
在C#中调用它,对于lua元表,简单说明一下,点击查看
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:
1.在表中查找,如果找到,返回该元素,找不到则继续
2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
3.判断元表有没有 index 方法,如果 index 方法为 nil,则返回 nil;如果 index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。
private void Start()
{
//加载上面的lua脚本
asset = Resources.Load<TextAsset>("LuaDemo2.lua");
//定义一个table
LuaTable table = lua.NewTable();
//为table设置元表
LuaTable metaTable = lua.NewTable();
metaTable.Set("__index", lua.Global);
table.SetMetaTable(metaTable);
metaTable.Dispose();
table.Set("self", this);
lua.DoString(asset.text, "LuaDemo2.lua", table);
//通过Action获取lua中的函数
Action _luaStart = table.Get<Action>("luaStart");
//执行,Awake和Update类似,只是在不同的地方调用,例如如果实现了_luaUpdate则在Update函数中调用
_luaStart();
}
XLua控制UI事件
修改前面的luaStart函数为下所示,(注意,C#调用的脚本需要挂在Button按钮上,因为文中使用了self:GetComponent)
--通过lua为unity中的ui添加事件
function luaStart()
print("This is lua Start")
input = CUG.Find("InputField")
--查找到按钮并且为按钮添加事件
self:GetComponent("Button").onClick:AddListener(function()
val = input:GetComponent("InputField").text
if(val == "")
then
--print("Can't input nil")
input:GetComponent("InputField").text = "Can't input nil"
else
--print("clicked, you input is '" ..val .."'")
input:GetComponent("InputField").text = "clicked, you input is '" ..val .."'"
end
end)
end