用 Codex 做游戏,3D 点球 Last Kick

我用 Codex 做了一款只有三脚射门的 3D 网页游戏。这篇复盘第一版为什么无聊,以及门将记忆、三种射法、声音、分享图和测试怎样把体验补完整。

2026年7月10日 · 更新于 2026年7月10日 · Jackie

Last Kick 的 120 分钟生死球开场

GPT-5.6 出来后,我想拿它做一款网页作品。目标也很具体,要有手感,还要让人愿意录下来分享。

一开始的想法很散。3D 小游戏、网页版侠盗猎车手、在球形星球上散步,甚至还想过把世界杯、球星和偶遇故事全塞进去。

后来只留下三样东西。

三脚球,一堵墙,一个会记住你的门将。

这就是 Last Kick。手机和电脑都能直接玩,不需要登录。完整源码放在 GitHub,使用 MIT License。

这篇和 《球的另一边》 都属于「用 Codex 做游戏」系列。一个把质量集中在几十秒的射门,另一个把体验展开成一段 5 到 8 分钟的球形星球故事。

先看完整演示

视频保留了开场、蓄力、三种射法、门将扑救、进球反馈和最后的挑战结果。

如果播放器没有正常显示,可以直接去 B 站观看

不接运行时 AI

项目很早就定下了一条边界。AI 只参与开发,玩家打开网页后不会调用模型,也不需要输入文字、上传图片或录制语音。

3D 场景、射门判定、门将动作、音效和分享图都在浏览器本地完成。没有服务端推理和按次计费,模型接口出问题也不会让足球停在半空中。

我不想为了证明它是 AI 做的,硬塞一个聊天框进去。AI 负责创意、代码、测试、素材判断和迭代,浏览器负责把作品跑起来。

第一版能踢,但很无聊

第一版很快就出来了。有球门,有足球,按住、拖动、松手就能射门,门将也会扑。

但我的反馈很直接。

哎呀,我有点难过,因为你这个很无聊,这操作起来没有任何感受啊,没有任何让人觉得很兴奋的点,这个创意也不太行。

技术上能跑只过了最低标准。玩家松手的那一刻,镜头、声音、运动、等待和结果要一起给出反馈。

能射门,不等于还想再射一脚。

后面的迭代停止增加球员、地图和比赛,把问题缩小成一次拖拽射门,能不能在十几秒内让人紧张起来。

不做粗糙的小人

我很担心模型做出一批粗糙的拟真人物。脸和动作都不可信,最后只会像一个临时 3D Demo。

所以门将改用切面几何体,观众也不再是一个个人。看台由上万张折叠卡片组成,平时像一圈压住情绪的纸片。进球后,它们一起翻成珊瑚红,再像机械波一样向外散开。

项目还试过发光信号柱和纸鸟群。静帧都能看,但真正踢起来,折叠卡片最接近「一万人同时起身」的感觉,所以最后保留了它。

由程序生成的球场、门将和折叠观众

整个场景由程序生成,没有外部人物模型。Three.js 在这里不只负责画出 3D 物体,镜头、灯光、雨、球网和看台都可以围着射门的瞬间一起变化。

Vozinha 和门将记忆

球门对面如果只是一个无名 NPC,玩家很难在意他扑不扑得到。

我当时想到佛得角门将 Vozinha。项目把开场设在加时赛第 120 分钟,比分 1 比 1。玩家只有三脚,Vozinha 站在门前,而且会记住上一脚。

代码会保存上一脚的方向和射法。如果继续用同一种射法打同一侧,门将就会提前向那边移动。换边,或者改踢弧线和勺子,球门才会重新打开。

代码真的会执行这条规则,不是等到结果页再随机写一句「门将看穿了你」。不到一分钟的小游戏也因此有了一个对手。

三种射法不用随机判定

游戏一度太容易,随手一拖就能进。第一次可能挺爽,第二次就没意思了。

我也不想在后台掷骰子。玩家明明踢得一样,这次进、下次不进,只会觉得游戏在耍赖。

最后用了确定性判定,每种射法都有自己的力量和落点窗口。

射法有效方式常见失败
爆射力量至少 64%,压向左下或右下力量不足,或者打得太正
弧线力量至少 52%,瞄准更宽的远角弧度不够,进入门将覆盖区
勺子力量控制在 45% 到 78%,走中路太轻被没收,太重撞横梁

蓄力时,瞄准区和力量条会提示偏轻、合适或过量。射丢后会说明具体原因,包括力量不够、位置不对、勺子过头,或者重复套路被门将读到。

