尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入解析石器时代gmsv与ABLua:原理、收发机制与实战踩坑

深入解析石器时代gmsv与ABLua:原理、收发机制与实战踩坑 搞石器时代服务端的人绕不开 gmsv 和 ABLua 这两个词。gmsv 是整个服务端里最核心的那个进程——GM Server角色进出、地图切换、战斗结算、任务流程全在它里面跑而它本身就是几十万行 C 语言堆出来的。ABLua 则是石器时代引擎后期引入的脚本系统它以 Lua 作为解释语言嵌入在 gmsv 的进程里让任务策划和功能扩展不用再改 C 代码重新编译。这篇文章把 ABLua 的原理、初始化过程、脚本与其他模块之间的“收发”机制以及我实际编译、调试、写脚本时踩过的坑全部摊开讲清楚。适合想读 gmsv 源码、想搞懂旧引擎脚本化改造路子、或者正在维护石器时代私服的人参考。1. gmsv 与 ABLua 的整体认识先搞清楚这俩是什么1.1 gmsv 在石器时代服务端里的定位石器时代的服务端可以拆成几个独立进程常见的有 saac账号认证、gmsv游戏主逻辑、还有处理存档和数据库交互的组件。gmsv 这个名字全称是 Game Master Server但不要被“Master”误导它不是单纯的管理工具而是真正承载游戏世界的主进程。玩家上线会被分到一个 gmsv 实例上管理整个世界地图、NPC 行为、战斗判定、道具挽留、任务推进全部由它实时计算并下发结果给客户端。gmsv 的主循环是典型的网络服务端结构初始化监听 socket、接受客户端连接、循环读取网络包、按包号分发给对应处理函数。处理函数再操作角色数据、地图数据、战斗数据最终把结果写回 socket 缓冲区发送给客户端。这个循环看着简单实际难度在数据结构和线程调度的复杂度上地图上几百个角色同时移动、施法、触发事件每个都要在几毫秒内完成状态同步。我从源码结构上总结gmsv 大体分这么几块网络层封装 socket、封包解析、协议号到处理函数的映射表角色系统角色属性、装备、技能、背包、骑宠等数据的读写地图/场景系统地图单元、遮挡关系、NPC 分布、玩家视野管理战斗系统战斗回合制逻辑、技能计算、宠物逻辑、掉落计算任务与脚本系统任务流程表、对话树、ABLua 解释器数据库接口定时或实时把角色数据存档回写数据库其中“任务与脚本系统”就是 ABLua 的栖身之地。gmsv 在启动时会把大量游戏数据读取到内存然后注册一批绑定函数把 Lua 环境备好之后整个主循环照跑只有在触发任务流程或脚本事件时才把控制权交给 Lua。1.2 为什么石器时代要引入 Lua 脚本老石器最早的任务逻辑是纯 C 写的比如 NPC 对话、任务物品发放、剧情触发全是一段一段 switch-case 加 if-else。问题非常明显改一个任务奖励要重新编译整个 gmsv风险大且不可热更新。后来引擎引入了轻量脚本早期是自家定义格式再到后期版本演变成 ABLua本质都是想解决一个核心矛盾——游戏逻辑需要频繁迭代而核心引擎代码需要稳定。Lua 嵌入方案在当时的游戏开发里几乎是标准答案。它体积小、解释器干净、和 C 互操作成本低加上堆栈式数据交换模型设计得极其浅显非常适合做配置与逻辑热插拔。ABLua 在我的理解里可以看作石器时代自己的 Lua 封装层它不是简单地把 Lua 源码编进 gmsv而是围绕游戏功能做了一套宿主接口让 Lua 脚本能取角色信息、改背包、发消息、触发战斗、推进任务状态。从架构结果看ABLua 一直用到现在还能被私服圈的人折腾原因就是它把“改动频率高”的部分和“稳定性要求高”的部分切开了。任务策划、活动脚本只碰 Luasocket 管理、地图寻路、数据库读写依旧待在 C 层。你甚至可以只改一个 .lua 文件在 gmsv 运行期间重载某段任务的逻辑这在当年那个要停机编译的年代是很珍贵的体验。2. ABLua 的设计思路C 语言里嵌脚本边界在哪2.1 ABLua 本质上是一个怎样的扩展模块ABLua 不是一个独立程序它是 gmsv 内的一组 C 编译单元依赖 Lua 解释器源码。编译之后Lua 虚拟机、绑定函数、脚本状态这些全部跑在 gmsv 进程空间里。那个年代 Windows 私服最常见的 gmsv 二进制有几十 MB其中一大部分就是 Lua 的运行时加调试符号。从模块划分上看ABLua 由三层组成。最底下是 Lua 内核负责语法分析、字节码执行、垃圾回收中间是绑定函数层由 C 实现逐个注册到 Lua 的全局表里让脚本可以调用游戏功能最上层是策略层也就是一堆 .lua 脚本它们决定具体任务怎么走、NPC 说什么话、战斗怎么奖励。我读 gmsv 源码时最先找的就是绑定函数层的注册表。这个表通常是一个静态数组里面存着 Lua 函数名字符串和对应的 C 函数指针初始化时用一个循环全部注册。比如脚本里写SACharID()解释器就会通过全局表解析到 C 函数SACharID的实现两个世界就这样接上了。2.2 C 层与 Lua 层的职责划分ABLua 的精华不是 Lua 本身而是“谁负责做什么”的边界划分。处理得当脚本写起来很爽处理错了往往是 C 层操作 Lua 栈时崩掉或者 Lua 层误传了指针导致数据损坏。我见过有些私服维护者把很重的逻辑写进 Lua比如把寻路循环、大批量地图对象遍历全塞进脚本结果性能惨不忍睹。正确姿势应该是Lua 只做流程判断、数值计算和 UI 文案拼装凡是涉及全局数据结构扫描、高频网络处理、数据库读写都必须留在 C 层以封装函数的形式给 Lua 调用。ABLua 这么做是因为 Lua 是解释执行紧凑循环和大量对象访问的开销远大于 C 函数调用的一点点栈消耗。举一个实际例子玩家点 NPC 对话。C 层负责把协议包解析成 npcIndex、charIndexLua 层处理“这个 NPC 属于哪个任务、对话分支是什么、给不给道具”而发放道具这个操作本身还是回到 C 函数直接修改角色背包结构然后由 C 层去同步给客户端和存档模块。这样职责非常清澈。2.3 初始化流程从 gmsv 启动到 Lua 环境就绪gmsv 初始化 ABLua 的顺序一般是固定的我梳理过一遍源码调用链大致是这样gmsv 启动后先做基础资源加载地图数据、NPC 表、角色模板等都先就位初始化 Lua 虚拟机调用luaL_newstate创建 lua_State打开 Lua 标准库base、string、table、math 等供脚本使用基础能力注册游戏绑定函数把 ABLua 的 C 函数表逐个挂到 Lua 全局表加载全局配置文件比如哪些 NPC 绑定哪个脚本、哪些物品触发哪段逻辑加载任务脚本目录通常是把每个独立 Lua 文件 load 进一个全局表供后续按名称调用进入主循环开始网络收发和游戏逻辑如果第 5、6 步里某份脚本语法错误gmsv 通常会把这当作启动失败处理但不同私服分支的容错不一样。有的 build 会打印“Lua runtime error”仍然继续启动有的则直接干净利落地拒绝启动因为全局脚本加载失败意味着整个世界都没法走任务了。这里有个值得注意的实现细节ABLua 的绑定函数注册早期版本用的是全局函数方式也就是你写的 Lua 脚本可以直接调用SACharIntData这种裸名字后来有的分支为了隔离命名空间改成了放在一个全局表里类似AB.SACharIntData。读源码时如果发现脚本调用不了某个函数先检查注册表和脚本调用的命名空间是否一致。3. 收与发ABLua 脚本参与的完整数据链3.1 网络层的收包处理客户端的报文怎么进入 gmsv很多人看到“收发”两个字第一反应是网络收发。确实ABLua 的一切动作源头都来自客户端发来的协议包。客户端与服务端之间的封包结构通常不是纯文本而是带 packet header 的二进制结构gmsv 网络层收到数据后先做半包粘包处理再按协议号找到对应的处理入口。gmsv 的协议映射关系多半维护在一个大 switch 或函数指针数组里。当协议是“移动”“打怪”“对话”“使用道具”时对应处理函数就开始工作。这个处理函数首先更新玩家对象的状态然后判断是否涉及脚本事件。比如收到对话协议网络层不会再自己判断 NPC 逻辑而是把事件交给 NPC 成员里的“脚本字段”来驱动。这里有个细节ABLua 并不是每个协议包都会直接进入而是“事件驱动”。事件由 C 层主动抛出Lua 脚本只是作为事件消费者。这意味着网络收发与 Lua 之间隔着一条抽象层没有协议包是直接进 Lua 的。所有数据要先被 C 层翻译成脚本参数整数索引、字符串消息体、布尔状态然后才进 Lua 栈。3.2 从 C 函数到 Lua 回调一个事件是怎么“发”进脚本里的C 层触发 Lua 回调的标准手法叫“压栈调用”。gmsv 里一旦发生某个脚本事件C 代码会拿到 Lua 虚拟机句柄按固定顺序把参数依次压入 Lua 栈然后调用一个 Lua 全局函数名对应的函数地址。举个例子处理 NPC 对话事件时C 层大致执行这些步骤从角色对象拿到 charIndex从 NPC 表查到 npcIndex打开 Lua 全局函数npcTalk把 charIndex、npcIndex 依次压栈调用lua_pcall执行检查返回值如果是脚本报错打印日志并回退到安全逻辑这个“事件发进脚本”的过程最关键的坑在参数顺序。C 层压栈顺序必须和 Lua 函数声明的参数顺序严格一致不然脚本里拿到的全是错位数据。踩过这个坑的人都知道ABLua 的调试没那么方便没有像样的调试器能断点跟踪大部分时候靠日志输出和反复试读数据。“发”进脚本还有一层含义是脚本自身的函数调用。比如 ABLua 里可以做定时器类型的延迟触发C 层在主循环里检查时间戳到点了把一个闭包或函数名 push 进 Lua 栈并执行。这在活动任务、延时奖励、战斗后处理里很常见。实现上C 层不能持有 Lua 函数的裸指针而要用 registry 或全局函数名来引用避免 Lua GC 把函数回收掉。3.3 Lua 层的结果怎么样“收”回 C 层事件发进 Lua 之后脚本执行完会产生结果。C 层“收”结果的方式主要有三种。第一种是函数返回值。Lua 脚本return 1, textC 层调用完lua_pcall后检查栈顶留了多少个值然后用lua_tointeger、lua_tolstring提取。这种模式适合状态判断比如“任务是否完成”“是否接受该任务”这种布尔或整数结果。第二种是直接调用绑定函数修改 C 数据。比如脚本里调用SAAddItemToChar(charIndex, itemId, count)这个 C 函数当场处理角色背包不需要返回什么数据已经实时写进内存。这是最常用的模式也是“收”数据最隐蔽但最高效的办法。数据修改后C 层自然会在后面的帧循环里把变化广播给客户端。第三种是通过全局表或注册表传递复杂结果。脚本处理完事件把结果写到一个约定的全局表里比如taskResults.charIndex 101然后 C 层在某个时点去读。这种方式适合异步或批量结果但容易脏读脏写实际项目里要谨慎用。我实际读代码时发现很多旧任务脚本偏爱第二种方式几乎不用 return而是大量调用绑定函数靠副作用推进逻辑。这种方式让脚本读起来像一段面向过程的命令列表排查状态时确实费点劲但胜在简单直接不需要惦记返回值去哪了。3.4 应用到游戏逻辑的典型范例任务对话、战斗结算把上面的机制串起来看一个完整例子。玩家在村里点了一个叫“神秘商人”的 NPC客户端把对话请求发给 gmsv。gmsv 网络层解析出 npcIndex 和 charIndex查到该 NPC 在 NPC 表里标记了 Lua 脚本mystic_merchant.lua于是 C 层打开全局函数npcTalk压入两个参数调用 Lua。Lua 脚本先判断玩家等级判断是否满足领取任务的条件然后调用 C 绑定函数读取当前任务状态。如果没接任务就调用SASendMsgToClient给客户端发一段话如果接了任务再判断背包里有没有信物。整段逻辑跑完后Lua 返回 0 表示“没有需要 C 层额外处理的事情”C 层收栈回到主循环。战斗结算走的是类似链路只是入口触发点在 C 层战斗系统内部。战斗结束后C 层把出战宠物、玩家等级、怪物 ID 等压进 Lua脚本决定掉落概率、是否触发下一段剧情、要不要给一个特殊称号。这种设计让服务端可以在不更新客户端的情况下调整活动规则在当年的运营环境里是极大的便利。4. 实际落地的过程编译、配置、写脚本4.1 gmsv 的编译注意要点想真正掌握 ABLua光读源码不够还得亲手把 gmsv 编出来。gmsv 的老代码常年标配 Visual C 6 / Visual Studio 2003 的工程文件.dsp / .vcproj但现在没人愿意装上古 IDE常见的处理是把工程迁移到 VS2015 以上的工具集同时解决好几个老代码兼容问题。我在迁移过程中碰到的主要障碍是这几个wchar_t 的类型定义不一样老代码经常用unsigned short替代wchar_t新编译器默认wchar_t是内置类型需要对比修改for 循环里声明变量VC6 不支持老代码通常避开但偶尔有漏网的Lua 版本必须匹配 ABLua 的封装接口老 ABLua 大多不兼容 Lua 5.4我建议锁 5.1匹配度最好工程字符集要统一石器时代的中文编码在不同版本里可能是 Big5 也可能是 GBK编译阶段的字符串字面量编码会对后来消息收发产生直接干扰编译时另一个重点是线程模型。gmsv 的收包线程和游戏逻辑线程是否分开直接影响你后面写绑定函数时会不会死锁。如果用同步网络模型就彻底别在 Lua 脚本里做耗时循环如果网络层独立线程绑定函数里访问共享数据就得小心加锁或切线程。很多私服版 gmsv 用上了“主逻辑单线程 网络线程只接收并写入队列”的模型这样 Lua 层始终跑在主逻辑线程里反而安全。4.2 配置 ABLua 需要的文件脚本目录与绑定表gmsv 启动时加载 Lua 脚本的路径通常写在配置文件里常见的叫setup.cf或直接硬编码到代码里。我维护的版本里会有这几个关键路径Script/Lua目录存放所有任务脚本一个记录全局函数名映射的表或者配置文件定义每个 NPC/任务 ID 对应哪个 Lua 函数可能的日志文件路径用于接收 Lua 错误信息和调试打印绑定表不一定需要手工改很多分支把这部分硬编码进 C 层编译时写死。如果你拿到的是需要自由配置的版本就可以在表格里指定某号 NPC 绑定某个 Lua 文件里的函数名。注意函数名最好不要带空格和特殊字符老的 ABLua 解析器对文件路径和函数名的合法性检查很弱容易在启动阶段就报错。最稳妥的做法是先编译一个最简 gmsv只注册基础绑定函数然后用一个什么都做的 hello.lua 去试探。能跑通再逐步加业务脚本。千万别上来就塞整个商业服脚本错误定位会非常痛苦。4.3 一个最小 ABLua 脚本的完整写法这里给一个最小可跑的思路。假设 gmsv 里注册了一个绑定函数SACharIntData(charIndex, dataId)用来读角色的整型数据另一个SASendMsgToClient(charIndex, msg)用于发消息给客户端。脚本可以这样写function npcTalk(charIndex, npcIndex) local lv SACharIntData(charIndex, 1001) if lv 20 then SASendMsgToClient(charIndex, 等级不够先练练再来。) return 0 end SASendMsgToClient(charIndex, 勇士你终于来了。) return 1 endC 层触发时调用全局函数npcTalk传入两个参数脚本返回 1 表示事件消费完毕。这已经是一段完整的 ABLua 逻辑了。实际工程里你会把return 1当作“进入任务后续分支”的信号C 层再据此事后回调另一个 Lua 函数。最小脚本的作用是验证注册函数、参数传递、字符串编码、错误处理四条链路是否通畅。我用它排查过大量环境问题十有八九的“脚本没生效”其实是编码炸了服务端发过去的 UTF-8 中文客户端按 Big5 解屏幕上直接乱码还连带后续协议解析错乱。5. 源码阅读路线与常用调试方法5.1 gmsv 源码里从哪开始读 ABLua 相关代码面对动辄几十万行的 gmsv 源码不建议从头到尾线性读。我自己的阅读路线是先抓全局注册表再追一两个事件入口最后再碰 Lua 内核接口。第一步全工程搜lua_register、luaL_register、LuaRegister之类的关键字。找到那张函数注册表就能看到 ABLua 给脚本开放了哪些能力。这张表通常是定位 ABLua 源码范围最快的锚点。第二步找一个你会玩的任务事件顺着它的 C 处理函数往上追。比如 NPC 对话先找到网络层协议分发的 switch再跳到对话处理函数再看它何时打开 Lua 函数。这条链路走一遍你对“收发”的理解会从抽象概念变成具体代码。第三步看lua_pcall前后的错误处理代码。ABLua 的容错能力取决于 C 层在调用 Lua 失败后做了什么是回滚状态、打日志、还是直接把进程弄崩了。这里的代码质量直接关系到你的运维体验。5.2 常用 API 和内部数据结构ABLua 常见的 C 层 API 大体分几类。运行环境类创建、关闭 Lua 虚拟机加载脚本文件执行脚本块检查错误。这些直接对应 Lua 官方 API封装不多。事件触发类压入参数、取出返回值、调用命名的全局函数。数据访问类读角色、宠物、道具、任务状态写背包、属性、任务进展。消息类对客户端发文本消息、系统公告、通知某个 UI 弹出。内部数据结构上最要紧的是角色对象和 NPC 对象的索引方式。ABLua 脚本里你拿到的通常不是指针而是整数索引后台 C 函数会拿索引去查全局对象表。所以脚本传入越界索引时C 函数自己必须做安全校验否则空指针访问是家常便饭。很多老版本 gmsv 崩溃都源于这个某个 Lua 脚本里引用了一个已下线角色的索引。5.3 调试思路Lua 报错怎么定位到 C 层调试 ABLua 没有现成的可视化工具我平常用的是“日志打点”和“二分注释”的组合拳。ABLua 的 C 层如果捕获到 Lua 异常通常会把错误消息写进日志文件或者 gmsv 的调试输出窗口。报错信息会包含脚本文件名和行号这是最重要的起点。拿到报错后先别急着看脚本。确认当前触发方式是直接调用还是定时器回调如果是定时器回调脚本里引用的角色可能已经下线许多数据访问函数会返回 0 或者空字符串。这个不是脚本逻辑问题而是“收和发”的时机错了。如果日志里没有任何 Lua 错误信息但脚本效果没出现那大概率是事件根本没进 Lua。此时在 C 层触发点加临时日志看函数有没有被调进来、参数压栈后是否有值。还有一种隐蔽问题C 层调用了 Lua 函数但函数名写错了Lua 解释器执行时报“attempt to call a nil value”而旧版 ABLua 对 nil 调用可能直接吞掉不打印。这时就要检查注册表里名字大小写是否完全一致。给一个我自己的排查清单是不是函数名拼错或命名空间错了是不是参数压栈顺序错了是不是回调时机在角色对象无效之后是不是 Lua 脚本里有变量名覆盖了全局绑定函数是不是返回值的类型和 C 层预期不符是不是脚本文件编码不对导致字符串解析错乱6. 常见问题与坑长期维护积累的经验6.1 绑定函数的数据类型陷阱ABLua 的绑定函数本质上就是一个 C 函数出口。这个出口的通用问题是“Lua 里的 number 和 C 里的 int / float 转换不直观”。Lua 5.1 里所有数字都是 double当脚本里拿到一个超大的整数索引再转成 C 的 int 时精度损失或溢出是常事。比如角色 ID 或宠物 ID如果直接用lua_tointeger取在某些编译器下会截断。另一个陷阱是字符串返回。C 层绑定的函数如果返回const char*要确认它指向的内存是静态缓冲区还是局部数组。局部数组在函数返回后就会失效Lua 层拿到悬垂指针轻则乱码重则随机崩溃。正确做法是使用 Lua 提供的lua_pushlstring主动拷贝把 C 字符串复制进 Lua 托管内存里。我碰到过一次怪问题服务端每隔一段时间就会在随机位置崩溃事后定位到某个绑定函数返回了局部char[512]缓冲区的指针当脚本层正好在 GC 触发时读取内存已经被回收。这类问题平时不发作一发作就是随机事故排查极其费神。所以我在给 ABLua 写新绑定函数时立了一条规矩所有跨语言字符串都要显式lua_pushlstring坚决不返回裸 C 指针。6.2 脚本全局变量与回调重入ABLua 是单虚拟机模型所有脚本共享同一个 lua_State意味着全局变量也是共享的。多个任务脚本如果都用了flag、count、data这种通用名字当全局变量互相覆盖就是必然的事。写脚本时务必把所有状态封装在局部变量或专用全局表中。我见过的私服里最常见的诡异 Bug 之一就是“任务 A 跑着跑着把任务 B 状态改了”十有八九是全局变量命名冲突。回调重入也是一个隐蔽问题。假如 A 事件的 Lua 处理函数里又调用了一个绑定函数该绑定函数内部再次触发同一个 Lua 函数就形成了重入调用。如果你的绑定函数里持有全局对象表锁重入时就会死锁不做锁保护则会产生迭代深度不可控的风险。我的经验是在绑定 C 函数里加一个“是否正在执行 Lua 回调”的守卫标志重入时直接拒绝或排队。这个问题的变体是Lua 函数里调用绑定函数绑定函数又触发一个新的 C 事件那个 C 事件再次进 Lua。整个调用栈像面条一样缠在一起。调试这种问题时日志里时间戳都不可靠最佳办法是给每次 Lua 调用分配一个自增序号打日志时带上序号跑完一整个任务就能还原出调用顺序。6.3 内存泄漏与垃圾回收ABLua 的内存问题分两种。第一种明显是 C 层泄漏绑定函数里 malloc 了一块内存但没有对应 free也没有交给 Lua 托管。这种泄漏在任务系统高频调用时尤其致命跑几天后内存稳步上涨。定位手段是在 Windows 上用性能计数器观察进程内存曲线配合代码审查。第二种比较轻微但也要命Lua 表无限增长。脚本里如果用全局表存玩家临时数据比如onlinePlayers[charIndex] true但角色下线时没有清理这个表就会越积越大。ABLua 本身不会因为你加了 key 就自动回收必须由脚本逻辑在角色下线事件里执行删除。我写脚本时坚持一个原则所有临时的玩家状态数据结构生命周期显式管理要么统一写在时间戳清理函数里要么在事件末尾立即置 nil。GC 时机也会影响体验。比如某个战斗中绑定函数频繁创建临时 tableLua GC 触发时刚好卡在关键逻辑里服务端就会出现短暂卡顿。改进办法不是禁用 GC而是把lua_gc的阈值调高并错峰执行比如在主循环的空闲时间或任务批次之后触发。不同 gmsv 分支对 GC 的处理差异很大有的完全交给 Lua 默认策略有的在每帧末尾主动调用效果天差地别。6.4 版本兼容ABLua 和 Lua 的搭配问题石器时代私服圈里流传的 gmsv 分支极多ABLua 版本也各不相同。有的绑定的 Lua 5.1有的用 Lua 5.3函数签名有细微差别。比如 5.1 的luaL_register在 5.2 起就改成了luaL_setfuncs如果两份代码混在一起编译链接错误会把人逼疯。我建议在选择服务端版本时先确认它捆绑的 Lua 版本然后尽量不要再升级。ABLua 的封装代码往往不是为多版本兼容设计的升级 Lua 内核会牵扯到函数注册、栈操作、字符串 intern 等多个方面。除非有明确需求否则别没事升级底层解释器。真正值得做的是把源码里的 Lua 版本号记牢写脚本时避开版本差异大的 API比如table.getn在 5.1 有而在后续版本移除这类写法在老脚本里很常见。私有二进制和源码不匹配也是常见的坑。有人直接拿别人的 gmsv.exe 跑又自己写了脚本但其实那个二进制根本不会加载新 Lua 文件因为路径、函数名都是写死的。排查半天发现不是脚本错是程序没读到脚本。这种情况我建议老老实实拿到完整匹配的源码自己编译自己维护别在黑盒上浪费时间。7. 从源码看懂收发理解了才能真正改好gmsv 和 ABLua 的组合本质上是一个“C 为骨架、Lua 为血肉”的混合体。骨架负责网络收发、数据结构、稳定运行血肉负责任务呈现、活动变化、运营迭代。理解这个关系再回看“收发”这个词就别只把它当成网络报文的事。每一次任务推进都是三层数据的传递客户端与服务端之间的网络收发、C 层与 Lua 层之间的事件收发、以及脚本修改数据时对内存结构的“写入”。这三层链路通畅整个 ABLua 系统才立得住。我给想深入源码的人一句话先把注册表和初始化流程吃透再挑一个熟悉的任务事件把代码链路走通之后几乎所有问题都会回到“这个事件是谁发起、谁消费、结果谁收走”这个模型里。换个角度说ABLua 不是什么高深魔法它就是把游戏逻辑从编译期解放到了运行期。那一年代能做出这种架构放到今天依然有很多值得借鉴的地方。
返回列表