
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,并复制挑战链接。

自动录制也翻过车
为了自动制作横版和竖版视频,我最早让脚本连续抓取浏览器截图,再交给 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 能力测试。最后留下来的,是一个更普通的开发过程。先允许第一版很差,再把最大的体验问题一件件修掉。

Comments
Comments appear after approval.