30.xLua基础知识总结

  1. 30.总结
    1. 30.1 知识点
      1. 系列模块
    2. 30.2 核心要点速览
      1. 热更场景与前置
      2. 学习目标
      3. 环境与工程搭设
      4. C# 侧:虚拟机与 require
      5. 自定义 Loader
      6. LuaManager、双 Loader 与后缀
      7. C# 取 Lua:全局量与函数
      8. 表映射到 List / Dictionary / 类 / 接口 / LuaTable
      9. Lua 调 C#:CS、. / :、MonoBehaviour
      10. 集合:在 Lua 里按 C# 规则
      11. 扩展方法、ref/out、重载、委托与事件
        1. 扩展方法
        2. ref / out
        3. 重载
        4. 委托与事件
      12. 二维数组、Unity 判空
      13. 系统委托清单、协程、get_generic_method
      14. xLua 热补丁
    3. 30.3 面试题精选
      1. 基础题
        1. 1. LuaEnv 的 Tick、Dispose 一般在什么时机调用?DoString 第二个参数为什么要填?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. require 时 Loader 与 Resources 的先后顺序是什么?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. Lua 里 : 和 . 何时用?MonoBehaviour 为什么配合 typeof 与 AddComponent?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. 热补丁从工程到 xlua.hotfix:宏、类标记、菜单顺序?实例与静态顶替函数签名差在哪?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      2. 进阶题
        1. 1. Generate Code、Clear Generated Code、Hotfix Inject 各自解决什么问题?工程上要按什么顺序记?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 同一张 Lua 表映射成 class、带 [CSharpCallLua] 的接口、LuaTable,写回与释放上差在哪?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. Lua 调带 ref、out 的 C# 方法:实参与多返回值如何对齐?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. 热补丁里 [".ctor"]、Finalize、IEnumerator 方法各注意什么?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      3. 深度题
        1. 1. 从 Lua Global.Get 取「非 Action/Func 能表达」的函数,为什么要自定义 delegate、[CSharpCallLua] 和 Generate?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 改不了 UnityAction<float> 源码时,怎么让 Slider.onValueChanged.AddListener 接受 Lua 函数?get_generic_method 在 IL2CPP 下多什么坑?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. 热补丁中 add_事件 / remove_事件 拦截的是什么?为何 C# 里事件字段仍像 null?泛型类如何写 hotfix 目标?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章

30.总结


30.1 知识点

系列模块

  • 概述与环境搭建
  • C# 调用 Lua:解析器、加载、管理器、变量与函数、表与多种映射
  • Lua 调用 C#:类、值类型、集合、扩展方法、重载、委托与事件等互操作细节
  • xLua 热补丁:单函数到泛型类、属性、事件等替换维度

30.2 核心要点速览

热更场景与前置

常见拆法:引擎侧 C# 尽量稳定,业务逻辑放 Lua;资源侧「包内 + 远端 AB」并进。下表按客户端 / 服务端分工收起要点。

说明
客户端 C# 做引擎与宿主;Lua 承载希望线上可换的逻辑;Resources 多在包内
服务端 资源 AB + Lua 脚本 AB,检查版本后下载、进内存执行
前置 AssetBundle 会打包与下发;Lua 语法 会写脚本——缺一则「脚本进包 + 互操作」都别扭

学习目标

整条线按「能跑起来 → 双向互操作 → 热补丁兜底」排;热补丁与普通 Lua 逻辑解决的问题不同,复习时别混成一条链。

环节 要交付的能力
导入 xLua 工程里框架就绪,LuaEnv 与 Unity 正确链接
C# 调用 Lua Global、表映射、require 与 Loader,从 C# 驱动脚本
Lua 调用 C# CS、集合规矩、委托与扩展,脚本里直接用引擎 API
xLua 热补丁 IL 侧把标靶方法转进 Lua,修 已进包 的 C# 行为
  • 前四项是日常互操作;热补丁单独对付商店包内已编译、不便整包更新的 C# 行为。

