资讯详情

资讯详情

AI编程实测:Opus 5.5 从零开发《秋名山车神》赛车游戏全过程

事情的起因是一张截图。群里有人甩出一张截图说“Opus 5.5 写小游戏已经属于有手就行”配的样例是个接水管 Demo。我第一反应是不太信因为接水管这种逻辑密度的小项目根本触发不了大模型真正的上限。真正能测出模型底子的是那种看起来简单、实际全是状态管理和手感的项目。于是我想到了赛车游戏连名字都取好了——《秋名山车神》。我打算用一个晚上让 Opus 5.5 从零给我交出一个能在浏览器里跑起来、并且真的有驾驶手感、能计时、能评价玩家成绩的赛车游戏 Demo。这篇文章算是这轮大考的过程记录。它不只是给你看 Opus 5.5 写了多少代码更想讲清楚我当时怎么出题、考卷怎么拆、代码交回来后发现哪几个环节最容易翻车以及最终是怎么一轮一轮把驾驶手感从“滑冰”调成“柏油路”的。不管你是对 AI 编程好奇还是自己也想用对话式模型做游戏原型这篇都值得你留个参考。1. 为什么要拿 Opus 5.5 去跑一场“秋名山”大考1.1 赛车游戏不是随便选的考题很多人以为“跑得起来”就是游戏开发但实际上赛车游戏在单屏网页游戏里是难度偏高的题目。它有三个明显难点第一输入维度多。玩家不是只按一个键而是同时要管方向、油门、刹车键盘的按键组合和角色运动之间必须时刻保持线性响应。第二物理手感难调。加速度、转向速度、抓地力、漂移判定任何一个参数不对游戏玩起来就像在冰面开车。第三渲染有连续帧压力。赛道要跟着车辆平滑滚动边缘不能断裂HUD 要实时刷新这些对代码结构要求不低。拿这种题目去考 Opus 5.5比考它写一个待办清单有意义得多。后者背模板就能过前者得靠模型真正理解“状态如何随时间变化”以及“参数之间如何耦合”。这也是我给它取“大考”这个说法的原因。1.2 考试规则与验收标准我给自己定的考试规则很具体不给它留任何偷懒空间只用 HTML CSS JavaScript零第三方库必须在浏览器里直接跑双击 HTML 文件就能玩必须有“秋名山”的赛道特征连续 S 弯、尾段一个发卡弯、路边有树或护栏必须有圈速计时跑完一圈给出“秋名山车神”评价物理上要有漂移手感不能只是左拐右拐的移动方块。为什么锁死浏览器零依赖因为这样才能排除“环境问题”的干扰。游戏渲染、逻辑、交互全部在一个 Canvas 里完成Opus 5.5 只要有一点上下文断裂代码立刻跑不起来这对模型是极其苛刻的单点验证。1.3 我的准备工作我唯一的准备是把“秋名山”这个需求从情怀词翻译成可量化特征。我给它的参考信息是一条带有树和护栏的山路窄路面起伏不强调但弯道要多。我把这些直接写进后续提示词里。我不先写任何代码不允许自己下游泳池完全看它独立完成。2. 把“秋名山车神”翻译成 AI 听得懂的需求2.1 先说清楚“秋名山”到底指什么如果直接跟 AI 说“做一个秋名山赛道”它大概率给你画一个圆形赛道出来。这不行。我得先把“秋名山”这个意象拆成三个要素连续复合弯、尾段发卡弯、道路狭窄。连续复合弯指的是 S 弯弯与弯之间没有长直道衔接发卡弯指接近 180 度的大回旋通常放在整圈路线的末尾制造一种“跑完前段以为稳了最后弯道见真章”的感觉道路狭窄则强制玩家在弯中必须走线而不是随便绕大圈。为了给 AI 一个可落地的基准我给了它一组赛道控制点坐标让它用这些点生成样条曲线路径。控制点设计成起点在画布下方一路螺旋上升经过两个反向弯最后兜一个大半圈回到起点附近。2.2 玩法闭环计时、漂移、车神评价一个赛车游戏如果只是“跑圈计时”其实是缺少情绪反馈的。我在需求里加了一条跑完一圈后依据圈速给出称号。比如 30 秒以内叫“秋名山车神”35 秒以内叫“山路熟手”超过 35 秒叫“新手车手”。为了让玩家有反复挑战的动力我要求加入最佳圈速记录并且每次冲线时都显示“新纪录”或“差了多少秒”。这种小循环能显著提升游戏粘性代码量却不大。它考验的是 AI 对“游戏状态”的管理能力玩家当前在第几圈、起点线在哪里、经过起点线时有没有漏判这些逻辑一旦写错整个游戏就是不可玩的状态。2.3 我发给 Opus 5.5 的第一版提示词这部分我直接贴出来你可以照着用。第一版提示词的关键是把验收标准变成模型能执行的指令请用 HTML CSS JavaScript 写一个网页赛车游戏《秋名山车神》要求 1. 画布 900×600使用 Canvas 2D 渲染不允许引入任何第三方库。 2. 赛道用 Catmull-Rom 样条曲线连接控制点生成控制点如下 (450,520)-(150,480)-(180,280)-(450,320)-(680,180)-(760,400)-(520,420)-(380,540) 3. 玩家用方向键控制一辆俯视角小车上键油门、下键刹车、左右键转向。 4. 驾驶手感必须包含漂移高速入弯时车辆会有明显侧滑出弯后能恢复抓地力。 5. 要有圈速计时、最佳圈速记录冲线后根据总用时给出称号车神/熟手/新手。 6. 路边要渲染树或护栏赛道边缘要有视觉区分。 7. 代码结构拆成多个函数赛道生成、车辆物理、渲染、HUD 状态更新不要全部堆在主循环里。这版提示词写得很“甲方”但每一句都是可验证的约束。后来看回放Opus 5.5 第一版基本全做到了只是手感有一堆问题这个后面细说。2.4 为什么技术栈锁死 HTML5 Canvas有朋友问过我为什么不干脆用 Unity、Godot 甚至 Phaser。我的理由很实际Canvas 2D 的最小可运行闭环最短。Unity 要装编辑器、要导 WebGL 包Godot 也要导出工程Phaser 本身就是一个库。考试的目标是验证 Opus 5.5 的独立编程能力不是验证它会不会背引擎 API。Canvas 把渲染、输入、逻辑全塞在一个页面里恰好让模型的上下文管理能力暴露无遗——它必须在同一个文件里维护大量全局状态还不能把逻辑写乱。这也是为什么我用它当“考卷纸”。方案环境依赖上手成本适合作为模型考试载体Canvas 2D 原生零依赖低是约束清晰Phaser需要引库中偏模板Godot编辑器导出高不适合Unity编辑器导出高不适合3. 第一轮交付Opus 5.5 产出的核心代码与运行验证3.1 赛道生成Catmull-Rom 样条铺路Opus 5.5 第一版代码结构比我想象的干净。它用一个catmullRom函数在控制点之间做插值每隔一段距离取一个采样点然后把这些点连成赛道中心线。我截取一段核心代码function catmullRom(p0, p1, p2, p3, t) { const t2 t * t; const t3 t2 * t; return { x: 0.5 * ((2 * p1.x) (-p0.x p2.x) * t (2 * p0.x - 5 * p1.x 4 * p2.x - p3.x) * t2 (-p0.x 3 * p1.x - 3 * p2.x p3.x) * t3), y: 0.5 * ((2 * p1.y) (-p0.y p2.y) * t (2 * p0.y - 5 * p1.y 4 * p2.y - p3.y) * t2 (-p0.y 3 * p1.y - 3 * p2.y p3.y) * t3) }; } function buildTrackPath(controlPoints) { const samples []; for (let i 0; i controlPoints.length; i) { const p0 controlPoints[(i - 1 controlPoints.length) % controlPoints.length]; const p1 controlPoints[i]; const p2 controlPoints[(i 1) % controlPoints.length]; const p3 controlPoints[(i 2) % controlPoints.length]; for (let t 0; t 1; t 0.02) { samples.push(catmullRom(p0, p1, p2, p3, t)); } } return samples; }选 Catmull-Rom 而不是普通贝塞尔原因是它天然经过所有控制点生成的曲线不会“漂”到控制点外部这对赛道边界判断是好事。我后来查看赛道渲染效果时这条路线的弯道分布也确实贴近预期前段两个反向小弯中段一个高速左弯尾段兜一个大回旋就是发卡弯。3.2 车辆运动学从按键到轮胎车辆部分是我最关心的。第一版代码里车辆模型大概长这样const car { x: 450, y: 520, heading: -Math.PI / 2, speed: 0, maxSpeed: 240, accel: 90, steerRate: 2.4, grip: 0.92, driftFactor: 0.78 }; function updateCar(dt) { if (keys[ArrowUp]) car.speed Math.min(car.speed car.accel * dt, car.maxSpeed); if (keys[ArrowDown]) car.speed Math.max(car.speed - 140 * dt, 0); const steer car.steerRate * Math.min(car.speed / 80, 1.0); if (keys[ArrowLeft]) car.heading - steer * dt; if (keys[ArrowRight]) car.heading steer * dt; const vx Math.sin(car.heading) * car.speed; const vy -Math.cos(car.heading) * car.speed; car.x vx * dt; car.y vy * dt; }第一版有个很典型的问题car.x vx * dt直接按朝向角度移动整车这等价于默认车辆永远指向运动方向。真实赛车不是这样尤其入弯时车头方向和实际运动方向会有一个夹角——专业说法叫“滑移角”。没有这个夹角就不存在侧滑漂移手感自然无从谈起。3.3 圈速计时与 HUD 状态计时逻辑它也做了思路是预先算出赛道采样点的首尾位置当车辆与起点连线距离小于某个阈值、且当前圈数大于 0 时判定为冲线。圈速分成三档用时区间称号≤ 30 秒秋名山车神31 ~ 35 秒山路熟手 35 秒新手车手HUD 部分用 Canvas 绘制速度表、计时、最佳圈速这一块没有太大问题。首次跑起来的时候游戏能正常开局车能动赛道完整HUD 在刷新第一版的“骨架”是过审的。3.4 第一轮运行现场的可用性判断我自己试玩了五分钟发现“能玩”和“好玩”之间隔着一整条秋名山。主要症状是车辆转向太灵敏速度一高就根本拉不回来没有漂移只有“原地打转式减速入弯”撞到赛道边缘不会停下来而是直接穿模到外侧圈速计时偶尔会漏判跑了两圈才记一圈。这些都是典型的“代码逻辑对但参数手感差”表现。严格来说第一版只是“能运行”离“可玩”还差一轮精细调教。4. 从“滑冰”到柏油路三轮手感调教的完整记录4.1 现象一方向盘好像只是在装饰第一轮试玩时最直接的感受是方向键看起来正常但车转弯完全“不跟手”。速度低的时候转向像蜗牛速度一到 150 以上转向立刻变成“过山车式甩尾”想纠正方向只会更歪。我暂停游戏去看车辆的更新逻辑。问题出在这个公式const steer car.steerRate * Math.min(car.speed / 80, 1.0);它的问题在于转向能力随着速度线性增加速度 100 时转向是 1.25 倍基础值速度 200 时直接封顶 1.0。这违背真实驾驶直觉。真实情况下速度越高方向盘的有效转角应该越小不然高速时轻轻一碰方向就会失控。正确的做法是对转向做“速度衰减”const speedFactor Math.max(0.25, 1 - car.speed / car.maxSpeed); const steer car.steerRate * speedFactor;这样低速倒车入库时转向有力高速巡航时转向变“钝”驾驶才稳定。4.2 根因诊断速度向量没有拆成纵向与横向第二轮的提示词我只给了一句车辆运动模型太简单了。请把速度向量拆分为沿车头方向的纵向分量和垂直车头的横向分量横向分量要乘以抓地力系数当横向分量超过阈值时进入漂移状态。这一刀切中了要害。我拿到修改后的代码车辆更新变成了这样function updateCar(dt) { // 油门与刹车略 // 纵向车头方向 const forwardX Math.sin(car.heading); const forwardY -Math.cos(car.heading); // 当前速度向量 const vx car.velocityX; const vy car.velocityY; // 投影到纵向与横向 const forwardSpeed vx * forwardX vy * forwardY; const lateralSpeed vx * (-forwardY) vy * forwardX; // 抓地力处理横向速度被衰减产生“逐渐回正”的物理感 let newLateral lateralSpeed * car.grip; // 漂移判定横向速度过大说明车辆在侧滑 const isDrifting Math.abs(newLateral) 60; if (isDrifting) { newLateral * car.driftFactor; } // 重新合成速度向量 car.velocityX forwardX * forwardSpeed (-forwardY) * newLateral; car.velocityY forwardY * forwardSpeed forwardX * newLateral; car.x car.velocityX * dt; car.y car.velocityY * dt; }这版代码跑起来手感立刻不一样了。转向时车头先转车身会因为横向速度的残存而“慢半拍”跟上视觉上就出现了侧滑轨迹。漂移的“滑”感本质上就是横向分量从大到小衰减的这段时间差。只要grip和driftFactor配合得当玩家就能感到车辆先甩尾、然后轮胎重新咬住地面。4.3 发卡弯的奖励与惩罚设计光有物理还不够发卡弯如果没有“奖励正确操作、惩罚错误操作”的机制玩家跑一圈就腻了。我跟 Opus 5.5 追加了一个需求在发卡弯入口加一段减速提示区域玩家如果不减速高速入弯车辆会在弯道外侧拉出一条很长的漂移轨迹并且明显损失速度如果提前减速到合理范围漂移过弯后速度损失不超过 20%。这个设计的本质是让玩家学会“走线”。我给它发了控制点数据之后它就找到发卡弯对应的路段坐标画了一段半透明的红色减速提示带。跑了几圈之后我终于体验到“入弯减速-弯心出弯-全速冲刺”的节奏感。之前那种滑冰一样的失控感到这里才算真正消除大半。4.4 最终参数对照表三轮调校下来的参数变化我整理成一张表参数第一轮值最终值作用grip0.920.88横向抓地力越低越容易滑driftFactor0.780.72漂移中横向速度衰减速度steerRate2.42.8低速转向手感speedSteerFalloff无0.25高速时转向衰减下限maxSpeed240220赛道尺寸对应的合适极速参数是给参考的不代表你照抄就有同样的手感。每个赛道的宽度、每个控制点生成的弯道半径都不一样参数必须跟着赛道走。做法是先把赛道控制点定死再让 AI 生成一个参数配置文件单独调。5. 避坑记录AI 写游戏时最容易翻车的四个环节5.1 时间步长没归一化帧率一变就变慢第一版的游戏循环用的是requestAnimationFrame(timestamp)但它把timestamp直接用成了间隔时间。问题在于刷新率高的显示器 120Hz 跑起来游戏会加速60Hz 跑起来又会明显变慢。排查思路很简单在每次动画帧回调里用当前时间戳减上一次时间戳得到差分时间dt所有物理更新都乘以dt。如果一帧卡了一下dt变大车辆移动距离也变大但不会跳帧。这个坑从代码上非常好发现但 Opus 5.5 第一次交的代码里确实漏了归一化处理。提示凡是涉及动画内物体移动的逻辑都必须先算dt (now - lastTime) / 1000再作为系数乘进位移和速度变化。这是 AI 写游戏最容易踩、也最容易被忽略的一条。5.2 键盘事件没做状态管理方向直接“瞬移”第二版代码里键盘监听用的是keydown和keyup直接改布尔值这本来没大问题。但 Opus 5.5 在处理组合键时写成了if (e.code ArrowLeft) { car.heading - 0.1; }这种“每次按键事件转一个固定角度”的写法。这种设计一旦按键重复触发车辆转向就会瞬移根本没法平稳过弯。我把它换成基于事件状态的“按键锁存”const keys {}; document.addEventListener(keydown, (e) { keys[e.code] true; e.preventDefault(); }); document.addEventListener(keyup, (e) { keys[e.code] false; });这样主循环里只需要查询keys[ArrowLeft]是否为真就能持续执行转弯不会因为键盘重复触发而跳变。5.3 赛道曲线闭环处出现接缝第一版赛道渲染时起点和终点之间有明显的“断口”因为采样点在首尾衔接处没有闭合。修复方式是对曲线采样做环形索引处理取控制点时用(i - 1 length) % length的方式回绕。这个环形索引在第一版里其实已经写了但还是断了。我检查后发现问题是最后一段采样点没有把t1这一帧包含进去导致首尾顶点没有重合。修法是在循环里把采样步长改为for (let t 0; t 1; t 0.02)强制包含终点。这一个等号不写视觉断口就会一直存在。5.4 迭代中途的连带回归问题这是整个大考中最麻烦的坑。Opus 5.5 在修改一个逻辑时经常会把另一个原本正常的逻辑改崩。比如为了加漂移判定它改动车辆状态更新函数结果把keys对象的初始化位置给挪错了整个页面一加载就报keys is undefined。我后来想了一个办法给它立了两条“协作规矩”每次只改一个函数不要跨函数修改改完必须说明“修改会影响哪些调用位置”如果说不清楚禁止动。这不是让模型自我约束而是让我自己能快速定位问题。每次对话只给它一个最小改动指令跑通后再提下一个需求。代码量不大的场景下这样做迭代速度反而是最快的。6. 这场大考的成绩单以及下一步玩法扩展6.1 我给 Opus 5.5 打的外部分数第一轮交付加三轮调校后游戏最终状态是可玩、有漂移手感、有圈速评价、有视觉辨识度、完全零依赖。以一个游戏原型标准衡量这已经是一个合格的小品级作品。我按工程视角打了分维度分数我的评价代码完整性9/10一次生成基本闭环结构清晰物理手感7/10前两轮像滑冰调参后才像开车视觉表现7/10树和护栏有了但层次感一般状态管理8/10圈速与 HUD 状态没出大错迭代友好度6/10连带回归问题需要我强约束才能避免总分 7.4 左右属于“能作为原型开发主力但不能完全放手”的水平。6.2 哪些环节可以放心交给它哪些必须自己盯用 Opus 5.5 做这种单文件小游戏我觉得可以放心交给它的部分有三块一是赛道生成与数学计算。样条曲线、采样、坐标变换这些有标准答案的内容它写得又快又稳。二是HUD 布局与视觉绘制速度表、计时器、状态提示这些重复性较强的画布绘制基本不用管。三是代码结构拆分它自主拆分函数的自觉性比很多初级开发者都高。必须自己盯的则是车辆物理参数矩阵。模型给出的初始参数大概率不对需要你亲手上车试然后给它精确反馈事件响应逻辑。键盘状态管理这类代码如果一次生成不对后面多次修补容易越修越乱任何涉及“全局变量生命周期”的内容例如对象初始化顺序、游戏状态重置这些必须逐行审查。6.3 后续可以扩展的玩法方向游戏最终跑通后我还留了几个待办给后面继续做原型时用。第一是加漂移积分系统。在漂移状态下持续保持侧滑屏幕边缘会逐渐累积积分积分可以兑换加速道具。第二是加上下坡坡度变量。秋名山毕竟是山路如果能在样条曲线控制点上加一个高度维度车辆上坡减速、下坡加速的感觉会非常强化驾驶沉浸感。第三是手机触控适配。把左右键换成屏幕底部左右两侧的虚拟按钮再加一个重力感应的快速倾侧判定。这些扩展都能在现有代码基础上接插件式追加不需要重写核心模型。我个人的经验是既然模型已经把骨架和手感都铺好了后续加功能的速度会比自己从零写快不少但每加一个模块都要像前面那轮调教一样盯参数、盯回归。最后再分享一个小技巧跟 Opus 5.5 这类模型协作做游戏原型不要一开始就让它输出“完整游戏”而要把它当实习工程师先把赛道、车辆、HUD 分阶段交出每阶段固定一个可运行版本。这比让它一口气写 800 行代码再回头改参数效率高得多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →