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

XLua-Unity框架初探

[复制链接]
发表于 2021-8-12 20:49 | 显示全部楼层 |阅读模式
文章目录



        前言应用场景XLua 常用 API框架实例搭建
          需求实现
        实例,实现一朵云的运动其他



前言

Lua由于其简单易用,方便热更等性质,一直是游戏行业的首推脚本语言。Unity引擎也诞生了很多款为其适配的Lua虚拟机运行环境,主要有XLua,SLua,ToLua和ULua,本文不会着力比较这几种框架的实现差异,只讨论其中背靠大厂腾讯的XLua框架。
应用场景

    UI逻辑代码Hotfix网络层除计算以外的常规逻辑(此处的计算指涉及矩阵等运算的场合)
XLua 常用 API

此处只会介绍常用的,合乎规范的内容,拓展阅读请访问XLua github。
    Lua虚拟机
XLua中对Lua虚拟机进行了封装,建立和移除一个全局环境很简单:
  1. _luaEnv =newXLua.LuaEnv();
  2. _luaEnv.Dispose();
复制代码
在大致查看XLua C#部分封装中,可以看到,它将Lua的c通过dll形式引入 LuaAPI,然后对线程安全,异常等做了特殊处理。
    require
XLua为了方便使用require,添加了 AddLoader 方法:
  1. _luaEnv.AddLoader(LoadLua);staticbyte[]LoadLua(refstring luaname){string realPath =@"LuaScripts/"+ luaname +".lua";TextAsset luaContent = Resources.Load(realPath)as TextAsset;return luaContent.bytes;}
复制代码
此处LoadLua是一个委托,使用Lambda函数亦可。
XLua支持多个Loader,会去遍历调用,直到读出数据为止。如果不添加任何Loader,它支持默认从Resources根目录读取后缀为“.txt”的lua文件。
    dostring
使用 DoString 来实现Lua载入字符的功能,这块也是热更的重要功能。
  1. _luaEnv.DoString("require('main')");
复制代码
    c# call lua
使用LuaTable来映射Lua表:
  1. LuaTable luaMonoMap = _luaEnv.Global.Get<LuaTable>("luaMonoMap");
复制代码
LuaTable是XLua的重要基类,其中封装了对Lua键值对的调用,Global是_G表,同样是一个LuaTable,通过Get来访问各个键值。
使用[CSharpCallLua]声明一个映射的interface
  1. [CSharpCallLua]publicinterfaceLuaContent{voidthis_print();}
  2. _luaEnv.Global.Get<LuaContent>("testContent");
复制代码
我们在指明了该attribute后,再generate后,会产生一个名为ClassNameBridge的新类(此例中为LuaContentBridge),该类会继承这个interface和LuaBase,LuaBase主要是处理gc等内容,后面的文章会详细研究。在重写的函数实现中,会对LuaState直接进行操作,将函数和参数入栈,进行调用。
注意: 该映射并非真正的一一对应,只需要在运行时,lua函数能正常被访问到即可,也就是说,Lua侧可以远远多于该interface的内容。
还有两种方式,自定义类访问 和 Dictionary或List访问,但是由于这两种类型都是值类型访问,上面的是引用类型访问,而且只能访问同样的类型,就是说限定Lua表中都是同一种值,或者被转化为同一种值,但是Lua的TValue是很难限定为一种值的,所以此处不多介绍,有兴趣可以访问上面官方库。
    lua call c#
通过CS.namespace.class.func_or_property来访问c#内容。
UnityEngine的类在虚拟机构造时已经将大部分类和类指针注入到 fix_cs_functions 中,所以可以访问到这些函数。
自定义类需要添加标签[LuaCallCSharp]:
  1. [LuaCallCSharp]publicclassLuaCall{}
复制代码
构造函数直接赋值类名即可:
  1. local lc = CS.LuaCall