环境与工程搭设

  • 源码 Tencent/xLuaCode → Download ZIP;顶层含 AssetsTools 等,本质是完整 Unity 工程目录。
  • 新项目只拷 Assets/Plugins(各平台 native)+ Assets/XLuaEditorGenSrc 等)到自家 Assets
  • 首次:XLua → Clear Generated CodeGenerate Code;同级还有 Hotfix Inject In EditorExamples
  • Asset Bundle Browser(Package Manager)→ Window → Asset Bundle Browser:Configure / Build / Inspect;新 Unity 若主用 Addressables,仍属「分包 + 远端」同一类问题。
  • 示例工程 BaseFramework(普通 / MonoBehaviour 单例)+ AssetBundleManager,把 AB 与 Lua 管线收拢,避免管理器散落。

C# 侧:虚拟机与 require

LuaEnv 即虚拟机外壳;chunkName 进异常栈,排错应填。

LuaEnv env = new LuaEnv();
env.DoString("print('ok')", "MyChunk");
env.Tick();
env.Dispose();

默认 require('模块名')Resources.Load,故 Resources 里常用 模块名.lua.txt,避免裸 .lua 扩展名加载不到。

自定义 Loader

  • AddLoader(byte[] Loader(ref string filePath))filePathrequire 括号内模块名。
  • 顺序:先入先试;首个返回非 null 的字节块即命中并停止 → 再 内置 Resources → 仍失败则抛错。
  • 磁盘示例:Application.dataPath + "/LuaScripts/" + filePath + ".lua" + File.ReadAllBytes
luaEnv.AddLoader((ref string fp) => {
    string p = Application.dataPath + "/LuaScripts/" + fp + ".lua";
    return File.Exists(p) ? File.ReadAllBytes(p) : null;
});

LuaManager、双 Loader 与后缀

工程里常在 Init 里叠磁盘 Loader 与 AB Loader,再统一用 DoLuaFile 包一层 require,和上一节「自定义 Loader」是同一机制的组合用法。

说明
Init LuaEnv + 磁盘 Loader + AB Loader(disk miss 再 TextAsset)
DoLuaFile DoString(require('name'))
Global _G 上的 Get<T> / Set
  • 后缀:Resources → .lua.txt;自管目录 → .lua;打进 AB 常改 .txt 当 TextAsset,LoadAsset 名与打包配置一致。

C# 取 Lua:全局量与函数

  • Global.Get/Set 只对 _Glocal 取不到,硬转 intInvalidCastException
  • 简单签名可用 **Action / Func**;复杂签名 → 自定义 delegate + [CSharpCallLua] + Generate;多返回用 out/ref 位;LuaFunction.Call 万能但 GC 重
  • 变长参走委托时,pairs 可能不稳,同逻辑可用 LuaFunction.Call 兜底。

表映射到 List / Dictionary / 类 / 接口 / LuaTable

目标 改 C# 后 Lua 原表
List<T>List<object> 不动
Dictionary<…> 不动;遍历无序正常
class + public 字段 不动;private/protected 不进;多键可弃、多字段默认
[CSharpCallLua] interface 写属性会回写表
LuaTable Set 改表;缺键 Get;须 Dispose

Lua 调 C#:CS. / :MonoBehaviour

local go = CS.UnityEngine.GameObject("Demo")
local colType = typeof(CS.MyBehaviour)
go:AddComponent(colType)
go:GetComponent(colType):SomeMethod()
local x = CS.UnityEngine.GameObject.Find("Demo").transform.position -- 字段用 .
  • 静态成员:Type.Member;实例方法:obj:Method();Lua 侧没有 C# 的 newCS.Foo(...) 即构造。
  • MonoBehaviour 不能当普通类直接 CS.MyMb(),须 AddComponent(typeof(CS.X))

集合:在 Lua 里按 C# 规则

  • 下标 从 0;长度 Length / Count不用 #
  • 新建数组:CS.System.Array.CreateInstance(typeof(CS.System.Int32), n)
  • Listlist:Add(v)list:Remove(v)。老版本泛型类型名字符串形如:
