Google Chrome浏览器是我非常喜欢使用的出色的网络浏览器。 几天前,当我的互联网连接中断并且正在浏览器上显示此屏幕时,我正在一个Web项目上工作。

您可能已经知道此屏幕上存在一个隐藏的游戏,可以通过按键盘上的空格键, 向上键或向下键来激活该游戏。
游戏真的很简单,但是玩起来很有趣。 基本上,它是一种霸王龙恐龙,它不断地奔跑,应该避免跳跃或躲避撞到障碍物。

我已经有一段时间没有玩这个游戏了,所以我没有尝试固定互联网连接,而是决定尝试一下,并尝试打破我的最佳记录,即大约5500。当我因打铃而分心时,我得到了将近4800分电话,然后我撞到一棵大仙人掌树,迷路了。
我感到沮丧,但后来我决定重试并打破自己的记录……不是通过再次玩,而是通过编写用于玩游戏的JavaScript代码。 在本文中,我将解释我是如何做到的。
计划
基本上,我们需要弄清楚如何:
1.模拟键盘事件,特别是向上和向下按键事件。
2.检测前方的障碍物。
3.恐龙即将撞到障碍物时跳/鸭。
4.让恐龙得分超过6000分。
模拟键盘事件
这部分很简单,游戏会监听文档对象上的键盘事件,因此我们需要分派一个事件,该事件模拟向上箭头的按键来使恐龙跳跃,而事件则模拟一个向下箭头键的事件来使恐龙鸭。
函数jump(){
var e = new Event('keydown');
e.keyCode = 38; //向上箭头键的keyCode为38
document.dispatchEvent(e);
}
函数duck(){
var e = new Event('keydown');
e.keyCode = 40; //向下箭头键的keyCode为40
document.dispatchEvent(e);
}
除keyCode部分外,两个函数都相同,因此我们可以按以下方式重构代码:
const dispatchKeyEvent = function(keyCode){
var e = new Event('keydown');
e.keyCode = keyCode;
document.dispatchEvent(e);
}
const jump = function(){
dispatchKeyEvent(38); //向上箭头键的keyCode为38
};
const duck = function(){
dispatchKeyEvent(40); //向下箭头键的keyCode为40
};
现在我们已经能够模拟键盘事件,我们需要检测即将到来的障碍。
检测障碍
这部分也很容易,因为我们可以简单地从游戏单例对象访问障碍物数组:
const障碍= Runner.instance_.horizon.obstacles;
此阵列中的第一项是即将到来的障碍。 以下代码将其位置记录到控制台:
(函数tick(){
const障碍= Runner.instance_.horizon.obstacles;
如果(obstacles.length){
console.log(obstacles [0] .xPos);
}
requestAnimationFrame(tick);
}());
window.requestAnimationFrame()
方法告诉浏览器您希望执行动画,并请求浏览器在下一次重绘之前调用指定的函数来更新动画。 该方法将重画之前要调用的回调作为参数。
如果要了解有关requestAnimationFrame
更多信息,请单击此处。
我们将使用requestAnimationFrame
在每次浏览器重绘时执行代码,但是如果游戏暂停或游戏结束,我们不希望执行代码,因此我们将检查该代码:
const game = Runner.instance_;
(函数tick(){
/ *如果游戏未运行,则不执行任何操作* /
如果(game.crashed || game.paused){
返回requestAnimationFrame(tick);
}
const障碍= game.horizon.obstacles;
如果(obstacles.length){
console.log(obstacles [0] .xPos);
}
requestAnimationFrame(tick);
}());
跳,鸭…鸭,跳!
如果恐龙与下一个障碍物之间的距离小于25个像素,让我们修改代码以进行跳转。
注意:数字25只是一个任意值,我们稍后将使用更准确的数字,但现在我们仅使用25。
每个对象的水平原点位于对象的左侧,因此我们需要减去恐龙的宽度才能获得正确的值。
(函数tick(){
/ *如果游戏未运行,则不执行任何操作* /
如果(game.crashed || game.paused){
返回requestAnimationFrame(tick);
}
const障碍= game.horizon.obstacles;
如果(obstacles.length){
const tRex = game.tRex;
const tRexWidth = tRex.config.WIDTH;
const障碍=障碍[0];
如果(obstacle.xPos-tRex.xPos-tRexWidth <= 25){
如果(!tRex.jumping){
跳();
}
}
}
requestAnimationFrame(tick);
}());
现在,T-Rex仅在前方有障碍物时才跳跃,但它不知道如何处理翼手龙。
翼手龙的问题在于它们并非都以相同的高度飞行,其中一些飞得很低,因此恐龙需要跳下,而其他的则飞中层所以恐龙需要躲避(虽然可以跳,但更安全)鸭子)和其他人飞得太高,以至于恐龙无法直接进入它们的下方。

