|
文章目录
前言应用场景XLua 常用 API框架实例搭建
实例,实现一朵云的运动其他
前言
Lua由于其简单易用,方便热更等性质,一直是游戏行业的首推脚本语言。Unity引擎也诞生了很多款为其适配的Lua虚拟机运行环境,主要有XLua,SLua,ToLua和ULua,本文不会着力比较这几种框架的实现差异,只讨论其中背靠大厂腾讯的XLua框架。
应用场景
UI逻辑代码Hotfix网络层除计算以外的常规逻辑(此处的计算指涉及矩阵等运算的场合)
XLua 常用 API
此处只会介绍常用的,合乎规范的内容,拓展阅读请访问XLua github。
XLua中对Lua虚拟机进行了封装,建立和移除一个全局环境很简单:- _luaEnv =newXLua.LuaEnv();
- _luaEnv.Dispose();
复制代码 在大致查看XLua C#部分封装中,可以看到,它将Lua的c通过dll形式引入 LuaAPI,然后对线程安全,异常等做了特殊处理。
XLua为了方便使用require,添加了 AddLoader 方法:- _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 来实现Lua载入字符的功能,这块也是热更的重要功能。- _luaEnv.DoString("require('main')");
复制代码 使用LuaTable来映射Lua表:- LuaTable luaMonoMap = _luaEnv.Global.Get<LuaTable>("luaMonoMap");
复制代码 LuaTable是XLua的重要基类,其中封装了对Lua键值对的调用,Global是_G表,同样是一个LuaTable,通过Get来访问各个键值。
使用[CSharpCallLua]声明一个映射的interface- [CSharpCallLua]publicinterfaceLuaContent{voidthis_print();}
- _luaEnv.Global.Get<LuaContent>("testContent");
复制代码 我们在指明了该attribute后,再generate后,会产生一个名为ClassNameBridge的新类(此例中为LuaContentBridge),该类会继承这个interface和LuaBase,LuaBase主要是处理gc等内容,后面的文章会详细研究。在重写的函数实现中,会对LuaState直接进行操作,将函数和参数入栈,进行调用。
注意: 该映射并非真正的一一对应,只需要在运行时,lua函数能正常被访问到即可,也就是说,Lua侧可以远远多于该interface的内容。
还有两种方式,自定义类访问 和 Dictionary或List访问,但是由于这两种类型都是值类型访问,上面的是引用类型访问,而且只能访问同样的类型,就是说限定Lua表中都是同一种值,或者被转化为同一种值,但是Lua的TValue是很难限定为一种值的,所以此处不多介绍,有兴趣可以访问上面官方库。
通过CS.namespace.class.func_or_property来访问c#内容。
UnityEngine的类在虚拟机构造时已经将大部分类和类指针注入到 fix_cs_functions 中,所以可以访问到这些函数。
自定义类需要添加标签[LuaCallCSharp]:- [LuaCallCSharp]publicclassLuaCall{}
复制代码 构造函数直接赋值类名即可:为了支持lua的多返回值,c#侧使用ref,out等关键字来实现。
参数处理规则:Lua调用C#方法的时候,C#方法中的参数,从左到右,普通参数算一个,ref修饰的算一个,out修饰的不算。
返回值处理规则:Lua接受C#方法返回值的时候,C#方法的返回值(如果有)算一个返回值,参数ref算一个返回值,out算一个返回值。
注意: 重载函数,XLua支持多参数重载,意思是重载函数的参数不同,能够正常支持,lua默认类型,number内部不支持重载,number和string支持重载,否则调用生成代码的第一个函数副本,如果此时不能实现强制转换,则会抛出异常,比如:- 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#侧的实现- [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侧的实现:- -- abstract
- _G.LuaMonoBase = _G.LuaMonoBase or {
- owner = nil,
- _instNum = 0,
- }
- ...
- -- 省略生命周期函数
- ...
- -- 继承方法
- function LuaMonoBase:extends()
- local obj = {}
- obj._instNum = 0
- self.__index = self
- setmetatable(obj, self)
- return obj
- end
- -- for gc
- _G.luaMonoMap = _G.luaMonoMap or {}
- -- 生成实例
- function LuaMonoBase:CreateInst(originClassName)
- self._instNum = self._instNum + 1
- local instName = string.format("%s_INST_%d", originClassName, self._instNum)
- local obj = self:extends()
- _G.luaMonoMap[originClassName] = _G.luaMonoMap[originClassName] or {}
- _G.luaMonoMap[originClassName][instName] = obj
- return instName
- end
- function LuaMonoBase:DestroyInst(instName)
- if instName == "" then
- return
- end
- local nameArr = string.split(instName, "_")
- local className = nameArr[1]
- local num = nameArr[3]
- _G.luaMonoMap[className][instName] = null
- end
- function LuaMonoBase:LinkValue(key, value)
- self[key] = value
- end
复制代码 此处注意,在lua侧我们必须使用面向对象的方案。试想一下,如果我们调用全局table,那么所有的lua脚本都是索引到同一个table,除非在UI层和使用单例的mng层,否则都会要求生成table的实例。
我们这里提供了CreateInst方法来实现实例化,并将实例对象存放在一个全局管理的表里面,方便我们做gc和分析,甚至某些时候我们能重写该函数,使用对象池的调用,而不是将其直接进行垃圾回收。
3、MonoBehavior的脚本绑定- 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);
- _instName = ClassObj.CreateInst(ScriptName);
- _luaObj =GetInst(ScriptName, _instName);
- _luaObj.owner =this;int len = LuaKV.Length;for(int i =0; i < len;++i){
- _luaObj.LinkValue(LuaKV[i].Key, LuaKV[i].Value);}}if(_luaObj !=null)
- _luaObj.Awake();}...voidOnDistroy(){if(_luaObj !=null)
- _luaObj.OnDistroy();
- _luaObj.DestroyInst(_instName);
- _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实现- local GameObject = CS.UnityEngine.GameObject
- local Transform = CS.UnityEngine.Transform
- local Vector3 = CS.UnityEngine.Vector3
- local Time = CS.UnityEngine.Time
- local Quaternion = CS.UnityEngine.Quaternion
- CloudBehaviour = LuaMonoBase:extends()
- function CloudBehaviour:Start()
- self._center = Vector3(70, 254, 45)
- local pos = self.owner:GetComponent(typeof(Transform)).position
- local x = pos.x - self._center.x
- local z = pos.z - self._center.z
- self._radius = math.sqrt(x * x + z * z)
- local arc = math.asin(z / self._radius)
- self._curRotation = x < 0 and math.pi - arc or arc
- end
- function CloudBehaviour:FixedUpdate()
- local trans = self.owner:GetComponent(typeof(Transform))
- local pos = trans.position
- self._curRotation = math.lerp(self._curRotation, self._curRotation + self._rSpeed, Time.deltaTime)
- local x = self._center.x + math.cos(self._curRotation) * self._radius
- local z = self._center.z + math.sin(self._curRotation) * self._radius
- local y = pos.y + math.sin(Time.realtimeSinceStartup) * self._floatSpeed
- trans.position = Vector3(x, y, z)
- pos = trans.position
- trans.rotation = Quaternion.LookRotation(Vector3(self._center.x, pos.y, self._center.z) - pos)
- end
复制代码 该类是对LuaMonoBase的派生。
我们在文件开头对常用类型进行了索引,避免去c#侧多次索引,可以建立一个表将常用类全部索引一次。不过XLua对这些行为是进行了优化的,在CS表的__index里面有很多优化,后面的文章会详细测试。
其他
注意,每次写完带有XLua属性的代码时,需要重新generate,generate时注意先clear。某些ide禁止外部读写,所以有时会生成代码失败,需要关闭ide。
Resources目前不能加载.lua类型的文件,所以要添加后缀.txt,此处为了方便编辑,可以写一个py进行复制重命名。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|