XLua热更新三部曲
一、获取全局基本类型
可按照以下方法获得全局变量:
luaenv.Global.Get<int>("a")
luaenv.Global.Get<string>("b")
完整示例如下:
- lua文件代码
name = "fanfan"
age = 18
money = 19.9
isFemale = true
- c#获得lua文件里的变量 -- 关键代码
{
env = new LuaEnv();
env.AddLoader(HelloWorld.CustomMyLoader); // 调用方法
env.DoString("require 'LuaScripts'");
string name = env.Global.Get<string>("name");
int age = env.Global.Get<int>("age");
float money = env.Global.Get<float>("money");
bool isFemale = env.Global.Get<bool>("isFemale");
Debug.Log("我的名字是" + name + ", 今年" + age + "岁," + "我是女的吗:" + isFemale);
-- 输出 我的名字是fanfan, 今年18岁, 我是女的吗:True
}
- c#完整代码
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;
public class HelloWorld : MonoBehaviour
{
//XLua 的 环境核心类
LuaEnv env = null;
//定义调用unity的系统类
// string strLua = "CS.UnityEngine.Debug.Log('Hello World !')";
// Start is called before the first frame update
void Start()
{
env = new LuaEnv();
env.AddLoader(HelloWorld.CustomMyLoader); // 调用方法
env.DoString("require 'LuaScripts'");
string name = env.Global.Get<string>("name");
int age = env.Global.Get<int>("age");
float money = env.Global.Get<float>("money");
bool isFemale = env.Global.Get<bool>("isFemale");
Debug.Log("我的名字是" + name + ", 今年" + age + "岁," + "我是女的吗:" + isFemale);
}
public static byte[] CustomMyLoader(ref string fileName)
{
byte[] byArrayReture = null;
//脚本的路径
string luaPath = Application.dataPath + "/Scripts/LuaScripts/" + fileName + ".lua";
//读取lua路径中指定的lua文件
string strLuaContent = File.ReadAllText(luaPath);
//数据类型转换
byArrayReture = System.Text.Encoding.UTF8.GetBytes(strLuaContent);
return byArrayReture;
}
private void OnDestroy()
{
env.Dispose();//释放env
}
二、访问一个全局的table
2.1 映射到普通class或struct
在c#定义一个class,有对应于table的字段的public属性. 这种方式下xLua会帮你new一个实例,并把对应的字段赋值过去。
---lua 的table
fuirtList = {fruit1 = "orange", fruit2 = "banana", fruit3 = "peach", fruit4 = "apple"}
//c# 定义的class
public class FruitList
{
public string fruit1, fruit2, fruit3, fruit4;
}
//c#中获取table
void Start()
{
env = new LuaEnv();
env.AddLoader(HelloWorld.CustomMyLoader); // 调用方法
env.DoString("require 'LuaScripts'");
FruitList fruitList = env.Global.Get<FruitList>("fuirtList"); //输入表的名字"fuirtList"
Debug.Log(fruitList.fruit1 +" " + fruitList.fruit2 + " " +fruitList.fruit3 + " " +fruitList.fruit4);
//输出 orange banana peach apple
}
注意:过程是值拷贝,如果class比较复杂代价会比较大。而且修改class的字段值不会同步到table,反过来也不会(只读不能写)。
fruitList.fruit1 = "修改为猕猴桃";
env.DoString("print(fruit.fruit1)"); --输出还是"orange",没有改变
2.2 映射到接口(推荐)
注意:
- 需要打标签 [CSharpCallLua]
- 引用拷贝,应用广泛
简单表映射
[CSharpCallLua] //打标签后,在xlua插件中建立桥(1.clear generated code; 2.generate code)
public interface IFruitList
{
string fruit1 { get; set; }
string fruit2 { get; set; }
string fruit3 { get; set; }
string fruit4 { get; set; }
}
void Start()
{
env = new LuaEnv();
env.AddLoader(HelloWorld.CustomMyLoader); // 调用方法
env.DoString("require 'LuaScripts'");
//改成接口的名字
IFruitList fruitList = env.Global.Get<IFruitList>("fuirtList"); //输入表的名字"fuirtList"
Debug.Log(fruitList.fruit1 +" " + fruitList.fruit2 + " " +fruitList.fruit3 + " " +fruitList.fruit4);
//输出 orange banana peach apple
}
综合表映射
- lua定义综合表
gameUser = {
name = "simon",
age = 25,
ID ="8888",
speak = function()
print("玩家"..gameUser.name.."在说话")
end,
walking = function()
print("玩家"..gameUser.name.."在健身训练")
end,
calculate = function(this,num1,num2)
return this.age + num1 + num2
end
}
- c#代码(记得重新构造桥)
[CSharpCallLua] //打标签后,在xlua插件中建立桥(1.clear generated code; 2.generate code)
//定义复合型接口
public interface IGameUser
{
string name { get; set;}
int age { get; set; }
string ID { get; set; }
void speak();
void walking();
int calculate(int num1, int num2);
}
void Start()
{
env = new LuaEnv();
env.AddLoader(HelloWorld.CustomMyLoader); // 调用方法
env.DoString("require 'LuaScripts'");
IGameUser gameUser = env.Global.Get<IGameUser>("gameUser"); //输入表的名字"gameUser"
//输出显示属性
Debug.Log("我是" + gameUser.name + " 今年" + gameUser.age + "了" + ", 我的id是" + gameUser.ID); --输出 我是simon 今年25了, 我的id是8888
gameUser.speak(); -- 输出 LUA: 玩家simon在说话
gameUser.walking(); -- 输出 LUA: 玩家simon在健身训练
int temp = gameUser.calculate(100, 200);
Debug.Log("Lua计算结果是" + temp); -- 输出 Lua计算结果是325
}
使用接口,可以直接修改lua的值(引用拷贝)
Debug.Log(gameUser.name); -- simon
gameUser.name = "改成刘亦菲";
Debug.Log(gameUser.name); -- 改成刘亦菲
2.3 更轻量级的by value 方式:映射到Dictionary<>,List<>
注意:
- table下key和value的类型需一致。
- 适合获取变量,不适合处理方法(方法用接口获取)
- lua代码
fuirtList = {fruit1 = "orange", fruit2 = "banana", fruit3 = "peach", fruit4 = "apple"}
- c#代码
void Start()
{
env = new LuaEnv();
env.AddLoader(HelloWorld.CustomMyLoader); // 调用方法
env.DoString("require 'LuaScripts'");
Dictionary<string, string> fruitList = env.Global.Get<Dictionary<string, string>>("fuirtList"); //输入表的名字"fuirtList"
foreach(var value in fruitList.Values)
{
Debug.Log(value);
}
------输出----
orange
apple
peach
banana
}
2.4 by ref 方式,映射到LuaTable类
使用的lua表为 “gameUser”(见上文)
void Start()
{
env = new LuaEnv();
env.AddLoader(HelloWorld.CustomMyLoader); // 调用方法
env.DoString("require 'LuaScripts'");
//调用属性
LuaTable gameUser = env.Global.Get<LuaTable>("gameUser");
Debug.Log("name=" + gameUser.Get<string>("name"));
Debug.Log("age=" + gameUser.Get<string>("age"));
Debug.Log("ID=" + gameUser.Get<string>("ID"));
//调用方法
LuaFunction funSpeak = gameUser.Get<LuaFunction>("speak");
funSpeak.Call();
LuaFunction walking = gameUser.Get<LuaFunction>("walking");
walking.Call();
LuaFunction calculate= gameUser.Get<LuaFunction>("calculate");
object[] res = calculate.Call(gameUser, 10, 20);
Debug.Log("结果是" + res[0]);
}
三、访问一个全局的function
方法一: 映射到delegate(官方推荐)
- 优点:性能好,安全
- 缺点:带有out\ref的方法要生成代码
- 注意
- 含有out和ref关键字委托的 也要加标签
- 委托引用后,退出luaEnv前,要释放委托
- c#和unity的复杂类api,必须加入Xlua的配置文件,经过生成代码后才能正确使用。例如Action<int,int,int> , Func<int,int,int>
实例演示
lua代码
--定义函数
function myfunc1()
print("myfunc1 无参函数")
end
function myfunc2(num1, num2)
print("myfunc2 两个参数的和"..num1 + num2)
end
function myfunc3(num1, num2)
print("有返回值的函数")
return num1 + num2
end
function myfunc4(num1, num2)
print("由多个返回值的函数")
return num1 + num2, num1 - num2
end
c#调用
- 定义委托
- public delegate void delAdding3(int num1, int num2, out int res1, out int res2);
- delAdding3 act3 = null;
- 映射方法
- int res1 = 0; int res2 = 0;
- act3 = env.Global.Get("myfunc4");
- 方法调用(res1 = 20+30 = 50; res2 = 20-30=-10)
- act3(20, 30, out res1,out res2);
- Debug.Log(string.Format("res1 = {0}, res2= {1},", res1, res2));
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;
public class HelloWorld : MonoBehaviour
{
LuaEnv env = null;
//定义委托
Action act = null;
public delegate void delAdding(int num1, int num2);
delAdding act1= null;
[CSharpCallLua]
//有返回值的委托,要加标签
public delegate int delAdding2(int num1, int num2); // 有返回值
delAdding2 act2 = null;
[CSharpCallLua]
//有返回值的委托,要加标签,out,ref也是
public delegate void delAdding3(int num1, int num2, out int res1, out int res2); // 有返回值
delAdding3 act3 = null;
[CSharpCallLua]
//有返回值的委托,要加标签,out,ref也是
public delegate void delAdding4(ref int num1, ref int num2, out int res1, out int res2); // 有返回值
delAdding4 act4 = null;
// Start is called before the first frame update
void Start()
{
env = new LuaEnv();
env.AddLoader(HelloWorld.CustomMyLoader); // 调用方法
env.DoString("require 'LuaScripts'");
//通过系统action进行映射,得到lua中的方法
act = env.Global.Get<Action>("myfunc1");
act.Invoke();
//通过自定义的委托
act1= env.Global.Get<delAdding>("myfunc2");
act1(10, 20);
act2 = env.Global.Get<delAdding2>("myfunc3");
int res = act2(10, 20);
Debug.Log("结果是" + res);
//通过out关键字映射的多返回值函数
int res1 = 0;
int res2 = 0;
act3 = env.Global.Get<delAdding3>("myfunc4");
act3(20, 30, out res1,out res2);
Debug.Log(string.Format("res1 = {0}, res2= {1},", res1, res2));
//通过ref关键字映射的多返回值函数
int outref1 = 10;
int outref2 = 20;
int resultref1 = 0;
int resultref2 = 0;
act4 = env.Global.Get<delAdding4>("myfunc4");
act4(ref outref1, ref outref2, out resultref1, out resultref2);
Debug.Log(string.Format("outref1 = {0}, outref2= {1},resultref= {2},resultref= {3}", outref1 , outref2, resultref1, resultref2));
}
public static byte[] CustomMyLoader(ref string fileName)
{
byte[] byArrayReture = null;
//脚本的路径
string luaPath = Application.dataPath + "/Scripts/LuaScripts/" + fileName + ".lua";
//读取lua路径中指定的lua文件
string strLuaContent = File.ReadAllText(luaPath);
//数据类型转换
byArrayReture = System.Text.Encoding.UTF8.GetBytes(strLuaContent);
return byArrayReture;
}
private void OnDestroy()
{
//结束要置空
act = null;act1= null;act2 = null;act3 = null;act4 = null;
//释放env
env.Dispose();
}
}
方法二: 映射到LuaFunction (不推荐)
- 优点:不用生出代码
- 缺点:性能低
void Start()
{
env = new LuaEnv();
env.AddLoader(HelloWorld.CustomMyLoader); // 调用方法
env.DoString("require 'LuaScripts'");
//通过LuaFunction映射函数
LuaFunction luaFunction1 = env.Global.Get<LuaFunction>("myfunc1");
LuaFunction luaFunction2 = env.Global.Get<LuaFunction>("myfunc2");
LuaFunction luaFunction3 = env.Global.Get<LuaFunction>("myfunc3");
//调用由多个返回值的函数
LuaFunction luaFunction4 = env.Global.Get<LuaFunction>("myfunc4");
//使用方法
luaFunction1.Call();
luaFunction2.Call(10, 20);
object[] res3 = luaFunction3.Call(20, 30);
Debug.Log("luaFunction3 = " + res3);
object[] res4 = luaFunction4.Call(30, 40);
Debug.Log("luaFunction4 = " + res4[0] +" "+ res4[1] );
}
四、官方使用建议
- 访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
- 如果lua侧的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。