被游戏虐烦后,我让 Seed-2.1-pro 帮我做了一款自己当王的 3D 游戏

0 阅读20分钟

live.csdn.net/v/545060

玩游戏的时候,我总被别人虐。次数一多,我冒出了一个很直接的想法:既然在别人的游戏里总挨打,那我能不能自己做一款游戏,把规则、技能和敌人都掌握在自己手里?这一次,我要做游戏里的王,目标也很简单——进场,放技能,把敌人打败。

恰好 Seed-2.1-pro 更新了。我不想只让它写一个炫技页面,也不想生成一段代码就结束,而是准备把这个想法完整交给它:从技术路线、第一版场景、五种技能、敌人 AI,到人物动作、生命值、胜负结算和公开试玩,一步一步做成真正能玩的 3D Demo。

最后的结果已经放到了线上。它当然还不是一款包含关卡、联网、存档和商业美术的完整游戏,但我最初想要的那一局已经成立了:主角能移动和瞄准,五种技能能打出不同效果,敌人会游走、拉弓和射箭,双方有生命值,打赢后会出现结算界面,还能马上重新开一局。

我更关心的,是它怎样从一个火球长成一局完整对战。

图片

第一步:先选一条能把想法做完的路线

我的需求听起来很简单:一个主角,一个敌人,几个很爽的技能,最后能分出胜负。但真正开始前,Seed-2.1-pro 先帮我把问题拆开了。

画面要做成 3D,最好打开网页就能玩,这样不用安装客户端,也方便最后发给别人试玩。五种技能不能只是几个颜色不同的圆球,它们要有各自的飞行方式、落点反馈和伤害表现。人物也不能永远站着不动,至少要有待机、行走、奔跑和敌人拉弓动作。等这些内容接起来,还得让敌人能够还手,让一局游戏有掉血、有胜负,也能重新开始。

沿着这些要求,项目采用浏览器 3D 方案。Three.js 负责场景、相机、灯光、模型和动画,three.quarks 负责火焰、烟雾、闪电碎光这类粒子效果;角色部分先用最简单的几何体火柴人验证玩法,等交互跑通后,再接入 Soldier.glb。GLB 是一种能把三维模型、骨骼和动作装在同一个文件里的格式。这个顺序非常重要:如果一开始就追求精细人物和复杂场景,任何一个方向、骨骼或缩放问题都会把基础玩法一起拖住。先让角色能移动、技能能命中,再逐步换掉临时表现,返工成本会小很多。

第一版场景非常朴素:一块地面、一个圆头火柴人、一圈技能图标。它谈不上好看,却证明浏览器中的 3D 场景、人物移动、鼠标瞄准和技能输入已经接通。

图片

这张图现在看起来有点简陋,但它是后面所有内容的地基。就这个 Demo 而言,我更愿意先回答“玩法能不能成立”,再把时间花在天空、草地和人物细节上。

Seed-2.1-pro 继续把实现路线拆开:场景和玩法逻辑分开,每个技能自己管理从生成到回收的过程,敌人按游走、瞄准、射击等状态切换行动,角色模型也留出替换接口。后面从火柴人换成带骨骼的角色时,技能系统因此不用推倒重写,这一点对长流程开发很关键。

这里还有一个取舍:我没有一开始就让它做地图、关卡、背包、任务和联网。那些功能当然更像“大游戏”,却不会让眼前这一局更好玩,反而会让验证目标变得模糊。对这个 Demo 来说,我先盯住四件事:能控制主角,技能有明显差别,敌人会主动还击,一方生命值归零后能结束并重开。四件事成立以后,再做菜单和场景装饰才有意义。

代码组织也围绕这个闭环展开。主循环只负责每一帧推进时间、更新角色、敌人、技能和渲染;角色模块管理移动、朝向、动画与受击;敌人模块管理游走、瞄准、拉弓和射击;各技能负责自己的生成、运动、碰撞、结束与回收。它们通过清楚的状态和位置数据连接,而不是把所有逻辑塞进一个文件。这样做的直接好处是,后面发现火球穿透时只需要改投射物碰撞,发现敌人不射箭时只检查状态机,不必担心同时弄坏相机和界面。

