XLua-Unity框架初探
文章目录前言应用场景XLua 常用 API框架实例搭建
需求实现
实例,实现一朵云的运动其他
前言
Lua由于其简单易用,方便热更等性质,一直是游戏行业的首推脚本语言。Unity引擎也诞生了很多款为其适配的Lua虚拟机运行环境,主要有XLua,SLua,ToLua和ULua,本文不会着力比较这几种框架的实现差异,只讨论其中背靠大厂腾讯的XLua框架。
应用场景
UI逻辑代码Hotfix网络层除计算以外的常规逻辑(此处的计算指涉及矩阵等运算的场合)
XLua 常用 API
此处只会介绍常用的,合乎规范的内容,拓展阅读请访问XLua github。
Lua虚拟机
XLua中对Lua虚拟机进行了封装,建立和移除一个全局环境很简单:
_luaEnv =newXLua.LuaEnv();
_luaEnv.Dispose();在大致查看XLua C#部分封装中,可以看到,它将Lua的c通过dll形式引入 LuaAPI,然后对线程安全,异常等做了特殊处理。
require
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
使用 DoString 来实现Lua载入字符的功能,这块也是热更的重要功能。
_luaEnv.DoString("require('main')");
c# call lua
使用LuaTable来映射Lua表:
LuaTable luaMonoMap = _luaEnv.Global.Get<LuaTable>("luaMonoMap");LuaTable是XLua的重要基类,其中封装了对Lua键值对的调用,Global是_G表,同样是一个LuaTable,通过Get来访问各个键值。
使用声明一个映射的interface
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是很难限定为一种值的,所以此处不多介绍,有兴趣可以访问上面官方库。
lua call c#
通过CS.namespace.class.func_or_property来访问c#内容。
UnityEngine的类在虚拟机构造时已经将大部分类和类指针注入到 fix_cs_functions 中,所以可以访问到这些函数。
自定义类需要添加标签:
publicclassLuaCall{}构造函数直接赋值类名即可:
local lc = CS.LuaCall为了支持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#侧的实现
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 = _G.luaMonoMap or {}
_G.luaMonoMap = obj
return instName
end
function LuaMonoBase:DestroyInst(instName)
if instName == "" then
return
end
local nameArr = string.split(instName, "_")
local className = nameArr
local num = nameArr
_G.luaMonoMap = null
end
function LuaMonoBase:LinkValue(key, value)
self = 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.Key, LuaKV.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进行复制重命名。
页:
[1]