如何修复 Javascript 游戏中的按键延迟?

How can I fix keydown delays in Javascript game?

我正在创建一个 javascript 贪吃蛇游戏,并且 运行 遇到按顺序按某些键太快的问题。例如,(向右走时)按向上箭头然后向左箭头键太快会使我的蛇完全转身并 运行 进入自身,而忽略向上箭头键的按下。是否有任何代码可以确保始终呈现任何按键?提前致谢。

let d = "RIGHT";
document.addEventListener("keydown", direction);
function direction(event) {
  let key = event.keyCode;
  if (key == 37 && d != "RIGHT" && d != "LEFT") {
    d = "LEFT";
  } else if (key == 38 && d != "DOWN" && d != "UP") {
    d = "UP";
  } else if (key == 39 && d != "LEFT" && d != "RIGHT") {
    d = "RIGHT";
  } else if (key == 40 && d != "UP" && d != "DOWN") {
    d = "DOWN";
  }
}

在单独的函数中:

if (d == "LEFT") snakeX -= box;
  if (d == "UP") snakeY -= box;
  if (d == "RIGHT") snakeX += box;
  if (d == "DOWN") snakeY += box;

您也可以通过转至 https://jssnake.glitch.me/ 并稍微尝试一下来查看此问题。

这里需要的是延迟按键的效果。您可以通过将最后按下的键存储在变量中并仅在蛇准备好转身时读取键来实现。

let pressedKey;

document.addEventListener("keydown", event => {
    pressedKey = event.keyCode;
});

我简要地查看了您的代码。您每秒渲染 10 次,因此如果您设法在该间隔内按下多个键,则会出现所描述的问题:

For example, (while going right) hitting the up arrow and then the left arrow key too fast will make my snake turn around completely and run into itself, ignoring the up arrow key press.

有两种可能的解决方案:

  1. 运行 渲染循环更快,因此在该间隔期间没有人可以同时按下两个键。
  2. 不要只存储最后一个键,而是自上次渲染调用以来按下的所有键。
  3. 避免半圈。

我认为解决方案 1 并不理想,您应该永不言败。那么让我们继续第 3 条(黑客),然后继续第 2 条(正确和干净的方式)。

避免半圈(选项 3)

这个小技巧并没有解决问题的根源,但它会让蛇的行为有点正确。蛇可以向 4 个方向移动,但它始终只能向两个方向转动。您可以使用双键控件来触发 CW/CCW 更改,例如

let currentDir = "RIGHT"; //note I renamed your d to currentDir
let nextDir = undefined;
document.addEventListener("keydown", direction);

function direction(event) {
  const key = event.keyCode;
  while (~currentDir) {}; //wait until the control function is finished
  switch (currentDir) {
    case "LEFT": nextDir = (key === 37 ? "DOWN" : (key === 39 ? "UP" : nextDir)); break;
    case "UP": nextDir = (key === 37 ? "LEFT" : (key === 39 ? "RIGHT" : nextDir)); break;
    case "RIGHT": nextDir = (key === 37 ? "UP" : (key === 39 ? "DOWN" : nextDir)); break;
    case "DOWN": nextDir = (key === 37 ? "RIGHT" : (key === 39 ? "LEFT" : nextDir)); break;
  }
}

//and later in the movement control function:
currentDir = undefined; //avoid overwriting nextDir during this update,
// i.e. the while-loop inside of direction() will wait
switch (tmp) {
  case "LEFT": snakeX -= box; break;
  case "UP": snakeY -= box; break;
  case "RIGHT": snakeX += box; break;
  case "DOWN": snakeY += box; break;
}
currentDir = nextDir;
nextDir = undefined;

四键版本的工作方式类似,您可以轻松地将其集成到您的代码中。关键是使用 currentDirnextDir 对,并在渲染调用之间的整个 0.1 秒时间内保持 currentDir 不变。但你的问题会留下来。如果您紧接着按 ↑ 和 ← 键,一条向右行进的蛇只会继续向上。

let currentDir = "RIGHT";
let nextDir = undefined;
document.addEventListener("keydown", direction);

function direction(event) {
  const key = event.keyCode;
  while (~currentDir) {}; //wait until the control function is finished
  switch (currentDir) {
    case "LEFT":
    case "RIGHT":
        nextDir = (key === 38 ? "UP" : (key === 40 ? "DOWN" : nextDir)); break;
    case "UP":
    case "DOWN":
        nextDir = (key === 37 ? "LEFT" : (key === 39 ? "RIGHT" : nextDir)); break;
  }
}

密钥缓冲区(选项 2)

正确解法更简单,但需要一个数组。它将自上次渲染调用以来按下的所有键存储在 queue.

keysPressed = [];
document.addEventListener("keydown", event => 
  keysPressed.push(event.keyCode); //enqueues the key pressed

按下两个或三个键,您几乎可以在 0.1 秒间隔内更新蛇的位置,在每一帧中应用一个有效的转弯。如果您能够使用命令快速填充缓冲区,这可能会导致蛇形移动延迟。尝试作为一项有趣的练习可能会很有趣。四键控制的移动功能如下所示:

{
  if (keysPressed.length > 0 {
    const key = keysPresses.shift(); //dequeues the oldest key
    //if there are more keys in the queue, they have to wait until next time
    switch (d) {
      case "LEFT":
      case "RIGHT":
          d = (key === 38 ? "UP" : (key === 40 ? "DOWN" : d)); break;
      case "UP":
      case "DOWN":
          d = (key === 37 ? "LEFT" : (key === 39 ? "RIGHT" : d)); break;
    }
  }

  switch (d) {
    case "LEFT": snakeX -= box; break;
    case "UP": snakeY -= box; break;
    case "RIGHT": snakeX += box; break;
    case "DOWN": snakeY += box; break;
  }
}