第二步:从一个火球开始,把五种技能一点点补出来

第一个技能是火球。我的要求不是点击一下凭空爆炸,而是要有“蓄力—飞行—命中—爆炸”的完整手感。按住鼠标左键时,能量在手边聚集;松开后火球沿瞄准方向飞出,碰到敌人或到达目标区域再爆开。

第一版火球已经能炸,但画面更像一个突然变大的光团,缺少速度感和方向感。

图片

我继续要求 Seed-2.1-pro 完善。它给火球增加了焰舌、拖尾、核心亮度变化和命中反馈,又把鼠标射线投到地面上计算瞄准点。火球不再只是从角色位置移动到另一个坐标,而是有了明显的前进方向。命中时还会触发震屏、短暂停顿和伤害数字,视觉上终于有了一点“打中东西”的重量。

图片

火球跑通后,我没有让模型简单复制四份再换颜色,而是要求每种技能都有自己的出手逻辑。

天雷是从高空落下。目标点先出现短暂预警,随后主雷击中地面,周围再出现错时落下的小雷。为了让闪电不像一根发光直线,雷电路径被拆成多段并加入随机偏移,形成分叉和折线。

图片

冰技能是一片从空中快速坠落的冰刺。它不走火球的水平飞行路线,而是在一定范围内生成多根细长冰锥,利用高度差和时间差制造“冰雨”效果。

图片

水弹则走抛物线。它从角色手边抛出,中途能看到弧线,落地后炸成大水花。这里不仅是动画轨迹不同,命中判定也要跟着投射物当前位置更新。

图片

陨星更强调落点威胁。先在地面显示红色预警区,然后让陨星从高处坠落,接触地面后触发大范围爆炸,并留下短暂焦痕。

图片

冰刺落地时也补了独立反馈,避免它们像穿过地面后直接消失。

图片

到这里,五种技能已经不是同一套特效换皮:火球适合蓄力直射,天雷从天而降,冰刺覆盖区域,水弹沿抛物线前进,陨星有明显预警和范围爆炸。数字键 1 到 5 或点击图标可以切换技能,鼠标左键负责释放;火球需要按住蓄力,其他技能会按各自逻辑触发。

为了让差异不只停留在截图里,技能参数也必须服务于操作。火球的优势是瞄准清楚,蓄力过程能让玩家决定何时出手;天雷几乎没有水平飞行时间,预警圈会先标出落点;冰刺覆盖更宽,还承担短暂冻结的控制作用;水弹用抛物线把飞行过程和直射火球区分开;陨星前摇更明显,换来更大的范围和更强的落地反馈。我希望切换技能时真的像换了一种打法,而不是只换了颜色。

这也改变了我给 Seed-2.1-pro 的反馈方式。我不再只说“再酷一点”,而是说明哪里不对:看不清飞行方向、落地没有重量、范围技能像单体、预警时间太短、连续释放后画面变卡。描述越接近玩家真正感受到的问题,模型越容易把要求落到速度、半径、持续时间、材质、粒子数量和生命周期这些具体变量上。

我还提出了一个有点“任性”的要求:技能不要冷却,我就是想连续放。这个需求很符合我最初“做游戏里的王”的想法,却立刻带来了性能问题。同屏技能一多,粒子、焦痕、闪电、冰刺和投射物会不断累积。Seed-2.1-pro 因此给每类临时对象补上生命周期,到时间就停止发射并清理;对高频对象增加数量护栏,避免连续释放后页面越来越慢。

图片

做到这里,我确认 Seed-2.1-pro 不只会在空文件里写第一版。需求变化以后,它仍能沿用前面的场景和接口,把新技能接进同一个项目,并处理无冷却连放带来的性能副作用。

第三步:第一版跑起来后,问题一个接一个冒出来

如果只截最终画面,这个项目会显得很顺。但实际过程里,最花精力的部分恰恰是一次次“不对劲”。3D 开发中的很多错误不会直接弹出一句清楚的提示,它可能只是画面太亮、火球穿过敌人、人物朝向反了,或者一把弓突然飞到天上。第一版代码只是起点,后面这些问题能否被定位和修好,决定了游戏能不能继续往下做。

