我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上

0 阅读9分钟

先看一段 61.5 秒的实际录屏。这是换用 Ming 前录下的 Qwen 版,拆机、出货和取货的过程都在里面:

视频传不了,直接点开看吧:

观看视频:自动讲解、拆机、出货、取货

视频里的机械演示是 2 倍速,旁白保持正常 1 倍速,播放器不用再加速。

点一下“开始自动讲解”,售货机就会自己拆给你看,用中文讲清楚里面的机构怎么配合。外壳透视、货架分离,讲到电机时给个内部特写,饮料掉下来,镜头还会跟着它走。

代码和工程迭代用的是 GPT-6.1-sol,三维场景用 Three.js。中文旁白几经调整,现在用 Ming-3.0-tts,默认是灵小柔女声,也能切到灵楷男声。最后还做了个单文件离线版,断网也能拆着玩。

做下来最费心的,一件是让罐子老老实实落进取货仓,另一件是让讲解员从头到尾听起来像同一个人。

这个项目是分阶段迭代出来的。第一版连货都不会卖,后面的购买、拆解和语音,都是一轮轮补上去的,提示词也跟着问题往下改。

00-cover.png

第一版很老实,先当个不会卖货的售货机

起步时,目标就是在浏览器里放一台能转着看的红白售货机。

透明玻璃门,多排饮料,深色展台。鼠标能旋转、缩放、平移,光照和阴影像回事,别一打开像几个塑料盒子叠在一起。

技术栈用了 TypeScript、Vite 和 Three.js。前期研究过开源项目 Teardown 的参数化建模和展示组织方法,售货机的模型、机构与交互则在独立工程里实现。

机身是几何体,罐子也是几何体。饮料包装、屏幕、标签在本地用代码生成,没有去找一套现成售货机模型塞进来,也没有拿别人的饮料品牌贴上去。

罐身标签和屏幕用的是程序生成的贴图。准确说,这个场景没有外部模型和图片素材依赖;要是写成“零贴图”,就把自己代码里的 CanvasTexture 开除了。

第一版放了 48 罐饮料。摆着看挺好,等到要往里加出货机构,问题来了:原来的货道尺寸,没给弹簧和驱动留够位置。

这就跟装修差不多。效果图里沙发冰箱都放得下,真买回来,发现门打不开。

弹簧加进来以后,机身开始接受现实教育

第二阶段要做机械结构和拆解,之前那套“摆得好看就行”的布局得改。

最终调整成四层货架,每层五条货道,每道三罐,一共 20 条独立货道、60 罐饮料。每条货道后面有减速电机,再接短轴、联轴器和螺旋。

这些东西得对上位置。罐子要站在托盘上,螺旋绕着前后方向延伸,罐体处在相邻螺距之间。电机在后面,轴要接得上,前面还得留出一条能让饮料掉下去的通道。

不能为了展示内部,把商品出口顺手用一块漂亮的板封死。那样外观是挺完整,饮料也就完整地待在里面了。

尺寸参考了售货机的常见构型和公开服务资料,但整台机器采用原创概念尺寸,没有真实厂商 CAD。它适合拿来观察原理,别照着下单加工。

最后整理出了 159 个可检查零件。可以选中、高亮、隐藏,也可以把某个电机单独拎出来看。

02-exploded.png

实际录屏第 21 秒:门、面板、货架和机构分阶段让位,左侧仍在进行中文语音讲解。

拆解还不能只管散开,不管回来。

我的做法是用一个从 0 到 1 的进度值,计算所有零件相对装配原位的偏移。门先往前脱离,再向两侧让位;外壳移开,货架抽出分层,商品抬起,最后分离螺旋、电机和轴。

往回装,就沿同一条路径反向求值。滑块拖到一半再反悔,也从当前进度接着走。

所有位置都从装配原位计算,不用让每个零件各自记一本位移账。否则来回拆几次,很容易出现“每次只偏一点,最后谁也回不去”的家庭装修现场。

让它卖一罐饮料,比让它看起来像售货机麻烦多了

做出货之前,先检查了旋转轴心。

原来螺旋所在的组,原点并不在实际轴线上。直接旋转整个组,弹簧就会绕着错误的位置走,看着像在表演。

修正后,电机外壳固定,输出端、轴、联轴器和螺旋围绕真实轴线一起转动。商品推进距离也跟着螺距和转数算:

推进距离 = 螺距 × 转数

模型里螺距是 0.18 米,转一圈,货道里剩下的罐子同步往前推进一个螺距。前罐完全越过支承边缘,才释放给重力阶段。

03-drive-detail.png

实际录屏第 31 秒:驱动机构已经复位,进入电机工作阶段;机身屏幕和侧栏读取同一份交易状态。

