11.xLua实践项目总结

11.总结


11.1 核心要点速览

项目概述

C# 侧 Init + DoLuaFile 起 Lua,AB 提供 UI 与 JSON;Lua 里 InitClassCS.* 拉到全局,BasePanel 按命名规则扫控件,背包ItemGrid + PlayerData + ItemData 联调;编辑器工具把 .lua 批成 .lua.txt 打进 luascripts 包,为热更脚本做准备。

实现顺序建议

  1. 环境:xLua、AB、Object / SplitTools / JsonUtility,C# 能稳定执行 LuaMain
  2. InitClass:全局别名与 Canvas 引用就位,再读表、再挂面板
  3. 资源与表:Excel→JSON、图集、UI 预制打 AB;ItemDataPlayerData:Init()
  4. UI 框架BasePanelMainPanel / BagPanel / ItemGrid,LuaMain 里 require 顺序 与下文骨架一致
  5. 热更链路Lua→txtAssetImporter 批设包名、整包验证

环境准备与脚本栈

工程拆三块:基础框架xLua 与 ABLua 工具脚本。工具链重点三件事:面向对象底座JSON字符串拆分;运行入口是 C# 初始化 Lua 管理器后再拉起 Lua 主文件

模块 作用 要点
Object.lua 极简 OOP:new/subClass new:实例元表指到类表,类表上挂 __index 指回自己,实例找不到成员就走到类方法。subClass:在 _G 上注册子类表,__index 指子类自身,base 指父类,setmetatable(子类, 父类) 继承父类成员
JsonUtility.lua dkjson(模块 + 可选全局 json Json.decode 等与 InitClassJson = require("JsonUtility") 对齐;encodefunction 等类型有跳过逻辑,别把任意 Lua 函数写进 JSON
SplitTools.lua string.split 空分隔符返回 false;用 plain 模式的 string.find 循环切分,尾部剩余一段最后 insert

启动链(正文示例)

void Start()
{
    LuaManager.Instance.Init();
    Debug.Log("C#主脚本准备就绪");
    LuaManager.Instance.DoLuaFile("LuaMain");
}
print("Lua主脚本准备就绪")

C# 侧保证 Init 完成再 DoLuaFile,避免虚拟机或路径没就绪就去 require/执行主脚本;Lua 主脚本里再逐步挂 UI 与业务。

UGUI 面板拼装

  • 导入 UICanvas → 拼 主面板 / 背包 / 格子 预制;导图小节当步骤 checklist,层级一次摆对、命名一次对齐,后面 GetComponentsInChildren 才省事。

InitClass 与全局别名

  • 聚合 requireObjectSplitToolsJsonUtility)并把常用 CS.* 缩写到全局:GameObjectResourcesRectTransformUI 下的 Image/Text/Button/Toggle/ScrollRectVector2/3SpriteAtlasAssetBundleManager 等。
  • Canvas = GameObject.Find("Canvas").transformrequire 时执行一次,全系列共用;场景里必须存在该名字的 Canvas。
  • 依赖顺序:先有 InitClass,再 ItemData、面板脚本;否则 typeof(TextAsset)JsonAssetBundleManager 等未就绪会直接报错。

配置表、AB 与运行时数据

步骤 产出 脚本/注意
配置 Excel→JSON 数组 字段示例:id、name、icon、type、tips
资源 icon 图集、UI 预制 AB;xLua 报生成缺失则 补生成再打包
运行时表 ItemData[id] 字典 json 包加载 TextAssetJson.decode 后转存
玩家背包 equips / items / gems 元素 {id,num}Init 当前为写死测试数据,可换本地存档或服务器

