Unity-Xlua

1. Xlua

1. 简介
  1. Xlua下载地址:https://github.com/Tencent/xLua 在右侧 Releases 中获取
  2. 导入包中 Assets 文件夹下的Plugins和XLua
2. 基本使用
  1. LuaEnv 类用于在Unity中运行lua代码
private LuaEnv luaEnv;
void Start()
{
    
    
    // 创建 Lua 虚拟机
    luaEnv = new LuaEnv();
    // 运行 lua 代码
    luaEnv.DoString(@"print('Hello xlua')");
    // 运行 Unity 代码
    luaEnv.DoString(@"CS.UnityEngine.Debug.Log('Hello Unity')");
}

private void OnDestroy()
{
    
    
    // 释放
    luaEnv.Dispose();
}
  1. Resources目录下可加载lua代码并执行,因为lua后缀文件并不能让unity识别,所以后缀需改为lua.text
  2. 利用lua指令 require 'path' ,可在Resources目录下自动加载后缀为lua.text的文件
//创建 LuaEnv 类
LuaEnv luaEnv = new LuaEnv();
// 1. 加载并运行
TextAsset ta = Resources.Load<TextAsset>("hello");
luaEnv.DoString(ta.text);
// 2. 自动加载运行
luaEnv.DoString("require 'hello'");
3. 通过Loader自定义require加载路径
  1. 使用方法 AddLoader 添加加载路径函数,返回值为脚本数据,入参为 require 路径
  2. 使用 File.ReadAllText(path) 获取路径下脚本文本,使用 System.Text.Encoding.UTF8.GetBytes(data) 将文本转化为字节数组
void Start()
{
    
    
    // 创建 LuaEnv 类
    LuaEnv luaEnv = new LuaEnv();
    luaEnv.AddLoader(MyLoader);
    luaEnv.DoString("require 'stream'");
    // 释放
    luaEnv.Dispose();
}

private byte[] MyLoader(ref string filePath)
{
    
    
    string path = Application.streamingAssetsPath + "/" + filePath + ".lua.txt";
    print(path);
    string data = File.ReadAllText(path);
    return System.Text.Encoding.UTF8.GetBytes(data);
}
4. C#访问Lua
  1. 获取Lua中的全局变量 luaEnv.Global.Get<T>(string)
// 必须先运行脚本,内存中生成变量后方可获取数值
// 创建 LuaEnv 类
LuaEnv luaEnv = new LuaEnv();
// 2. 自动加载运行
luaEnv.DoString("require 'hello'");

