基于xLua实现一个简单的Lua解析器管理器

一、引言

在游戏开发中,Lua 作为一种轻量级脚本语言,被广泛用于实现热更新、游戏逻辑脚本化等功能。xLua 是 Unity 下的 Lua 开发解决方案,它为 Unity 开发者提供了强大的 Lua 交互能力。本文将详细介绍如何基于 xLua 实现一个简单的 Lua 解析器管理器。

二、整体思路

创建一个 Lua 解析器管理器(LuaMgr),该管理器主要负责 Lua 解析器的初始化、Lua 文件的加载、Lua 代码的执行、垃圾回收以及资源释放等操作。通过封装这些功能,我们可以方便地在 Unity 项目中使用 Lua 脚本。

三、实现细节

1. 单例模式
private static LuaMgr instance = new LuaMgr(); 
public static LuaMgr Instance => instance; 

使用单例模式确保 LuaMgr 类只有一个实例,方便在整个项目中全局访问。

2. Lua 解析器与全局表
private LuaEnv luaEnv; 
public LuaTable _G 
{ 
    get 
    { 
        return luaEnv.Global; 
    } 
} 

LuaEnv 是 xLua 提供的Lua解析器,用于管理 Lua 环境。_G 属性返回 Lua 中的全局表,方便在 C# 代码中访问 Lua 的全局变量和函数。

3.初始化方法
[SerializeField] private string luaABName = "lua"; 
[SerializeField] private string localPath = "/Scripts/Lua"; 
public void Init() 
{ 
    if (luaEnv == null) 
    { 
        luaEnv = new LuaEnv(); 
        luaEnv.AddLoader(LoadFromLocal); 
        luaEnv.AddLoader(LoadFromAB); 
    } 
} 
private byte[] LoadFromLocal(ref string fileName) 
{ 
    string path = Application.dataPath  + localPath + "/" + fileName + ".lua"; 
    if (File.Exists(path)) 
    { 
        return File.ReadAllBytes(path); 
    } 
    Debug.Log("没有在本地" + path + "路径中找到" + fileName + ".lua文件"); 
    return null; 
} 
 
private byte[] LoadFromAB(ref string fileName) 
{ 
    TextAsset lua = ABManager.Instance.LoadRes<TextAsset>(luaABName, fileName + ".lua"); 
    if (lua == null) 
    { 
        Debug.Log("没有在" + luaABName + "包中找到" + fileName + ".lua文件"); 
        return null; 
    } 
    return lua.bytes;  
} 

 在 Init 方法中,我们首先检查 luaEnv 是否为空,如果为空则创建一个新的 LuaEnv 实例。同时,我们添加了两个文件加载器:LoadFromLocal 方法根据文件路径检查本地是否存在指定的 Lua 文件,如果存在则返回文件的字节数组;LoadFromAB 方法通过 ABManager 从 AB 包中加载 Lua 文件,同样返回文件的字节数组,需要注意的是,由于 TextAsset 无法直接识别 Lua 文件,我们需要将 Lua 文件转成 .txt 文件才能进行读取。

关于AB包管理器可以看这篇文章:实现一个简单的AB包资源管理器-优快云博客

4.基本操作
    bool CheckLuaEnvIsNull()
    {
        if (luaEnv == null)
        {
            Debug.Log("LuaEnv未初始化,请先调用Init()方法初始化");
            return true;
        }
        return false;
    }
    public void DoString(string str)
    {
        if (CheckLuaEnvIsNull()) return;
        luaEnv.DoString(str);//执行lua语句
    }

    public void Tick()
    {
        if (CheckLuaEnvIsNull()) return;
        luaEnv.Tick();//释放垃圾
    }

    public void Dispose()
    {
        if (CheckLuaEnvIsNull()) return;
        luaEnv.Dispose();//销毁解析器
        luaEnv = null;
    }

封装Lua 解析器的代码执行、垃圾回收和资源释放功能,方便我们在 Unity 项目中使用 。