让我们通过检测障碍物的垂直位置并基于该位置来确定恐龙应采取的动作: 跳跃 , 躲避或不采取任何措施来解决该问题。
通过在控制台中进行浏览,我们可以发现中级飞行翼手龙的垂直位置距离canvas元素的顶部75像素。
如果障碍物的yPos为75像素,则说明它是中级飞行翼手龙,因此恐龙应该躲避。 对于地面上的障碍物, yPos会大于75像素,然后恐龙应该跳跃。 否则,恐龙应该忽略障碍物并继续奔跑。
(函数tick(){
/ *如果游戏未运行,则不执行任何操作* /
如果(game.crashed || game.paused){
返回requestAnimationFrame(tick);
}
const障碍= game.horizon.obstacles;
如果(obstacles.length){
const tRex = game.tRex;
const tRexWidth = tRex.config.WIDTH;
const障碍=障碍[0];
如果(obstacle.xPos-tRex.xPos-tRexWidth <= 25){
如果(obstacle.yPos> 75){
如果(!tRex.jumping){
跳();
}
}其他
如果(obstacle.yPos === 75){
如果(!tRex.ducking){
鸭();
}
}
}
}
requestAnimationFrame(tick);
}());
甜! 现在,霸王龙知道什么时候该跳,什么时候该死,什么时候什么也不做,但是有一个问题……一旦它下降,它就不会再站直了。
在我们当前的实现中, jump和duck函数触发一个keydown事件,但是它们从不触发后续的keyup事件。 这就像按住箭头键或向下箭头键一样。 我们需要通过在每个keydown事件之后触发一个延迟的keyup事件来纠正此问题。

让我们修改函数,以在调度keydown事件后300毫秒后触发keyup事件。
const dispatchKeyEvent = function(eventName,keyCode){
var e = new Event(eventName);
e.keyCode = keyCode;
document.dispatchEvent(e);
}
const SimulationKeyPress = function(keyCode){
dispatchKeyEvent('keydown',keyCode);
setTimeout(function(){
dispatchKeyEvent('keyup',keyCode);
},300);
}
const jump = function(){
SimulationKeyPress(38); //向上箭头键的keyCode为38
};
const duck = function(){
SimulationKeyPress(40); //向下箭头键的keyCode为40
};
恐龙现在能够避免正确地碰到所有障碍,但是,当速度提高时,事情变得非常混乱。 T-Rex不能勉强获得1000分,而我们需要获得6000分以上。
现在是时候诉诸物理定律了。
完美的得分
对于每个障碍物,我们将使用弹丸运动方程式找出恐龙需要跳跃以避免撞到障碍物的速度( Vy )。 根据弹丸运动方程:

