用另一个键按下时发生两次键事件

Key Event happening twice when pressed with another key

在我的游戏中,我使用事件侦听器 keyupkeydown 来跟踪按下了哪些键(this.input 是对下面 Input 函数的引用) :

$(document.body).on('keydown', this.input.keyPress.bind(this.input));
$(document.body).on('keyup', this.input.keyPress.bind(this.input));

一切都按预期进行,直到我同时按下 2 个键。左键将玩家向左移动,Shift 键开火。另外,它们工作正常。但是,如果同时按下并按住,则玩家会射击两次。

这是我用来处理输入的代码(为简洁起见删除了其他关键事件)

shootSpecial 应该只调用一次,因为 keyMap.shiftkeydown 上只有 true。一旦 keyup 被触发,它被设置为 falsekeyMap[key] = e.type === 'keydown';

var Input = function () {
  var keyMap = {};

  this.handleKeyPress = function () {
    if (keyMap.arrowleft === true) {
      game.player.moving.left = true;
    } else if (keyMap.shift === true) {
      game.player.shootSpecial();
    }
  };

  this.keyPress = function (e) {
    var key = e.key.toLowerCase();
    if (key === ' ') {
      key = 'space';
    }
    keyMap[key] = e.type === 'keydown';
    console.log(key, keyMap[key]);
    this.handleKeyPress();
  };
};

console.log 报告 leftshiftkeydowntrue 然后falsekeyup 上,所以我不太确定为什么它会触发两次 shootSpecial 事件。

var keyMap = {};
var handleKeyPress = function () {
  if (keyMap.arrowleft === true) {
    console.log('left');
  } else if (keyMap.shift === true) {
    console.log('shift');  
  }
};

var keyPress = function (e) {
  var key = e.key.toLowerCase();
  keyMap[key] = e.type === 'keydown';
  if (document.getElementById(key))
    document.getElementById(key).innerText = e.type === 'keydown';
  
  handleKeyPress();
}

document.addEventListener('keydown', keyPress);
document.addEventListener('keyup', keyPress);
LEFT:<div id="arrowleft">false</div>
SHIFT:<div id="shift">false</div>

正如您在 fiddle 中所见,一个事件被调用了两次。

您的问题是您使用相同的代码处理 keydown 和 keyup,结果是在按下 key2 的同时释放 key1 与按下 key2 的效果相同。

修复非常简单:根本不要在 keyup 事件上调用 handleKeyPress 函数。

var keyMap = {};
var handleKeyPress = function () {
  if (keyMap.arrowleft === true) {
    console.log('left');
  } else if (keyMap.shift === true) {
    console.log('shift');  
  }
};

var keyPress = function (e) {
  var key = e.key.toLowerCase();
  keyMap[key] = e.type === 'keydown';
  if (document.getElementById(key))
    document.getElementById(key).innerText = e.type === 'keydown';
  
  // Only call handleKeyPress if the key is pressed
  if (keyMap[key]) {
    handleKeyPress();
  }
}

document.addEventListener('keydown', keyPress);
document.addEventListener('keyup', keyPress);
LEFT:<div id="arrowleft">false</div>
SHIFT:<div id="shift">false</div>


添加一条建议以回应 OP 的评论。你的问题是你的 handleKeyPress() 没有关于键是被按下还是向上按下的信息,它也不知道甚至按下了哪个键,只知道在它被调用时哪些键是按下的。为了得到你的目标,我会做这样的事情:

var keyPress = function (e) {
  var key = e.key.toLowerCase(),
      isKeyDown = e.type === "keydown",
      output = key + (isKeyDown ? "-down" : "-up") + ": ";
  
  switch (key) {
    case "shift":
      if (isKeyDown) {
        output += "pew pew";
      } else {
        output += "doing nothing"
      }
      break;
      
    case "arrowleft":
      output += "updating walking_left";
      document.getElementById("walking_left").innerText = isKeyDown;
      break;
      
    default:
      output += "doing nothing";
  }

  console.log(output);
};

document.addEventListener('keydown', keyPress);
document.addEventListener('keyup', keyPress);
Walking left:<div id="walking_left">false</div>

如果您按下两个键,您将触发两个事件,无论您是否同时按下它们。 keyup 事件也是如此。所以 handleKeypress 函数总共被调用了四次。

顺序可能恰好是:

key        | event   | keyMap.arrowleft | keyMap.shift | effect
-----------+---------+------------------+--------------+----------
shift      | keydown |     false        |     true     | shoot
arrowleft  | keydown |     true         |     true     | moving.left=true
arrowleft  | keyup   |     false        |     true     | shoot
shift      | keyup   |     false        |     false    | nothing

所以在那种情况下你射击了两次。当然,情况并非总是如此,因为顺序可能会有所不同。

解决方案

将键作为参数传递给 handleKeyPress 函数以确保您只执行与该键对应的操作。

var Input = function () {
  var keyMap = {};

  this.handleKeyPress = function (key) {
    switch (key) {
    case 'arrowleft':
      game.player.moving.left = keyMap[key]; // also sets back to false
      break;
    case 'shift':
      if (keyMap.shift) game.player.shootSpecial();
      break;
    }
  };

  this.keyPress = function (e) {
    var key = e.key.toLowerCase();
    if (key === ' ') {
      key = 'space';
    }
    keyMap[key] = e.type === 'keydown';
    this.handleKeyPress(key); // pass key
  };
};