ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

xLua 教程实战:在微信小游戏 Unity WebGL 工程中打通 Lua 与 C 双向调用

xLua 教程实战:在微信小游戏 Unity WebGL 工程中打通 Lua 与 C 双向调用 游戏开发移动开发WebAssembly【免费下载链接】minigame-unity-webgl-transform微信小游戏Unity引擎适配器文档。项目地址https://gitcode.com/GitHub_Trending/mi/minigame-unity-webgl-transform点击查看免费下载xLua 为 Unity、.NET、Mono 等 C# 环境提供 Lua 脚本编程能力本仓库的 Demo/xLua_WebGL 工程内置了完整的 xLua 源码、教程文档与配套示例是研究Lua 脚本驱动微信小游戏业务逻辑最直接的起点。本文以 XLua_Tutorial_EN.md 为主线结合 Tutorial 目录下的真实示例代码系统讲解如何加载 Lua 文件、如何从 C# 访问 Lua 数据结构与函数、以及如何从 Lua 反向调用 C# 对象、方法、事件与操作符。读完本文你将掌握 xLua 最核心的三类开发范式并能在自己的 Unity WebGL 工程中直接复用。加载 Lua 脚本从字符串到自定义 Loader方式一直接执行字符串最基础的执行方式是通过LuaEnv.DoString执行一段符合 Lua 语法的字符串luaenv.DoString(print(hello world))完整示例见 ByString.cs 与对应场景ByString.unity其核心结构为void Start() { luaenv new LuaEnv(); luaenv.DoString(print(hello world)); } void Update() { if (luaenv ! null) luaenv.Tick(); } void OnDestroy() { luaenv.Dispose(); }从示例代码可以看到 xLua 开发的两个关键约定生命周期LuaEnv是 Lua 虚拟机实例按官方建议全局唯一Update中驱动luaenv.Tick()推进脚本时间与回调OnDestroy中调用Dispose()释放虚拟机资源使用倾向教程明确提示直接执行字符串这种方式不推荐因为把整段业务代码塞进字符串既不便于维护也无法利用 Lua 的require模块机制。方式二通过 require 加载 Lua 文件更推荐的做法是用 Lua 的require函数加载文件luaenv.DoString(require byfile);完整示例见 ByFile.cs被加载的脚本内容是 byfile.lua.txt 中的print(hello world)。require的底层机制是依次调用各个 Loader 去加载文件一旦某个 Loader 成功就停止如果全部失败则报找不到文件错误。xLua 在原生的文件加载器之外额外注册了从Resources目录加载脚本的 Loader。由于 Unity 的Resources对扩展名有数量限制放在 Resources 下的 Lua 文件必须追加.txt扩展名如byfile.lua.txt加载时require byfile会自动匹配。官方推荐的工程化组织方式是整个程序入口只做一次DoString(require main)在main.lua内部继续require其他脚本模块。这与 Lua 命令行lua main.lua的执行模型完全一致让 Lua 代码保持模块化、可维护。方式三自定义 Loader如果你的 Lua 文件是下载得到、从自定义格式压缩包中解压、或者需要解密的自定义 Loader 就是答案。xLua 的自定义 Loader 只涉及一个接口public delegate byte[] CustomLoader(ref string filepath); public void LuaEnv.AddLoader(CustomLoader loader)通过AddLoader注册一个回调参数是字符串当 Lua 代码中调用require时该参数会被原样透传给回调回调据此加载指定文件若需要调试支持可以把filepath修改为真实路径再透传回调返回byte[]返回null表示该 Loader 未命中否则返回 Lua 文件内容字节。实战意义可以写一个 Loader 调用 IIPS 等接口读取文件内容支持 IIPS 资源也可以写一个 Loader 读取文件并在返回前解密支持加密脚本。完整示例见 CustomLoader.cs其回调用内存字符串模拟了一次文件加载luaenv.AddLoader( (ref string filename) { if (filename InMemory) { string script return {ccc 9999}; return System.Text.Encoding.UTF8.GetBytes(script); } return null; } ); luaenv.DoString(print(InMemory.ccc, require(InMemory).ccc));从源码结构看require的 Loader 链与AddLoader注册机制分布在 Src 下的 LuaEnv、LuaTable 等相关实现中CopyByValue.cs、DelegateBridge.cs等文件则分别负责值类型拷贝与委托桥接共同构成脚本加载与调用的底层支撑。C# 访问 Lua读取数据、映射表与函数C# 访问 Lua指的是 C# 主动读取 Lua 侧的数据结构。本节示例全部位于 CSharpCallLua 目录核心演示见 CSCallLua.cs。读取全局基本类型通过LuaEnv.Global提供模板化的GetT方法可指定返回类型luaenv.Global.Getint(a) luaenv.Global.Getstring(b) luaenv.Global.Getbool(c)对应 Lua 侧脚本为a 1 b hello world c true访问全局 table四种映射方式对同一个 Lua tabled {f1 12, f2 34, 1, 2, 3, add function(...) ... end}根据场景可选用四种映射映射到普通 class 或 struct按值拷贝定义与 table 字段对应的公有字段的类有无参构造函数均可。例如对{f1 100, f2 100}定义含public int f1; public int f2;的类xLua 会自动创建实例并填充字段。table 字段数可以多于或少于类字段类中可以嵌套其他复杂类。注意该过程是值拷贝修改 class 字段不会同步回 table反之亦然复杂类开销更高。把该 class 加入GCOptimize配置可以减少生成开销见 Configure_EN.md 中 XLua.GCOptimize 一节GCOptimize 默认只对公有字段做打包/解包私有字段需通过额外配置指定属性名列表。示例中的DClass即此模式DClass d luaenv.Global.GetDClass(d); // 按值映射映射到接口引用模式依赖生成代码xLua 代码生成器会生成接口的实现实例读取属性时取对应 table 字段写入属性时写回 table 字段甚至可以通过接口方法访问 Lua 函数。该模式依赖生成代码——未生成代码时会报InvalidCastException。示例中的ItfD即此模式[CSharpCallLua] public interface ItfD { int f1 { get; set; } int f2 { get; set; } int add(int a, int b); }映射到Dictionary/List轻量按值模式不想定义类型或接口时可用前提是 table 的 key 和 value 类型一致。示例同时演示了Dictionarystring, double与Listdouble两种取值方式。映射到LuaTable类引用模式优点是无需生成代码缺点也很明显——比接口模式慢一个数量级且没有类型检查。示例通过d4.Getint(f1)取值。访问全局函数仍用Get方法但映射到不同目标类型映射到 delegate推荐性能更好、类型安全性更高代价是需要生成代码未生成时报InvalidCastException。声明规则是函数每个参数对应 delegate 的一个输入参数Lua 多返回值从左到右映射为 C# 的输出参数返回值、out、ref 依次排列。支持任意复杂参数与返回值包括 out、ref 修饰参数乃至返回另一个 delegate。示例中的FDelegate展示了标准用法[CSharpCallLua] public delegate int FDelegate(int a, string b, out DClass c); FDelegate f luaenv.Global.GetFDelegate(f); DClass d_ret; int f_ret f(100, John, out d_ret); // Lua 多返回值 → C# 输出参数而GetE则演示了 delegate 返回另一个 delegate 的用法[CSharpCallLua] public delegate Action GetE(); GetE ret_e luaenv.Global.GetGetE(ret_e); Action e ret_e(); e();注意CSharpCallLua特性标注的 delegate/interface 需要进入生成列表否则Get会返回 null而不是抛出异常这也是该模式建议用法的前提。映射到LuaFunction优缺点与 delegate 完全相反。使用简单LuaFunction提供可变参数的Call方法可传任意类型、任意数量参数返回值是object[]数组对应 Lua 的多返回值LuaFunction d_e luaenv.Global.GetLuaFunction(e); d_e.Call();使用建议访问 Lua 全局数据开销较高尤其是 table 和函数应尽量少做。典型做法是初始化时把后续要调用的 Lua 函数取出来映射为 delegate 并缓存之后直接调用缓存table 同理如果 Lua 侧的实现全部走 delegate 与接口模式业务代码就可以与 xLua 完全解耦由专门的模块负责 xLua 初始化与 delegate/interface 映射再把映射结果注入到使用方业务模块不再感知 xLua 的存在。Lua 调用 C#全面覆盖对象、成员、事件与高级特性本节示例位于 LuaCallCSharp 目录演示脚本内嵌于 LuaCallCs.csC# 侧被测类型DerivedClass、BaseClass、TestEnum、ICalc等均在同一文件定义。创建 C# 对象C# 写法new UnityEngine.GameObject()对应 Lua 写法local newGameObj CS.UnityEngine.GameObject()两者基本相同区别在于Lua 中没有new关键字所有 C# 相关内容构造、静态成员、静态方法都放在CS命名空间下。xLua 支持构造重载例如带字符串参数的构造local newGameObj2 CS.UnityEngine.GameObject(helloworld)访问静态成员与成员成员读静态属性CS.UnityEngine.Time.deltaTime写静态属性CS.UnityEngine.Time.timeScale 0.5调静态方法CS.UnityEngine.GameObject.Find(helloworld)性能提示对频繁访问的类型先用局部变量缓存再使用既能减少输入又能提升性能local GameObject CS.UnityEngine.GameObject GameObject.Find(helloworld)成员属性读写直接使用.语法testobj.DMF 1024 -- 写成员属性 print(testobj.DMF) -- 读成员属性调用成员方法时第一个参数需要传对象本身官方建议用冒号语法糖testobj:DMFunc()基类属性与方法xLua 支持通过派生类型访问基类的静态属性与静态方法通过派生类型实例访问基类的成员属性与成员方法。示例DerivedClass : BaseClass中BSF、BSFunc()、BMF、BMFunc()的访问均验证了这一点。out / ref 参数处理规则输入侧C# 的普通参数是输入形参ref修饰的也是输入形参但out不是。它们从左到右对应 Lua 实际参数列表。返回值侧C# 函数返回值如有是返回值out是返回值ref也是返回值其余从左到右对应 Lua 的多返回值。示例中的ComplexFunc(Param1 p1, ref int p2, out string p3, Action luafunc, out Action csfunc)演示了复杂参数组合Lua 侧调用local ret, p2, p3, csfunc testobj:ComplexFunc({x3, y john}, 100, function() print(i am lua callback) end) csfunc()注意这里同时体现了table 直接作为复杂类型实参与Lua 函数作为Action实参两个能力。方法重载可以通过不同参数类型直接访问重载函数testobj:TestFunc(100) -- 命中 TestFunc(int) testobj:TestFunc(hello) -- 命中 TestFunc(string)需要说明的限制xLua 只在一定程度内支持重载。因为 Lua 类型远少于 C#存在一对多映射——C# 的int、float、double都对应 Lua 的number。若存在这些重载参数TestFunc(100)无法区分它们只能调用其中一个生成代码中靠前的那个。操作符支持、-、*、/、、一元-、、、%、[]。示例中的DerivedClass重载了操作符Lua 侧(testobj testobj2).DMF直接可用。默认值参数、可变参数与扩展方法默认值参数与 C# 调用行为一致实参少于形参时自动补默认值。DefaultValueFunc(int a 100, string b cccc, string c null)在 Lua 中可只传1或全传。可变参数void VariableParamsFunc(int a, params string[] strs)在 Lua 中调用testobj:VariableParamsFunc(5, hello, john)扩展方法C# 中定义后 Lua 可直接使用示例GetSomeData、GetSomeBaseData访问基类扩展方法均直接可用。泛型模板方法不直接支持但可以通过扩展方法包装后调用。示例正是此思路public static void GenericMethodOfString(this DerivedClass obj) { obj.GenericMethodstring(); }Lua 侧调用testobj:GenericMethodOfString()即可间接执行泛型方法。枚举类型枚举值等价于枚举类型的静态属性testobj:EnumTestFunc(CS.Tutorial.TestEnum.E1)枚举提供__CastFrom方法支持从整数或字符串转换CS.Tutorial.TestEnum.__CastFrom(1) CS.Tutorial.TestEnum.__CastFrom(E1)注意示例中还验证了枚举作用域CS.Tutorial.DerivedClass.TestEnumInner.E3可访问而CS.Tutorial.BaseClass.TestEnumInner为nil。Delegate 与事件调用 delegate与调用普通 Lua 函数一致操作符对应 C# 的把两个调用合并成调用链右操作数可以是同类型 C# delegate 或 Lua 函数-操作符与相反从调用链中移除某个 delegatedelegate 属性可以用 Lua 函数赋值。示例完整演示了 combine/remove 链路testobj.TestDelegate lua_delegate testobj.TestDelegate testobj.TestDelegate(hello) testobj.TestDelegate testobj.TestDelegate - lua_delegate事件使用/-字符串操作符增删回调testobj:TestEvent(, lua_event_callback1) testobj:CallEvent() testobj:TestEvent(-, lua_event_callback1)64 位整数支持在 Lua 5.3 版本中long、ulong直接映射为原生 64 位整数在 LuaJIT 版本相当于标准 Lua 5.1中不支持 64 位整数xLua 提供 64 位扩展库long与ulong映射为 userdata64 位整数的运算、比较、打印以及与 Lua number 的混合运算均受支持比较时的特殊规则64 位扩展库中int64与ulong会先强转成 long 再传给 Luaulong的部分操作为了便于比较采用与 Java 相同的方式——提供一组 API详见 XLua_API_EN.md。示例验证testobj:TestLong(11)返回ulong在 Lua 中可执行l 100等运算。C# 复杂类型与 table 的自动转换对无参构造函数的 C# 复杂类型Lua 侧可直接用 table 替代——table 的字段对应复杂类型的公有字段支持作为函数参数传递、属性赋值等。例如 C# 定义public struct A { public int a; } public struct B { public A b; public double c; } // void Foo(B b)Lua 调用obj:Foo({b {a 100}, c 200})typeof 与 强转换 casttypeof(CS.UnityEngine.ParticleSystem)等价于 C# 的typeof可用于AddComponent等场景newGameObj:AddComponent(typeof(CS.UnityEngine.ParticleSystem))cast(obj, typeof(CS.Tutorial.Calc))Lua 本身无强类型转换但可指定用某类型生成的代码去访问对象。典型场景是第三方库暴露接口或抽象类型而实现类型隐藏无法为实现类型生成代码该实现会被识别为未生成代码而走反射频繁调用性能损耗明显。把接口或抽象类型加入生成代码再通过cast指定访问方式即可规避local calc testobj:GetCalc() cast(calc, typeof(CS.Tutorial.ICalc)) calc:add(1, 2) -- 改用 ICalc 的生成代码访问反射路径下的 calc.id 将不可见工程落地与进一步阅读在 README.md 的快速入门里xLua 给出 3 行代码的完整闭环——new LuaEnv()、DoString(CS.UnityEngine.Debug.Log(hello world))、Dispose()并在 C# 主动调用 Lua 时推荐声明[XLua.CSharpCallLua]delegate →Global.GetInPathLuaMax(math.max)绑定一次 → 重复调用生成代码后调用不产生 GC alloc。这与教程中的 delegate 缓存建议完全一致。在此基础上可继续阅读以下配套资料深化实践Configure_EN.mdGCOptimize、CSharpCallLua、LuaCallCSharp等生成与优化配置XLua_API_EN.mdLuaEnv、LuaTable、LuaFunction等 API 细节Faq_EN.md日常开发高频问题解答Hotfix_EN.md如需热补丁能力可了解侵入性小、运行时影响小的补丁方案。在微信小游戏 Unity WebGL 的适配场景中xLua 教程所覆盖的三种加载方式与双向调用范式正是用 Lua 驱动小游戏热更新逻辑、降低包体与迭代成本的核心基础本仓库Tutorial目录下的四个可运行示例ByString、ByFile、CustomLoader、CSharpCallLua、LuaCallCSharp可直接对照演练。赞分享游戏开发移动开发WebAssembly【免费下载链接】minigame-unity-webgl-transform微信小游戏Unity引擎适配器文档。项目地址https://gitcode.com/GitHub_Trending/mi/minigame-unity-webgl-transform点击查看免费下载相关推荐xLua 完整教程在 Unity 与 jynew 项目中加载 Lua 脚本并实现 C 与 Lua 双向调用xLua 完整教程在 Unity 与 jynew 项目中加载 Lua 脚本并实现 C 与 Lua 双向调用 本教程以仓库中的 XLua_Tutorial_EN游戏开发Celery 远程控制命令深度解析celery control / inspect / status 的底层原理与实战用法Celery 远程控制命令深度解析celery control / inspect / status 的底层原理与实战用法 celery control 、游戏开发移动开发WebAssemblyjynew 项目中的 xLua 集成教程Lua 文件加载、C 与 Lua 双向互调实战jynew 项目中的 xLua 集成教程Lua 文件加载、C 与 Lua 双向互调实战 导读 本教程以 jyx2/Assets/XLua/Doc/XLua教程文档教程知识库人工智能上一篇Docker Swarm模式深度解析节点架构与工作原理下一篇告别对方已撤回RevokeMsgPatcher防撤回补丁终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表