乐于分享
好东西不私藏

用AI把一个cocos2d-x卡牌游戏从死线拉回来

用AI把一个cocos2d-x卡牌游戏从死线拉回来

 一个基于 cocos2d-x + Lua 的卡牌手游,原本跑在 Axmol 3.0.0 引擎上。引擎停维护,文档全无,545 个 Lua 文件里充满了历史编码风格的兼容性问题。 

 这篇文章记录了整个迁移过程中遇到的四个典型问题,以及 AI 在其中扮演的角色。 

项目信息

 引擎:Axmol 3.0.0(cocos2d-x 分支) · 语言:C++ + Lua 5.5 · 平台:Android 

 代码量:545 Lua 文件 / 2154 张 UI / 427 个帧动画 / 57 个 Spine 动画 

 开源:github.com/wgt19861219/CardGameAxmol 

 问题一:触摸事件在 Android 上完全不响应 

现象:主场景按钮可点击,但 popwindow(英雄详情、商店等)打开后触摸完全无响应,无报错无警告。 

根因分析

 项目的触摸监听通过一个兼容层函数 registerScriptTouchHandler 注册。这个函数内部调用了 Axmol 的 addEventListenerWithFixedPriority。 

 问题在于,FixedPriority 模式在 Android 平台上不会将触摸事件传递给 Lua 回调函数。事件被引擎层吸收了,Lua 层收到的只有沉默。没有报错,没有异常,就是不响应。 

修复方案

— 兼容层修改:compat_cocos2dx.lua — 修改前: listener = cc.Director:getInstance():getEventDispatcher()   :addEventListenerWithFixedPriority(touchListener, -129) — 修改后: listener = cc.Director:getInstance():getEventDispatcher()   :addEventListenerWithSceneGraphPriority(touchListener, node)

 改用 SceneGraphPriority 后,触摸事件按场景树 Z-order 分发,所有 popwindow 的触摸正常工作。修改量:1 行。排查时间:1 天。 

技术要点:FixedPriority 事件监听器的优先级是全局的,而 SceneGraphPriority 跟随节点的生命周期。当 Lua 回调需要节点上下文时,必须用 SceneGraphPriority。这是 cocos2d-x 到 Axmol 迁移中最常见的坑之一。 

 问题二:角色动画花屏——重写 C++ 动画引擎 

现象:战斗场景中角色的帧动画全部乱序,像素随机散布,无法辨认角色轮廓。 

根因分析

 项目使用自定义的 LegendAnimation 系统,基于 FCA 帧动画格式(ZIP 包含 sheet.plist + sheet.png)。原版 cocos2d-x 中使用私有 SpriteFrameCache + SpriteBatchNode 的架构,迁移到 Axmol 时被简化为全局缓存 + 独立 Sprite。 

 排查过程: 

 ❌ 排除:全局 SpriteFrameCache 帧名冲突 → 改为私有缓存,未解决 

 ❌ 排除:plist 帧名前缀缺失 → 完全私有缓存无需前缀,未解决 

 ❌ 排除:仿射变换不完整 → 改用 setAdditionalTransform 完整 6 分量矩阵,未解决 

 ✅ 最终:对标原版 C++ 源码,完整重写 LegendAnimation 系统 

修复方案

 重写涉及三个层面: 

1. LegendAnimationFileInfo — 私有 sprite frame 缓存,每个角色独立加载动画帧,避免全局冲突 

2. LegendAnimation — SpriteBatchNode 批量渲染 + setAdditionalTransform 应用完整仿射变换 

3. GameLuaBindings — 增强 Lua 绑定,支持外部定位、独立缩放等接口 

技术要点:SpriteBatchNode 子节点使用 setAdditionalTransform 而非直接修改节点变换,否则会与 batch 的批量渲染矩阵冲突。这个问题在 Axmol 文档中完全没有提及,是对标原版 cocos2d-x 源码才发现的。 

 问题三:JSON 模块返回布尔值 true 

现象:require “util/json” 返回 true 而非 json 表,导致存档系统无法序列化。 