List`1[System.String]

新版本常用 CS.System.Collections.Generic.List(CS.System.String)(),以工程 xLua 版本为准。

  • Dictionarypairs(d);字符串键 d["k"] 可能 nil,用 **get_Item / set_Item / TryGetValue**。

扩展方法、ref/out、重载、委托与事件

扩展方法

承载扩展的 static[LuaCallCSharp] 并 Generate;Lua:实例:扩展名()

ref / out

多返回值:第一个 = C# 返回值,其后按形参顺序对应 ref/out 出参;**ref 要占位初值**,out 不占 Lua 实参位。

重载

Lua 只有 number1010.0 可能都贴 int;要 float 重载:

local m = typeof(CS.MyType):GetMethod("Foo", { typeof(CS.System.Single) })
local f = xlua.tofunction(m)
f(obj, 10.2)

委托与事件

委托 **nil 时不能 +**,先 =;事件用 obj:event("+", fn),清理由 C# 提供方法再在 Lua 调。

二维数组、Unity 判空

  • 二维:GetLength(维)GetValue(i,j),不能写 [i,j]
  • Object 假 null:Lua 里 == nil 不可靠;真 nil 不能 :Equals。可自定 IsNull 扩展[LuaCallCSharp])+ 短路:obj == nil or obj:Equals(nil) or obj:IsNull()

系统委托清单、协程、get_generic_method

  • [CSharpCallLua] static List<Type> 登记 UnityAction<float> 等系统委托,才能在 Lua 里 AddListener[LuaCallCSharp] 另一张表登记希望 Lua 高效调 的 C# 类型。
  • 协程:require("xlua.util")StartCoroutine(util.cs_generator(fn)),体内 coroutine.yield(WaitForSeconds(...))
  • 泛型方法推断失败:xlua.get_generic_method(CS.Type, "Name"),再 (类型实参…);成员调用 首参 selfIL2CPP + 值类型实参 常需 C# 侧 已具现化调用过

xLua 热补丁

步骤 内容
标记 [Hotfix]
HOTFIX_ENABLE → Scripting Define Symbols
菜单 Generate CodeHotfix Inject In Editor
排错 ToolsAssets 同级;路径忌中文;改热补 C# 后 再 Generate + Inject
-- 实例方法:首参 self
xlua.hotfix(CS.Foo, "Add", function(self, a, b) return a + b end)
-- 静态:无 self
xlua.hotfix(CS.Foo, "Speak", function(s) print(s) end)
-- 批量
xlua.hotfix(CS.Foo, { Update = function(self) end, Add = function(self,a,b) return a+b end })
  • .ctor / Finalize:特殊键;正文语义为 先 C# 再叠 LuaFinalize 要求类 有析构
  • IEnumerator:补丁 return util.cs_generator(function() … coroutine.yield(CS.UnityEngine.WaitForSeconds(1)) … end)
  • 属性 / 索引器:**get_Age / set_Age(self,v)get_Item / set_Item(self,i,v)**。
  • 事件add_事件名 / remove_事件名;补丁里不要用 obj:event("+", d) 再挂 listener,容易和拦截逻辑形成递归;C# 侧事件字段可看似一直为 null,实际订阅记录在 Lua 侧表里。
  • 泛型类xlua.hotfix(CS.MyGeneric(CS.System.String), { … })每个 T 一套

30.3 面试题精选

基础题

1. LuaEnvTickDispose 一般在什么时机调用?DoString 第二个参数为什么要填?

题目

长时间持有 LuaEnv 却从不调 Tick 会有什么风险?

深入解析
  • 正文里 Tick 用来 做 Lua 侧 GC、清掉不再要的 Lua 对象,常见做法是 每帧或切场景 里打一打。
  • Dispose不再使用该虚拟机 时释放资源。
  • DoString 的第二个参数是 chunkName,Lua 报错栈里会带这个名字,用来对照到是哪段 C# 里注入的脚本。
答题示例

长生命周期 LuaEnv 要按项目节奏 Tick,否则 Lua 垃圾回收堆着不跑,对象可能清理不及时。不用了整个 DisposeDoString(code, "可读名字") 让异常栈能指回业务而不是「string chunk」。

参考文章
  • 3.CSharp调用Lua-Lua解析器

2. require 时 Loader 与 Resources 的先后顺序是什么?

题目

filePath 是什么?全部找不到会怎样?

深入解析
  • AddLoader 注册顺序 返回字节; 内置 Resources;再失败 抛异常
答题示例

自定义 Loader 谁先返回 byte[] 谁赢;都没了才 Resources;仍无模块名则报错。filePath 就是 require('xxx') 里的 xxx

参考文章
  • 4.CSharp调用Lua-Lua文件加载重定向
  • 5.CSharp调用Lua-Lua管理器

3. Lua 里 :. 何时用?MonoBehaviour 为什么配合 typeofAddComponent

题目

CS.MyMb() 为什么不成立?

深入解析
  • :self 调实例方法;. 取字段、调静态。
  • MonoBehaviour 必须挂 GameObjecttypeofType 重载,避开 Lua 侧不便写的泛型 AddComponent<T>
答题示例

实例方法 go:Foo(),静态 Type.Bar()。脚本用 go:AddComponent(typeof(CS.X));不能 CS.MyMb() 当普通类构造。

参考文章
  • 12.Lua调用CSharp-类

4. 热补丁从工程到 xlua.hotfix:宏、类标记、菜单顺序?实例与静态顶替函数签名差在哪?

题目

HOTFIX_ENABLE 写在哪?改过热补 C# 之后要做什么?

深入解析
  • [Hotfix]GenerateInjectTools 与 Assets 同级、路径忌中文。
  • 实例 function(self,...)静态 function(...);改 C# 后 再 Generate + Inject
答题示例

Scripting Define SymbolsHOTFIX_ENABLE,类标 [Hotfix]GenerateInject。实例补丁带 self,静态不带。

参考文章
  • 24.xLua热补丁-单函数替换

进阶题

1. Generate Code、Clear Generated Code、Hotfix Inject 各自解决什么问题?工程上要按什么顺序记?

题目

空白工程接入时最少要把哪两个目录拷进 Assets?为什么要 ClearGenerate?只做「下发补丁 Lua」、从未 Inject 过,能改掉包内 C# 吗?

深入解析
  • 最少 Assets/Plugins(Lua 与各平台 native)+ Assets/XLua(框架与编辑器脚本)。
  • Generate:生成 C#↔Lua 互操作桩,把 Lua 函数、表映射绑到托管委托上,少踩反射。
  • Clear:清旧桩;改 [LuaCallCSharp] / [CSharpCallLua] 列表或生成异常时 先清再生 ,避免新旧桩 半套混用
  • Inject:改 IL,让标了 [Hotfix] 的方法 运行时跳进 Lua;和「能不能从 C# 调 Lua」不是一回事。
  • 客户端包若 没注入过,只丢 .lua 上去 改不了 已编译程序集里的 C#。
答题示例

拷贝 Plugins 与 XLua。互操作改配置:Clear → Generate。热修还要 Inject,把 [Hotfix] 方法接到 Lua;出包用的程序集必须带注入结果,之后线上多更补丁 Lua。改过热补相关 C# 要 再 Generate + Inject 再发版。从没注入过的包,光下 Lua 换不了包内 C#。

参考文章
  • 2.环境准备
  • 24.xLua热补丁-单函数替换

2. 同一张 Lua 表映射成 class、带 [CSharpCallLua] 的接口、LuaTable,写回与释放上差在哪?

题目

private 字段为何映射不进来?

深入解析
  • class + public:快照式填充,改 C# 不回写 Lua。
  • interface:代理 写属性会改表
  • LuaTable:**Set 直接改表Get 缺键抛**;须 Dispose
答题示例

类映射是拷贝,接口映射可双向;LuaTable 动态访问要释放。只有 public 参与类字段映射。

参考文章
  • 9.CSharp调用Lua-表映射到类
  • 10.CSharp调用Lua-表映射到接口
  • 11.CSharp调用Lua-表映射到LuaTable

3. Lua 调带 refout 的 C# 方法:实参与多返回值如何对齐?

题目

少传参数会怎样?

深入解析
  • ref初值占位out 不占 Lua 实参槽;第一返回值 = C# return,其后 = ref/out 出参
  • 少传会按正文规则 用默认填,易 静默错位
答题示例

ref 占位,out 跳过;第一个返回值是函数返回值,后面按声明接 ref/out。生产侧写满参数。

参考文章
  • 16.Lua调用CSharp-ref和out函数

4. 热补丁里 [".ctor"]FinalizeIEnumerator 方法各注意什么?

题目

构造补丁会不会「完全不要 C# 构造体」?

深入解析
  • 正文:先 C# 再 LuaFinalize 要类 有析构
  • 协程顶替:return util.cs_generator(...)coroutine.yield 对接 yield return
答题示例

.ctor/Finalize 是特殊键;构造是 叠加语义(以正文为准)。协程补丁返回 cs_generator 包好的 IEnumerator

参考文章
  • 25.xLua热补丁-多函数替换
  • 26.xLua热补丁-协程函数替换

深度题

1. 从 Lua Global.Get 取「非 Action/Func 能表达」的函数,为什么要自定义 delegate[CSharpCallLua] 和 Generate?

题目

漏生成典型现象?多返回、变长参怎么铺平?

深入解析
  • 自定义 delegate + [CSharpCallLua] 声明 允许 Lua 函数绑到的 CLR 签名Generate 把绑定编译进桩。
  • 漏生成:Get 失败或调用期异常。多返回:**返回值 + out/ref**;变长参可 LuaFunction.Call
答题示例

复杂签名靠 自声明 delegate + [CSharpCallLua] + Generate;否则只能 LuaFunction 硬 Call。改完特性要 Clear + Generate

参考文章
  • 7.CSharp调用Lua-函数的获取和调用

2. 改不了 UnityAction<float> 源码时,怎么让 Slider.onValueChanged.AddListener 接受 Lua 函数?get_generic_method 在 IL2CPP 下多什么坑?

题目

List<Type> 上两个特性各管哪类需求?

深入解析
  • [CSharpCallLua] List<Type>:登记 要从 Lua 当委托塞进 C# 的系统类型(如 UnityAction<float>),再 Generate
  • get_generic_method:具现化 推断失败 的泛型方法;IL2CPP值类型实参 常需 C# 已调用过同类型组合AOT 预热
答题示例

系统委托进 CSharpCallLua 静态列表 + Generate。泛型方法裸调失败用 xlua.get_generic_method;真机 IL2CPP 对 值类型泛型实参 要防 未具现

参考文章
  • 21.Lua调用CSharp-系统代码交互
  • 23.Lua调用CSharp-泛型

3. 热补丁中 add_事件 / remove_事件 拦截的是什么?为何 C# 里事件字段仍像 null?泛型类如何写 hotfix 目标?

题目

补丁里为何不能 self:myEvent("+", delegate)?属性与默认索引器键名?

深入解析
  • add/remove 拦截 **+=/-=**,委托列表 落 Lua;补丁里再写 self:myEvent("+", fn) 一类语法,容易和拦截逻辑形成 递归
  • C#get_Age/set_Ageget_Item/set_Item泛型类CS.Foo(CS.System.Int32)T 注册
答题示例

加减事件由 add/remove 拦到 Lua 表;补丁里别再写 obj:event("+", fn) 叠订阅,容易递归。属性/索引器用 getter/setter 方法名顶替。泛型 CS.MyType(具体T) 逐个 **Txlua.hotfix**。

参考文章
  • 27.xLua热补丁-属性和索引器替换
  • 28.xLua热补丁-事件加减操作替换
  • 29.xLua热补丁-泛型类替换


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