尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Claude-of-Duty核心引擎揭秘:固定步长帧循环、事件总线与确定性RNG的Three.js设计模式

Claude-of-Duty核心引擎揭秘:固定步长帧循环、事件总线与确定性RNG的Three.js设计模式 Claude-of-Duty核心引擎揭秘固定步长帧循环、事件总线与确定性RNG的Three.js设计模式【免费下载链接】Claude-of-DutyA Call of Duty-quality FPS in Three.js, built from a single prompt.项目地址: https://gitcode.com/gh_mirrors/cl/Claude-of-DutyClaude-of-Duty 是一款用 Three.js WebGL2 打造的《使命召唤》级网页第一人称射击游戏FPS全部 5.5 万行代码、11 个子系统没有一张美术贴图、没有一段音频文件全部由代码在加载时程序化生成。比无素材生成更值得普通读者学习的是它的核心引擎设计固定步长帧循环、事件总线、确定性 RNG三大模式。它们解决的是所有游戏开发者迟早都会遇到的问题——帧率抖动、模块耦合、结果不可复现。一个浏览器里的 FPS先认识 Claude-of-Duty项目基于 Three.js r180唯一运行时依赖就是three。游戏运行在一个约 120×120 米的市场街景中包含renderHDR 管线、级联阴影、GTAO、TAA、运动模糊physics手写物理引擎BVH 加速结构、扫掠胶囊体角色控制器、布娃娃weapons / ai / fx / audio弹道、敌人 AI、GPU 粒子、Web Audio 合成音效子系统清单见 README.md。引擎的宪法是一份仅 200 来行的契约文档 ARCHITECTURE.md——所有 AI Agent 协作开发前必须读它这本身就是这个项目最有意思的地方。固定步长帧循环物理为什么不能跟着帧率走问题帧率不固定物理就会漂移网页里requestAnimationFrame的帧间隔是浮动的60Hz、144Hz、卡顿掉帧都可能出现。如果物理更新直接用dt本帧耗时同一颗子弹在 60fps 和 144fps 下会打出完全不同的弹道玩家角色甚至会贴墙穿模。Claude-of-Duty 的解法是经典的固定步长累加器模式实现在 src/core/engine.js每帧 1. 累计本帧真实耗时到累加器 _accum 2. while (_accum FIXED_DT) → 执行 N 次固定步长物理 3. 剩余残差保留留给下一帧关键参数集中定义在 src/core/config.js常量值含义PHYSICS_HZ120物理每秒钟精确执行 120 次FIXED_DT1/120每个物理步的固定时长MAX_SUBSTEPS8单帧最多补 8 步防死亡螺旋两个细节非常实用死亡螺旋保护卡顿一帧后欠下的物理债如果无上限追赶会越追越卡。引擎在达到MAX_SUBSTEPS上限后直接丢弃积压if (steps MAX_SUBSTEPS) this._accum 0宁可牺牲一点模拟精度也保证不雪崩见 src/core/engine.js。dt钳制到 0.1 秒切后台、打断点调试回来时不会让角色瞬移见 src/core/engine.js。渲染用 alpha 插值补帧物理是 120Hz但渲染帧率可能是 60、144 或任何值。如何让画面顺滑引擎在每帧结束算出一个alpha _accum / FIXED_DT0~1 之间的小数代表当前时刻处于最近两个物理步之间的进度。渲染系统用这个 alpha 在两个物理位置之间插值角色移动就永远丝滑。实际使用例子物理子系统在 src/physics/index.js 读取ctx.time.alpha做位置插值第一人称相机在 src/player/camera.js 用同一机制平滑视角。这套elapsed / dt / fixed / alpha时间字段统一挂在引擎上下文上任何子系统都能取到。事件总线11 个子系统如何不打架一条硬规则禁止跨子系统 import11 个子系统渲染、物理、武器、特效、AI……如果互相import依赖图会缠成一团。引擎约定子系统之间绝不直接 import只通过运行时注册表ctx.get(id)拿对象注册表会按deps声明做拓扑排序决定初始化顺序见 src/core/registry.js。跨系统通信走 EventBus更松耦合的协作走事件总线。EventBus只有 40 来行支持on / once / off / emit见 src/core/registry.js。它有两个值得抄的设计on()直接返回退订函数子系统在dispose()时一行清理所有监听不泄漏emit时先拷贝监听者集合再逐个调用并 try/catch监听者可以安全地在回调里退订一个监听者抛错也不会打断其他监听者。项目把跨系统事件做成了固定词汇表完整契约写在 ARCHITECTURE.md 的事件表中常用的有事件含义发射方 → 接收方weapon:fire开火含起点、方向、随机种子weapons → fx / audio / physicsbullet:impact子弹命中点、法线、材质physics → fx / audiodamage:dealt造成伤害是否爆头、是否击杀ai / physics → ui / playerplayer:land/player:footstep落地、脚步player → audio脚步声按材质换音色explosion爆炸任意 → physics / fx举个实际链路武器发射器发出weapon:fire见 src/weapons/index.js弹道飞行后物理系统检测到命中并发出bullet:impactsrc/physics/index.js特效系统订阅它生成火花与贴花src/fx/index.js音频系统根据撞击面材质混凝土/金属/木头……合成不同音效。武器模块从头到尾不知道特效怎么画、音效怎么合成——只发事件不管谁听。确定性 RNG让随机可以逐字节复现为什么游戏里禁止 Math.random()ARCHITECTURE.md的硬规则第 4 条游戏逻辑和画面中禁止Math.random()一切随机必须走ctx.rng。原因是这个项目有一个像素级验证闸门tools/baseline.mjs 用固定帧预算截图 → tools/imagediff.mjs 逐像素比对任何像素变化就报错。如果随机数不可复现这个闸门永远会误报。于是引擎内置了一个xoshiro128**伪随机数生成器实现在 src/core/rng.js一个 32 位种子经 SplitMix32 扩散成 4 个状态字提供float() / range() / int() / gauss() / disc()等游戏常用分布——disc()专门生成单位圆盘内的均匀点用于子弹散布和粒子喷射fork()派生独立随机流每个子系统从主种子 fork 出自己的流武器散布和粒子抖动互不干扰任何一方加多少次随机调用都不会打乱另一方的序列。确定性种子在启动时注入?capture1截图模式下使用固定种子0x5eed1234否则用系统随机种子见 src/core/engine.js。这就是为什么优化前后两次运行、全部 11 张截图能做到0 像素差异见 README.md 的 Performance 一节。三个模式如何串成一条完整管线看启动流程 src/main.js 就能把三条线索串起来注册表拓扑排序11 个子系统按依赖声明排序初始化注册顺序无关紧要预热prewarm首帧前把每个子系统能产生的材质/着色器全部编译一遍src/core/prewarm.js。项目实测没有这一步游戏中途会懒编译 86 个着色器程序单帧卡 3.1~3.9 秒——而预热之所以能证明像素中性靠的正是确定性 RNG 固定步长循环让输出可比对帧循环驱动输入 → 固定步长物理0~8 次→ 变步长 update → lateUpdate → 渲染 → 输入收尾事件总线贯穿其间传递状态。一句话总结固定步长保证物理正确事件总线保证模块解耦确定性 RNG 保证一切可验证。三者叠加才让这个由 AI Agent 舰队并行开发的项目能建立逐像素门禁的质量防线。新手可以立刻借鉴的清单 写自己的游戏循环时先把物理频率固定本例 120Hz渲染用 alpha 插值对齐 给单帧最大追帧数设上限防止卡顿雪崩 跨模块通信用事件 固定事件词汇表词汇表写进文档并随代码一起评审 把随机数收敛到统一入口fork()给每个模块独立流为日后复现/回归测试留后路 把可调参数集中到一份 config本项目是 src/core/config.js含四级画质预设禁止魔法数字散落各处。这套模式不依赖 Three.js任何一个需要物理稳定 模块解耦 结果可复现的前端项目都能直接套用。【免费下载链接】Claude-of-DutyA Call of Duty-quality FPS in Three.js, built from a single prompt.项目地址: https://gitcode.com/gh_mirrors/cl/Claude-of-Duty创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表