vibe coding 做小游戏·打包篇:发 Mac 试玩包、查启动慢,和一次 AI 自己改出来的崩溃

前三篇讲了开工、美术和战役玩法。这一篇讲第一次把游戏交给别人玩:打一个 Mac 试玩包。

10 月 26 日 17:00 到 18:16,我对 Claude Code 说了 4 句话:「帮我打包一个安装包」「启动太慢了,性能优化一下」「打开一下,我试玩一下」「崩了?」。这 76 分钟里,AI 只下载了导出模板里需要的 126 MB(完整包 1281 MB),打出了带签名和打开说明的安装包;把 1.5 到 3.5 秒的启动时间拆开,确认游戏自己只占 0.5 秒左右;然后它的启动优化让安装包一点「出征」就崩。本文按顺序讲这三段,重点是最后一段:崩溃怎么来的,以及之后打包流程改成了什么样。

崩溃这件事值得单独说。那一版交付前,412 个测试全部通过,从项目目录运行也没有任何报错,AI 交付时写的是「新包也确认过启动图已经打进去」。问题在于它验证的是项目,我和试玩的人用的是安装包,两者加载资源的方式不一样。测试数量再多,测的不是交付物,就证明不了交付物能用。修完之后,AI 把「用导出的安装包实际打一关,看退出码」加进了打包命令,这条检查第一次运行就抓到了第二个崩溃。

76 分钟里的 4 句话

四句话都很短,没有一句是技术要求。「启动太慢」没说慢多少、要快到多少;「崩了?」连在哪一步崩都没说。后面三节分别讲 AI 怎么把这几句话变成可以验证的事情。

1281 MB 的导出模板,只下了 126 MB

Godot 打包需要官方的导出模板。本机没有,项目里也还没有导出配置。AI 先查了 GitHub 发布页:4.7.2 的导出模板是一个 1281 MB 的压缩包,里面装着所有平台。

下载之前它问了我两件事:

  • 能不能只下 macOS 那一部分(约 123 MB),还是下完整包;
  • 没有 Developer ID,没法做苹果公证,只能用临时签名,别人第一次打开会被系统拦下,这样行不行。

我都选了推荐项。只下一部分的做法是:压缩包的文件目录在末尾,先用 HTTP Range 请求读目录,找到 templates/macos.zip 的偏移,再只取这一段。最后实际下载 125.8 MB,解压时逐个文件做了校验。

下载的几分钟里,AI 同时做了这几件事:

  • 用背景画里的城堡做应用图标;
  • 写导出配置:通用二进制(Apple 芯片和 Intel 都能跑),临时签名,排除测试框架、测试和工具目录;
  • 写一份《打开说明》,讲怎么在「隐私与安全性」里点「仍要打开」,或者用一行 xattr 命令放行;
  • 跑全套 406 个测试和 3 关自动对局冒烟测试。

第一次导出报错:通用二进制和 arm64 必须打开 ETC2 ASTC 纹理导入。它改了这一项设置,第二次导出成功,79.6 MB。

导出之后它验证了三件事:包里没有测试和工具代码,签名有效(Signature=adhoc,Mach-O universal (x86_64 arm64)),导出的应用能无界面启动并录出画面。打包前的代码审查还查出一个问题:援军卡片弹出时,下面的战场仍然能点、能放技能。它加了全屏遮罩,补了测试,再重新打包。交付时 410 个测试通过,并新增了 make mac 命令,以后打包一行搞定。

启动 1.5--3.5 秒,游戏自己约占 0.5 秒

我第二句话是「你这个启动太慢了,性能优化一下」。AI 没有直接改代码,先量。

第一组数据就把问题范围缩小了:同一个导出的应用,开窗口启动 2.21、3.93、3.20 秒,加 --headless 不开窗口只要 0.53 秒,两次一样。不开窗口时游戏照样读完全部资源,所以大部分时间不在游戏的加载逻辑里。

接着它在启动过程里加了分段打点:引擎横幅、OpenGL 就绪、读主题和音频、主场景读完、主场景就绪、第一帧画完。三次运行的差别分散在每一段,没有集中在某个资源上。

中间有一段 0.3 秒的空白,它怀疑是第一次排中文时向系统查字体,单独写了探针测:0.7 毫秒,排除。再用 macOS 自带的 sample 对启动过程采样,主线程 790 个样本里有 727 个停在 CGLFlushDrawable,也就是在等系统把画面送上屏幕。

最后它看了系统状态:WindowServer 占 99% CPU,平均负载 6,还有安全软件在扫描新启动的程序,新打的包第一次打开会多扫约 3 秒。它也试了换渲染后端(ANGLE、Metal)和关垂直同步,结果和原来一样。

量完之后改了三处:

  • 启动图:以前窗口出来的一两秒是灰底加引擎图标,看着像卡住了。现在窗口一出现就是游戏的背景画。
  • 地图按需加载:以前一启动就把三张手工地图的贴图全部装进来。改成启动时只读关卡数据(0.2 秒降到 0.08 秒),进入征途界面后在后台预加载下一关的地图。
  • 开发命令 :make run 和 make test 每次都要先导入资源,花 3.2 秒。改成没有文件变化就跳过,降到 0.03 秒。

交付时它的说法很直接:「游戏自己能省的部分本来就不多,剩下的慢主要在系统那边」,这台机器最快约 1.4 秒出界面。系统那部分它改不了,就照实说,没有算进自己的优化成果。

点「出征」就崩:导出时被清空的字符串数组

我让它打开新包试玩,一分钟后问:「崩了?」

日志里只有一行报错:No loader found for resource: res://,有代码在加载一个空路径。AI 先在项目目录里运行,没有这个报错;只有导出的安装包有。它临时加了一行日志重新导出,看到 map_scenes=[]:上一轮做「地图按需加载」时,地图路径从直接引用场景改成了一个字符串数组,项目里这个数组有三条路径,导出后变成了空数组。

接下来它试了三种办法找原因:去掉 @export_file 标注、清空导出缓存再导出、在本地手动把资源从文本格式转成二进制。前两种导出后还是空的,第三种转出来路径都在,复现不了。原因没有查到底,它选择换一个不依赖这个数组的写法:地图场景和关卡数据文件同名、放在同一个目录,场景路径直接由数据文件的路径推出来。

gdscript 复制代码
func scene_path(index: int) -> String:
    return maps[index].resource_path.get_basename() + ".tscn"

同时保留一条测试,确保读取战役数据时不会顺带把地图场景装进内存,不然按需加载就白做了:

gdscript 复制代码
func test_loading_the_campaign_leaves_the_map_scenes_on_disk() -> void:
    for dependency in ResourceLoader.get_dependencies(CAMPAIGN_PATH):
        assert_false(dependency.ends_with(".tscn"), "the campaign pulls in a map scene: %s" % dependency)

第二个崩溃,是新加的检查抓出来的

修完第一个问题,AI 给 make mac 加了一步:导出后挂载 dmg,用里面的应用无界面、8 倍速自动打第 2 关,有报错就算打包失败。

这一步第一次运行的输出里同时有两行:Segmentation fault: 11 和 mac: exported app played level 2 cleanly。应用一启动就段错误,一行日志都没来得及写;检查只在日志里找 ERROR,空日志自然找不到,于是判了通过。AI 注意到了这两行矛盾,手动重跑,退出码 139;从项目目录跑也异常退出,退出码 134。

崩溃前的日志里有一行:1 orphaned lambdas becoming invalid at destruction of script 'res://game/main.gd'。这是它上一轮为了测启动耗时,在主场景里挂的一个「第一帧画完」打点回调,退出时访问了已经释放的回调。删掉之后,征途界面和第 1、3、4、5 关自动打完、开窗口运行,退出码都是 0。

检查也一起改了:退出码不是 0,或者日志里有报错,都算打包失败。

makefile 复制代码
if [ $$status -ne 0 ] || grep -E "ERROR|SCRIPT ERROR" build/mac_smoke.log | grep -v "resources still in use at exit"; then \
    echo "mac: the exported app failed (exit $$status), see build/mac_smoke.log"; exit 1; fi

18:16 它交付修好的版本,第一句是:「是崩了,责任在我上一轮的性能改动。」原因也说清了:按需加载那次只在项目里验证过。最后它提醒我,如果已经把 17:52 那版 zip 发给别人,要让对方换掉,那一版点出征就会崩。

这一次的几条做法

做法 原因
大文件先问再下,只取需要的部分 1281 MB 的模板只用到 123 MB;下载是否值得由人决定
「太慢」先量再改,量到每一段 开窗口与不开窗口差 1.7--3.4 秒,先确认慢在哪一边
改不动的如实说 系统那部分改不了,就写清楚,不包装成优化成果
验证交付物本身,而不是项目 412 个测试全过的那一版,安装包点出征就崩
自动检查要看退出码 段错误时日志是空的,只查日志会判通过
出了问题先说责任,再提醒受影响的人 已经发出去的旧包需要换掉

下一篇讲微信小游戏:普通小程序账号变不成小游戏、包体从 22.58 MB 压到 12.9 MB,以及 iPhone 上的黑屏。

相关推荐
LV技术派2 小时前
【成为 AI Native Builder · 02】什么是 AI Native Builder:一个人 + AI,交付完整闭环
前端·程序员·ai编程
GoldenSpider.AI3 小时前
一张户型图做成浏览器 3D 漫游:house.json 统一模型、1.2 m 剖切图纸与分阶段验收
ai编程·webgl·three.js·3d漫游·claude code
应用市场4 小时前
用本机跑的开源音乐模型,给安卓小游戏做 20 秒无缝循环背景音乐
android·python·游戏开发·soundpool·音频处理
limuyang211 小时前
我在 2核2G 云主机上跑 OpenViking:一次关于云主机、模型选型与长期成本的真实复盘
人工智能·开源·ai编程
金銀銅鐵14 小时前
[Java] 用 CLI 展示 EnumMap 增删键值对时的内部变化
java·源码·ai编程
SmalBox17 小时前
07-02-打包系统-压缩与加密策略
unity3d·游戏开发
李剑一17 小时前
没收用户的钱冲史上最大IPO?2 万亿美元的估值有多少是罚没来的
aigc·openai·ai编程
张益达Yida18 小时前
我把 0.6B 大模型塞进安卓手机:596 秒到 10 秒
ai编程
Aman198418 小时前
Claude Code做Unity项目移植(一):从零散工具到团队式
unity·游戏开发·vibe coding·claude code·项目迁移