10天,186次提交,一个人做出一款同时跑在iPhone和网页上的游戏。这位法国独立开发者把账算得很清楚:代码大部分不是他写的,是终端里的AI编程代理写的。
游戏叫Rush QTE,玩法是每道题只给两秒,同时用各种方式干扰你:前任发来的假短信、假来电、裂开的屏幕、会换位置的答案,还有得用拇指打掉的Boss。浏览器打开就能玩,不用注册,App Store上也有。
第一次提交在10月16日。十天后,这款游戏有19个街机模式、8章带Boss战的故事模式、每日挑战、每分钟开一局的实时竞技场、好友对决,iPhone和网页共用同一套代码。
把引擎写成纯的
他认为最好的决定,是把引擎写成没有UI、没有时钟的东西。每个方法接收当前时间,返回一串事件;UI只负责对事件做反应:放音效、抖屏幕、显示“PERFECT”。
这么做白捡了三样东西。一是测试,传入假时间戳就能在几毫秒内模拟一整局,没有定时器,不会时好时坏。二是种子,同一个种子,所有人拿到的题目和顺序完全一样,好友对决就是分享一个链接,对方玩的是你那一局。三是机器人,自动游玩模式像玩家一样驱动引擎,录预告片、测Boss战都靠它,不用自己打一百遍。
没有游戏服务器的竞技场
每到整分钟,新一局竞技场开始,当时在玩的人拿到同一个种子,答同一批题。没有实时服务器:客户端向Supabase问一次服务器时间,自己算出下一局,按语言从固定批次里取种子,分数最后统一写入。
人不够的时候,竞技场用“幽灵”填满——同一颗种子上早先对局的回放,一律带标记,不会让人误会。这套东西便宜、能缩到零,20个玩家的时候依然显得热闹。
网页版是同一套代码,但有几个坑花了他几个小时:在React Native Web里,鼠标点击不触发onPressIn,而对反应类游戏来说onPress又太晚,所以网页用onPointerDown、iPhone用onPressIn;Skia画布会吞掉点击,盖在按钮上的装饰画布能让按钮直接失效,他在HTML外壳里把画布对指针事件设为透明;NativeWind会忽略Pressable上的函数式样式,他那个大PLAY按钮一度完全没有背景,只能改用固定样式。测试要用真实点击,Playwright里在元素位置调page.mouse.click(),才能抓到dispatchEvent轻松掩盖的bug。
第一批玩家用数字说话
他很早就接了Mixpanel,只看一个简单漏斗:新玩家是否在头10分钟内开一局,第二天是否回来。
- iPhone:10分钟内开局73%,次日回访18%
- 网页:10分钟内开局46%,次日回访2%
结论是玩家喜欢第一局,只是没有理由回来,快玩一局之后无处可去。于是他做了故事模式:戴眼镜的猫被“原初山羊”抢走王冠,玩家沿8章48关的地图往上爬,每关有自己的规则(生存、连击、无失误、速度、突然死亡、最大混乱),最多三颗星;每章末尾有带血条的Boss、吓人的音乐和反应类攻击,拿几颗星取决于剩余生命。另外还有简单模式,给更多时间和更适合小孩的混乱,因为他的玩家里有孩子。首页那个大PLAY按钮现在直接进下一关故事,而不是随机开一局。次日回访这个数字会不会动,他说一周后才知道。
AI代理干了多少
“独立”指的是一个人。大部分代码由终端里的AI编程代理Claude Code写成,它能访问代码仓库、模拟器、App Store Connect接口和浏览器。
代理做得好的部分:端到端整块功能,“加一个带8种关卡、Boss和宝箱的故事模式”会变成引擎改动、地图界面、测试、网页部署和一次空中更新;那些无聊的40%,比如App Store Connect脚本(175个国家的定价、截图、内购)、商店截图、预告片、媒体资料包、分析报表;以及在被要求时检查自己的活——lint、类型检查、测试,然后截真实UI的图,才说“做完了”。
留给他的部分:产品决策,让大PLAY按钮进故事模式是看了留存数字之后定的,不是代理提的;品味,代理交付过一个重叠的logo和过于拥挤的首页,他不得不多次说“这太多了”;以及任何不可逆或公开的事,发邮件、提交苹果审核、花钱,代理都会先问。
他认为差别最大的一招是一个项目文件AGENTS.md:每次踩坑就立刻写进去——“NativeWind忽略Pressable上的函数式样式”“iOS本地构建,云构建要花钱”“网页点击用page.mouse测”。代理每次开工先读它,同一个错误不会犯第二次。
如果重来,他会从第一天就测留存,在堆功能之前;故事模式应该排在那19个模式里的某些前面;先发网页版,因为那是最快的反馈路径,不用审核、不用安装,Reddit评论里丢个链接就行;引擎从第一天就保持纯净——他做到了,这也是对决、竞技场和机器人只花几天而不是几周的原因。
现在Boss大概还是太难,他想知道玩家卡在哪一关。
热门跟贴