介绍当下多数在java下执行lua脚本的程序都是用了luajava。然而luajava存在一些严重的问题,它会将byte数组和string等同对待,而且它的反射执行效率比较低。为了弥补这些问题,我参考luajava,重写了它的java和jni代码,并以mLua为名重新发布。特点描述和luajava类似的,mLua也有内置的全局lua函数;java对象和lua对象可以通过jni层代码进行交换。但是mLua禁止lua直接操作java对象,如果想在lua中使用java对象,必须使用内置的全局函数实现。mLua区分byte数组和string。在mLua中,java的byte数组对lua端而言,只是一个普通的userdata。在跨语言数据交换的时候,string是被复制的,因此当一个string从lua传递到java后再于lua中修改它,它在java端的对应版本并不会随着改变。将lua端的number传递给java后,会被优先解释为byte类型,否则将依照byte - short - int - long - float - double链条来尝试解释。mLua不对外暴露lua解析器实例,所有的操作都基于MLua实例完成。java端方法描述mLua的java端方法集中在MLua中:方法名称方法解释setBasedir(String)设置lua代码的最外层目录,所有lua代码都应该存放在这个目录或其子目录下pushGlobal(String, Object)设置全局lua的全局变量或函数,可以push普通的Object,或者JavaFunction。后者表示一个lua函数的java实现。只有在start方法执行前,设置的数据才会生效start(String)启动lua解析器,传递的参数表示lua代码的入口文件stop()停止lua解析器并释放资源除此之外,JavaFunction也是使用者可能需要用到的接口。它表示一个lua函数的java实现。其回调方法execute(Object[])方法会传入从lua端输入的数据,并输出一个结果传回lua端。如果方法本身不需要返回数据,则返回null即可。lua端函数描述在mLua下,lua原来的require、print函数已经被改写。requirerequire必须使用设置在java端的basedir为根目录的相对路径引用其他lua脚本:require "dir1/dir2/script1" require "script2"print支持输出一个或多个对象,但是不能将string与java对象作拼接:-- 正确的做法 -- print("hello mLua") print("context: ", getContext()) print("string " .. 111) -- 错误的做法 -- print("context: " .. getContext())通过逗号分隔的对象会在java端以tab号分隔显示操作java对象mLua也采用反射来操作java对象,不过mobTools的ReflectHelper具备缓存功能,理论上会比luajava每次直接反射更快。mLua提供了如下的内置函数:函数名称函数解释import(className)向ReflectHelper类缓存中导入一个类,此函数将返回一个string,用于后续代码从缓存中重新获取导入的类实例import(name, className)向ReflectHelper类缓存中导入一个类,并将此缓存的key设置为指定名称new(className, ...)构造一个java实例,参数className是import函数的返回值,后续参数为java构造方法的输入参数invokeStatic(className, methodName, ...)调用一个java的静态方法invoke(receiver, methodName, ...)调用一个Java的实例方法getStatic(className, fieldName)获取一个java的静态字段setStatic(className, fieldName, value)设置一个java的静态字段get(receiver, fieldName)获取一个java的实例字段set(receiver, fieldName, value)设置一个java的实例字段createProxy(proxyTable, ...)构造一个java接口代理。参数proxyTable是一个lua的table,其中的key必须与java接口类的方法名称相同,key对应的value是一个lua的function,function的参数列表和返回值也必须与java接口相同。proxyTable后的参数是被实现的接口列表名称,皆为string,由import函数返回。此函数将返回一个java接口代理实例,可将此实例传回java端并进行操作,当实例中的接口函数被调用时,mLua会调用proxyTable中的对应funtion代码完成操作例子java端代码public class MainActivity extends Activity {     private MLua lua;     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         // 构造一个解析器实例         lua = new MLua();         // 设置lua代码存放位置         lua.setBasedir("/sdcard/mLua/LuaTest");         // push全局对象         lua.pushGlobal("getContext", new JavaFunction() {             public Object execute(Object[] args) throws Throwable {                 return getApplication();             }         });         try {             // 启动解析器,设置main.lua为入口代码             lua.start("main");         } catch (Throwable e) {             e.printStackTrace();         }     }     protected void onDestroy() {         // 关闭解析器         lua.stop();         super.onDestroy();     } }lua端代码-- 导入ReflectHelper.ReflectRunnable类,并命名为ReflectRunnable -- import("ReflectRunnable", "com.mob.tools.utils.ReflectHelper$ReflectRunnable") local function main()   -- 演示print和invoke --   print("hello world from mLua")   local context = getContext()   print("current context: ", context)   local packageName = invoke(context, "getPackageName")   print("packageName: ", packageName)   -- 演示java接口代理 --   local luaCode = {     run = function(arg)       print("luaCode.run(), input: ", arg)       return "yoyoyo"     end   }   local proxy = createProxy(luaCode, "ReflectRunnable")   local res = invoke(proxy, "run", packageName)   print("luaCode.run(), output: ", res)   -- 演示数组复制 --   local bArray = new("[B", 16)   for i = 0, 15 do     set(bArray, "[" .. i .. "]", i   1)   end   local bArray2 = new("[B", get(bArray, "length"))   invokeStatic("System", "arraycopy", bArray, 0, bArray2, 0, 16)   for i = 0, 15 do     print("bArray2[" .. i .. "]: ", get(bArray2, "[" .. i .. "]"))   end end main()扩展mLua默认只能从文件系统中加载lua代码,但是如果对MLua的setBasedir方法进行重写,以其他的方式实现SourceLoader,则可以加载任意方式的lua代码,包括assets中的,和加密的。 标签:mLua
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值