[Xlua] C#调用Lua
阅前须知
由于本文为个人笔记,再加上本人喜欢将说明放在注释中,所以只有一些特别重要的内容才会写在代码块外部,整体的内容都是以代码为主
Lua调用C
1. LuaEnv
使用LuaEnv的DoString方法执行lua脚本
- 由于C#字符串使用
"String"所以在DoString中编写的lua代码需要使用 '' 包裹字符串 - 默认DoString在Resource文件夹下加载,不能加载lua文件,只能加载TextAsset,所以要给lua脚本加上
.txt后缀 - LuaEnv建议全局唯一
void Start()
{
//Lua解析器 能够让我们在Unity中执行Lua
//一般情况下 保持全局唯一
LuaEnv env = new LuaEnv();
//执行Lua语言
env.DoString("print('Hello World')");
/*
执行一个Lua脚本
多脚本执行使用require
默认寻找脚本的路径为Resources
因该是通过 Resources.Load加载Lua脚本 TextAsset等
所以Lua脚本加txt后缀
*/
env.DoString("require('Main')");
//清除Lua中没有手动释放的对象 垃圾回收
//帧更新中定时执行 或者 切场景时执行
env.Tick();
//销毁Lua解析器
env.Dispose();
}
2. CunstomLoader:自定义lua脚本加载器
利用CunstomLoder可以自定义lua脚本的加载方式,Xlua会按照顺序依次去尝试加载
- 添加CunstomLoder使用
env.AddLoader(MyCustomLoader); ,可以添加多个Loder
private byte[] MyCustomLoader(ref string fillPath)
{
string path = Application.dataPath + "/Lua/" + fillPath + ".lua";
Debug.Log($"load lua file from path: {path}");
if (!File.Exists(path))
{
Debug.LogError("Lua File is not found");
return null;
}
return File.ReadAllBytes(path);
}3. Global.Set()和Global.Get()
使用Global.Set()和Global.Get() 获取和设置_G表中的变量
使用方法
LuaMgr.GetInstance().Global.Set("testNumber", 55); int i2 = LuaMgr.GetInstance().Global.Get<int>("testNumber");支持double=float之类的自动转换,但因为lua中只有number,要注意溢出问题
double d = LuaMgr.GetInstance().Global.Get<double>("testFloat");- 获取变量的方式为值拷贝
4. [重要] C#调用Lua的Function
使用 委托或LuaFunction借助Global.Get<>()获取lua中的全局方法(_G)
lua代码如下:
testFun =function()
print('无参数无返回值方法')
end
testFun2=function(a)
print('有参数有返回值')
return a+1
end
testFun3 =function(a)
print("多返回值")
return a+1,10,false,"123",a
end
testFun4 =function (a,...)
print('变长参数' .. a)
arg={...}
for k,v in pairs(arg) do
print(k,v)
end
end对于无参数可无返回值的方法:
//对于无参数无返回值UnityAction、自定义delegate等都可以
Action action = LuaMgr.GetInstance().Global.Get<Action>("testFun");
LuaFunction lf3 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun3");对于有参数有返回值的方法:
- 添加CSharpCallLua特性
[CSharpCallLua]
public delegate int CustomCall2(int a);
- 采取相同的方式获取方法(注意返回值、参数要能对应)
CustomCall2 call2 = LuaMgr.GetInstance().Global.Get<CustomCall2>("testFun2");
Debug.Log("有参有返回" + call2(10));
- 在运行脚本前需要 Xlua->Generate Code
- 对于多返回值方法 使用ref、out
//out 必须在方法返回前被赋值
int b;
bool c;
string d;
int e;
Debug.Log("第一个返回值" + call3(100, out b, out c, out d, out e) + $",{b},{c},{d},{e}");
CustomCall4 call4 = LuaMgr.GetInstance().Global.Get<CustomCall4>("testFun3");
//ref需要先初始化,在作为参数传入,可以通过引用在方法中修改原有数
int b1 = 0;
bool c1 = false;
string d1 = "1";
int e1 = 0;
Debug.Log("第一个返回值" + call4(200, ref b, ref c, ref d, ref e) + $",{b1},{c1},{d1},{e1}");- 变长参数
CustomCall5 call5 = LuaMgr.GetInstance().Global.Get<CustomCall5>("testFun4");
//int为变长部分
call5("1", 1, 2, 3, 4, 5);LuaFunction(不建议使用,效率低)
LuaFunction lf3 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun3");
object[] objects = lf3.Call(300);
for (int i = 0; i < objects.Length; i++)
{
Debug.Log(objects[i]);
}5. C#使用List和Dictionary映射Table
这里的List和Dictionary其实代表的就是Lua的Table,对应我们将Table类比List和Dictionary去使用的情况(因为Lua中只有table)
- List用于映射没有自定义索引的表
- Dictionary用于映射有自定义索引的表
- 值拷贝
lua代码如下:
testList={1,2,3,4,5,6}
testList2={"123","123",true,1,1.2}
testDic={
["1"] = 1,
["2"] = 2,
["3"]=3,
["4"]=4,
}
testDic2={
["1"]=1,
[true]=1,
[false]=true,
["123"]=false
}C#代码如下
//同一类型List
List<int> list = LuaMgr.GetInstance().Global.Get<List<int>>("testList");
Debug.Log("*******************List************************");
for (int i = 0; i < list.Count; ++i)
{
Debug.Log(list[i]);
}
//值拷贝 浅拷贝 不会改变lua中的内容
list[0] = 100;
List<int> list2 = LuaMgr.GetInstance().Global.Get<List<int>>("testList");
Debug.Log(list2[0]);
//不指定类型 object
List<object> list3 = LuaMgr.GetInstance().Global.Get<List<object>>("testList2");
Debug.Log("*******************List object************************");
for (int i = 0; i < list3.Count; ++i)
{
Debug.Log(list3[i]);
}
Debug.Log("*******************Dictionary************************");
Dictionary<string, int> dic = LuaMgr.GetInstance().Global.Get<Dictionary<string, int>>("testDic");
foreach (string item in dic.Keys)
{
Debug.Log(item + "_" + dic[item]);
}
dic["1"] = 100000;
//值拷贝 不会改变lua中的内容
Dictionary<string, int> dic2 = LuaMgr.GetInstance().Global.Get<Dictionary<string, int>>("testDic");
Debug.Log(dic2["1"]);
Debug.Log("*******************Dictionary object************************");
Dictionary<object, object> dic3 = LuaMgr.GetInstance().Global.Get<Dictionary<object, object>>("testDic2");
foreach (object item in dic3.Keys)
{
Debug.Log(item + "_" + dic3[item]);
}输出符合预期,由于太长就不展示了
C#使用Class、Interface、LuaTable映射Lua的Table
后续映射的Table对应的lua代码均为:
testTable={
testInt=2,
testBool=true,
testFloat=1.2,
testString="123",
testFunc= function()
print("table s func")
end,
testFun2= function(a)
print("testFun2" .. a)
end,
testInTable={
testInInt=10
}
}使用Class映射Table
- 保证参数名一致
- 映射的类对应的参数、方法可多可少,例如可以增加额外的方法,或者减少对于某一个索引的映射(无非就是获取不到)
- 对于有参数或有返回值的方法仍然要使用
CsharpCallLua特性并Generate Code
C#代码:
[CSharpCallLua]
//有参数或有返回值的方法同样要添加特性
//记得Generate Code
public delegate void TestFun2(int a);
/// <summary>
/// 使用自定义类映射Table,变量名需一致
/// </summary>
public class CallLuaClass
{
//变量名需和lua保持一致
//必须是public,private和protected无法赋值
public int testInt;
public bool testBool;
public float testFloat;
public string testString;
public UnityAction testFunc;
public TestFun2 testFun2;
public TestInClass testInTable;
//Table映射类中的内容可多可少
public int testInt2 = 1;
public void Test()
{
Debug.Log($"testInt value is {testInt}");
}
}
public class TestInClass
{
public int testInInt;
}
public class Learn7_CallTable : MonoBehaviour
{
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");
//仍然是值拷贝
CallLuaClass callLuaClass = LuaMgr.GetInstance().Global.Get<CallLuaClass>("testTable");
Debug.Log(callLuaClass.testInt2);
callLuaClass.Test();
callLuaClass.testFun2(1000);
Debug.Log(callLuaClass.testInTable.testInInt);
}
}输出如下:
使用Interface映射Table
- 接口必须要使用
CsharpCallLua特性 - 对于Lua中的变量接口要使用属性来映射
- 非常不一样的一点就是,接口映射是引用拷贝!!!!
C#代码:
using UnityEngine;
using UnityEngine.Events;
using XLua;
/// <summary>
/// 接口映射Table
/// </summary>
//1.如果使用接口映射表需使用Attribute
//2.属性和方法同样可少可多
//3.如果接口结构更改,需要清空Xlua代码,重新生成
[CSharpCallLua]
public interface CsharpCallInterface
{
int testInt { get; set; }
bool testBool { get; set; }
float testFloat { get; set; }
string testString { get; set; }
UnityAction testFunc { get; set; }
}
public class Learn8_CallInterface : MonoBehaviour
{
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");
CsharpCallInterface callLuaClass = LuaMgr.GetInstance().Global.Get<CsharpCallInterface>("testTable");
callLuaClass.testFunc();
Debug.Log(callLuaClass.testInt);
callLuaClass.testInt = 10000;
//使用接口映射Table 引用拷贝!
CsharpCallInterface callLuaClass2 = LuaMgr.GetInstance().Global.Get<CsharpCallInterface>("testTable");
Debug.Log(callLuaClass2.testInt);
}
}
输出:
使用LuaTable(不推荐,效率低)
- LuaTable使用完要使用Dispose显示释放
- 仍然为引用拷贝
using UnityEngine;
using XLua;
/// <summary>
/// 不推荐使用LuaTable,效率低
/// </summary>
public class Learn9_CallLuaTable : MonoBehaviour
{
void Start()
{
LuaMgr.GetInstance().Init();
LuaMgr.GetInstance().DoLuaFile("Main");
LuaTable table = LuaMgr.GetInstance().Global.Get<LuaTable>("testTable");
Debug.Log(table.Get<int>("testInt"));
Debug.Log(table.Get<bool>("testBool"));
Debug.Log(table.Get<float>("testFloat"));
Debug.Log(table.Get<string>("testString"));
table.Get<LuaFunction>("testFunc").Call();
//引用拷贝
table.Set("testInt", 1000);
LuaTable table2 = LuaMgr.GetInstance().Global.Get<LuaTable>("testTable");
Debug.Log(table2.Get<int>("testInt"));
//显示释放
table.Dispose();
table2.Dispose();
}
}总结
CsharpCallLua特性的使用
- 有参数或有返回值的自定义委托
- 接口
引用拷贝
- 接口映射Table
- LuaTable映射Table
本文是关于Xlua框架中C#调用Lua的详细指南,主要内容包括:
- LuaEnv基础:介绍如何使用LuaEnv解析器执行Lua脚本,注意事项如字符串包裹方式、加载路径限制等。
- 自定义加载器:通过CustomLoader实现自定义Lua脚本加载路径和方式,支持多加载器顺序尝试。
- 全局变量交互:使用Global.Set()/Get()方法在C#和Lua的_G表间传递数据,注意值拷贝和类型转换问题。
函数调用:重点讲解四种Lua函数调用场景:
- 无参无返回值:直接使用Action委托
- 有参有返回值:需添加[CSharpCallLua]特性并生成代码
- 多返回值:通过out/ref参数获取
- 变长参数:使用LuaFunction处理
文中包含大量代码示例和关键注释,特别强调Xlua的代码生成步骤和类型安全注意事项。











2 条评论
Very good!