BasePanel

  • 成员panelObjcontrolsisInitEvent
  • Init:AB 加载预制,SetParent(Canvas,false)GetComponentsInChildren(UIBehaviour),仅当 name 含 btntogimgsvtxt 才入库。
  • controlscontrols[节点名][C# 类型名] = 组件,同一物体多 UIBehaviour 互不争抢引用。
  • GetControl(name, typeName)ShowMeInit 再 **SetActive(true)**。

MainPanel

  • BasePanel:subClass("MainPanel")Initself.base.Init(self, name) 后用 isInitEvent 只挂一次监听。
  • btnRoleButton 回调里 **BagPanel:ShowMe(“BagPanel”)**。

ItemGrid

  • Init:拉 ItemGrid 预制,设父与 localPositionFind imgIconText
  • InitDataItemData[id]icon_ 拆分 图集名与精灵名;SpriteAtlas:GetSpriteText 写数量。
  • DestroyGameObject.Destroyobj = nil

BagPanel

  • ChangeType
    • nowType == type 直接 return,避免重复刷同一页签
    • DestroyItemGrid,清空 items
    • 按 type 取 PlayerData.equips / items / gems,循环 ItemGrid:new() → Init → InitData
    • 网格:x (i-1)%4*175y math.floor((i-1)/4)*175
    • 末尾 nowType = type,页签状态才与数据一致
  • InitsvBagScrollRectViewport/Content 手动物理路径 FindbtnClose;三个 Toggle 对应 装备 / 道具 / 宝石
  • ShowMebase.ShowMe 后若 nowType == -1,首次 ChangeType(1) 保底装备页

Lua 启动顺序骨架

require("InitClass")
require("ItemData")
require("PlayerData")
PlayerData:Init()
require("BasePanel")
require("MainPanel")
require("BagPanel")
require("ItemGrid")
MainPanel:ShowMe("MainPanel")

Lua 批量改后缀与 AB

  • 菜单 XLua/自动生成txt后缀的LuaLuaScripts/*.luaLuaScriptsTxt/*.lua.txt;可先清空目标目录旧 *.txt
  • Path.GetFileName,避免 Windows 下用 / 截取路径失败。
  • AssetDatabase.Refresh 后以 Assets/...GetAtPathassetBundleName = "luascripts" 统一进热更包。

11.2 面试题精选

基础题

1. Lua 里的 typeof 和 InitClass 有什么关系

题目

ItemData.luaLoadAssetBundleResource(..., typeof(TextAsset)) 中的 typeof 是什么?和 Lua 自带的 type() 有何区别?不用 InitClass 行不行?

深入解析
  • xLua 把 C# 的 typeof 暴露到 Lua,用来取 CLR 的 System.Type,交给 LoadAssetBundleResource 这类 C# API 做类型判别或重载选择。
  • type() 是 Lua 值类型标签(tablestring 等),与 typeof 完全不是一层概念,面试时别混答。
  • InitClass 里若未把 TextAsset 绑到 CS.UnityEngine.TextAsset(或未 require 到可用符号),Lua 侧 typeof(TextAsset) 会报未定义或类型不对;所以 别名脚本要先于 ItemData
答题示例

typeof 是走 xLua 调 C# 的,拿的是 C# 类型对象,给加载接口用。

Lua 的 type() 看的是 Lua 值种类,和 typeof 无关。

InitClass 先把 TextAsset 等指好,再跑 ItemData,否则 typeof(TextAsset) 根本就不可用。

参考文章
  • 4.常用类别名
  • 5.数据相关

进阶题

1. Object 里 new 和 subClass 各自改了哪些表、元表关系是什么

题目

Object.luaObject:new() 产出的实例,和 Object:subClass("Foo") 产出的类表,在元表与 __index 上分别是什么关系?子类实例调方法时,Lua 按什么顺序查找?

深入解析
  • newlocal obj = {}setmetatable(obj, self),当前 self 是类表;类表里用 self.__index = self,实例缺键时通过元表的 __index 找到类表本身,从而拿到类上的方法。
  • subClass:子类表 objobj.__index = objobj.base = selfsetmetatable(obj, self)。子类 类表 的未定义键会到 父类表;之后对子类做 子类:new() 时,产出的 实例 元表指向子类表,查找实例方法先命中子类,再沿继承链向上。
  • 易混点:不要误以为 subClass 里该反复改父类的 __index;应让 子类表自己的 __index 指向子类,继承靠 子类表的 metatable = 父类表
答题示例

new 出来的是实例,元表指向当前类,类上 __index 回指类,所以实例能调到类方法。

subClass 注册的是「类表」:子类 __index 指自己,setmetatable(子类, 父类) 管继承;实例再用子类去 new,查找会从子类类表开始。

调用实例方法时,先在实例表找,没有就看元表类的 __index,再沿父类链。

参考文章
  • 2.环境准备

2. BasePanel 的 controls 为什么做成「控件节点名 → 类型名 → 组件」

题目

BasePanelUIBehaviour 时,为什么用 controls[controlName][typeName] 两层表,而不是 controls[controlName] = 单个组件

深入解析
  • 同一 GameObject 上可能同时挂 Button + Image 等多个 UIBehaviour,若字典值只存一个引用,后写的会 覆盖 先写的,GetControl 时拿不到另一类型。
  • typeNameGetType().Name,与 GetControl 第二个参数传入的字符串 对齐,调用侧明确自己要 Button 还是 Image
  • 命名前缀(btn/tog/img…)把大量无关 Transform 过滤掉,减少字典噪音与误绑定。
答题示例

一层表只能放一个组件,同一物体多个 UI 脚本会互相覆盖。

外层用节点名、内层用 C# 类型名,和 GetControl(name, "Button") 这种取法一一对应。

前缀规则还能在拼面板阶段就挡住没命名的杂控件。

参考文章
  • 6.面板基类

深度题

1. 为何由 C# Start 里 Init 再 DoLuaFile,而不是反过来或直接 Awake 里全做

题目

示例把 LuaManager.Instance.Init()DoLuaFile("LuaMain") 放在 Start 里,这段顺序如果颠倒或挪到 Awake,在你做 xLua 工程时可能踩哪些坑?

深入解析
  • Init 在前:一般要完成 loader、路径、LuaEnv 构造、自定义 require 搜索路径等;未完成就去 DoLuaFile,常见是 找不到脚本Globals 未绑好初始化只执行了一半
  • 与 Unity 生命周期的关系:同物体上若还有别的脚本在 Awake 里依赖 Lua 已跑起来,会把启动点提到更晚或显式分阶段;反之若 Lua 里立刻要读 UnityEngine 对象,需保证 C# 侧该绑的已经绑完。
  • 工程取舍:实践里常把「虚拟机/搜索路径」与「首包 Lua 入口」拆成两步,便于打日志和排错;真要提前到 Awake,需核对 LuaManager 实现是否允许且 Execution Order 是否与其它依赖一致。
答题示例

InitDoLuaFile,是保证 Lua 环境、loader、路径就绪后再执行主脚本;顺序反了容易加载失败或半初始化。

AwakeStart 取决于项目里还有谁依赖 Lua:若别的 Awake 要等 Lua,就得调执行顺序或把 Lua 启动再提前;否则 Start 晚一帧往往能避开部分依赖顺序问题。

具体仍要以你项目 LuaManager 的实现为准,关键是 环境就绪依赖脚本 两件事不要竞态。

参考文章
  • 2.环境准备

2. 为什么要把 .lua 复制成 .lua.txt 再打 AB、并用编辑器批量设包名

题目

工具把 LuaScripts/*.lua 拷到 LuaScriptsTxt/*.lua.txt,刷新资源后把路径统一标成 luascripts AB。这样设计在玩 xLua + AssetBundle 时主要在解决什么问题?

深入解析
  • 源文件是 *.lua,导入工程参与打包的是 *.lua.txt,走 TextAsset 导入与 AssetBundle 赋值,和「热更包里是一段文本再由 xLua DoString/loader 执行」的路径对齐;仓库/编辑仍可用常规 .lua 扩展名写逻辑。
  • 整批复制 + 清空目标目录 避免删源 Lua 后目标目录仍残留旧文件进包。
  • AssetDatabase.Refresh 后再改 importer,否则 GetAtPath 可能仍指向旧缓存;统一 assetBundleName 便于运行时 单包拉齐 多份脚本,减少散包管理成本。
  • Path.GetFileName 规避 Windows 反斜杠路径 下用 / 手动截取文件名的坑。
答题示例

后缀改成 .lua.txt 是按 TextAsset 进包,再用统一 AB 名整批热更,运行时读文本交给 xLua。

先 Refresh 再改 AB 名,否则 Importer 还是旧的;统一 luascripts 便于一条包加载全部脚本。

Path.GetFileName 是为了在 Windows 上也能稳定取文件名。

参考文章
  • 10.批量转换Lua脚本后缀工具


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

×

喜欢就点赞,疼爱就打赏