// 仅当为整数时可正常获取,否则返回0
int iValue = luaEnv.Global.Get<int>("value");
float fValue = luaEnv.Global.Get<float>("value");
string data = luaEnv.Global.Get<string>("data");
bool flag = luaEnv.Global.Get<bool>("flag");
  1. 获取Lua中的table
    1. 用类获取(获得的是复制份,修改并不能更改原表
    LuaData luaData = luaEnv.Global.Get<LuaData>("table");
    public class LuaData
    {
          
          
        public int value;
        public string data;
        public bool flag;
    }
    
    1. 接口(接口为public且添加特性CSharpCallLua,若发现仍无法正常运行,可在 Xlua 栏下 -> Clear Generated Code -> Generated Code
    ILuaData luaData = luaEnv.Global.Get<ILuaData>("table");
    luaData.value = 234;
    luaData.say(2);
    
    [CSharpCallLua]
    public interface ILuaData
    {
          
          
        int value {
          
           get; set; }
        string data {
          
           get; set; }
        bool flag {
          
           get; set; }
        void say(int time);
    }
    
    table = {
          
          
        value = 100,
        data = "liluo",
        flag = false,
    }
    function table:say(time)
        print(self.data .. "第" .. time .. "次" .. "说")
    end
    
    1. 字典和列表
    // 返回键值对数据
    Dictionary<string, object> luaData = luaEnv.Global.Get<Dictionary<string, object>>("table");
    // 返回单纯的列表数据
    List<object> luaList = luaEnv.Global.Get<List<object>>("table");
    
    1. LuaTable
    LuaTable tab = luaEnv.Global.Get<LuaTable>("table");
    print(tab.Get<string>("data"));
    print(tab.Get<int>("value"));
    print(tab.Get<bool>("flag"));
    foreach (var key in tab.GetKeys())
        print(tab.Get<string, object>(key.ToString()));
    
5. C#访问Lua中的全局function
  1. Action 方式
luaEnv= new LuaEnv();
Action<string> act = luaEnv.Global.Get<Action<string>>("Add");
act("I say");
act = null;
// 若采用自定义委托
[CSharpCallLua]
delegate int Add(int a, int b);
function Add(data)
    print(data)
end
  1. 自定义委托
    需为委托添加特性 CSharpCallLua
Add act = luaEnv.Global.Get<Add>("Add");
int a, b;
// 额外的返回值用out接收
print(act(2, 3, out a, out b));
print(a + ", " + b);
act = null;
// 应为public,若发现仍无法正常运行,可 Generated Code
[CSharpCallLua]
public delegate int Add(int a, int b, out int resA, out int resB);
function Add(a, b)
    return a + b, a, b
end
  1. LuaFunction
// 创建 LuaEnv 类
luaEnv = new LuaEnv();
LuaFunction func = luaEnv.Global.Get<LuaFunction>("Add");
// 获得返回值列表
object[] os = func.Call(1, 2);
foreach (object data in os)
{
    
    
	print(data);
}
function Add(a, b)
    return a + b, a, b
end
6. Lua访问C#
-- 新建对象
CS.UnityEngine.GameObject()
CS.UnityEngine.GameObject("obj")
-- 访问成员属性和方法
print(CS.UnityEngine.Time.deltaTime)
CS.UnityEngine.Time.timeScale = 0.5
local camera = CS.UnityEngine.GameObject.Find("Main Camera")
camera.name = "luaupdate"
CS.UnityEngine.GameObject.Destroy(camera:GetComponent("Camera"))

注意:对于非静态对象,调用成员方法,第一个参数需要传该对象,建议用冒号语法糖,如下

testobj:DMFunc()

2. Xlua热更新

1. 启用热更新
  1. 想要启用热更新,首先要在项目设置的玩家中添加脚本定义符号 HOTFIX_ENABLE 并应用,这时Xlua选单中就会额外出现一个 Hotfix Inject In Editor (注意:各平台需要分别设置)
    在这里插入图片描述
  2. 添加 Xlua 中的 Tools 文件夹到 Assets 同级目录下
  3. 重新生成lua代码 (Xlua 栏下 -> Clear Generated Code -> Generated Code)
  4. 点击Xlua选单中的 Hotfix Inject In Editor 注入代码到编辑器
  5. 添加程序集:将 Unity\2020.3.8f1c1\Editor\Data\Managed 路径下的 Unity.Cecil.Pdb.dll Unity.Cecil.Mdb.dll Unity.Cecil.dll 三个配置文件加入工程的 Assets\XLua\Src\Editor目录下
2. 补丁开发过程
  1. 首先开发业务代码
  2. 在所有可能出现问题的类上打上hotfix的标签,在所有lua调用CSharp的方法上打上LuaCallCSharp,在所有CSharp调用Lua的方法上打上CSharpCallLua
  3. 修改bug时只需要更新Lua文件,修改资源时(声音,模型,贴图,图片,UI)只需要更新ab包。用户只需要去下载lua文件跟ab包。
3. 补丁应用实例
  1. 注意:补丁注册必须要在所有脚本之前,否则无法保证补丁应用成功!(比如对Start函数的修改)

  2. 想要添加补丁,需要为想要修改的类添加 Hotfix 热更新特性,再为将要修改的函数添加 LuaCallCSharp 特性,该类便可被热更新捕获
    编写lua代码,调用函数 xlua.hotfix(CS.class, 'functionName', function(self)) 更新函数,其中class为添加 Hotfix 热更新特性的类,functionName为添加 LuaCallCSharp 特性的函数名,function为替换的函数,self为类本身

    [Hotfix]
    public class Treasour : MonoBehaviour
    {
          
          
        public GameObject gold;
        public GameObject diamands;
        public Transform cavas;
        [LuaCallCSharp]
        private void CreatePrize()
        {
          
          
            for (int i = 0; i < 5; i++)
            {
          
          
                GameObject go = Instantiate(gold, transform.position + new Vector3(-10f + i * 30, 0, 0), transform.rotation);
                go.transform.SetParent(cavas);
                GameObject go1 = Instantiate(diamands, transform.position + new Vector3(0, 30, 0) + new Vector3(-10f + i * 30, 0, 0), transform.rotation);
                go1.transform.SetParent(cavas);
            }
        }
    }
    
    xlua.hotfix(CS.Treasour, 'CreatePrize', function(self)
        local GameObject = CS.UnityEngine.GameObject
        for i = 0, 4, 1 do
            local go = GameObject.Instantiate(self.gold, self.transform.position + CS.UnityEngine.Vector3(-10 + i * 100, 0, 0), self.transform.rotation);
            go.transform:SetParent(self.cavas)
            local go1 = GameObject.Instantiate(self.diamands, self.transform.position + CS.UnityEngine.Vector3(0, 30, 0) + CS.UnityEngine.Vector3(-10 + i * 100, 0, 0), self.transform.rotation);
            go1.transform:SetParent(self.cavas);
        end
    end)
    
  3. 使用完毕后,在 Dispose 虚拟机前应释放所有引用,hotfix引用可用 xlua.hotfix(CS.class, 'functionName', function(nil)) 进行释放

  4. 若发生访问不到的私有成员,可用方法 xlua.private_accessible(CS.class) 来允许访问

  5. 若想要在原本函数的基础上进行扩展,即实现类似override的功能,需执行以下几步:

    • 引入Xlua中的扩展包util (在路径Assets/XLua/Resources/xlua/util.lua.txt下)
    • lua中引入包 local util = require "util"
    • 使用函数 util.hotfix_ex(CS.class, 'functionName', function(self)) 原本的函数可用 self:Update() 来调用。
    • 解除引用仍使用 xlua.hotfix(CS.class, 'functionName', function(nil)) 进行释放
xlua.private_accessible(CS.Treasour)
-- data 是函数的入参
util.hotfix_ex(CS.Treasour, 'Do', function(self, data)
    print('say by lua')
    self:Update()
end)
4. 函数重载问题

在有差别仅为intfloat函数重载的函数中(如Range()函数),由于传入参数时二者皆可用int入参,Lua默认数值为float类型,导致入参被误判为float,返回float数值,而此时接受参数的c#变量为int,以至于无法正确接受,导致结果变0,无法得到正确的结果。
解决方案:
对于随机数取整,可用 UnityEngine.Mathf.FloorToInt() 将返回值转换成一个最接近的整数。

5. 热更新报错:xlua.access,no field __Hotfix0_Update

重新打包加载热更新c#代码即可

6. 标识热更新的简介办法

通过反射,在一个静态类的静态字段或属性里配置一个列表,属性可以用于实现的比较复杂的配置,比如根据命名空间做白名单。

public static List<Type> by_property
{
    
    
    get
    {
    
    
        // 反射对应C#程序集
        return (from type in Assembly.Load("Assembly-CSharp").GetTypes()
                // 命名空间
                // where type.Namespace == "XXXX"
                select type).ToList();
    }
}

猜你喜欢

转载自blog.csdn.net/qq_50682713/article/details/124819873