[XLua热更新]c#调用Lua

644 阅读6分钟

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<>

注意:

  1. table下key和value的类型需一致。
  2. 适合获取变量,不适合处理方法(方法用接口获取)
  • 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的方法要生成代码
  • 注意
    1. 含有out和ref关键字委托的 也要加标签
    2. 委托引用后,退出luaEnv前,要释放委托
    3. 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#调用

  1. 定义委托
    • public delegate void delAdding3(int num1, int num2, out int res1, out int res2);
    • delAdding3 act3 = null;
  2. 映射方法
    • int res1 = 0; int res2 = 0;
    • act3 = env.Global.Get("myfunc4");
  3. 方法调用(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] );
}

四、官方使用建议

  1. 访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
  2. 如果lua侧的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。