金庸群侠传3D重制版jynewxLua 脚本桥接 API 完全指南C#/Lua 互操作、类型映射与宏配置【免费下载链接】jynewJinYongLegend-like RPG Game Framework with full Modding support and 10 hours playable samples of game.项目地址: https://gitcode.com/GitHub_Trending/jy/jynew本指南以 jyx2/Assets/XLua/Doc/XLua_API.md 为骨架结合金庸群侠传3D重制版jynew仓库中的 xLua 运行时源码LuaEnv.cs、LuaTable.cs 等与实战接入代码LuaManager.cs、LuaMonoBehavior.cs展开。读者读完将掌握如何在 jynew 中创建与管理 LuaEnv 虚拟机、通过 C# 侧 API 执行/加载 Lua 代码块、用 LuaTable 读写 Lua 环境、在 Lua 侧调用 C# 类型与静态成员、理解两语言间的类型映射规则并利用宏开关定制 xLua 的生成行为。xLua 是 jynew 的 Lua 脚本桥接层游戏的战斗 AI、配置解析、事件逻辑等大量逻辑以.lua形式存在于 jyx2/Assets/LuaScripts 与各 Mod 目录而 C# 侧通过XLua.LuaEnv等类驱动它们。理解本文 API 是读懂、调试乃至二次开发 jynew 逻辑脚本的基础。一、C# 侧核心 API1.1 LuaEnv 类Lua 虚拟机的宿主LuaEnv实现见 LuaEnv.cs代表一个独立的 Lua 运行时状态。jynew 在 LuaManager.cs 中全局创建唯一实例文档亦明确建议全局就一个实例并在 Update 中调用 GC 方法完全不需要时调用 Dispose。DoString执行一段 Lua 代码public object[] DoString(string chunk, string chunkName chunk, LuaTable env null)chunkLua 代码字符串chunkName出错时的 debug 显示信息用于指明某某代码块的某行出错调试定位全靠它env该代码块运行时的环境变量LuaTable缺省为全局环境返回值代码块中return语句的返回值数组。例如代码块return 1, helloDoString返回包含两个 object 的数组一个是 double 类型的1一个是 string 类型的helloLuaEnv luaenv new LuaEnv(); object[] ret luaenv.DoString(print(hello)\r\nreturn 1); UnityEngine.Debug.Log(ret ret[0]); luaenv.Dispose();从实现看LuaEnv.csDoString内部先通过xluaL_loadbuffer将字符串按UTF-8 编码加载为 chunk再以lua_pcall保护调用执行出错会抛出封装后的LuaExceptionLuaEnv.cs。DoString另有接收byte[]的重载jynew 读取.lua文本文件后正是以Encoding.UTF8.GetBytes(...)传入见 LuaManager.cs、LuaTestStarter.cs。LoadString 加载但不执行public T LoadStringT(string chunk, string chunkName chunk, LuaTable env null)加载一个代码块但不执行返回类型T可指定为 delegate 或LuaFunction。实现中LuaEnv.cs会对T做合法性校验T必须是LuaFunction或 delegate 类型否则抛出InvalidOperationException。若传入env会调用lua_setfenv为加载的 chunk 设置环境。Global全局环境表public LuaTable Global { get; }代表 Lua 全局环境_G的LuaTable实现见 LuaEnv.cs。jynew 用它获取全局函数例如 LuaManager.cs 中luaEnv.Global.GetLuaFunction(name)从全局环境按名字取出 Lua 函数并缓存。Tick()定期回收未手动释放的 LuaBase 对象public void Tick()清除未手动释放的LuaBase对象如LuaTable、LuaFunction以及其它待回收资源需要定期调用比如在 MonoBehaviour 的 Update 中调用。Tick的实现LuaEnv.cs会先清空内部refQueue队列中的待释放引用再做 Unity 对象有效性检查。jynew 的 LuaMonoBehavior.cs 即以约 1 秒的间隔在Update中调用luaEnv.Tick()。LuaEnv还提供了GC()兼容 API内部即Tick()jynew 在 LuaManager.cs 中调用它并配合 Lua 侧get_lua_memory_cost做内存观测。AddLoader注册自定义 require loaderpublic void AddLoader(CustomLoader loader)CustomLoader委托类型为delegate byte[] CustomLoader(ref string filepath)LuaEnv.cs。当 Lua 中require一个文件时所有注册的 loader 会被依次回调参数为require所传的文件名loader 找到文件后将其读进内存并返回 byte 数组UTF-8 编码未命中返回null。如果需要支持调试需要把filepath设置为 IDE 能定位到的真实路径相对或绝对均可——这正是 jynew 支持编辑器热重载 Lua 的基础。jynew 注册了两个 loaderLuaManager.csluaEnv.AddLoader((ref string filename) { // require 时首先去当前的 LuaScript 目录查找 var luaFile ResLoader.LoadAssetSyncTextAsset($Assets/LuaScripts/{filename}.lua); if (luaFile ! null) return Encoding.UTF8.GetBytes(luaFile.text); return null; }); luaEnv.AddLoader((ref string filename) { // require 时默认去 baseasset 下加载 lua var luaFile ResLoader.LoadAssetSyncTextAsset($Assets/BuildSource/Lua/{filename}.lua); if (luaFile ! null) return Encoding.UTF8.GetBytes(luaFile.text); return null; });这条先 LuaScripts 后 BuildSource的查找链配合 LuaManager.cs 的LoadLua编辑器模式下直接读取Assets/Mods/{ModId}/Lua/目录文件实现热重载让 Mod 作者可以用同名 Lua 文件覆盖底层逻辑。Dispose()销毁虚拟机public void Dispose()释放该LuaEnv。实现LuaEnv.cs会先做FullGc()若仍有 C# 回调DelegateBridge未释放会抛出异常提示随后lua_close关闭原生 Lua 状态。jynew 在场景切换清理时调用LuaManager.cs并在Clear()中先执行Jyx2:DeInit()反初始化脚本再 DisposeLuaManager.cs。1.2 LuaTable 类读写 Lua 表的强类型接口LuaTable实现见 LuaTable.cs继承自LuaBase是对 Lua table 的引用封装所有访问 API 均与ObjectTranslator协作完成值推送与读取。API说明源码位置T GetT(string key)获取 key 下类型为 T 的 value不存在或类型不匹配返回 nullLuaTable.csT GetInPathT(string path)识别 path 中的.如tbl.GetInPathint(a.b.c)等价于 Lua 的i tbl.a.b.c避免多次 Get效率更高LuaTable.csvoid SetInPathT(string path, T val)对应GetInPathT的 setterLuaTable.csvoid GetTKey,TValue(TKey key, out TValue value)泛型键版 GetKey 不限于 stringLuaTable.csvoid SetTKey,TValue(TKey key, TValue value)对应泛型键 Get 的 setterLuaTable.csT CastT()把 table 转成 T 指明类型带CSharpCallLua的 interface、有默认构造的 class/struct、Dictionary、List 等LuaTable.csvoid SetMetaTable(LuaTable metaTable)设置 table 的 metatableLuaTable.cs源码补充说明GetInPathT/SetInPathT底层走xlua_pgettable_bypath/xlua_psettable_bypath是原生层面的路径寻址LuaTable.csGetTKey,TValue若取到nil且目标类型为值类型会抛InvalidCastException避免把 nil 赋给值类型LuaTable.csCastT与代码生成机制直接关联只有声明了[CSharpCallLua]的接口/委托才能无反射地完成转换详见下文宏与实战部分。jynew 中用LuaTable为每个 Lua 脚本创建独立环境以隔离全局变量LuaMonoBehavior.csscriptEnv luaEnv.NewTable(); // 为每个脚本设置一个独立的环境可一定程度上防止脚本间全局变量、函数冲突 LuaTable meta luaEnv.NewTable(); meta.Set(__index, luaEnv.Global); scriptEnv.SetMetaTable(meta); // 独立环境但可回退到全局 scriptEnv.Set(self, this); // 注入 C# 对象 foreach (var injection in injections) { scriptEnv.Set(injection.name, injection.value); // 注入 Inspector 拖拽的对象 } luaEnv.DoString(luaScript.text, LuaTestScript, scriptEnv); // 在该环境中执行随后通过scriptEnv.GetAction(awake)、scriptEnv.Get(start, out luaStart)等把 Lua 中定义的awake/start/update/ondestroy函数取出绑定到 Unity 生命周期回调。1.3 LuaFunction 类调用 Lua 函数的轻量入口LuaFunction用于从 C# 调用 Lua 函数但有 boxing/unboxing 开销文档明确警告需要频繁调用的地方不要用该类应改为table.GetABCDelegate()获取 delegate 后调用前提是先把ABCDelegate加入代码生成列表。API说明object[] Call(params object[] args)以可变参数调用 Lua 函数返回该调用的所有返回值object[] Call(object[] args, Type[] returnTypes)调用并指明返回参数类型系统按指定类型自动转换void SetEnv(LuaTable env)相当于 Lua 的setfenv设置函数环境jynew 的 LuaManager.cs 正是取函数 Call的典型用法getCachedFunction从luaEnv.Global.GetLuaFunction(name)取得函数并缓存到_cachedFunc字典随后func.Call(paras)执行并取返回值。二、Lua 侧 API如何反向调用 C#2.1 CS 对象Lua 里构造 C# 类型、访问静态成员-- 调用 C# 类型的构造函数返回类型实例 local v1 CS.UnityEngine.Vector3(1,1,1) -- 访问 C# 静态成员 print(CS.UnityEngine.Vector3.one) -- 访问 C# 枚举值 -- CS.namespace.enum.fieldCS命名空间由 xLua 在虚拟机初始化时注册LuaEnv.csAddBuildin(CS, StaticLuaCallbacks.LoadCS)并通过元表惰性加载类型LuaEnv.cs 的init_xlua代码中__index元方法负责按全限定名import_type。访问 C# 对象与访问 table 一样调用函数与调用 Lua 函数一致还可以用操作符访问 C# 重载运算符local v1CS.UnityEngine.Vector3(1,1,1) local v2CS.UnityEngine.Vector3(1,1,1) v1.x 100 v2.y 100 print(v1, v2) local v3 v1 v2 print(v1.x, v2.x) print(CS.UnityEngine.Vector3.one) print(CS.UnityEngine.Vector3.Distance(v1, v2))jynew 的战斗 AI 脚本即大量使用该语法例如 AIManager.luaai.rangeLogic CS.Jyx2.BattleManager.Instance:GetRangeLogic() ai.BattleModel CS.Jyx2.BattleManager.Instance:GetModel() MAX_ROLE_TILI CS.GameConst.MAX_ROLE_TILI result CS.Jyx2.AIResult()2.2 typeof 函数类似 C# 的typeof关键字返回一个Type对象。典型场景GameObject.AddComponent的重载需要Type参数newGameObj:AddComponent(typeof(CS.UnityEngine.ParticleSystem))其实现即 LuaEnv.cs 中的typeof function(t) return t.UnderlyingSystemType end。2.3 无符号 64 位uint64支持Lua 的 number 不足以精确表示无符号 64 位整数xLua 提供了独立模块在LuaEnv构造时通过luaopen_i64lib注册见 LuaEnv.cs函数说明uint64.tostring无符号数转字符串uint64.divide无符号数除法uint64.compare无符号比较相等返回 0大于返回正数小于返回负数uint64.remainder无符号数取模uint64.parse字符串转无符号数2.4 xlua 工具函数函数说明xlua.structclone(value)克隆一个 C# 结构体xlua.private_accessible(class)让一个类的私有字段、属性、方法等可用xlua.get_generic_method(class, methodName)获取泛型方法xlua.private_accessible示例xlua.private_accessible(CS.UnityEngine.GameObject)xlua.get_generic_method完整示例先取泛型方法定义再用具体类型实例化后调用local foo_generic xlua.get_generic_method(CS.GetGenericMethodTest, Foo) local bar_generic xlua.get_generic_method(CS.GetGenericMethodTest, Bar) local foo foo_generic(CS.System.Int32, CS.System.Double) local bar bar_generic(CS.System.Double, CS.UnityEngine.GameObject) -- call instance method local o CS.GetGenericMethodTest() local ret foo(o, 1, 2) print(ret) -- call static method bar(2, nil)2.5 cast 函数以特定接口访问对象指明以特定的接口访问对象这在实现类无法访问时比如internal修饰很有用。假设calc对象实现了 C# 的PerformentTest.ICalc接口cast(calc, typeof(CS.PerformentTest.ICalc))cast在init_xlua中被直接绑定为xlua.castLuaEnv.cs。除上述 API 外文档特别注明然后就木有其它 API 了——访问 C# 对象与访问 table、调用函数与调用 Lua 函数同构无需额外学习成本。三、C# 与 Lua 的类型映射3.1 基本数据类型C# 类型Lua 类型sbytebyteshortushortintuintdoublecharfloatnumberdecimaluserdatalongulonguserdata / lua_IntegerLua 5.3byte[]stringboolbooleanstringstring值得注意byte[]直接映射为 Lua string这使得在 Lua 侧处理二进制数据时天然复用 Lua 字符串 APIlong/ulong在 Lua 5.3 下可映射为lua_Integer否则以 userdata 承载配合上文uint64模块。3.2 复杂数据类型C# 类型Lua 类型LuaTabletableLuaFunctionfunctionclass 或 struct 的实例userdatatablemethoddelegatefunction各类别的具体规则LuaTableC# 侧指明从 Lua 侧输入C# 方法参数或 Lua 方法返回值LuaTable类型时要求 Lua 侧为 tableLua 侧 table 在 C# 侧未指明类型时默认转换为LuaTable。LuaFunction同理指明LuaFunction要求 Lua 侧为 function未指明类型时 Lua function 默认转换为LuaFunction。LuaUserData对应非 C# Managed 对象的 Lua userdata。class 或 struct 实例从 C# 传实例映射为 Lua 的 userdata并通过__index访问其成员C# 侧指明从 Lua 侧输入指定类型对象时若 Lua 侧是该类型实例的 userdata 可直接使用若该类型有默认构造函数而 Lua 侧是 table则会自动转换——调用构造函数构造实例并用 table 对应字段转换到 C# 对应值后逐一赋值成员。method、delegate成员方法与 delegate 均对应 Lua 侧函数。C# 侧的普通参数以及引用参数对应 Lua 侧函数参数C# 侧返回值对应 Lua 的第一个返回值引用参数ref和 out 参数按序对应 Lua 的第 2 到第 N 个返回值——这一约定在编写跨语言回调时务必遵守。jynew 中这一映射的实际佐证是 Jyx2LuaToCsBridge.cs 中大量[CSharpCallLua]接口如LBattleConfig、LExtraConfig、LRoleSkill、LRoleItem用于让 C# 直接以强类型接口解读 Lua 侧的配置表将 Lua table 通过CastT无反射地转换到 C# 接口。四、宏定制代码生成与功能开关xLua 通过 C# 条件编译宏控制生成行为宏作用HOTFIX_ENABLE打开 hotfix热补丁功能NOT_GEN_WARNING反射时打印 warningGEN_CODE_MINIMIZE以偏向减少代码段的方式生成代码其中HOTFIX_ENABLE开启后LuaEnv会启用线程锁与热更新相关路径LuaEnv.cs 中#if THREAD_SAFE || HOTFIX_ENABLE分支且xlua.hotfix全局函数在 LuaEnv.cs 中定义GEN_CODE_MINIMIZE会让LuaEnv构造时设置CSharpWrapperCallerLuaEnv.cs并影响 Editor/Template 下各 wrap 模板的生成策略可对比LuaRegister.tpl.txt与LuaRegisterGCM.tpl.txt两套模板。配合使用的还有两个代码生成特性定义于 GenAttributes.cs[LuaCallCSharp]声明某个 C# 类/方法要被 Lua 调用并生成 wrap 代码。jynew 的 Jyx2LuaBridge.cs 即用其标注Jyx2LuaBridge静态类作为 Lua 调 C# 的统一桥。[CSharpCallLua]声明某个 C# 接口/委托要被 Lua 侧赋值Lua 函数赋给 C# delegate并生成无反射的转换代码。jynew 的 Jyx2LuaToCsBridge.cs 中的接口群即属此类。五、综合实战jynew 中 Lua 桥接的完整调用链将上文 API 串联起来jynew 的 Lua 脚本系统工作流如下初始化LuaManager.Init 创建唯一LuaEnv注册两个AddLoaderLuaScripts 目录 → BuildSource/Lua 目录再DoString执行入口脚本入口引导入口脚本 InitLuaScripts.lua 维护全局Jyx2表通过require LuaModuleList批量注册模块Jyx2:AddModule/Jyx2:GetModule并把Jyx2Utils挂到全局jy_utils环境隔离LuaMonoBehavior 为每个挂在 GameObject 上的 Lua 脚本创建独立LuaTable环境NewTableSetMetaTableSet注入DoString执行后把awake/start/update/ondestroy取回 C# 侧按 Unity 生命周期驱动双向调用Lua 侧通过CS.Jyx2.BattleManager.Instance等访问 C# 单例见 AIManager.luaC# 侧通过Global.GetLuaFunctionCall调 Lua见 LuaManager.cs回收Update中周期调用Tick()LuaMonoBehavior.cs场景切换时执行Jyx2:DeInit()后Dispose()LuaManager.cs。进一步阅读可参考同目录下的 XLua教程.md、XLua的配置.md、XLua增加删除第三方lua库.md、XLua复杂值类型structgc优化指南.md 与 XLua性能分析工具.md它们共同构成了本项目 xLua 体系从 API 到性能调优的完整文档链。【免费下载链接】jynewJinYongLegend-like RPG Game Framework with full Modding support and 10 hours playable samples of game.项目地址: https://gitcode.com/GitHub_Trending/jy/jynew创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考