因此,我们将其翻译为JavaScript:
(函数tick(){
/ *如果游戏未运行,则不执行任何操作* /
如果(game.crashed || game.paused){
返回requestAnimationFrame(tick);
}
const障碍= game.horizon.obstacles;
如果(obstacles.length){
const tRex = game.tRex;
const tRexWidth = tRex.config.WIDTH;
const障碍=障碍[0];
const barrierHeight =障碍物[0] .typeConfig.height;
const引力= game.config.GRAVITY;
const jumpVelocityY = Math.sqrt(2 *重力* barrierHeight);
如果(obstacle.xPos-tRex.xPos-tRexWidth <= 25){
如果(obstacle.yPos> 75){
如果(!tRex.jumping){
跳();
tRex.jumpVelocity = -jumpVelocityY;
}
}其他
如果(obstacle.yPos === 75){
如果(!tRex.ducking){
鸭();
}
}
}
}
requestAnimationFrame(tick);
}());
这样,恐龙将做出一个足以越过障碍的跳跃。

不过,跳到障碍物的确切高度还不够安全,我们需要添加更多像素才能保持安全。
const安全= 15;
const barrierHeight =障碍物[0] .typeConfig.height;
const jumpHeight =障碍物高度+安全性;
一旦提高速度,安全裕度将无用,所以让它与游戏速度成正比:
const安全= 2 * game.currentSpeed;
我们的代码仍未完成,我们确实确定了所需的跳跃速度,但我们不知道确切的跳跃时间……还记得那些任意的25个像素吗?
回到弹丸运动方程,可以计算出恐龙在空中飞行时所经过的距离:

我们假设恐龙将以60度角跳动,因此在JavaScript中,上述等式为:
const jumpAngle = 60 * Math.PI / 180;
const jumpVelocity = jumpVelocityY / Math.sin(jumpAngle);
const jumpDistance = Math.pow(jumpVelocity,2)* Math.sin(2 * jumpAngle)/重力;
我们将调整如何计算jumpDistance
以包括我们的safety
度,因此最终而完整的代码应如下所示:
const dispatchKeyEvent = function(eventName,keyCode){
var e = new Event(eventName);
e.keyCode = keyCode;
document.dispatchEvent(e);
}
const SimulationKeyPress = function(keyCode){
dispatchKeyEvent('keydown',keyCode);
setTimeout(function(){
dispatchKeyEvent('keyup',keyCode);
},300);
}
const jump = function(){
SimulationKeyPress(38); //向上箭头键的keyCode为38
};
const duck = function(){
SimulationKeyPress(40); //向下箭头键的keyCode为40
};
const game = Runner.instance_;
(函数tick(){
/ *如果游戏未运行,则不执行任何操作* /
如果(game.crashed || game.paused){
返回requestAnimationFrame(tick);
}
const障碍= game.horizon.obstacles;
如果(obstacles.length){
const tRex = game.tRex;
const tRexWidth = tRex.config.WIDTH;
const障碍=障碍[0];
const barrierWidth =障碍物[0] .width;
const barrierHeight =障碍物[0] .typeConfig.height;
const引力= game.config.GRAVITY;
const安全= 2 * game.currentSpeed;
const jumpHeight =障碍物高度+安全性;
const jumpVelocityY = Math.sqrt(2 *重力* jumpHeight);
const jumpAngle = 60 * Math.PI / 180;
const jumpVelocity = jumpVelocityY / Math.sin(jumpAngle);
const jumpDistance =安全+ Math.pow(jumpVelocity,2)* Math.sin(2 * jumpAngle)/重力;
如果(obstacle.xPos-tRex.xPos-tRexWidth <= 0.5 *(jumpDistance-barrierWidth)){
如果(obstacle.yPos> 75){
如果(!tRex.jumping){
跳();
tRex.jumpVelocity = -jumpVelocityY;
}
}其他
如果(obstacle.yPos === 75){
如果(!tRex.ducking){
鸭();
}
}
}
}
requestAnimationFrame(tick);
}());
打开您的Chrome开发工具,然后将代码粘贴到控制台中,然后开始游戏,并像专业人士一样欣赏霸王龙的表演!
挑战
我运行了此脚本,以查看恐龙是否能够打破我的5500分的得分,但实际上它的得分超过了30,000分。 我试图打破这个分数,但我不能……可以吗?
免责声明
我并不是说恐龙永远不会输。 它会尽力而为,但是最终它将遇到障碍并失败。 无论如何, 永不丢失不是目标。