如何在Javascript中防止多次按键

3

我是JS的初学者,正在开发一个游戏项目,我的项目是贪吃蛇游戏。一切都很顺利,只是当我快速按下多个键时,蛇就会死亡,因为(我认为)这是一种碰撞。这就是为什么我想要禁用多次按键,以尝试解决这个问题。 我的代码:

var Snake = function()
{
  //this is the direction table; UP, RIGHT, DOWN, LEFT
  this.directions = [[0, -1], [1, 0], [0, 1], [-1, 0]];
}

function onKeyDown(event)
{
  if (gameover)
    tryNewGame();
  else 
  {
    if (event.keyCode == 37 || event.keyCode == 65)
    {
      if (snake.direction != 1)  
        snake.direction = 3;
    } 
    else if (event.keyCode == 38 || event.keyCode == 87) 
    {
      if (snake.direction != 2) 
        snake.direction = 0;
    } 
    else if (event.keyCode == 39 || event.keyCode == 68) 
    {
      if (snake.direction != 3) 
        snake.direction = 1;
    } 
    else if (event.keyCode == 40 || event.keyCode == 83) 
    {
      if (snake.direction != 0)
        snake.direction = 2;
    }   
  }
}

我花了一分钟才意识到你在询问多个同时按键。这很重要。 - isherwood
你可以在同一个按键的 keyup 事件之前锁定输入。 - isherwood
1
可能是如何使用JavaScript检测同时按下多个键?的重复问题。 - isherwood
4个回答

1
问题可能是在蛇的形状更新之前方向变化了两次,因此这两次方向变化中的第一次实际上被忽略了。
克服这个问题的方法是将方向变化缓冲到队列中(作为数组实现)。
因此,在您的键事件处理程序中,您不会这样做:
if (snake.direction != 1)  
    snake.direction = 3;

但是,相反:
if ((snake.queue.length ? snake.queue[0] : snake.direction) != 1) 
    snake.queue.unshift(3);

这个队列应该在Snake构造函数中初始化:

this.queue = [];

当你更新蛇的位置(在时间间隔内),如果队列中有东西,你需要消耗它:
if (snake.queue.length)
    snake.direction = snake.queue.pop();
// Now apply this direction:
//   ... this would be code you already have...

您可以设置此队列大小的最大值,因为如果用户按键速度比蛇更新速度更快,这可能会变得尴尬。


非常感谢,你帮了我很多 =) - Zsolti

0

你可以在按键功能上使用setTimeout函数,像这样...

编辑:2000毫秒只是一个示例时间。您可以将其设置为每半秒500毫秒或任何其他毫秒数。

setTimeout(
function onKeyDown(event)
{
  if (gameover)
    tryNewGame();
  else 
  {
    if (event.keyCode == 37 || event.keyCode == 65)
    {
      if (snake.direction != 1)  
        snake.direction = 3;
    } 
    else if (event.keyCode == 38 || event.keyCode == 87) 
    {
      if (snake.direction != 2) 
        snake.direction = 0;
    } 
    else if (event.keyCode == 39 || event.keyCode == 68) 
    {
      if (snake.direction != 3) 
        snake.direction = 1;
    } 
    else if (event.keyCode == 40 || event.keyCode == 83) 
    {
      if (snake.direction != 0)
        snake.direction = 2;
    }   
  }
},
2000ms
);

1
似乎2秒的锁定时间会使游戏无法玩。更短的超时时间会变得不可靠。 - isherwood
2000毫秒是任意的,你可以设置任何你想要的时间限制。 - jtylerm
这里有一个类似的堆栈问题/答案:https://dev59.com/9G435IYBdhLWcg3wyzaa - Vbudo
无论时间值是多少,都会出现问题。它要么锁定游戏,要么不足以防止按键重复(或两者兼而有之)。 - isherwood

0

如此处所述,对事件进行限流将非常有效。以下是一个简单的类,可作为您的节流器:

class CooldownTimer {
  constructor(time) {
    this.cooldownTimeout = null
    this.cooldownTime = time
    this.startedAt = null
  }

  isReady = () => {
    return !this.cooldownTimeout
  }

  start = () => {
    if (!this.cooldownTimeout) {
      clearTimeout(this.cooldownTimeout)
    }

    this.startedAt = Date.now()
    this.cooldownTimeout = setTimeout(() => {
      this.cooldownTimeout = null
    }, this.cooldownTime)
  }
}

你需要在某个地方定义一个 CooldownTimer,可能是在绑定事件的地方:

let keyPressCooldown = new CooldownTimer(200)

然后您可以在事件代码中使用它:

function onKeyPress(event) {
  if (keyPressCooldown.isReady()) {
    console.log('key pressed')
    keyPressCooldown.start() // Do not forget to start the cooldown here
  }
}

0

我总是建议使用防抖节流函数来处理这种情况。毫无疑问,它是最好的。

Lodash有一个体面的实现方法_.debounce_.throttle。同时,请确保阅读this article以充分理解相关概念。

基本思路是在第一次调用后,你会“冻结”事件处理程序一小段时间。它仍然被调用并接收事件,但没有任何效果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接