这次实测的起因其实很普通:前几天我想拿一个小体量 3D 游戏当代码生成测试题,正好手边有 Step 5 Preview、DeepSeek V4 Pro 和 GLM5.3 三个大模型可以调,干脆约了一场“同题考试”。同一个提示词、同一个验证目标、同一个 Android 设备,三个模型各自独立生成一套实现,我再把三份代码都拉起来跑一遍。
实测下来的结论不是“谁最强”,而是更实用的一条经验:这类 3D 游戏题目,真正拉开差距的地方根本不在“会不会写渲染”,而在逻辑细节、项目结构、错误处理和后期调试效率上。这篇文章就是完整记录,从题目设计、提示词写法、生成结果对比,到真机调优和翻车坑位,基本把能踩的都踩了一遍。
如果你最近也在用这些模型写图形学或工程类代码,这篇文章应该能帮你省掉不少试错时间。
1. 实测前,我先想清楚了三个问题
开始动手之前,我先把“用三个模型同时做一个 3D 游戏”这件事拆成了三个问题:为什么用 3D 游戏、为什么选这三个模型、怎么评价“做得好不好”。这三个问题想不清楚,后面对比出来的结果就只是一堆印象流,没有价值。
1.1 为什么拿 3D 游戏当测试题
很多人喜欢用 LeetCode、SQL、爬虫这些题目测模型,我倒是觉得,3D 小游戏是更刁钻的测试题。原因很简单:它同时压测了编程能力的四个方向。
第一是数学能力。三维坐标、矩阵变换、透视投影、碰撞检测,这玩意儿不是背几个 API 就能糊弄过去的,模型要是对矩阵乘法理解错了,画面直接不显示。
第二是工程组织能力。一个小游戏至少包含渲染循环、输入处理、游戏状态、对象管理、生命周期回调。模型如果只会生成一堆函数,不会把它们串起来,项目根本跑不了。
第三是平台知识。比如在 Android 上要用 GLSurfaceView,要在 Manifest 里配置横屏,要在 onPause 时正确暂停渲染线程。这些经验很多教程里都不写,只有写过的人才知道。
第四是调试能力。代码生成一次通过的概率其实不低,但真机运行后黑屏、闪退、卡顿才是大问题。这是模型之间的分水岭。
所以我不是真想做一个 3D 游戏,我只是把 3D 游戏当成一张综合卷子来用。
1.2 为什么是这三个模型
选模型的时候我考虑了两层。
第一层是代表性。DeepSeek V4 Pro 属于通用能力比较均衡的选手,代码生成和推理都稳定,适合做任何题目的基准线。GLM5.3 在中文场景和工具调用上一直口碑不错,而且它有一个比较“激进”的代码风格:喜欢用新特性,喜欢精简,有时会为了短小而牺牲一点点可读性。Step 5 Preview 属于这一批新出不久的能力型模型,主打复杂推理,很多人说它“写出来的东西更像一个工程师在改代码”,这次正好验证这个说法。
第二层是比赛公平性。三个模型我都用同一个账户体系下的 Web 接口来调,不额外加 system prompt,也不在对话过程中给提示词之外的任何引导。先把答案生成出来,再统一人工比对。
这里有个建议:如果你也想横向对比模型,不要给模型“定向提示”。比如明确告诉它“你应该用什么库”“你应该按什么结构写”,这是不公平的。我这次的提示词只描述需求,不给实现方案,因为我想看模型自己的设计能力。
1.3 我自己定的验收标准
没有验收标准的对比就是耍流氓。我定了一个非常明确的清单。
- 必须能在 Android Studio 空项目里直接编译通过;
- 安装后不能黑屏,必须稳定显示 3D 场景;
- 游戏逻辑可以玩:有障碍、有计分、可以跳、能重开;
- 真机帧率不低于 40fps;
- 加分项:代码结构清晰、没有内存泄漏、能看懂、能二次改。
我特别说明一点:编译通过只是及格线,不是加分项。现在很多模型生成的代码在编译层面很干净,但跑起来完全不是那么回事,比如相机矩阵算错、资源没加载、GL 线程崩溃。这类问题只能真机验证,无法靠静态代码判断。
2. 同一道题,三个模型交出了三份完全不同的答案
题目敲定之后,我设计了一个固定提示词,严格保证三个模型收到的输入一模一样。
2.1 固定提示词长什么样
我最终用的提示词是:
请用 Kotlin 编写一个可在 Android 上运行的 3D 跑酷小游戏: - 使用 GLSurfaceView 和 OpenGL ES 2.0 - 包含一条行进道路、左右摆放的方块障碍、跳跃、计分、速度递增、游戏结束和重新开始 - 不依赖任何第三方库 - 代码需要能直接放入 Android Studio 空项目注意,我特意没有指定坐标系怎么建立、相机放在什么位置、场景怎么组织。这些都是留给模型自由发挥的设计题。
结果三个模型给出了三种逻辑:
- Step 5 Preview 给的是单文件 Kotlin,把 GLSurfaceView、Renderer、shader、游戏对象全部压在一个文件里,大概 700 行。优点是结构自洽,缺点是阅读起来有点拥挤。
- DeepSeek V4 Pro 给的是多文件结构,Handler 和 GL 线程分开,Initializer 单独成类,甚至分出了 model 和 view 的包。很像一个老工程师在写业务代码。
- GLM5.3 给的是单文件但更紧凑,代码量在 550 行左右,用了不少 Kotlin 新语法,比如 data class、when 表达式、lambda 回调。看起来很“聪明”,但缩进和括号嵌套多了以后,真机调试时比较考验眼力。
这三种结构本身没有绝对优劣,但在后续集成过程中影响很大。DeepSeek V4 Pro 的结构最好改,Step 5 Preview 的代码最完整,GLM5.3 的最“酷”但也最容易在修改时碰到耦合问题。
2.2 第一轮生成结果的横向对比
我把三份代码分别建了三个 Android 项目跑了一次,结果形成了一张很有意思的对比表。
| 评测维度 | Step 5 Preview | DeepSeek V4 Pro | GLM5.3 |
|---|---|---|---|
| 输出结构 | 单文件约 700 行 | 多文件分包 | 单文件约 550 行 |
| 首次编译 | 一次通过 | 一次通过 | 一次通过 |
| 投影矩阵 | 透视投影 | 透视投影 | 正交投影 |
| 默认表现 | 场景可看,移动偏慢 | 跑酷节奏合理 | 画面正常,碰撞偏松 |
| 代码注释 | 中等 | 较多 | 较少 |
| 最明显短板 | 文件太长难维护 | 依赖 16KB 页面大小优化 | 坐标轴处理有点怪 |
你能看到,三个模型在“编译”这个层面全都及格了,但到了实际表现就开始分化。
举个例子,GLM5.3 用了正交投影。正交投影的好处是不会产生透视变形,画面边缘清晰,适合做“低多边形风格”的小游戏。但它因此把 Z 轴当成纵深方向时,遮挡关系很容易出问题。跑酷场景里道路和障碍物一多,就会出现“明明跳到前面了,还是被判定撞上”的现象,问题就出在碰撞检测的空间坐标系和渲染坐标系不一致上。
DeepSeek V4 Pro 给我留下了比较深的印象,它的代码把“游戏状态机”写得很清楚:READY、RUNNING、OVER 三种状态分别处理了输入和渲染逻辑。这省了我后面不少改代码的工夫。
Step 5 Preview 的强项是错误处理严谨。它的 onSurfaceCreated 内部检查了每个 shader 编译结果,编译失败会打印出锅底日志并销毁资源。这看起来基础,但你会发现很多模型生成代码时不写这一步,结果 GL 报错时一片黑屏,完全无从查起。
2.3 第一眼印象与潜在隐患
第一轮跑完,我没有急着下结论,而是先看代码里的“味道”。
Step 5 Preview 的代码像是一个人在给你演示规范写法,逻辑很正,但文件太长。如果后面要继续加玩法,我必须先拆文件,否则没法维护。
DeepSeek V4 Pro 的代码像一份可直接上线的项目骨架,分包合理,启动逻辑清楚。但它有一个实际问题:为了结构清晰,深挖三层调用,真机上排查 GL thread 和 UI thread 的同步问题时会绕一点。
GLM5.3 的紧凑风格我最担心。它把 shader 的源码写成了三个长字符串,而且没有提炼成资源文件,后来我想调整着色器,就得在 Kotlin 文件里改字符串,很容易出现转义符错误。
这里分享我的一个习惯:看 AI 生成的代码,不要太在意首验编译是否通过,重点看两个地方——资源的存放方式,以及状态管理的边界。资源如果散落在代码里,很难复用;状态如果混成一锅粥,后面所有逻辑都会互相打架。这两个点才是影响真实开发的元凶。
3. 从“能编译”到“真机能玩”,中间隔着一个完整的调优过程
代码能编译只是第一步。接下来我从 Android Studio 工程配置开始,一步步把三份代码都跑到了真机上,期间亲手整理了渲染循环、障碍物逻辑和帧率优化。这个过程比生成代码更有参考价值。
3.1 把代码装进 Android Studio 的步骤与踩坑
不管用哪个模型的代码,把它变成可运行的 App 都需要经过以下几个步骤。我用的是 Android Studio Koala 版本,AGP 8.5,minSdk 24。
第一步,新建 Empty Views Activity 项目,包名用 com.example.race3d,语言选择 Kotlin。
第二步,把生成的代码复制到 MainActivity.kt。要注意,如果模型给的是多文件结构,直接把对应文件复制到相应包路径下就行。模型之间真正差在工程组织,所以我这里没有偷懒,完全按模型自己的目录结构存放。
第三步,修改 AndroidManifest.xml。必须加上横屏锁定和全屏主题。
<activity android:name=".MainActivity" android:screenOrientation="landscape" android:theme="@android:style/Theme.Black.NoTitleBar.FullScreen" />如果不锁横屏,手机一旋转,GLSurfaceView 会重新创建,后面的资源加载就全要重新走一遍,很容易内存抖动。
第四步,把 GLSurfaceView 设置到 setContentView。我这里直接用了代码写法:
val glView = GLSurfaceView(this) glView.setEGLContextClientVersion(2) glView.setRenderer(Renderer()) setContentView(glView)第五步,重写 onPause 和 onResume。这个步骤很多人会漏,但属于踩坑高发区。
override fun onPause() { super.onPause() glView.onPause() } override fun onResume() { super.onResume() glView.onResume() }GLSurfaceView 的渲染线程是独立线程,如果不正确暂停,切换后台时渲染线程还在跑,轻则耗电,重则直接崩掉。
一个细节:真机安装时,如果系统提示“安装包异常”,可以检查 APK 里是否包含 64 位 so 库。我这次用的是纯 OpenGL 代码,没有 so 依赖,所以没遇到这个问题。但如果你让 AI 帮你接 Box2D 或 Bullet 物理引擎,这问题就会出现。
3.2 跑酷逻辑:障碍物生成与碰撞处理是重灾区
三个模型的代码真机跑起来之后,第一个肉眼可见的问题都是同一个:障碍物生成太随机,经常出现左右两边连续两个方块,玩家根本没有躲避路径,这是 3D 跑酷最忌惮的无解布局。
我最初从 GLM5.3 的代码里看到,它生成障碍物时用的是连续 random 判断:
if (Random.nextBoolean()) { leftObstacle.visible = true } else { rightObstacle.visible = true }没带安全间隔,连续几帧都是 left-left-right,就不存在安全通道。后来我在生成逻辑里加了一个safeGap状态:
private var safeGap = 0 private fun spawnObstacle() { if (safeGap > 0) { safeGap-- return } val side = if (Random.nextBoolean()) Side.LEFT else Side.RIGHT spawnOnSide(side) if (Random.nextBoolean()) { safeGap = 1 } }这段逻辑保证每两次“非空障碍生成”之间,至少留下一个可通行帧,可玩性立刻提升。
另一个重灾区是碰撞检测。很多模型写的碰撞检测只判断“物体在同一个格子”,却没有考虑格子和方块中心点偏移,导致玩家看着跳过障碍,系统仍然判定为撞上。要做这套逻辑,最直接的办法是把跳高过程切分成多个离散位置来判断。
for (i in 0..jumpSteps) { val t = i / jumpSteps.toFloat() playerY = jumpBase + jumpHeight * 4f * t * (1f - t) if (playerY < obstacle.height * 0.8f && abs(playerX - obstacle.x) < 0.6f && abs(playerZ - obstacle.z) < 0.6f ) { GameOver() } }这个判断的本质是不依赖碰撞引擎的简化版,用 8 帧采样代替连续碰撞,帧率稳定性更好。看到 AI 第一版写出来的碰撞“糊成一团”之后,我也马上明白了一个道理:如果你让 AI 直接写引擎级物理,它会写一堆抽象的东西;但如果你把需求拆成“每帧采样判断”,它能做得又快又准。
3.3 帧率突破:把 OpenGL 的 draw call 砍到三个以内
三份代码刚跑起来时,帧率都不算差,但也没有特别稳。Step 5 的初始版本在我这台 Redmi K60 上大约是 38fps,GLM5.3 则一度只有 26fps,原因很典型:每帧都做了大量重复的 matrix 计算,而且为每个障碍物单独调用了一次 draw call。
我做的优化有两个方向。
第一个方向是合并顶点数据。把同一类型障碍物的顶点放到同一个 FloatArray 里,共用一个 VBO,根据物体索引来画不同的部分。这样 30 个障碍物就从 30 次 draw call 变成了 1 次。
第二个方向是减少每帧矩阵乘法。把相机矩阵、投影矩阵只计算一次,放进 uniform 传给 shader,而不是每绘制一个物体重新乘一遍。修改后大部分矩阵运算从逐物体量级降到了逐帧一次量级。
我实测的帧率变化如下。
| 测试状态 | 平均帧率 | 掉帧情况 |
|---|---|---|
| GLM5.3 初版 | 26fps | 碰撞检测时掉到 18fps |
| 合并 VBO 后 | 43fps | 基本稳定在 40fps 以上 |
| 再移除相机矩阵重复计算 | 55fps | 测试 15 分钟无严重掉帧 |
这轮调优之后,三个模型生成的代码在真机上的表现已经非常接近了。说明一个更重要的结论:模型起步的代码质量会影响调试成本,但最终的优化空间始终在自己手里。AI 不是背锅侠,也不是救世主。
4. 实测中最容易翻车的现场与排查技巧
三个模型可以说各有各的翻车姿势。我把这次实测过程中最典型的六类问题和对应的排查思路整理出来,这会比任何教程都更能帮你省时间。
4.1 三个模型各自的翻车现场
我按模型的维度整理了一个速查表。
| 模型 | 翻车场景 | 原因 | 我的处理方式 |
|---|---|---|---|
| Step 5 Preview | 代码太长,加载后 IDE 卡顿 | 单文件 700 行加大量内联 | 抽成 3 个类:Renderer、GameLogic、ShaderLoader |
| DeepSeek V4 Pro | onSurfaceChanged 里没有正确重建视口 | 对生命周期处理教条化 | 把视口尺寸与宽高比缓存到变量,避免二次计算 |
| GLM5.3 | 着色器字符串频繁转义错误 | 长字符串拼接时反斜杠丢失 | 改用字符串数组,逐行拼接,降低转义错误率 |
这里我想专门说一下 Step 5 Preview。它的代码初次运行是最稳的,基本没有逻辑错误,但问题也出在这里:它生成了太多“防御性代码”,比如每个函数都做空判断、每次计算都检查 null。这些代码增加了可维护成本。我用了一段时间后,被迫把它的单文件拆成三个类。这一步如果一开始就做,后续改跳跃手感会容易很多。
4.2 让 AI 自己修 Bug 的提示词模板
遇到 bug 时,我强烈建议先让 AI 自己修,而不是自己一头扎进去翻代码。但“让 AI 修”是有技巧的,直接说“你的代码有 bug”效果很差。有效的方法是:把错误信息完整返回给模型,并明确限制它只输出修复后的完整函数。
我用的提示词是这样的:
你的代码在运行到第 87 行时崩溃,以下是 Logcat 的堆栈: <粘贴 Logcat 原文> 请只给我修复后的完整函数,不要给修改说明。这个模板的魔力在于它提供了两个关键信息:错误发生的精确位置,以及模型需要交付的具体产物。模型不需要分析全局,只需要基于报错上下文做局部修改,准确率会高很多。
这次实测中,我用这个模板让 Step 5 Preview 修复了一个矩阵内存越界问题,它只改了两行代码就解决了。如果我自己去查这个 bug,可能需要翻半天源代码才能定位到数组下标错误,效率差距特别明显。
有一个细节要提醒:喂给模型的 Logcat 一定要包含具体的堆栈行号。有些模型会自己脑补错误原因,如果看到的信息太模糊,它就只会瞎改。我建议在 Logcat 里截取包含 “Caused by” 的部分,这行后半段会直接指出崩溃点。
4.3 不同模型的“性格差异”记录
经过三轮调优,我对三个模型在真实项目里的角色也有了一个更立体的认知。
Step 5 Preview 像一个严谨的工程师。你让它做什么它都做得完整,边界条件处理得也细,但它不会主动帮你简化设计。如果你希望它给“最小可行方案”,你得明确告诉它。
DeepSeek V4 Pro 更像一个架构师。同样是写一个小游戏,它会把 handler、renderer、model 拆得很干净。小项目看不出优势,但如果你要做大一点的游戏,或者之后要合并其他功能,这种天然的结构优势就体现出来了。
GLM5.3 像一个聪明的急脾气。它的代码最短,看着最像人写的,但有的时候为了短,会牺牲掉一部分严谨性。比如它生成的碰撞检测过于宽松,导致游戏判定不准确。不过它有一个很特殊的优点:如果你把错误信息贴回去,它的修复准确率非常高,甚至比另外两个模型更少出现“修复后引入新 bug”的情况。
所以我的建议是:如果你在做一个可快速验证的原型,可以优先考虑 GLM5.3;如果你要生成一个长期维护的项目,DeepSeek V4 Pro 的结构更稳;如果你希望第一次代码就跑得最稳,Step 5 Preview 是个不错的选择。
5. 顺手补充:自己部署 GLM5.3 时的两个实用经验
这次实测主要走的是 Web 接口,但后面我想把 GLM5.3 搬到本地环境做进一步测试,结果踩了两个坑,顺手记录在这里,或许能帮到想在本地跑模型的人。
5.1 GLM5.3 与 vLLM 的版本兼容性记录
我先说结论:GLM5.3 对 vLLM 的版本要求比较高,建议不要用太旧的版本,否则会出现算子不匹配和显存溢出的问题。
我之前先用某个较早版本的 vLLM 跑,加载模型时直接报出了算子错误。排查后发现是模型编码方式和旧版 vLLM 的 attention 实现不兼容。换到新版 vLLM 镜像后,问题立刻消失。
另外,真机如果显存只有 16GB 左右,建议使用 FP8 量化方式加载。我实测下来,在 FP8 下模型输出质量和 BF16 几乎无差,但显存占用下降明显,生成速度稳定在每秒 40 token 以上,足够做交互式调用。
这里有一个通用经验:部署任何新模型前,先去自己对应的推理框架 Release Notes 里看兼容列表,不要盲目用最新版,也不能迷信旧版“稳定”。最省事的办法是先用官方推荐的版本,跑通一条验证样例,再考虑升级。
5.2 开源安卓 3D 游戏还能往哪个方向扩展
最后回到游戏本身。我在最终版本里把道路、障碍物、玩家模型全部换成了程序化生成,没有用任何外部模型文件。这样做有一个好处:代码直接开源后,别人拉下来就能编译,不需要额外下载美术资源。
如果你也想把这个项目继续做成开源安卓 3D 游戏,我建议优先补充三个方向。
第一是换一种更现代的渲染方式,比如 OpenGL ES 3.0 加浮点纹理,效果更细腻;第二是增加多人在线得分排行,这部分用 WebSocket 很容易接;第三是增加触摸手势控制,比如滑动跳跃,这个相比按键模式更适合手机玩家。
这些扩展方向里,尤其推荐第一个,因为它在视觉上最能“一鸣惊人”。把光照从简单的方向光升级到多光源后,游戏质感会立刻提升一个档次。实现过程并不复杂,因为着色器代码已经开好了口子,你只需要把两个 uniform 改成数组,再循环几次即可。
最后再分享一个我个人的体会:这次三模型同做一个 3D 游戏,让我最意外的不是某个模型能力多强,而是它们的代码风格差异会直接决定你后面要花多少精力去维护。我的原则是,AI 生成代码解决“从 0 到 1”,但“从 1 到能维护”还得靠你自己。别指望任何模型一次写出完美项目,真正有价值的操作是:读它、拆它、在它的基础上继续迭代。
如果你也准备搞类似的 AI 写游戏测试,建议从 3D 跑酷这种小体量玩法开始,环环相扣、具备渲染与逻辑压力,同时又不至于复杂到让人失去兴趣。跑通一次之后,你就能直观感受到每个模型的真实脾气了。