用于调参的脚本把新手输入建成一个模拟模型,算出的单脚进球率约为 13.9%,三脚只进零到一个球的概率约为 94.7%。这不是玩家统计,只是为了避免完全凭感觉调难度。

难,但失败原因要看得懂。这样射丢以后,下一脚还有可以调整的地方。

出脚前后的两秒

后面一轮改动,几乎都集中在出脚前后的两秒钟。

按住足球后,镜头慢慢推近,环境声收窄,心跳随力量加快。进入有效区间时,瞄准和力量反馈改变颜色。松手的一瞬间短暂静音,镜头再切到飞行路径。

进球后,球网变形,灯光和曝光抬起,看台翻色,镜头震动。真实球场录音叠在 Web Audio 合成的击球、风切和低频上。

弧线球入网后的灯光、球网和看台反馈

失败也有不同反馈。扑救是手套撞击和人群叹息,门柱是金属声和回弹,撞横梁的节奏更干脆。

雨声最早使用合成噪声,太吵,听起来更像坏掉的收音机。后来换成本地托管的 CC0 轻雨录音,并把音量压到只剩环境底色。进球欢呼和扑救后的失望声也使用了本地 CC0 素材,来源和处理方式保留在源码仓库里。

开场从教程改成比赛

有一版开场把三步操作写得很大,选择射法、按住足球、松手射门。

信息没错,但看起来像产品说明书。放进短视频以后,观众第一秒先看见了教程,比赛反而被压在后面。

后来开场改成转播画面,先出现 120 分钟、1 比 1 和剩余三脚,再给一句很短的挑战。操作提示放回球旁边,等玩家真的按下去时再出现。

网站没有变成一个更复杂的游戏,只是把玩家进入它的方式改对了。

分享的是这一局的结果

最早的分享按钮只会打开系统面板。能分享,但用户看不到自己发出去的东西。

后来每局结束,浏览器会生成一张 1080 × 1920 的竖版战绩图。图里有最后一脚的真实 WebGL 画面、进球数、射法、力量、称号和 lastkick.01mvp.com

支持文件分享的手机会带着图片打开系统分享面板。其他浏览器会保存 PNG,并复制挑战链接。

Last Kick 在浏览器里生成的竖版挑战战绩图

自动录制也翻过车

为了自动制作横版和竖版视频,我最早让脚本连续抓取浏览器截图,再交给 FFmpeg 合成视频。

文件信息看起来正常,1080p、30fps,码率也不低。真正播放起来却发糊、卡顿。

检查时间轴后才发现,活动画面实际只抓到了大约 17 到 20 帧。编码器补成 30fps,只是在重复已有画面。低分辨率 WebGL 画布再被放大,边缘和文字也会变软。

最后发布的视频由我直接录制,没有继续使用不合格的自动成片。以后要重做自动录制,可以用 ScreenCaptureKit 这类原生窗口录制连续画面,或者用固定时间步逐帧渲染。验收也不能只看分辨率和 fps 标签,还要检查重复帧、帧间隔和真实画布尺寸。

技术、测试和上线边界

页面和交互使用 React 19、TypeScript。3D 场景使用 React Three Fiber 和 Three.js,Zustand 管理三脚挑战、上一脚记忆和阶段切换,Web Audio 负责声音,Canvas 生成分享图。

游戏的正式站点部署在 lastkick.01mvp.com,静态资源和音频由 Cloudflare Workers Static Assets 提供。运行时没有数据库、登录和 AI 接口。

公开源码包含一个难度校准脚本,用来验证三种射法的有效窗口、重复套路判定和新手输入模型。相同输入会连续运行 100 次,确认结果不依赖随机数。仓库还保留了 TypeScript 构建、Cloudflare dry run、部署命令、音频来源和 MIT License。

本地运行可以执行。

git clone https://github.com/makerjackie/lastkick.git
cd lastkick
pnpm install
pnpm dev

部署前使用的命令是。

pnpm test:difficulty
pnpm build
pnpm deploy:dry-run
pnpm deploy

一条完整 Prompt 没有直接生成成品。真正影响结果的是那些很具体的反馈,第一版很无聊,观众不要做成粗糙小人,雨声太吵,射门太容易,失败原因看不懂,分享前看不到图片,视频只有参数像高清。

这些反馈没有指定函数和文件,而是说清了哪里不对,以及最后要验收什么。

Last Kick 最早只是一次 GPT-5.6 能力测试。最后留下来的,是一个更普通的开发过程。先允许第一版很差,再把最大的体验问题一件件修掉。

在线踢三脚,或者查看 GitHub 源码

發表評論

登入後即可參與評論。

去登入