Tencent/xLua下载地址:https://github.com/Tencent/xLua
Lua文件的加载
通过LuaEnv类来执行字符串lua代码
- luaEnv.DoString(“lua代码”);
- luaEnv.DisPose(); //销毁lua代码
public class HelloWorld01 : MonoBehaviour {
private LuaEnv luaEnv;
void Start () {
luaEnv = new LuaEnv();
//luaEnv.DoString("print(\"Hello World!\")");
luaEnv.DoString(" CS.UnityEngine.Debug.Log('Hello World!') ");
}
void Update () {
}
private void OnDestroy()
{
luaEnv.Dispose(); //销毁
}
}
加载Lua文件的两种方式
1.通过TextAsset获取lua代码的文本资源:
TextAsset luaText = Resources.Load(“HelloWorld.lua”);
2.用lua的require函数即可
比如:DoString(“require ‘HelloWorld’”)
using UnityEngine;
using XLua;
public class HelloWorld02 : MonoBehaviour {
private LuaEnv luaEnv;
void Start () {
luaEnv = new LuaEnv();
//TextAsset luaText = Resources.Load<TextAsset>("HelloWorld.lua"); //HelloWorld.lua.txt,不区分大小写
//luaEnv.DoString(luaText.ToString());
luaEnv.DoString("require 'HelloWorld'"); //HelloWorld.lua.txt
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
自定义Loader
1.在xLua加自定义loader是很简单的,只涉及到一个接口:
public delegate byte[] CustomLoader(ref string filepath);
通过LuaEnv.AddLoader可以注册个回调,该回调参数是字符串(filePath为lua文件的路径)。lua代码里头调用require时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把filepath修改为真实路径传出。该回调返回值是一个byte数组,如果为空表示该loader找不到,则执行系统默认的loader,如果不为空则把返回的byte数组当做要执行的lua代码。
using UnityEngine;
using XLua;
public class HelloWorld03 : MonoBehaviour
{
private LuaEnv luaEnv;
void Start()
{
luaEnv = new LuaEnv();
luaEnv.AddLoader(myLoader);
luaEnv.DoString(" require 'HelloWorld' ");
}
private byte[] myLoader(ref string filePath)
{
Debug.Log(filePath); //输出lua文件名,不是真实路径
string s = "print('123')";
return System.Text.Encoding.UTF8.GetBytes(s); //输出"123" 不会执行HelloWorld.lua中的内容
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
2.将filepath修改为真实路径,并执行真实路径下的lua文件
注意:要将lua文件放置在Application.streamingAssetsPath支持的默认路径Assets/streamingAssets路径下.
using UnityEngine;
using System.IO;
using XLua;
public class HelloWorld04 : MonoBehaviour {
private LuaEnv luaEnv;
void Start()
{
luaEnv = new LuaEnv();
luaEnv.AddLoader(myLoader);
luaEnv.DoString(" require 'HelloWorld' ");
}
private byte[] myLoader(ref string filePath)
{
//Debug.Log(Application.streamingAssetsPath);
string path = Application.streamingAssetsPath + "/" + filePath + ".lua.txt";//真实路径
Debug.Log(path);
//return File.ReadAllBytes(path);
return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(path));
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
C#访问Lua
从Lua中获取一个全局基本数据类型
使用LuaEnv类中Gloabl.Get<数据类型>(“Lua中定义的数据名”)方法获取
如int a = luaEnv.Gloabl.Get< int >(“a”);
CSharpCallLua.lua.txt文件内容:
a = 100
str = “oop”
isRun = false
using UnityEngine;
using XLua;
public class CShapeCallLua : MonoBehaviour
{
private LuaEnv luaEnv;
void Start()
{
luaEnv = new LuaEnv();
luaEnv.DoString(" require 'CSharpCallLua' ");
int a = luaEnv.Global.Get<int>("a");
string str = luaEnv.Global.Get<string>("str");
bool isRun = luaEnv.Global.Get<bool>("isRun");
Debug.Log(a);
Debug.Log(str);
Debug.Log(isRun);
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
从Lua中访问一个全局的table
1.映射普通的class或者struct
要注意的是,映射的过程是值拷贝,如果class比较复杂代价会比较大。而且修改class的字段值不会同步到table,反过来也不会。
CSharpCallLua.lua.txt文件内容:
person = {
name = “cch”,
age = 20,
}
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour
{
private LuaEnv luaEnv;
void Start()
{
luaEnv = new LuaEnv();
luaEnv.DoString(" require 'CSharpCallLua' ");
Person person = luaEnv.Global.Get<Person>("person");
Debug.Log(person.name + person.age);
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
class Person {
public string name;
public int age;
}
2、映射到一个interface(推荐使用)
》在接口上使用[CSharpCallLua]特性,自动生成代码来对应Lua中的table字段
》table中函数的第一个字段默认为传过来来的对象自身,必须添加对应形参self,如:function(self,a,b)
CSharpCallLua.lua.txt文件内容:
person = {
name = “cch”,
age = 20,
eat = function(self,a,b)
print(a+b)
CS.UnityEngine.Debug.Log(“我正在吃饭”)
return a+b
end
}
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour
{
private LuaEnv luaEnv;
void Start()
{
luaEnv = new LuaEnv();
luaEnv.DoString(" require 'CSharpCallLua' ");
IPerson person = luaEnv.Global.Get<IPerson>("person");
Debug.Log(person.name + person.age);
Debug.Log(person.eat(10, 20));
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
[CSharpCallLua] //生成代码对应table的字段
interface IPerson
{
string name { set; get; }
int age { get; set; }
int eat(int a, int b);
}
3、更轻量级的by value方式:映射到Dictionary< string,T >,List< T >
Dictionary< string,T >只能映射键值对,List< T >智能映射单个数据类型,T是什么类型就会映射什么类型
CSharpCallLua.lua.txt文件内容:
person = {
name = “cch”,
age = 20,1,true,100.01,
eat = function(self,a,b)
print(a+b)
CS.UnityEngine.Debug.Log(“我正在吃饭”)
return a+b
end
}
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour
{
private LuaEnv luaEnv;
void Start()
{
luaEnv = new LuaEnv();
luaEnv.DoString(" require 'CSharpCallLua' ");
//映射到一个Dictionary<string,T>,T是什么类型就接受什么类型,且该映射只接受键值对
Dictionary<string, object> dict = luaEnv.Global.Get<Dictionary<string, object>>("person");
foreach (string key in dict.Keys)
{
Debug.Log(key + ":" + dict[key]);
}
//映射到一个List<T>,T是什么类型,就只会接受该类型数据,且该映射只接受单个数据类型,不接受键值对
List<object> list = luaEnv.Global.Get<List<object>>("person");
foreach (object obj in list)
{
Debug.Log(obj);
}
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
4.另外一种by ref方式:映射到LuaTable类
》这种方式好处是不需要生成代码,但也有一些问题,比如慢,比方式2要慢一个数量级,比如没有类型检查。
》luaTable.length //表示数组长度,不包括键值对
》luaTable.GetKeys(); //取得所有的key值,包括数组的索引(非键值对数据)
》luaTable.Get< keyT,valueT >(key); //通过key值取得value值
CSharpCallLua.lua.txt文件内容:
person = {
name = “cch”,
age = 20,
phone = “17879508158”,
1,
“lua”,
true,
100.01,
eat = function(self,a,b)
print(a+b)
CS.UnityEngine.Debug.Log(“我正在吃饭”)
return a+b
end
}
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour
{
private LuaEnv luaEnv;
void Start()
{
luaEnv = new LuaEnv();
luaEnv.DoString(" require 'CSharpCallLua' ");
LuaTable luaTable = luaEnv.Global.Get<LuaTable>("person");
Debug.Log(luaTable.Length); //luaTable.length表示数组长度,不包括键值对
foreach (object key in luaTable.GetKeys())
{
Debug.Log(key+":"+luaTable.Get<object,object>(key));
}
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
访问一个全局的function
1.映射到delegate(推荐使用)
[CSharpCallLua]
delegate int Add(int a,int b,out int resa,out int outb);
》当lua函数有多个返回值,使用out参数或者ref参数
》方法定义钱必须加上[CSharpCallLua]特性
CSharpCallLua.lua.txt文件内容:
add = function(a,b)
print(“add”)
return a+b,a,b
end
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour
{
private LuaEnv luaEnv;
void Start()
{
luaEnv = new LuaEnv();
luaEnv.DoString(" require 'CSharpCallLua' ");
//使用委托delegate映射lua中的函数
int resa, resb;
Add add = luaEnv.Global.Get<Add>("add");
int res = add(10, 20, out resa, out resb);
Debug.Log("res=" + res + ",resa=" + resa + ",resb=" + resb);
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
[CSharpCallLua]
delegate int Add(int a,int b,out int resa,out int outb);
2.映射到LuaFunction
缺点:性能不佳,性能不如delegate的映射
》使用XLua中提供的LuaFunction类映射
》LuaFunction上有个变参的Call函数,可以传任意类型,任意个数的参数,返回值是object的数组,对应于lua的多返回值。
CSharpCallLua.lua.txt文件内容:
add = function(a,b)
print(“add”)
return a+b,a,b
end
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour
{
private LuaEnv luaEnv;
void Start()
{
luaEnv = new LuaEnv();
luaEnv.DoString(" require 'CSharpCallLua' ");
//使用XLua中提供的LuaFunction类来映射lua中的函数
LuaFunction luaFunc = luaEnv.Global.Get<LuaFunction>("add");
object[] objs = luaFunc.Call(10, 30);
foreach (object obj in objs)
{
Debug.Log(obj);
}
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
[CSharpCallLua]
delegate int Add(int a,int b,out int resa,out int outb);
Lua调用C
以下是C#代码,不会变,各个小节贴上Lua代码
using UnityEngine;
using XLua;
public class LuaCallCSharp : MonoBehaviour {
private LuaEnv luaEnv;
void Start () {
luaEnv = new LuaEnv();
luaEnv.DoString(" require 'LuaCallCSharp' ");
}
private void OnDestroy()
{
luaEnv.Dispose();
}
}
Lua中写C#代码时,所有的类前要加上完整的命名空间CS
例如:CS.UnityEngine.Debug.Log(“这是在Lua里面写C#代码”);
new C#对象
C#中创建对象:GameObject newGameObj = new UnityEngine.GameObject();
LuaCallCSharp.lua.txt代码:
local newGameObject1 = CS.UnityEngine.GameObject()
--重载方法
local newGameObject2 = CS.UnityEngine.GameObject("LuaCreateNewGameObject")
访问C#静态属性,方法
1.读静态属性
CS.UnityEngine.Time.deltaTime
2.写静态属性
CS.UnityEngine.Time.timeScale = 0.5
3.调用静态方法
CS.UnityEngine.GameObject.Find(‘helloworld’)
4.如果需要经常访问的类,可以先用局部变量引用后访问,除了减少敲代码的时间,还能提高性能:
例如:local mainCamera = CS.UnityEngine.GameObject.Find(“Main Camera”)
mainCamera.name = “LuaCamera”
LuaCallCSharp.lua.txt代码:
--获取静态属性
local deltaTime = CS.UnityEngine.Time.deltaTime;
print(deltaTime)
--设置静态属性
CS.UnityEngine.Time.timeScale = 0.5
--调用静态方法
local mainCamera = CS.UnityEngine.GameObject.Find("Main Camera")
mainCamera.name = "LuaCamera"
访问C#成员属性,方法
local cube = CS.UnityEngine.GameObject.Find(“Cube”);
1.读成员属性
cube.name
2.写成员属性
cube.name = “luaCube”
3.调用成员方法
cube:GetComponent(“BoxCollider”);
4.重点:调用成员方法时,统一用冒号“:”,不用点“.”,用点必须把自身对象作为参数传递进去
–错误用法:cube.GetComponent(“BoxCollider”);
–正确用法1:cube.GetComponent(cube,”BoxCollider”);
–正确用法2:cube:GetComponent(“BoxCollider”);
--调用成员方法
--统一用冒号“:”,不用点“.”,用点必须把自身对象作为参数传递进去
local cube = CS.UnityEngine.GameObject.Find("Cube");
--错误:local cubeBoxCollider = cube.GetComponent("BoxCollider");
--正确:local cubeBoxCollider = cube.GetComponent(cube,"BoxCollider");
cube.name = "luaCube"
local cubeBoxCollider = cube:GetComponent("BoxCollider");
CS.UnityEngine.GameObject.Destroy(cubeBoxCollider)
父类属性,方法
xlua支持(通过派生类)访问基类的静态属性,静态方法,(通过派生类实例)访问基类的成员属性,成员方法
参数的输入输出属性(out,ref)
Lua调用测的参数处理规则:C#的普通参数算一个输入形参,ref修饰的算一个输入形参,out不算,然后从左往右对应lua 调用测的实参列表;
Lua调用测的返回值处理规则:C#函数的返回值(如果有的话)算一个返回值,out算一个返回值,ref算一个返回值,然后从左往右对应lua的多返回值。
重载方法
直接通过不同的参数类型进行重载函数的访问,例如:
testobj:TestFunc(100)
testobj:TestFunc(‘hello’)
将分别访问整数参数的TestFunc和字符串参数的TestFunc。
注意:xlua只一定程度上支持重载函数的调用,因为lua的类型远远不如C#丰富,存在一对多的情况,比如C#的int,float,double都对应于lua的number,上面的例子中TestFunc如果有这些重载参数,第一行将无法区分开来,只能调用到其中一个(生成代码中排前面的那个)
操作符
支持的操作符有:+,-,*,/,==,一元-,<,<=, %,[]
参数带默认值的方法
和C#调用有默认值参数的函数一样,如果所给的实参少于形参,则会用默认值补上。
可变参数方法
对于C#的如下方法:
void VariableParamsFunc(int a, params string[] strs)
可以在lua里头这样调用:
testobj:VariableParamsFunc(5, ‘hello’, ‘john’)
使用Extension methods
在C#里定义了,lua里就能直接使用。
泛化(模版)方法
不直接支持,可以通过Extension methods功能进行封装后调用。
枚举类型
枚举值就像枚举类型下的静态属性一样。
testobj:EnumTestFunc(CS.Tutorial.TestEnum.E1)
上面的EnumTestFunc函数参数是Tutorial.TestEnum类型的
另外,如果枚举类加入到生成代码的话,枚举类将支持__CastFrom方法,可以实现从一个整数或者字符串到枚举值的转换,例如:
CS.Tutorial.TestEnum.__CastFrom(1)
CS.Tutorial.TestEnum.__CastFrom(‘E1’)
delegate使用(调用,+,-)
C#的delegate调用:和调用普通lua函数一样
+操作符:对应C#的+操作符,把两个调用串成一个调用链,右操作数可以是同类型的C# delegate或者是lua函数。
-操作符:和+相反,把一个delegate从调用链中移除。
Ps:delegate属性可以用一个luafunction来赋值。
event
比如testobj里头有个事件定义是这样:public event Action TestEvent;
增加事件回调
testobj:TestEvent(‘+’, lua_event_callback)
移除事件回调
testobj:TestEvent(‘-‘, lua_event_callback)
64位整数支持
Lua53版本64位整数(long,ulong)映射到原生的64未整数,而luaji版本t,相当于lua5.1的标准,本身不支持64位,xlua做了个64位支持的扩展库,C#的long和ulong都将映射到userdata:
1、支持在lua里头进行64位的运算,比较,打印
2、支持和lua number的运算,比较
3、要注意的是,在64扩展库中,实际上只有int64,ulong也会先强转成long再传递到lua,而对ulong的一些运算,比较,我们采取和java一样的支持方式,提供一组API,详情请看API文档。
C#复杂类型和table的自动转换
对于一个有无参构造函数的C#复杂类型,在lua侧可以直接用一个table来代替,该table对应复杂类型的public字段有相应字段即可,支持函数参数传递,属性赋值等,例如:
C#下B结构体(class也支持)定义如下:
public struct A
{
public int a;
}
public struct B
{
public A b;
public double c;
}
某个类有成员函数如下:
void Foo(B b)
在lua可以这么调用
obj:Foo({b = {a = 100}, c = 200})
获取类型(相当于C#的typeof)
比如要获取UnityEngine.ParticleSystem类的Type信息,可以这样
typeof(CS.UnityEngine.ParticleSystem)
“强”转
lua没类型,所以不会有强类型语言的“强转”,但有个有点像的东西:告诉xlua要用指定的生成代码去调用一个对象,这在什么情况下能用到呢?有的时候第三方库对外暴露的是一个interface或者抽象类,实现类是隐藏的,这样我们无法对实现类进行代码生成。该实现类将会被xlua识别为未生成代码而用反射来访问,如果这个调用是很频繁的话还是很影响性能的,这时我们就可以把这个interface或者抽象类加到生成代码,然后指定用该生成代码来访问:
cast(calc, typeof(CS.Tutorial.Calc))
上面就是指定用CS.Tutorial.Calc的生成代码来访问calc对象。
后续边学习边更新…
(敬请期待)