Key Event происходит дважды при нажатии другой клавиши

В моей игре я использую прослушиватели событий keyup и keydown для отслеживания нажатых клавиш (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.shift является только true на keydown. После срабатывания keyup устанавливается значение false: keyMap[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 сообщает, что left и shift равны true на keydown, а затем оба false на keyup, так что я не совсем уверен, почему событие 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>

Как видно по скрипке, одно событие вызывается дважды.


person Carl Markham    schedule 07.10.2016    source источник
comment
Не могли бы вы разместить фрагмент, демонстрирующий ошибку на jsfiddle? Тогда будет намного проще отследить вашу проблему. Чтение вашего кода, который вы опубликовали, очень затрудняет решение проблемы, поскольку в нем явно отсутствует какая-то логика (например, нет кода, который устанавливает значения keyMap обратно в false при отпускании ключа).   -  person AmericanUmlaut    schedule 07.10.2016
comment
В процессе добавления одного сейчас   -  person Carl Markham    schedule 07.10.2016
comment
Неважно, какой ключ я использую, событие все равно запускается дважды. (Я подумал, может быть, моя клавиша Shift была немного хитрой)   -  person Carl Markham    schedule 07.10.2016


Ответы (2)


Если вы нажмете две клавиши, вы запустите два события, независимо от того, нажмете ли вы их одновременно или нет. То же самое для события 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
  };
};
person trincot    schedule 07.10.2016

Ваша проблема в том, что вы обрабатываете keydown и keyup с одним и тем же кодом, в результате чего отпускание key1 при нажатии key2 имеет тот же эффект, что и нажатие key2.

Исправить это довольно просто: вообще не вызывайте функцию handleKeyPress при событиях keyup.

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>


Добавление предложения в ответ на комментарий ОП. Ваша проблема в том, что ваш 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>

person AmericanUmlaut    schedule 07.10.2016
comment
Проблема в том, что мне все еще нужно проверять ложные значения, поскольку это то, что используется, чтобы сказать игроку, чтобы он прекратил движение. Если значение истинно, двигаться влево, если нет, остановить движение влево. - person Carl Markham; 07.10.2016
comment
@CarlMarkham Я добавил предложение о том, как вы могли бы сделать то, что вы пытаетесь заставить работать, которое демонстрирует, что вам нужна ваша логика, чтобы иметь возможность реагировать на то, какая клавиша обрабатывается и была ли она нажата или вверх - нет необходимо использовать карту текущих состояний всех ключей. - person AmericanUmlaut; 08.10.2016