
你有没有过这种经历电脑断网Chrome 浏览器跳出一只像素霸王龙你百无聊赖地按空格跳仙人掌然后死在第三只鸟上越想越气又按一下空格重新开始。我就是这么入坑的。作为一个喜欢折腾脚本的人我很快产生了“歪念头”——能不能给这个谷歌 Chrome 恐龙小游戏写个外挂让霸王龙自动跳甚至让我想看它跑多远就能跑多远这篇文章就把我折腾“Chrome 恐龙小游戏外挂”的完整过程记录下来。不吹牛不引流就是纯技术拆解。内容包括这个游戏的内核是怎么跑的、为什么几个 JavaScript 命令就能改游戏逻辑、我写的三版外挂脚本分别怎么实现、以及实际操作中踩过的一堆坑。适合对浏览器脚本、Canvas 游戏实现、Chrome DevTools 感兴趣的人参考也适合把“游戏外挂”当成前端技术练习的入门者。1. 先想清楚要做什么外挂需求拆解与方案选型1.1 常见的“外挂”需求有哪些我一开始以为恐龙小游戏的外挂只有“自动跳跃”一种真正上手拆解后发现需求其实可以拆成好几类每一类对应着不同的技术切入点。自动跳跃检测前方障碍物位置在合适的时机触发霸王龙跳跃解决手残党的痛点。这个需求最主流玩家人群最大。无限复活与无敌让霸王龙撞到障碍物也不死或者说直接绕过碰撞检测逻辑。这类需求适合“我就想看看游戏能跑到多少分”的人。变速与加速把游戏速度调快或调慢既能制造变态难度也能把游戏降速分析每一帧的障碍物生成规律。清空障碍物一键抹除当前画布上的所有仙人掌和翼龙体验“无阻力跑酷”。刷分与修改距离直接修改计分变量让分数瞬间爆表满足晒截图的需求。锁定白天/黑夜模式正常游戏白天黑夜会交替有些人不喜欢画面闪变可以强制固定模式。自动化测试辅助把游戏变成一个天然的前端性能测试场通过脚本控制人物行为然后观察 Canvas 渲染帧率这个需求比较硬核但同样有价值。你要做“外挂”第一件事不是写代码而是先明确到底要解决哪一个需求。因为不同需求对应的改造点完全不同有的改方法有的改变量有的改循环逻辑混在一起容易乱。以我这次实践为例主线需求是“自动跳跃 无敌 刷分”这样既能跑得快又死不了还能快速验证效果。1.2 技术选型控制台注入、油猴脚本与扩展的取舍明确了需求第二个问题是脚本以什么形式注入到游戏里。我对比了三种主流方案各有各的适用场景。注入方式优点缺点适合场景DevTools 控制台手动注入零安装打开就能用调试方便每次刷新页面后需要重新执行代码量大时容易乱快速验证思路、临时使用油猴/Tampermonkey 等用户脚本自动注入可持久化带规则匹配chrome:// 页面默认拦截扩展脚本需要额外设置或绕行配合本地镜像页长期使用Chrome 扩展插件正规、能力最强可以做到按钮式控制开发门槛稍高chrome:// 页面同样受限想做成工具分发给别人用CDP 远程调试协议可完全接管浏览器能模拟按键、读取状态需要外部程序配合无法单独在页面内运行自动化测试、Python/Node 控制浏览器我做第一版时直接用的控制台。因为恐龙小游戏在 chrome://dino 页面里运行打开 DevTools 就可以直接访问页面上下文里的 JavaScript 对象效率最高。油猴脚本对 chrome:// 这类浏览器内部页面默认不生效扩展也需要额外权限属于进阶方案我在后文会专门讲怎么处理。这里先记住一个原则先控制台后自动化不要一上来就写一堆配置框架容易把简单的事搞复杂。2. 这个游戏到底是怎么实现的2.1 找到游戏本体与实例想要给游戏做外挂第一步不是写脚本而是找到游戏代码里的关键对象。Chrome 恐龙小游戏本质上是一个基于 Canvas 的 JavaScript 单机游戏所有逻辑都在浏览器里跑没有服务器校验这给了我们很大的操作空间。在 chrome://dino 页面里按 F12 打开 DevTools切到 Console 标签输入一行命令console.dir(Runner.instance_);这个Runner.instance_就是整个游戏的核心实例。它能被访问是因为游戏源码里有一个全局的单例引用恰好没有设置成不可访问这在许多早期 Canvas 小游戏里非常常见。运行后你会看到一个巨大的对象里面包含了霸王龙tRex、地平线系统horizon、距离计数器distanceRan、速度speed等等。我试过用Object.keys(Runner.instance_)去列所有的属性常见的直接可用的字段包括tRex霸王龙对象里面有跳跃状态、位置坐标、跳跃起始函数horizon整个场景系统包含云、障碍物、地面纹理horizon.obstacles当前画布上所有障碍物的数组currentSpeed/speed游戏当前速度distanceRan已经奔跑的距离直接对应分数setSpeed()运行时修改速度的方法checkForCollisions()碰撞检测方法找到这个对象等于拿到了游戏的控制台钥匙。2.2 帧循环、速度与碰撞检测理解游戏实现机制核心要看三个逻辑帧循环、速度计算、碰撞检测。帧循环用的是浏览器标准的requestAnimationFrame每一帧都会更新画面状态。游戏为了性能会根据当前速度动态调整每次更新的步长所以当速度数值变大时你会发现霸王龙的前进速度明显加快但帧率并没有变这种感觉就像跑步时步幅变大而不是脚步变快。速度计算很有意思。正常游戏里速度会随着奔跑距离缓慢增加这也是为什么越跑越难。Runner实例里的update()方法会按时间累计距离再换算成当前速度。我们可以直接调用setSpeed(30)强制塞一个很大的速度游戏就会立刻进入“疯狗模式”。碰撞检测的核心就是checkForCollisions()它遍历当前所有障碍物检查霸王龙和障碍物之间有没有发生矩形重叠。如果重叠了就触发死亡逻辑播放动画结束本局。这个逻辑非常典型很多 2D 小游戏都是同样的套路。哪天真要“无敌”最简单的办法就是让这个碰撞检测函数什么都不做。因为游戏逻辑全部在客户端没有后端做任何校验我们修改函数体不会被任何人拦。2.3 哪些属性改了就能生效为了不让你对着巨大的对象一脸懵我整理了一份“改造点速查表”都是实测过可以直接用的。目标和效果关键操作说明无敌Runner.instance_.checkForCollisions function(){}覆盖实例上的碰撞检测方法撞到障碍物也不会死变速Runner.instance_.setSpeed(50)调到 50 后游戏瞬间变快跳跃时机需要重新适应清障Runner.instance_.horizon.obstacles []清空当前所有障碍物数组画布上立刻干干净净刷分Runner.instance_.distanceRan 1000直接加距离分数跟着变但会被每帧更新覆盖需要循环执行强制跳跃Runner.instance_.tRex.startJump()让霸王龙立刻起跳不依赖按键强制蹲下Runner.instance_.tRex.setDuck(true)模拟按下蹲键可以躲过飞鸟锁定黑夜Runner.instance_.horizon.nightMode true强制夜晚模式画面色调变深看到这里你应该明白了对这类单机 Canvas 小游戏做外挂本质上不是“破解”而是“运行时修改 JavaScript 对象”跟调试普通前端项目几乎没区别。理解了这一点后面写脚本就是水到渠成的事。3. 手写脚本从“自动跳”到“定制挂”3.1 第一版直接绕开碰撞检测我先写了最暴力的“无敌版”代码逻辑极其简单。(function () { const runner Runner.instance_; if (!runner) { console.error(未找到游戏实例); return; } runner.checkForCollisions function () {}; console.log(无敌模式已开启); })();把这段代码粘贴到控制台立刻生效。霸王龙撞到仙人掌时不会再停下游戏会一直跑下去直到你手动刷新页面。这个脚本的核心在于覆盖了实例方法而不是修改原型方法。这里有个细节值得注意游戏每帧更新都会调用this.checkForCollisions()这里this指向的是Runner实例本身所以实例上的方法优先级高于原型链上的同名方法我们覆盖实例方法就能生效。但不要高兴太早第一版有个致命缺点。如果你在游戏已经 Game Over 之后运行这段代码场景里霸王龙可能已经倒地游戏不会自动恢复运行。正确姿势是先按空格开一局或者调用runner.restart()重新开始再注入脚本。还有一个隐藏问题页面刷新后变量全部重置之前打的“补丁”会全部消失。所以我一开始就建议开发阶段用控制台想要长期使用就要做持久化后文会讲。3.2 第二版自己判断该不该跳无敌模式虽然爽但少了“跑酷”的刺激感。我决定做一个更智能的版本自动跳跃。这需要读取障碍物位置判断距离再触发跳跃本质上是一个简单的 AI 决策。(function () { const runner Runner.instance_; if (!runner) { console.error(未找到游戏实例); return; } const trex runner.tRex; const horizon runner.horizon; setInterval(() { const obstacles horizon.obstacles; if (!obstacles.length) return; // 找到离霸王龙最近的障碍物 const next obstacles.reduce((a, b) (a.xPos b.xPos ? a : b)); const distance next.xPos - trex.xPos; // 满足距离条件且霸王龙不在空中时起跳 if (distance 120 distance 20 !trex.jumping !trex.ducking) { trex.startJump(); } }, 20); })();这段代码的核心逻辑是“找最近障碍物 - 算距离 - 跳”。每个参数都经过我的实测setInterval(..., 20)代表每 20 毫秒检测一次对应大约 50 帧的检测频率足够实时。distance 120是触发距离。速度较低时120 像素留出的反应时间很宽裕速度很高时最好把阈值提高到 150 左右否则跳得太晚容易撞上。distance 20防止霸王龙已经跟障碍物重叠时还在不停触发跳跃。!trex.jumping !trex.ducking避免跳跃过程中重复起跳导致动作卡顿。实测下来的效果是普通速度下基本能自动跳过所有仙人掌和翼龙跑几千分没有问题。但这个版本对“飞鸟低头”场景没有优化。正常游戏里翼龙飞得低时需要按蹲键而不是跳。我偷懒没有做蹲的逻辑因为跳跃的高度有时候也能擦着飞过去只是偶尔会被打中。如果你要完美版需要在检测到翼龙且高度较低时调用trex.setDuck(true)延迟几百毫秒后再设置setDuck(false)恢复站立。3.3 第三版变速、清障、刷分自动跳跃搞定后我继续往里面塞新功能最后做成了一个小工具箱。核心思路是把多个独立功能封装成函数需要哪个调哪个。(function () { const runner Runner.instance_; if (!runner) return; window.dinoHack { godMode() { runner.checkForCollisions function () {}; }, setSpeed(speed) { runner.setSpeed(speed || 60); }, clearObstacles() { runner.horizon.obstacles []; runner.horizon.obstacleTimer 0; }, addScore(score) { runner.distanceRan score || 1000; }, forceJump() { runner.tRex.startJump(); }, forceNight() { runner.horizon.nightMode true; } }; // 周期刷分因为 distanceRan 每帧都会被更新所以要不断追加 window.dinoHack.autoScore function (interval) { setInterval(() { runner.distanceRan 500; }, interval || 100); }; console.log(恐龙外挂工具箱已加载); })();这个版本里有一个我特别想强调的坑直接往runner.distanceRan赋值往往没有效果或者只闪一下就被重置。原因在于游戏每一帧都会在update()里重新计算距离把distanceRan当成一个基础变量来覆盖。想稳定刷分要么不断往上面“加”要么拦截游戏的距离更新逻辑前者简单粗暴后者复杂度高对咱们写外挂来说没必要。变速也一样。setSpeed(50)生效后游戏速度在新的一局开始时大概率会被重置。如果你想实现“每次开局都 50 迈”需要监听游戏的重置事件或者干脆用一个定时的setInterval循环强制设置速度。这种“抗重置”设计是外挂能否稳定工作的关键。4. 常见问题与排查技巧实录4.1 “改了没反应”到底卡在哪我做测试时最常遇到的情况是脚本贴进去控制台没有任何报错但游戏纹丝不动。这种问题多到我已经条件反射地按顺序排查了。第一确认页面是 chrome://dino。Chrome 新标签页会加载各种组件有些版本也会出现类似的小游戏入口但它的内部变量可能不是Runner.instance_。不要在错误的页面上浪费时间。第二确认游戏实例存在。在控制台输入Runner.instance_如果返回undefined说明页面还没初始化完成或者游戏没有开始。按一下空格开始游戏再试。第三确认代码执行时机。如果你在游戏已经 Game Over 后覆盖checkForCollisions此时霸王龙倒在地上不会自动站起来继续跑看起来就像“没反应”其实方法已经改了。此时调用Runner.instance_.restart()重新开局效果立刻出来。第四确认是否被游戏周期性还原。部分版本的游戏在调用restart()时会重建某些对象状态实例上的方法覆盖不会丢失但速度、距离、障碍物数组都会被重置。所以刷分脚本如果只执行一次肯定会被覆盖变速脚本只执行一次重开后速度也会归位。解决思路就是循环执行或者挂钩到对应的事件时机。4.2 Chrome 更新之后脚本失效Chrome 浏览器有过多次内部代码重构恐龙小游戏这个模块也在变化。我最早用的变量名和现在用的就有些许差异早期版本里碰撞检测函数是checkForCollisions这个到现在没变但一些内部属性如obstacleTimer的存放位置不同版本之间不一定相同。遇到脚本失效不要慌先打开 DevTools 查看Runner.instance_的真实结构看关键字段是否重命名然后按新字段名改写。这也是为什么我反复建议先把console.dir(Runner.instance_)打出来看一遍而不是死记硬背代码。游戏的实现在变调试思路不能变。4.3 想让脚本每次自动加载书签脚本与扩展控制台粘贴的缺点很明显每次刷新都要重新粘贴。我后续试了两种持久化方式。最简单的方式是“书签脚本”bookmarklet。把 JavaScript 代码压缩成一行保存为浏览器书签打开 chrome://dino 后点击书签即执行。示例javascript:(function(){if(Runner.instance_){Runner.instance_.checkForCollisionsfunction(){};}else{alert(未找到恐龙实例);}})();优点是无需安装任何工具缺点是每次开局仍然要手动点一下而且代码不能太长。Chrome 地址栏会过滤粘贴的 javascript 代码但收藏夹里点击执行是允许的实际测试可用。更进阶的方式是 Chrome 扩展。扩展的能力远强于书签但有一个很关键的限制Chrome 默认不允许扩展脚本在 chrome:// 开头的浏览器内部页面里运行。我折腾了一套方案思路是拉取恐龙游戏的离线版本放到本地静态服务器上然后在扩展的 manifest 里配置对应的域名权限再用 content script 注入自动跳跃脚本。这种方案稳定、可分发但已经绕了一圈严格来说改的是“互联网上的恐龙游戏镜像”不是 chrome://dino 本身。如果你只是自己玩书签脚本足够扩展适合想把工具分享给别人时再考虑。4.4 操作细节与踩坑经验这里整理几条我实际踩过的坑很多都是血的教训。不要在控制台把Runner.instance_整个对象覆盖掉。比如执行Runner.instance_ {}会导致所有内部方法失效页面直接卡死只能刷新。正确做法是修改这个对象上的属性或方法而不是替换整个对象。自动跳跃的检测间隔不要太短。我试过 5 毫秒的setIntervalCPU 占用明显变高而且跳跃指令可能在一帧里被触发多次导致动作异常。20 毫秒是性能与响应速度的平衡点。清空障碍物数组后游戏会在几百毫秒内立刻生成新的障碍物因为障碍物生成定时器并没有停止。想长时间保持清空必须同时把obstacleTimer改成一个很大的值或者也用一个定时器不停清空。刷分时不要一次加太多。distanceRan 999999999这样的操作可能导致记分板渲染溢出表现为数字变成诡异的字符串或者画面卡顿。用循环小步追加更稳定。部分脚本需要在游戏开始后运行。Runner.instance_.restart()可以在任何状态下调用但某些内部状态比如horizon的初始化只有在游戏启动时才会完整创建。不确定时就先按空格开一局。5. 进阶玩法从外挂到自动化测试5.1 做成 Chrome 扩展的具体步骤如果你想把这套脚本扩展成真正的浏览器插件我可以给你一个最小可运行的结构。这里假设你已经把恐龙小游戏跑在一个允许扩展注入的页面里比如本地开发的镜像站。扩展根目录下创建三个文件manifest.json{ manifest_version: 3, name: Dino Auto Runner, version: 1.0, content_scripts: [ { matches: [http://localhost/*], js: [content.js] } ] }content.js(function () { const checkAndInject () { if (window.Runner Runner.instance_) { Runner.instance_.checkForCollisions function () {}; console.log(扩展注入成功); } }; window.addEventListener(load, checkAndInject); setInterval(checkAndInject, 1000); })();popup.html可以做一个开关按钮通过chrome.tabs.sendMessage与 content script 通信这里不再展开。关键在于提取了“加载即监听、定时检测、注入后保持”这个稳定的生命周期逻辑。扩展的 content script 本质上还是在页面上下文里修改对象跟前文的原理一致只是触发时机和持久化由浏览器来保证。5.2 用 CDP 远程控制浏览器还有一个更底层的路子不需要在页面里执行代码而是从外部通过 Chrome DevTools ProtocolCDP控制浏览器模拟按键、执行脚本、读取日志。以前我见过有人用 VBA 通过 CDP 操控 Chrome 做自动化表格填报原理完全一样只是当初那套玩法被用在了办公自动化上。用浏览器自动化库来做代码量会小很多。比如 Node.js 环境中使用 puppeteer 连接一个带远程调试端口的 Chrome 实例打开恐龙游戏页面然后用page.keyboard.down(Space)模拟按键再通过page.evaluate读取页面里的障碍物信息做闭环判断。这套方案的优点是不污染页面代码缺点是依赖外部进程不适合纯休闲场景。无论哪种方式核心思路都是一样的游戏的运行状态在浏览器内存里谁能读取并修改这些状态谁就掌握了“控制权”。这也是自动化测试、爬虫、辅助脚本的共同底层逻辑。5.3 把“外挂”变成学习工具往回看这次折腾最大的收获不是刷了多少分而是我借这个机会把 Canvas 游戏开发里帧循环、碰撞检测、对象生命周期这些概念都实际摸了一遍。平时看文档觉得枯燥但当你发现改一个函数就能让霸王龙不死时你会忍不住去研究它旁边的结构体里还有什么好玩的。我建议你试试把自动跳跃脚本改造成“性能分析器”比如定期记录Runner.instance_.currentSpeed和distanceRan看游戏速度的增长曲线。你会发现它并不是线性的而是阶梯式上升到了某个距离后触发夜间模式速度再加一档。这种细节光靠看代码很难体会只有亲手打印日志才能建立起直观印象。也可以把它改成“无障碍辅助”。比如在检测到前方有障碍物时通过控制台输出一条可视化提示或者用 Web Audio API 播放一个短音。这样的思路做出来的工具可以帮助视障玩家或者单纯想闭眼听声音的玩家。外挂不只有作弊一个落点把技术能力用在正向场景里会更有成就感。最后再分享一个小技巧如果你经常玩这个游戏我建议把常用的脚本压缩成一行存在本地笔记里不要每次上网搜索占用时间。我现在的用法是打开 chrome://dino按 F12粘贴一行无敌代码然后调setSpeed(30)看霸王龙狂奔。等哪天电脑没有网络我又能对着这个像素小恐龙玩一下午。