使特定键的 onkeydown 事件只发生一次

Make onkeydown events occur only once for specific keys

我正在创建一个简单的游戏,其中包含让玩家向前移动、向左转、向右转或转身的功能。我希望按键仅触发一次特定的相应功能。我发现这个有用的代码只触发一次事件,但我似乎无法弄清楚如何在其中指定某些按键。

    var shouldHandleKeyDown = true;
    document.onkeydown = function(){
      if (!shouldHandleKeyDown) return;
      shouldHandleKeyDown = false;
      // HANDLE KEY DOWN HERE
    }
    document.onkeyup = function(){
      shouldHandleKeyDown = true;
    }
})();

我想要实现的是:

用户向上按? 向前移动功能出现一次(即使按住向上键)

用户按下左键?左转功能出现一次等....

感谢大家对此的帮助。

编辑澄清:

我正在尝试以 NES 的 Wizardry 等旧游戏风格构建第一人称地牢爬行器。基本上,我希望每次按下前进按钮都能让玩家前进一帧。如果他们向左按下,则玩家向左转一帧,依此类推。如果您熟悉游戏,您就会明白我的意思。

我会忽略 onkeydown 事件,只使用 onkeyup。这样一来,事件只会在用户抬起手指时触发,确保每次按键只移动一次。

要确定在您的事件处理程序中按下了哪个键,请将事件传递给您的函数。 Here 是键值的 link,所以你可以这样做:

document.onkeyup = function(event) {
    var keycode = event.keyCode;

    if (keycode === 38) {
        moveUp();
    }
}

function()改成function(e)然后按键在e.key

(在 运行 片段后点击 运行 按钮下方的白色矩形并按下按键)

var shouldHandleKeyDown = true;
document.onkeydown = function(e) {
  if (!shouldHandleKeyDown) return;
  shouldHandleKeyDown = false;
  console.log(e.key);
      // HANDLE KEY DOWN HERE
}

document.onkeyup = function(){
  shouldHandleKeyDown = true;
}

没有单一的好方法可以解决这个问题,这主要取决于您将如何处理这些关键输入。

例如,如果要控制一个总是更新的动画,那么在{left: bool, right: bool, up: bool, down: bool}的字典中的简单信号量在keydown中设置为true并且到 keyup 中的 false,并且在每一帧进行检查就足够了。

const directions = {
  up: false,
  right: false,
  down: false,
  left: false
};
onkeydown = e => {
  const key = e.key.replace('Arrow', '').toLowerCase();
  if(key in directions) {
    e.preventDefault();
    directions[key] = true;
  }
};
onkeyup = e => {
  const key = e.key.replace('Arrow', '').toLowerCase();
  if(key in directions) {
    e.preventDefault();
    directions[key] = false;
  }
};
const w = canvas.width = innerWidth;
const h = canvas.height = innerHeight;
const ctx = canvas.getContext('2d');
const character = {
  x: w/2,
  y: h/2,
  update: function() {
    const speed = 2;
    // this is where we will check our keyboard's status
    // we do it every frame
    let dir_x = 0,
      dir_y = 0;
    dir_x += directions.left ? -1 : 0;
    dir_x += directions.right ? 1 : 0;
    dir_y += directions.up ? -1 : 0;
    dir_y += directions.down ? 1 : 0;
    this.x += dir_x * speed;
    this.y += dir_y * speed;
  },
  draw: function() {
    ctx.fillRect(this.x - 20, this.y-20, 40, 40);
  }
};

function anim() {
  character.update();
  ctx.clearRect(0,0,w, h);
  character.draw();
  requestAnimationFrame(anim);
}
anim();
:root,body{margin:0px}
<canvas id="canvas"></canvas>

但是,如果您没有保持连续的动画循环并且确实需要在第一次操作时触发此事件,那么您可能需要在 keydown 处理程序中添加一个逻辑

...
if(key in directions) {
  e.preventDefault();
  const changed = directions[key] !== true;
  directions[key] = true;
  if(changed) {
    triggerChange(key);
  }
}

其中 triggerChange(key) 是准备好触发您的单人动作的函数。

const directions = {
  up: false,
  right: false,
  down: false,
  left: false
};

function triggerChange(dir) {
  character.update();
}

onkeydown = e => {
  const key = e.key.replace('Arrow', '').toLowerCase();
  if (key in directions) {
    e.preventDefault();
    const changed = directions[key] !== true;
    directions[key] = true;
    if (changed) {
      triggerChange(key);
    }
  }
};
onkeyup = e => {
  const key = e.key.replace('Arrow', '').toLowerCase();
  if (key in directions) {
    e.preventDefault();
    directions[key] = false;
  }
};

const w = canvas.width = innerWidth;
const h = canvas.height = innerHeight;
const ctx = canvas.getContext('2d');
const character = {
  x: w / 2,
  y: h / 2,
  update: function() {
    const speed = 5;
    // this is where we will check our keyboard's status
    // we do it every frame
    let dir_x = 0,
      dir_y = 0;
    dir_x += directions.left ? -1 : 0;
    dir_x += directions.right ? 1 : 0;
    dir_y += directions.up ? -1 : 0;
    dir_y += directions.down ? 1 : 0;
    this.x += dir_x * speed;
    this.y += dir_y * speed;
  },
  draw: function() {
    ctx.fillRect(this.x - 20, this.y - 20, 40, 40);
  }
};

function anim() {
  // it is now keydown that is responsible for character.update()
  ctx.clearRect(0, 0, w, h);
  character.draw();
  requestAnimationFrame(anim);
}
anim();
:root,body{margin:0}
<canvas id="canvas"></canvas>