第一个典型问题是火球爆白。粒子使用加法混合后,多个半透明亮色叠在一起,再叠加 Bloom 这种“让高亮区域向外泛光”的后期效果,中心区域直接糊成一团白光。单纯调暗颜色治标不治本。最后的处理是重新分配核心、焰舌和拖尾的材质混合方式,再调整 Bloom 的阈值和强度,让亮处仍然发光,但能看见火焰层次。

图片

第二个问题是高速火球穿透敌人。原先的碰撞只检查“这一帧火球的位置是否进入敌人半径”。当速度足够快时,上一帧还在敌人前面,下一帧已经跑到身后,中间虽然穿过了敌人,但两个采样点都不在碰撞圈里。Seed-2.1-pro 把判断改成线段扫掠:用火球上一帧和当前帧的位置组成线段,计算敌人中心到这段轨迹的最短距离。只要轨迹穿过碰撞范围,就判定命中。这个修复也让后面其他高速投射物有了可复用的办法。

第三个问题来自粒子从生成到回收的过程。某些粒子停止发射后又被错误重启,粒子库发现它没有挂在正常场景层级里,便提前释放了对象,最终表现为蓄力效果突然消失,或者命中后还有残留。处理时没有靠不断改延迟碰运气,而是检查粒子挂在哪里、何时停止产生新粒子、何时真正删除整个对象,把两个阶段分开处理。

图片

敌人加入后,错误变得更隐蔽。它明明进入了射击状态,却永远不放箭。检查状态切换逻辑后才发现:进入 shoot 时计时器被清零,而是否发射的判断放在计时器自增之后,原本用来识别“刚进入状态”的条件永远不会成立。最后增加一个“本轮是否已经射箭”的独立标志,一次射击周期只触发一支箭,状态结束后再复位。

还有一次,弓的位置飞到了很高的地方,箭的竖直速度也变成了离谱的大数。问题不在抛物线公式,而在角色横向翻转时使用了负缩放,程序用来包住整个模型的测量盒出现异常,算出的人物高度几乎为零。后续缩放系数因此放大了几百倍,连带把弓和箭的坐标一起放大。修复时改为读取各个骨骼关节在场景里的坐标,用它们计算实际身高和脚底位置。

这些问题看似分散,其实验证的是同一件事:模型能否读懂已经变复杂的旧项目,而不是每次都建议重写。Seed-2.1-pro 会先从运行时数据里找异常,比如粒子数量、世界坐标、动作权重、人物朝向点积和控制台报错,再修改最小范围的逻辑。对我来说,这比“第一次生成得很快”更重要,因为一个真正可玩的项目必然要经历返工。

第四步:让 Seed 读懂参考,把火柴人升级成真正会动的角色

技能越来越完整后,最显眼的短板变成了人物。最初的圆头火柴人只是为了快速验证移动和施法,敌人也不过是一个会走动的靶子。即使给敌人加一把弓,手臂仍然僵硬,拉弓更像弓自己在移动。

我先让 Seed-2.1-pro 给敌人补上基础形态:它会随机游走,停下来后转向主角,进入拉弓状态,再把箭按抛物线射向主角位置。

图片

图片

玩法已经有了,但人物表现离我想要的游戏还差一截。于是我给出 Three.js 官方动画示例作为参考,希望它不要继续用圆柱和球体硬拼人物,而是理解官方模型怎样使用骨骼动画。

Seed-2.1-pro 先没有直接改主项目,而是建立独立验证页,加载官方 Soldier.glb,检查模型包含的 Idle、Walk、Run 和 TPose 动画,以及骨骼数量和模型朝向。再用 Three.js 的动画播放器 AnimationMixer 播放走路动作,确认模型资源、版本和动画都正常后,才把它接回游戏。

图片

主角替换完成后,最直观的变化是人物不再像几何玩具。角色静止时播放 Idle,移动时切到 Walk,按住 Shift 奔跑时切到 Run。动作之间不是瞬间跳变,而是用权重淡入淡出过渡。

