1. 三个模型同台做3D游戏,我为什么选这个题
最近圈子里讨论度最高的一件事,就是几个主流代码大模型在同一类任务上的横向对比。Step 5 Preview、DeepSeek V4 Pro、GLM5.3 这三个名字频繁出现在各种技术群里,但真正拿它们做同一个完整项目、并且把过程记录下来的内容并不多。我花了大概一周的业余时间,让这三个模型分别从零开始做一个可运行的3D小游戏,统一需求、统一验收标准,然后把中间的过程、踩的坑、最终产物都整理出来。
先说清楚这个测试的定位:它不是跑分,不是刷榜,而是模拟一个真实开发者的工作流——你有一个模糊的想法,需要模型帮你把架构定下来、把核心玩法代码写出来、把资源组织好、最后能跑起来。这个过程中模型的理解能力、代码组织能力、纠错能力、对3D数学的掌握程度,都会暴露得很彻底。
为什么选3D游戏作为测试载体?因为3D游戏天然是一个"综合题"。它同时涉及场景图管理、相机与投影矩阵、光照模型、碰撞检测、输入处理、资源加载、帧循环调度这些子系统。一个模型如果只会写CRUD接口,做3D游戏会立刻露馅;如果它对向量运算、四元数、坐标系变换理解不到位,写出来的东西要么跑不起来,要么视觉上明显不对。这比让它写一个待办清单应用有区分度得多。
这篇文章适合几类人看:正在选型代码助手的开发者、想了解这几个模型真实能力边界的技术负责人、以及打算用AI辅助做小游戏或者图形类项目的独立开发者。我会把每个环节的具体做法、参数选择、以及"为什么这么选"都讲清楚,你照着复现基本不会卡住。
提示:本文涉及的所有模型名称仅作为技术对比对象出现,测试环境为本地开发机,不涉及任何网络配置类操作。
2. 测试方案怎么定才算公平
2.1 统一需求文档的写法
公平对比的前提是输入完全一致。我给三个模型的是同一份需求描述,措辞经过反复打磨,避免任何可能偏向某个模型的暗示。需求文档大致是这样的:
- 做一个第一人称视角的3D迷宫探索游戏,浏览器端运行
- 玩家用WASD移动,鼠标控制视角朝向
- 迷宫由程序化生成,每次刷新布局不同
- 场景中有若干可收集物品,收集后计数增加
- 有简单的碰撞检测,玩家不能穿墙
- 用Three.js作为渲染库,不引入物理引擎
- 代码组织成模块,主逻辑不超过500行
这份需求的关键在于:它足够具体,让模型不会跑偏;又足够开放,能看出模型的组织能力。比如"程序化生成"没有指定算法,模型可以选递归回溯、Prim算法或者随机挖洞,不同选择直接反映它对迷宫生成的理解深度。
2.2 验收标准与评分维度
光看"能不能跑"太粗糙了。我定了五个维度,每个维度按1到5分打分:
| 维度 | 考察点 | 权重 |
|---|---|---|
| 可运行性 | 首次运行是否直接报错,报错后能否自行修复 | 30% |
| 代码结构 | 模块划分是否合理,函数职责是否单一 | 20% |
| 3D正确性 | 相机、投影、碰撞的数学是否正确 | 25% |
| 交互体验 | 移动手感、视角平滑度、帧率稳定性 | 15% |
| 扩展性 | 加新功能是否容易,代码是否可读 | 10% |
可运行性权重最高,因为这是最硬的指标。一个模型讲得天花乱坠,代码跑不起来就是零。3D正确性单独拎出来,是因为这是3D游戏区别于普通应用的核心,很多模型在这里翻车。
2.3 环境与工具链的统一
三个模型都在同一台机器上测试,Node版本、Three.js版本、浏览器版本全部锁定。Three.js用的是r160,这个版本对ES Module支持完善,且API相对稳定。构建工具用Vite,启动快,热更新及时,方便快速验证。
测试时我刻意不给模型任何额外的库建议,让它自己在需求范围内选。如果它主动提出用某个辅助库,我会记录但不会阻止——这本身也是能力体现。实际跑下来,三个模型都老老实实只用了Three.js核心,没有引入额外依赖,这点倒是挺一致。
3. Step 5 Preview 的实际表现拆解
3.1 首次输出的代码骨架
Step 5 Preview 给我的第一印象是"结构感强"。它没有一上来就堆代码,而是先输出了一段架构说明,把整个项目分成maze.js(迷宫生成)、player.js(玩家控制)、collectibles.js(收集物)、main.js(主循环)四个模块。这个划分很符合直觉,每个模块职责清晰。
迷宫生成部分它选了递归回溯算法,代码写得相当紧凑:
function generateMaze(width, height) { const grid = Array.from({ length: height }, () => Array.from({ length: width }, () => ({ walls: [true, true, true, true], visited: false })) ); const stack = [[0, 0]]; grid[0][0].visited = true; const dirs = [[0, -1], [1, 0], [0, 1], [-1, 0]]; while (stack.length) { const [x, y] = stack[stack.length - 1]; const neighbors = dirs .map(([dx, dy], i) => [x + dx, y + dy, i]) .filter(([nx, ny]) => nx >= 0 && nx < width && ny >= 0 && ny < height && !grid[ny][nx].visited); if (neighbors.length === 0) { stack.pop(); continue; } const [nx, ny, dir] = neighbors[Math.floor(Math.random() * neighbors.length)]; grid[y][x].walls[dir] = false; grid[ny][nx].walls[(dir + 2) % 4] = false; grid[ny][nx].visited = true; stack.push([nx, ny]); } return grid; }这段代码的亮点在于用walls数组的四个布尔值表示四面墙,方向索引和dirs数组对应,拆墙时用(dir + 2) % 4找对面墙,逻辑干净。实测下来生成的迷宫没有孤立区域,连通性正确。
3.2 相机与移动控制的处理细节
第一人称控制是3D游戏最容易出问题的地方。Step 5 Preview 用了欧拉角方案,yaw控制水平旋转,pitch控制俯仰,并且给pitch加了-Math.PI/2到Math.PI/2的钳制,防止翻转。这个处理是对的,很多新手会忘记钳制导致视角翻转。
移动部分它用了相机朝向的前向量和右向量来计算位移:
const forward = new THREE.Vector3(); camera.getWorldDirection(forward); forward.y = 0; forward.normalize(); const right = new THREE.Vector3().crossVectors(forward, camera.up).normalize();把forward.y清零再归一化,保证移动只在水平面,这个细节说明它理解"第一人称移动不应该受俯仰影响"。实测手感很稳,没有出现斜着走或者速度异常的问题。
3.3 碰撞检测的实现思路
碰撞检测它没有用射线,而是用了简单的网格坐标判断:把玩家位置转换成迷宫格子坐标,检查目标格子是否有墙。这个方案在网格迷宫里完全够用,而且性能极好,不需要每帧做射线求交。
具体做法是先算出移动后的候选位置,然后分别检查X轴和Z轴方向是否撞墙,如果撞了就只保留另一个轴的分量。这种"分轴处理"能让玩家贴着墙滑动,而不是一撞就完全停住,手感明显更好。这个细节是很多模型会忽略的,Step 5 Preview 主动做了。
3.4 实测中暴露的两个小问题
第一个问题是收集物的碰撞判定用了球体距离,但阈值设得偏大,导致玩家还没走到物品跟前就触发了收集。这个属于参数调优问题,把阈值从1.0改成0.6就正常了。
第二个问题是初始视角朝向。它默认让相机朝向-Z方向,但迷宫入口的开口方向是随机的,导致有时候一开局面对的是墙。这个需要根据入口格子的墙状态动态设置初始yaw。我手动补了这段逻辑,大概十行代码。
注意:Step 5 Preview 在首次输出时没有处理"初始朝向"这个边界情况,这类"开局状态"的问题在3D项目里很常见,建议在需求里明确写出来。
4. DeepSeek V4 Pro 的代码组织与3D数学
4.1 模块划分的差异
DeepSeek V4 Pro 的模块划分和 Step 5 Preview 不太一样。它把迷宫生成和渲染合并成了一个world.js,理由是"迷宫数据和它的可视化是强耦合的"。这个观点有一定道理,但实际维护起来,我想单独调整渲染风格时就得动world.js,不如分开清爽。
不过它在player.js里做了一个我觉得很聪明的设计:把输入状态和物理更新分离。输入层只负责记录按键状态,物理层每帧读取状态计算位移。这样做的好处是输入响应和帧率解耦,即使某一帧卡顿,按键也不会丢。这个模式在游戏开发里叫"输入缓冲",是成熟做法。
4.2 四元数视角控制的取舍
DeepSeek V4 Pro 在视角控制上选了四元数方案,而不是欧拉角。它的理由是"避免万向锁"。这个选择在理论上更严谨,但实际代码复杂度上去了:
const quat = new THREE.Quaternion(); quat.setFromEuler(new THREE.Euler(pitch, yaw, 0, 'YXZ')); camera.quaternion.copy(quat);它用YXZ顺序构造欧拉角再转四元数,本质上还是欧拉角思路,只是借四元数存储。真正的纯四元数方案应该是用轴角增量累乘,但那样处理pitch钳制会更麻烦。所以它这个折中方案其实是合理的——既避免了直接操作欧拉角的一些坑,又保留了钳制的便利。
实测下来视角平滑度确实好,快速转视角时没有抖动。但代码可读性对新手不太友好,需要理解四元数和欧拉角的关系。
4.3 光照与材质的选择逻辑
DeepSeek V4 Pro 在光照上用了AmbientLight加DirectionalLight的组合,环境光强度0.4,平行光强度0.8。这个配比让场景既有基础亮度,又有方向感,不会一片死黑也不会过曝。
材质方面它给墙壁用了MeshStandardMaterial,给收集物用了MeshBasicMaterial。这个区分是有讲究的:墙壁需要受光照影响体现立体感,收集物用基础材质可以保证颜色鲜艳、始终可见,起到视觉引导作用。这个细节说明它在考虑"游戏性"而不只是"能渲染"。
4.4 性能相关的处理
它主动做了两件性能优化:一是把迷宫的墙壁合并成一个BufferGeometry,而不是每面墙一个Mesh,这样draw call从几百降到个位数;二是给收集物加了简单的距离剔除,超过一定距离的不参与渲染。
第一点效果很明显,我用性能面板看了下,合并后帧率从40多稳定到60。第二点在迷宫这种小场景里意义不大,但思路是对的。整体来看,DeepSeek V4 Pro 在工程化方面考虑得更周全。
5. GLM5.3 的交互手感与迭代响应
5.1 移动手感的调优
GLM5.3 最让我意外的是它对"手感"的重视。它在移动逻辑里加了加速度和阻尼:
velocity.x += (targetVelocity.x - velocity.x) * 0.15; velocity.z += (targetVelocity.z - velocity.z) * 0.15;这个0.15的插值系数让移动有轻微的加速和减速过程,而不是瞬间启停。实测手感确实比另外两个模型更"顺",有种惯性感。这个细节在需求里完全没提,是它自己加的,说明它对游戏体验有理解。
5.2 视角平滑的处理
视角方面它用了鼠标灵敏度系数加平滑插值。原始鼠标位移先乘以灵敏度,再和当前视角做插值,避免鼠标抖动直接传到相机。这个处理在快速甩视角时能明显感觉到更稳。
不过它默认灵敏度设得偏高,我调低了一半才觉得舒服。这个属于个人偏好,不算问题,但说明默认参数需要根据实际体验微调。
5.3 收集反馈的视觉设计
GLM5.3 在收集物被拾取时加了一个缩放消失的动画,用requestAnimationFrame驱动,大概0.3秒内从原尺寸缩到0。这个反馈让收集行为有了明确的"确认感",比物品直接消失体验好很多。
它还加了一个简单的计数器UI,用HTML覆盖层实现,不占用3D渲染资源。这个做法很务实,UI用DOM做比在3D里画文字省事得多。
5.4 迭代过程中的响应质量
我让三个模型都做了一轮迭代:增加"收集全部物品后显示胜利提示"的功能。GLM5.3 的响应最快,而且它主动问了两个澄清问题:"胜利提示是弹窗还是场景内文字?""是否需要重新开始按钮?"这种主动澄清需求的行为,在实际协作中能省很多返工。
Step 5 Preview 和 DeepSeek V4 Pro 都是直接实现了,没有反问。直接实现不一定错,但如果需求有歧义,反问能避免做偏。这一点上 GLM5.3 的交互风格更接近一个靠谱的协作伙伴。
6. 三个模型的能力边界对照
6.1 关键维度横向对比
把前面的观察汇总成一张表,方便对照:
| 维度 | Step 5 Preview | DeepSeek V4 Pro | GLM5.3 |
|---|---|---|---|
| 首次可运行 | 是,需微调参数 | 是,直接可跑 | 是,直接可跑 |
| 代码结构 | 模块清晰 | 工程化强 | 结构合理 |
| 3D数学 | 欧拉角,正确 | 四元数,严谨 | 欧拉角+平滑 |
| 手感调优 | 基础 | 基础 | 主动优化 |
| 性能意识 | 一般 | 强 | 中等 |
| 需求澄清 | 无 | 无 | 主动反问 |
| 扩展性 | 好 | 好 | 好 |
6.2 各自适合的场景
Step 5 Preview 适合"需要快速拿到一个结构清晰、容易接手维护的代码骨架"的场景。它的代码读起来最舒服,注释也到位,适合团队协作时作为起点。
DeepSeek V4 Pro 适合"对性能和数学严谨性有要求"的场景。它会在你没想到的地方做优化,适合做需要长期迭代、规模会变大的项目。
GLM5.3 适合"注重用户体验、需要频繁沟通需求"的场景。它对交互细节的敏感度高,主动澄清的习惯能减少沟通成本。
6.3 一个反直觉的发现
测试前我以为三个模型在3D数学上会有明显差距,实际跑下来,基础的向量运算、矩阵变换大家都没问题。真正的差距出现在"边界情况处理"和"体验细节"上——比如初始朝向、碰撞滑动、收集反馈这些需求里没明说但实际影响体验的地方。
这说明当前这几个模型在"把需求翻译成代码"这个层面已经相当成熟,差距更多体现在"理解需求背后的意图"和"主动补全隐含需求"上。这个观察对选型很有参考价值:如果你的需求写得很细,三个模型差别不大;如果需求比较粗,GLM5.3 和 DeepSeek V4 Pro 的主动补全能力会更有优势。
7. 复现这套测试的实操建议
7.1 需求文档的写法要点
如果你也想做类似的对比,需求文档要注意几点:一是明确技术栈版本,避免模型选了不兼容的API;二是把"验收标准"写进去,比如"必须能直接运行不报错";三是留出开放空间,不要把所有实现细节都规定死,否则测不出模型的判断力。
我这次的需求文档大概300字,既不过于简略也不过于详细。太简略模型会跑偏,太详细就变成"照着抄",测不出真实能力。
7.2 测试环境的隔离
三个模型的产物要放在独立目录,依赖版本锁定。我用的package.json里Three.js写死"three": "0.160.0",避免自动升级导致API变化。每次测试前清空node_modules重装,保证环境干净。
浏览器建议用Chrome,开发者工具的Performance面板能直接看帧率和draw call,方便验证性能相关的优化是否真的生效。
7.3 记录过程的方法
光看最终代码不够,过程中的迭代记录更有价值。我的做法是每次模型输出后立刻跑一遍,把报错信息、需要手动修改的地方、以及修改原因都记下来。这些记录最后汇总成了前面的对比表。
特别建议记录"模型自己发现并修复的问题"和"需要你手动修的问题",这个比例很能反映模型的自我纠错能力。这次测试里,GLM5.3 的自我修复比例最高,Step 5 Preview 次之,DeepSeek V4 Pro 因为首次输出质量就高,需要修的地方本来就少。
7.4 几个容易忽略的验证点
跑通不代表做对。有几个点建议专门验证:一是快速转视角时有没有万向锁或者抖动;二是贴着墙走时会不会卡住或者穿墙;三是长时间运行后帧率会不会下降(内存泄漏);四是窗口缩放时相机宽高比有没有正确更新。
最后一点特别容易漏。很多模型写的代码在固定窗口下正常,一缩放就变形,因为没监听resize事件更新camera.aspect和renderer.setSize。这次三个模型里有两个处理了,一个漏了,漏的那个需要手动补。
提示:窗口缩放适配是3D Web项目的常见遗漏点,建议在验收清单里单独列一条。
8. 我在这次对比里的个人体会
做完这轮测试,最大的感受是:这几个模型都已经能独立完成一个中小型3D项目的骨架,差距不在"能不能做",而在"做得多细"。如果你只是想要一个能跑的demo,随便选一个都行;如果你要把这个demo变成能长期维护的项目,那 DeepSeek V4 Pro 的工程化意识和 GLM5.3 的体验敏感度会更值钱。
另外一个体会是关于"提问"的价值。GLM5.3 主动澄清需求那次,我一开始觉得有点啰嗦,但后来发现它问的两个问题确实是我需求里没写清楚的,如果不问,做出来的东西大概率要返工。这种"先对齐再动手"的习惯,在实际项目里比写得快更重要。
最后分享一个小技巧:不管用哪个模型,第一轮输出后不要急着改代码,先让它自己解释一遍关键部分的实现思路。如果它解释得清楚,说明它真的理解;如果解释得含糊,那代码里大概率有它自己也没想明白的地方,这些地方就是你需要重点检查的。这个习惯帮我提前发现了好几个隐藏的逻辑问题。