找回密码
 立即注册
查看: 273|回复: 0

Unity-Xlua

[复制链接]
发表于 2022-11-4 09:23 | 显示全部楼层 |阅读模式
目录




          1. Xlua
            1. 简介2. 基本使用3. 通过Loader自定义require加载路径4. C#访问Lua5. C#访问Lua中的全局function6. Lua访问C#
          2. Xlua热更新
            1. 启用热更新2. 补丁开发过程3. 补丁应用实例4. 函数重载问题
              5. 热更新报错:xlua.access,no field __Hotfix0_Update6. 标识热更新的简介办法






1. Xlua

1. 简介

    Xlua下载地址:https://github.com/Tencent/xLua 在右侧 Releases 中获取导入包中 Assets 文件夹下的Plugins和XLua
2. 基本使用

    LuaEnv 类用于在Unity中运行lua代码
  1. privateLuaEnv luaEnv;voidStart(){// 创建 Lua 虚拟机
  2.     luaEnv =newLuaEnv();// 运行 lua 代码
  3.     luaEnv.DoString(@"print('Hello xlua')");// 运行 Unity 代码
  4.     luaEnv.DoString(@"CS.UnityEngine.Debug.Log('Hello Unity')");}privatevoidOnDestroy(){// 释放
  5.     luaEnv.Dispose();}
复制代码
    Resources目录下可加载lua代码并执行,因为lua后缀文件并不能让unity识别,所以后缀需改为lua.text利用lua指令 require 'path' ,可在Resources目录下自动加载后缀为lua.text的文件
  1. //创建 LuaEnv 类LuaEnv luaEnv =newLuaEnv();// 1. 加载并运行TextAsset ta = Resources.Load<TextAsset>("hello");
  2. luaEnv.DoString(ta.text);// 2. 自动加载运行
  3. luaEnv.DoString("require 'hello'");
复制代码
3. 通过Loader自定义require加载路径

    使用方法 AddLoader 添加加载路径函数,返回值为脚本数据,入参为 require 路径使用 File.ReadAllText(path) 获取路径下脚本文本,使用 System.Text.Encoding.UTF8.GetBytes(data) 将文本转化为字节数组
  1. voidStart(){// 创建 LuaEnv 类LuaEnv luaEnv =newLuaEnv();
  2.     luaEnv.AddLoader(MyLoader);
  3.     luaEnv.DoString("require 'stream'");// 释放
  4.     luaEnv.Dispose();}privatebyte[]MyLoader(refstring 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

    获取Lua中的全局变量 luaEnv.Global.Get<T>(string)
  1. // 必须先运行脚本,内存中生成变量后方可获取数值// 创建 LuaEnv 类LuaEnv luaEnv =newLuaEnv();// 2. 自动加载运行
  2. luaEnv.DoString("require 'hello'");// 仅当为整数时可正常获取,否则返回0int 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");
复制代码

  • 获取Lua中的table
      用类获取(获得的是复制份,修改并不能更改原表
    1. LuaData luaData = luaEnv.Global.Get<LuaData>("table");publicclassLuaData{publicintvalue;publicstring data;publicbool flag;}
    复制代码
      接口(接口为public且添加特性CSharpCallLua,若发现仍无法正常运行,可在 Xlua 栏下 -> Clear Generated Code -> Generated Code
    1. ILuaData luaData = luaEnv.Global.Get<ILuaData>("table");
    2. luaData.value=234;
    3. luaData.say(2);[CSharpCallLua]publicinterfaceILuaData{intvalue{get;set;}string data {get;set;}bool flag {get;set;}voidsay(int time);}
    复制代码
    1. table ={
    2.     value =100,
    3.     data ="liluo",
    4.     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");
    复制代码
      LuaTable
    1. 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

    Action 方式
  1. luaEnv=newLuaEnv();Action<string> act = luaEnv.Global.Get<Action<string>>("Add");act("I say");
  2. act =null;// 若采用自定义委托[CSharpCallLua]delegateintAdd(int a,int b);
复制代码
  1. functionAdd(data)print(data)end
复制代码
    自定义委托
    需为委托添加特性 CSharpCallLua
  1. Add act = luaEnv.Global.Get<Add>("Add");int a, b;// 额外的返回值用out接收print(act(2,3,out a,out b));print(a +", "+ b);
  2. act =null;// 应为public,若发现仍无法正常运行,可 Generated Code[CSharpCallLua]publicdelegateintAdd(int a,int b,outint resA,outint resB);
复制代码
  1. functionAdd(a, b)return a + b, a, b
  2. end
复制代码
    LuaFunction
  1. // 创建 LuaEnv 类
  2. luaEnv =newLuaEnv();LuaFunction func = luaEnv.Global.Get<LuaFunction>("Add");// 获得返回值列表object[] os = func.Call(1,2);foreach(object data in os){print(data);}
复制代码
  1. functionAdd(a, b)return a + b, a, b
  2. end
复制代码
6. Lua访问C#
  1. -- 新建对象
  2. CS.UnityEngine.GameObject()
  3. CS.UnityEngine.GameObject("obj")-- 访问成员属性和方法print(CS.UnityEngine.Time.deltaTime)
  4. CS.UnityEngine.Time.timeScale =0.5local camera = CS.UnityEngine.GameObject.Find("Main Camera")
  5. camera.name ="luaupdate"
  6. CS.UnityEngine.GameObject.Destroy(camera:GetComponent("Camera"))
复制代码
注意:对于非静态对象,调用成员方法,第一个参数需要传该对象,建议用冒号语法糖,如下
  1. testobj:DMFunc()
复制代码
2. Xlua热更新

1. 启用热更新

    想要启用热更新,首先要在项目设置的玩家中添加脚本定义符号 HOTFIX_ENABLE 并应用,这时Xlua选单中就会额外出现一个 Hotfix Inject In Editor (注意:各平台需要分别设置)


    添加 Xlua 中的 Tools 文件夹到 Assets 同级目录下重新生成lua代码 (Xlua 栏下 -> Clear Generated Code -> Generated Code)点击Xlua选单中的 Hotfix Inject In Editor 注入代码到编辑器添加程序集:将 Unity\2020.3.8f1c1\Editor\Data\Managed 路径下的 Unity.Cecil.Pdb.dllUnity.Cecil.Mdb.dllUnity.Cecil.dll 三个配置文件加入工程的 Assets\XLua\Src\Editor目录下
2. 补丁开发过程

    首先开发业务代码在所有可能出现问题的类上打上hotfix的标签,在所有lua调用CSharp的方法上打上LuaCallCSharp,在所有CSharp调用Lua的方法上打上CSharpCallLua修改bug时只需要更新Lua文件,修改资源时(声音,模型,贴图,图片,UI)只需要更新ab包。用户只需要去下载lua文件跟ab包。
3. 补丁应用实例

    注意:补丁注册必须要在所有脚本之前,否则无法保证补丁应用成功!(比如对Start函数的修改)
  • 想要添加补丁,需要为想要修改的类添加 Hotfix 热更新特性,再为将要修改的函数添加 LuaCallCSharp 特性,该类便可被热更新捕获
    编写lua代码,调用函数 xlua.hotfix(CS.class, 'functionName', function(self)) 更新函数,其中class为添加 Hotfix 热更新特性的类,functionName为添加 LuaCallCSharp 特性的函数名,function为替换的函数,self为类本身
    1. [Hotfix]publicclassTreasour:MonoBehaviour{publicGameObject gold;publicGameObject diamands;publicTransform cavas;[LuaCallCSharp]privatevoidCreatePrize(){for(int i =0; i <5; i++){GameObject go =Instantiate(gold, transform.position +newVector3(-10f+ i *30,0,0), transform.rotation);
    2.             go.transform.SetParent(cavas);GameObject go1 =Instantiate(diamands, transform.position +newVector3(0,30,0)+newVector3(-10f+ i *30,0,0), transform.rotation);
    3.             go1.transform.SetParent(cavas);}}}
    复制代码
    1. xlua.hotfix(CS.Treasour,'CreatePrize',function(self)local GameObject = CS.UnityEngine.GameObject
    2.     for i =0,4,1dolocal go = GameObject.Instantiate(self.gold, self.transform.position + CS.UnityEngine.Vector3(-10+ i *100,0,0), self.transform.rotation);
    3.         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);
    4.         go1.transform:SetParent(self.cavas);endend)
    复制代码
    使用完毕后,在 Dispose 虚拟机前应释放所有引用,hotfix引用可用 xlua.hotfix(CS.class, 'functionName', function(nil)) 进行释放
    若发生访问不到的私有成员,可用方法 xlua.private_accessible(CS.class) 来允许访问
    若想要在原本函数的基础上进行扩展,即实现类似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)) 进行释放

  1. xlua.private_accessible(CS.Treasour)-- data 是函数的入参
  2. util.hotfix_ex(CS.Treasour,'Do',function(self, data)print('say by lua')
  3.     self:Update()end)
复制代码
4. 函数重载问题

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

重新打包加载热更新c#代码即可
6. 标识热更新的简介办法

通过反射,在一个静态类的静态字段或属性里配置一个列表,属性可以用于实现的比较复杂的配置,比如根据命名空间做白名单。
  1. publicstaticList<Type> by_property
  2. {get{// 反射对应C#程序集return(from type in Assembly.Load("Assembly-CSharp").GetTypes()// 命名空间// where type.Namespace == "XXXX"select type).ToList();}}
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2025-2-23 02:29 , Processed in 0.090379 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表