图片

图片

换模型并不意味着原来的玩法作废。火球蓄力位置被改到右手骨骼的世界坐标,释放时也从手部飞出,原来的命中、爆炸和伤害逻辑继续保留。

图片

敌人同样替换为 Soldier 的第二个实例。这里不能直接复用同一个场景对象,否则第二个角色会把第一个角色的骨骼关系“抢走”。Seed-2.1-pro 使用专门处理骨骼模型的 SkeletonUtils.clone 复制出独立角色,再为两边分别创建动画播放器。这样两个角色能共享动画片段和模型资源,却拥有各自的姿态、位置与状态。

图片

图片

敌人的拉弓动作又是下一道题。现成 Soldier 动画里没有“站立射箭”,不能只播放一个现成动作解决。我要求 Seed-2.1-pro 根据角色骨骼继续完善。它先找出左右肩、手臂、前臂和手部骨骼,再在基础待机姿态上叠加旋转:左手向前持弓,右手屈肘向后拉弦。拉弓进度从 0 到 1 时,姿态通过四元数的平滑插值逐渐过渡,弓的位置跟随左手在场景中的真实坐标,箭的起点则跟随右手与弓弦之间的位置。

图片

图片

这里还解决了一个很容易忽略的问题:模型本身的正面方向是负 Z 轴。如果仍按普通二维角色的朝向公式旋转,就会出现向前走却背对移动方向、敌人瞄准主角时身体反转的情况。最后统一使用 atan2(-x, -z) 计算水平转向角,并检查模型正面与实际移动方向是否重合。W、A、S、D 四个方向的结果都完全同向,才算方向真正修好。

从第 10 张到第 19 张图,可以清楚看到角色是怎样被逐步替换和修正的。Seed-2.1-pro 并不是把参考页面照抄过来,而是先理解参考里的模型、动画和骨骼使用方式,再把它们接入已有的技能、敌人和输入系统。这正是我希望它具备的能力:读懂我给的参考,然后继续完成我的项目。

第五步:特效能放还不够,我要的是一局真正能赢的游戏

做到双角色对战以后,画面已经像一款游戏,但如果敌人只是无限挨打,玩家也不会失败,那仍然只是技能展示器。我的目标从一开始就是“玩游戏虐敌人”,所以最后必须补齐一局游戏的开始、交战、胜利或失败,以及重新开始。

Seed-2.1-pro 给玩家和敌人分别加入生命值,并把伤害真正接到技能和箭矢上。火球、天雷、冰刺、水弹和陨星的伤害、范围与反馈不同;敌人箭矢命中主角后也会扣血。冰刺还会让敌人短暂冻结,既改变移动速度,也给玩家一个继续输出的窗口。

敌人 AI 保留“游走—发现目标—停步转向—拉弓—放箭—继续游走”的状态循环。它不会像专业动作游戏 Boss 那样判断地形和连招,但已经会主动反击,不再是站着不动的木桩。玩家如果只顾着看特效,同样可能被箭射倒。

图片

人物状态也需要收口。生命值归零后,角色不能继续移动和施法;敌人死亡后要停止 AI、清掉正在生成的攻击;已经飞出的技能、箭矢和粒子要在重开时统一清理。否则第二局会带着第一局的残留对象开始,甚至一开场就被上一局的箭命中。

在这个过程中还修复了一个很小但会直接打断重开的错误:动画混合器停止动作的方法写错了,调用了不存在的 stopAll()。改为正确的 stopAllAction() 后,角色动画、状态和场景对象才能在重置时一致恢复。

图片

最终界面增加了双方生命值、当前技能与操作提示。玩家进入页面后不用猜现在选中了什么,也能看到敌人还剩多少血。

图片