根因分析

 json.lua 内部使用 module(“json”) 注册自身,这会把模块写入 package.loaded[“json”]。但 require 的调用方是 require(“util/json”),它查找的是 package.loaded[“util/json”]。 

 两个 key 不一样。找不到缓存,require 返回默认值 true。更委屈的是,即使手动从 package.loaded[“json”] 取到了表,里面的 encode/decode 在 Lua 5.5 下也是 nil——因为 Lua 5.5 移除了 module() 函数的一些内部行为。 

修复方案

— 修复:从全局缓存回退获取模块表 local json = require(“util/json”) if type(json) == “boolean” then   json = package.loaded[“json”] or {} end — 补充 Lua 5.5 下缺失的全局引用 if not json.encode then   json.encode = json.encode or rawget(_G, “json_encode”) end

 实际开发中写了三套 JSON 方案才完全覆盖所有边界情况。这个问题的本质是 Lua 模块系统在 5.1 → 5.3 → 5.5 的迭代中反复破坏向后兼容性,处理祖传代码时尤其常见。 

 问题四:链状闪电特效全屏乱飞 

现象:链状闪电技能的电流特效在屏幕上随机乱窜,而非沿目标路径传播。 

根因分析

 这是一个跨层问题。C++ 层的 LegendAnimation::applyFrame() 在每帧更新时调用 setNodeToParentTransform,直接覆盖了子精灵的位置矩阵。同时 Lua 层的 chain.lua 通过 setPosition 设置特效位置。 

 两层在抢控制权。每帧动画更新时 C++ 层覆盖 Lua 层的定位,特效被弹回动画内部的坐标,结果就是全屏乱飞。 

修复方案

 在 C++ 层新增一个 _externalPositioning 标志。当 Lua 层通过 setExternalPositioning(true) 接管定位时,applyFrame() 跳过坐标设置,只更新纹理和翻转。 

 同时修复了 projectile.lua 中 terminate 跳跃逻辑的多个 bug,确保特效在目标之间正确跳转。 

技术要点:当 C++ 引擎层和 Lua 逻辑层同时操作同一个节点的变换属性时,必须明确控制权归属。最简单的方案是加一个开关标志,让一方在接管时另一方自动退让。 

 AI 在这个项目中的角色 

 整个迁移过程用的是 AI + MCP 工具链,AI 可以直接读写项目文件、执行构建命令、查看日志输出。实际工作流如下: 

兼容层适配 — cocos2d-x 到 Axmol 有数百处 API 差异,AI 对照源码生成兼容代码,人工审核确认 

Bug 定位 — 提供日志片段,AI 分析异常调用链,对标 C++ 源码定位根因 

C++ 重写 — AI 生成代码框架,人工审核变换矩阵逻辑和边界情况 

开发日志 — 每日自动生成,23 篇日志覆盖了所有修复和踩坑 

构建与测试 — AI 执行 Gradle 构建、ADB 安装、logcat 拉取日志 

 值得注意的是,AI 不是自主做决策。架构方案的选择、哪条排查路径更优先、什么时候应该放弃一个方向——这些都是人工判断。AI 的价值在于加速执行,不是替代思考。 

 打包:不优雅但能用 

 游戏功能都正常了,但打包还是踩了一堆坑: 

R8 代码压缩 → 存档不加载,运行时行为异常。关掉 minifyEnabled 后恢复。 

Release 模式 → 比 debug 包还慢。原因未明,暂时放弃。 

setTimeScale → Axmol 中该方法不存在,调用静默失败。删除时漏了空括号,导致战斗界面崩溃。 

最终方案 → debug APK + RELEASE_MODE=true,关掉 R8,代码层屏蔽 GM 入口。 

 小结 

 整个迁移历时 24 天,记录了 23 篇开发日志,修复了四十多个 bug。几个关键经验: 

1. 引擎迁移的核心难点不是代码量,是隐式行为差异。

 像 FixedPriority 在 Android 上不传递 Lua 回调、setTimeScale 静默失败这类问题,不抛异常不打日志,单纯靠读文档很难发现。系统化的对标测试和日志记录是必不可少的。 

2. 跨层调试需要明确控制权边界。

 C++ 引擎层和 Lua 逻辑层同时操作节点变换时,必须设计清晰的接口边界。链状闪电的问题就是典型的控制权冲突。 

3. AI 加速执行,不替代决策。

 AI 能快速生成代码、分析日志、对标源码,但架构选型和方案判断仍然需要人工拍板。开发日志和知识库是整个过程中最有价值的投资。