复制代码
为了支持lua的多返回值,c#侧使用ref,out等关键字来实现。
参数处理规则:Lua调用C#方法的时候,C#方法中的参数,从左到右,普通参数算一个,ref修饰的算一个,out修饰的不算。
返回值处理规则:Lua接受C#方法返回值的时候,C#方法的返回值(如果有)算一个返回值,参数ref算一个返回值,out算一个返回值。
注意: 重载函数,XLua支持多参数重载,意思是重载函数的参数不同,能够正常支持,lua默认类型,number内部不支持重载,number和string支持重载,否则调用生成代码的第一个函数副本,如果此时不能实现强制转换,则会抛出异常,比如:
  1. trans.LookAt(Vector(1, 1, 1)) -- 报错,只支持GameObject作为参数
复制代码
调用delegate时重载了+,-运算符,和c#一样。
注意:XLua中还有强制转化table为c#类型的方案,但是由于lua层传递的仅仅是一个table,又没有生成wrapper的中间代码,所以在c#侧只能通过反射等方案实现赋值行为,效率存疑,此处不推荐使用。
框架实例搭建

需求

    像cs脚本一样调用lua执行Mono的生命周期函数属性在inspector可编辑
实现

1、 Env Mng
实现一个单例,将 _luaEnv 等属性挂在上面,在 GameInit 时初始化虚拟机,加载所有的lua文件到_G表,这一块比较简单,代码就不放了。
2、Lua到Mono的接口映射
先看c#侧的实现
  1. [CSharpCallLua]publicinterfaceLuaMonoReflection{voidAwake();voidOnEnable();voidStart();voidFixedUpdate();voidUpdate();voidLateUpdate();voidOnDisable();voidOnDistroy();stringCreateInst(string className);voidDestroyInst(string instName);voidLinkValue(string k,float v);MonoBehaviour owner {set;get;}}
复制代码
此处只绑定逻辑函数,其他渲染等一般不放在lua侧处理。
再来看看lua侧的实现:
  1. -- abstract
  2. _G.LuaMonoBase = _G.LuaMonoBase or {
  3.         owner = nil,
  4.         _instNum = 0,
  5. }
  6. ...
  7. -- 省略生命周期函数
  8. ...
  9. -- 继承方法
  10. function LuaMonoBase:extends()
  11.         local obj = {}
  12.         obj._instNum = 0
  13.         self.__index = self
  14.         setmetatable(obj, self)
  15.         return obj
  16. end
  17. -- for gc
  18. _G.luaMonoMap = _G.luaMonoMap or {}
  19. -- 生成实例
  20. function LuaMonoBase:CreateInst(originClassName)
  21.         self._instNum = self._instNum + 1
  22.         local instName = string.format("%s_INST_%d", originClassName, self._instNum)
  23.         local obj = self:extends()
  24.         _G.luaMonoMap[originClassName] = _G.luaMonoMap[originClassName] or {}
  25.         _G.luaMonoMap[originClassName][instName] = obj
  26.         return instName
  27. end
  28. function LuaMonoBase:DestroyInst(instName)
  29.         if instName == "" then
  30.                 return
  31.         end
  32.         local nameArr = string.split(instName, "_")
  33.         local className = nameArr[1]
  34.         local num = nameArr[3]
  35.         _G.luaMonoMap[className][instName] = null
  36. end
  37. function LuaMonoBase:LinkValue(key, value)
  38.         self[key] = value
  39. end
复制代码
此处注意,在lua侧我们必须使用面向对象的方案。试想一下,如果我们调用全局table,那么所有的lua脚本都是索引到同一个table,除非在UI层和使用单例的mng层,否则都会要求生成table的实例。
我们这里提供了CreateInst方法来实现实例化,并将实例对象存放在一个全局管理的表里面,方便我们做gc和分析,甚至某些时候我们能重写该函数,使用对象池的调用,而不是将其直接进行垃圾回收。
3、MonoBehavior的脚本绑定
  1. publicclassLuaBridge:MonoBehaviour{publicstring ScriptName ="DefaultName";public LuaKeyValue[] LuaKV ={};privatestring _instName ="";privateLuaMonoReflection _luaObj =null;// Start is called before the first frame updatevoidAwake(){if(_luaObj ==null){var ClassObj = GameInit.LUA_ENV.Global.Get<LuaMonoReflection>(ScriptName);
  2.             _instName = ClassObj.CreateInst(ScriptName);
  3.             _luaObj =GetInst(ScriptName, _instName);
  4.             _luaObj.owner =this;int len = LuaKV.Length;for(int i =0; i < len;++i){
  5.                 _luaObj.LinkValue(LuaKV[i].Key, LuaKV[i].Value);}}if(_luaObj !=null)
  6.             _luaObj.Awake();}...voidOnDistroy(){if(_luaObj !=null)
  7.             _luaObj.OnDistroy();
  8.         _luaObj.DestroyInst(_instName);
  9.         _instName ="";}LuaMonoReflectionGetInst(string className,string instName){return GameInit.LUA_ENV.Global.Get<LuaTable>("luaMonoMap").Get<LuaTable>(className).Get<LuaMonoReflection>(instName);}}
复制代码
这是直接绑定到gameobject上的脚本,注意我们需要将this传递到lua层。
此处lua层的属性可以通过LuaKeyValue来进行编辑,会在运行时对table进行赋值。
实例,实现一朵云的运动

此处实例为了此框架的运用范围,并未进行性能测试,后续完善后会进行性能测试并制定使用的规范。
1、首先添加C#脚本 LuaBridge, lua脚本命名为CloudBehaviour


2、CloudBehaviour实现
  1. local GameObject = CS.UnityEngine.GameObject
  2. local Transform = CS.UnityEngine.Transform
  3. local Vector3 = CS.UnityEngine.Vector3
  4. local Time = CS.UnityEngine.Time
  5. local Quaternion = CS.UnityEngine.Quaternion
  6. CloudBehaviour = LuaMonoBase:extends()
  7. function CloudBehaviour:Start()
  8.         self._center = Vector3(70, 254, 45)
  9.         local pos = self.owner:GetComponent(typeof(Transform)).position
  10.         local x = pos.x - self._center.x
  11.         local z = pos.z - self._center.z
  12.         self._radius = math.sqrt(x * x + z * z)
  13.         local arc = math.asin(z / self._radius)
  14.         self._curRotation = x < 0 and math.pi - arc or arc
  15. end
  16. function CloudBehaviour:FixedUpdate()
  17.         local trans = self.owner:GetComponent(typeof(Transform))
  18.         local pos = trans.position
  19.         self._curRotation = math.lerp(self._curRotation, self._curRotation + self._rSpeed, Time.deltaTime)
  20.         local x = self._center.x + math.cos(self._curRotation) * self._radius
  21.         local z = self._center.z + math.sin(self._curRotation) * self._radius
  22.         local y = pos.y + math.sin(Time.realtimeSinceStartup) * self._floatSpeed
  23.         trans.position = Vector3(x, y, z)
  24.         pos = trans.position
  25.         trans.rotation = Quaternion.LookRotation(Vector3(self._center.x, pos.y, self._center.z) - pos)
  26. end
复制代码
该类是对LuaMonoBase的派生。
我们在文件开头对常用类型进行了索引,避免去c#侧多次索引,可以建立一个表将常用类全部索引一次。不过XLua对这些行为是进行了优化的,在CS表的__index里面有很多优化,后面的文章会详细测试。
其他

注意,每次写完带有XLua属性的代码时,需要重新generate,generate时注意先clear。某些ide禁止外部读写,所以有时会生成代码失败,需要关闭ide。
Resources目前不能加载.lua类型的文件,所以要添加后缀.txt,此处为了方便编辑,可以写一个py进行复制重命名。

本帖子中包含更多资源

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

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

本版积分规则

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

GMT+8, 2024-11-28 02:10 , Processed in 0.130777 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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