我还专门按一局正常操作的顺序检查体验。进入页面后先移动,确认人物朝向与按键方向一致;切换五种技能,确认图标高亮和实际释放内容对应;连续施法观察敌人扣血与受击;停下来让敌人完成一次拉弓和射箭,确认玩家也会掉血;把敌人打到零血,确认它停止行动并进入胜利结算;最后点击重新开始,检查双方生命值、位置、敌人状态、场上箭矢和残留粒子是否一起复位。任何一步只能“看起来差不多”,都可能在下一局暴露问题。

这里最容易被忽略的是重开。重新设置两条生命值并不够,敌人可能仍停留在死亡状态,动画混合器可能还保存旧权重,上一局的投射物可能继续飞,键鼠输入也可能被结算层挡住。Seed-2.1-pro 最后为对战状态、角色状态和临时对象建立统一复位入口,让重开不是刷新整个网页,而是真正在同一个场景里开始新一局。对一个准备公开试玩的 Demo 来说,这比再加一种技能更重要。

连续释放技能、敌人射箭、双方掉血,这些内容终于连成了一局完整战斗。

敌人生命值降到零后,页面显示胜利结果;点击按钮或按 R 键可以清空上一局状态并重新开始。到这一刻,我已经能进场、战斗、获胜,再开下一局,最初设想的游戏闭环终于成立了。

图片

点击“重新开始”后,双方血量、位置、敌人状态和场上临时对象都会回到初始状态。

图片

为了避免“看起来能玩,换一个操作顺序就坏”,最终还做了自动验收。检查范围不只是页面能否打开,而是覆盖五种技能是否造成伤害、冰冻是否生效、敌箭能否命中玩家、生命值归零后能否进入正确结算、重开后状态能否复位、临时对象是否回收、窗口尺寸变化是否正常,以及浏览器控制台是否出现异常。

这些检查不能证明它已经是一款成熟商业游戏,却能排除 Demo 最常见的假完成:按钮能点但没有伤害,画面有爆炸但对象一直泄漏,第一次能玩而第二次无法重开,或者只有固定窗口大小才正常。

第六步:直接试玩

我把公开版重新整理了一遍,保留游戏源码、模型与纹理资源、自动测试、最终截图和第三方许可说明。

项目已经开源:

图片

当前版本更适合使用键盘和鼠标的桌面浏览器,试玩时可以用 W、A、S、D 移动,按住 Shift 奔跑,移动鼠标改变瞄准方向;数字键 1 到 5 或点击底部图标切换技能,鼠标左键释放,火球需要按住蓄力。空格配合鼠标拖动可以旋转视角,滚轮负责缩放。打完一局后,点击重新开始或按 R 即可复位。

如果在线页面第一次打开后人物没有立刻出现,可以稍等模型文件加载完成再操作;公开版需要加载约 2 MB 的角色文件。想在本地修改,可以准备 Node.js 20 或更高版本,克隆仓库后依次运行:

npm install
npm start

浏览器打开 http://127.0.0.1:8123 即可。出现双方生命值、底部五个技能图标和场景中的两个角色,说明资源已经加载成功。

图片

最后:Seed-2.1-pro 真正帮我完成的,不只是一段代码

回头看这次过程,最初的想法其实非常任性:我在别人的游戏里被虐,就想自己做一个战场,让主角拥有五种技能,还要敌人会走、会瞄准、会拉弓、会还手,最后我能把它打败。

单个功能都不算新发明,难的是它们进入同一个项目后会互相影响:角色反向、投射物穿透、粒子残留、骨骼共享。

Seed-2.1-pro 这次让我满意的地方,是它始终沿着同一个目标往前走。火球不好看就继续改,玩法单调就补成五种技能,火柴人僵硬就按参考换成骨骼角色,最后再把生命值、胜负、重开和公开试玩接起来。

原始过程图保留了粗糙版本和真实错误。没有在第一版不理想时停下,也没有每次出错就推倒重来,而是继续读已有代码、参考和反馈,把同一个想法磨成了可以操作的成品。

图片

所以如果只问我这次 Seed-2.1-pro 表现怎么样,我的答案很明确:真的很可以。它完全实现了我这次的想法——让我拥有了一个自己制定规则、自己释放技能、最后能亲手击败敌人的 3D 战场。

现在,我终于不用在别人的游戏里一直挨打了。