后面的下落,采用 1/240 秒固定步长的简化 2.5D 物理:货道横向位置固定,主要计算前后、上下运动,再处理斜板、底板和挡界的碰撞。

它有重力、摩擦和低反弹,但没有做通用三维刚体引擎,也没模拟罐内液体晃动。取货时抬起再向外拿,是明确设计的人为取货路径。

开发中还真抓到过取货仓上饰板干涉路径的问题。另一条需要防的情况是:罐子卡在斜板上不动了,系统以为“速度很低,送达成功”。

所以判定里要检查它确实接触取货底板并稳定。总不能钱收了,饮料在半路躺平,机器宣布自己完成任务。

04-delivery.png

实际录屏第 40 秒的局部裁图:绿色饮料罐已进入取货仓,翻板打开,方便观察。

出货动作跑通后,还得管住余额、库存和购买状态。金额用整数分,先校验余额与当前货道库存,再一次性扣款并预留商品。正在出货、仓里还有饮料、正在取货或关板,都不能接下一单。

拆解和购买也互锁。电机轴都拆飞了,就别让它继续隔空推罐子。

还有个很小、但不补就很离谱的规则:已经取走的饮料,不能因为点了“显示全部”又回到货架。机械科普可以,赛博永动售货不行。

这些全是本地模拟余额和库存,没有真实支付,也没接硬件。你可以放心投虚拟币,不会研究着研究着,钱包先少五块。

后来,它有点像一个能上手的展品了

到这一步,功能已经不少,但还需要让别人知道该看哪里。

我又补了一层展示和导览:红白机体放进石墨色展厅,金属、烤漆、塑料和玻璃分别调材质,配柔光和环境反射。内部观察时切换更清晰的透明表现,让机构露出来。

中文知识点沿着真实零件位置拉出引线。电机工作时,高亮当前货道的传动路径;前罐释放后,镜头跟随实际罐体的位置。手动拖动相机,可以接管镜头。

自动导览也沿用同一套购买流程。它会先把拆开的驱动装回去,再投虚拟币、出货和取货,确实消耗一件库存。

我不想维护一台负责互动的售货机,再维护另一台专门负责拍视频、什么都不用管的售货机。那样演示时越流畅,自己心里越没底。

后来又补上中文旁白,让导览能边演示边讲。

中文讲解用 Ming,女声和男声都能选

旁白用 Ming-3.0-tts,本地运行的是 0.5B 的 MLX 8bit 版。默认灵小柔女声,可以切换灵楷男声。

07-ming-narration.png

音频提前在本地合成,按导览章节播放。网页不运行模型,也不请求在线 TTS,断网照样能听。

播放由用户点击触发,支持试听、调音量和暂停。机械演示开 2 倍速,旁白仍保持 1 倍速;播放失败时保留文字说明。

最后,把售货机和讲解员一起装进 HTML

做本地语音,原本就有一个考虑:这个小展品最好能离线玩。

因此离线版把 JS、CSS 和 26 段 Ming MP3 都内嵌在一个 HTML 文件里。当前约 4.58 MB,在支持 WebGL 2 的 Chrome 里可以直接打开,购买、拆解、相机和语音都保留。

语音模型和推理环境都不打进 HTML。模型只在制作阶段生成音频,网页播放已经做好的文件,反复播放也不需要再调用 TTS 合成服务。

运行库许可和 AI 音频来源说明也随制品保留。把内容收进一个文件,不能顺便把这些说明弄丢。

Vercel 上的网页版也已经更新为 Ming,可以直接体验当前的灵小柔和灵楷配音:

在线查看Seller Bot 交互演示

这次用 AI,我实际在做什么

从第一版静态陈列,到可逆拆解、真实状态驱动的出货,再到中文语音导览,需求是逐步长出来的。

GPT-6.1-sol 帮我把这些模块写了出来,但该查的几何关系、暂停边界、库存守恒和听感,还是得一项项过。一个放错位置的旋转轴心,或者一个换了年龄感的女声,都能把“看起来已经做好了”打回去。

现在项目有 109 项单元测试,也做了浏览器交互检查。Ming 版检查过音频文件、控件和播放清理逻辑,保留的完整录屏则来自 Qwen 版,不能拿来当作 Ming 自然播放全程的验证。跨浏览器、真机和长期前台运行,还留着继续验证。

如果也想用 AI 做这类小项目,我会建议先把一个动作做明白。观察和拆解做稳了,再接出货流程。语音也先做一条覆盖几个章节的样例,听听换话题时的衔接,别等整套生成完,才发现讲解员在轮流上班。

我原本想做个能转着看的售货机,后来一路加到连它自己都能讲解自己了。

现在里面有 60 罐饮料。研究了这么久,还是一口没喝上。