简体   繁体   English

删除 Javascript 中的按键延迟

[英]Remove key press delay in Javascript

I have the following problem: I'm trying to write a Javascript game, and the character is being controlled by the arrow keys.我有以下问题:我正在尝试编写一个 Javascript 游戏,并且角色由箭头键控制。
The problem is, when one keeps the key pressed, there is a short delay between firing the first keypress and the repeated keypress .问题是,当一个人按住按键时,在触发第一次keypress和重复keypress之间会有短暂的延迟。
Also, when one presses the "right arrow key" and keeps it pressed, and then presses the "up arrow key" the character doesn't move to the top right corner, but stops the moving in the right direction and starts moving up.此外,当一个人按下“右箭头键”并保持按下状态,然后按下“向上箭头键”时,角色不会移动到右上角,而是停止向右移动并开始向上移动。
This is the code I'm using:这是我正在使用的代码:

<body onLoad="Load()" onKeyDown="Pressed(event)">
function Pressed(e) { 
        cxc = e.keyCode;
        if(cxc == 37)
            Move(-1,0);
        if(cxc == 38)
            Move(0,-1);
        if(cxc == 39)
            Move(1,0);
        if(cxc == 40)
            Move(0,1);
    }

Does anybody have an idea?有人有想法吗?

If you want key repeat in a controllable fashion, you will have to implement it yourself, as keypress events are fired dependent on the OS's idea of how keys should repeat.如果您希望以可控的方式重复按键,则必须自己实现它,因为按键事件的触发取决于操作系统对按键应如何重复的想法。 That means there may be variable initial and following delays, and holding down two keys at once will cause only one of them to repeat.这意味着可能存在可变的初始延迟和后续延迟,并且同时按住两个键只会导致其中一个重复。

You will have to keep a record of whether each key is currently pressed, and ignore keydown events when the key is already down.您必须记录当前是否按下每个键,并在键已按下时忽略keydown事件。 This is because many browsers will fire a keydown as well as a keypress event when an autorepeat occurs, and if you're reproducing key repeat yourself you'll need to suppress that.这是因为当自动重复发生时,许多浏览器会触发keydownkeypress事件,如果您要自己复制键重复,则需要抑制它。

For example:例如:

// Keyboard input with customisable repeat (set to 0 for no key repeat)
//
function KeyboardController(keys, repeat) {
    // Lookup of key codes to timer ID, or null for no repeat
    //
    var timers= {};

    // When key is pressed and we don't already think it's pressed, call the
    // key action callback and set a timer to generate another one after a delay
    //
    document.onkeydown= function(event) {
        var key= (event || window.event).keyCode;
        if (!(key in keys))
            return true;
        if (!(key in timers)) {
            timers[key]= null;
            keys[key]();
            if (repeat!==0)
                timers[key]= setInterval(keys[key], repeat);
        }
        return false;
    };

    // Cancel timeout and mark key as released on keyup
    //
    document.onkeyup= function(event) {
        var key= (event || window.event).keyCode;
        if (key in timers) {
            if (timers[key]!==null)
                clearInterval(timers[key]);
            delete timers[key];
        }
    };

    // When window is unfocused we may not get key events. To prevent this
    // causing a key to 'get stuck down', cancel all held keys
    //
    window.onblur= function() {
        for (key in timers)
            if (timers[key]!==null)
                clearInterval(timers[key]);
        timers= {};
    };
};

then:然后:

// Arrow key movement. Repeat key five times a second
//
KeyboardController({
    37: function() { Move(-1, 0); },
    38: function() { Move(0, -1); },
    39: function() { Move(1, 0); },
    40: function() { Move(0, 1); }
}, 200);

Although, most action-based games have a fixed-time main frame loop, which you can tie the key up/down handling into.虽然,大多数基于动作的游戏都有一个固定时间的主框架循环,您可以将按键上/下处理绑定到其中。

I've solved it like this:我已经这样解决了:

var pressedl = 0;
var pressedu = 0;
var pressedr = 0;
var pressedd = 0;

function Down(e) { 
        cxc = e.keyCode;
        if(cxc == 37)
            pressedl = 1;
        if(cxc == 38)
            pressedu = 1;
        if(cxc == 39)
            pressedr = 1;
        if(cxc == 40)
            pressedd = 1;
        //alert(cxc);
    }
    function Up(e) {
        cxc = e.keyCode;
        if(cxc == 37)
            pressedl = 0;
        if(cxc == 38)
            pressedu = 0;
        if(cxc == 39)
            pressedr = 0;
        if(cxc == 40)
            pressedd = 0;
        //alert(cxc);
    }

<body onLoad="Load()" onKeyDown="Down(event)" onKeyUp="Up(event)">

您可以在 onkeydown 开始移动并仅在 onkeyup 上结束它吗?

This is nearly the same as the excellent answer from @bobince这与@bobince 的出色回答几乎相同

I've amended it slightly to allow individual values for the interval我已经稍微修改了它以允许间隔的单个值

// Keyboard input with customisable repeat (set to 0 for no key repeat)
// usage
/**
KeyboardController({
    32: {interval:0, callback: startGame },
    37: {interval:10, callback: function() { padSpeed -= 5; } },
    39: {interval:10, callback: function() { padSpeed += 5; } }
});
*/

function KeyboardController(keyset) {
    // Lookup of key codes to timer ID, or null for no repeat
    //
    var timers= {};

    // When key is pressed and we don't already think it's pressed, call the
    // key action callback and set a timer to generate another one after a delay
    //
    document.onkeydown= function(event) {
        var key= (event || window.event).keyCode;
        if (!(key in keyset))
            return true;
        if (!(key in timers)) {
            timers[key]= null;
            keyset[key].callback();
            if (keyset[key].interval !== 0)
                timers[key]= setInterval(keyset[key].callback, keyset[key].interval);
        }
        return false;
    };

    // Cancel timeout and mark key as released on keyup
    //
    document.onkeyup= function(event) {
        var key= (event || window.event).keyCode;
        if (key in timers) {
            if (timers[key]!==null)
                clearInterval(timers[key]);
            delete timers[key];
        }
    };

    // When window is unfocused we may not get key events. To prevent this
    // causing a key to 'get stuck down', cancel all held keys
    //
    window.onblur= function() {
        for (key in timers)
            if (timers[key]!==null)
                clearInterval(timers[key]);
        timers= {};
    };
};

I've also got a plan to use setTimeout instead of setInterval for the reasons given in this question: setTimeout or setInterval?由于这个问题中给出的原因,我还计划使用 setTimeout 而不是 setInterval: setTimeout 或 setInterval?

I'll update this answer once I've amended and tested.修改和测试后,我将更新此答案。

The typical solution to triggering actions in a game is to add a layer of indirection: don't let the user's action update entity state until the game loop runs on the next frame.在游戏中触发动作的典型解决方案是添加一个间接层:在游戏循环运行到下一帧之前,不要让用户的动作更新实体状态。 (Yes, this applies to mouse events and just about anything else that affects game state as well in most cases) (是的,这适用于鼠标事件以及在大多数情况下影响游戏状态的任何其他事件)

It might make intuitive sense to trigger a game event as soon as a key is pressed;按下按键后立即触发游戏事件可能具有直观意义; after all, that's how you'd normally respond to an event: right away in the listener callback.毕竟,这就是您通常对事件的响应方式:立即在侦听器回调中。

However, in games and animations, the update/rendering loop is the only place where entity updates such as movement should occur.然而,在游戏和动画中,更新/渲染循环是唯一应该发生实体更新(如移动)的地方。 Messing with positions outside of the rendering loop bypasses the normal flow illustrated below:处理渲染循环之外的位置会绕过如下所示的正常流程:

[initialize state]
        |
        v
.-----------------.
|  synchronously  |
|  update/render  |
|  a single frame |
`-----------------`
   ^           |
   |           v
(time passes asynchronously, 
 events fire between frames)

When events fire, they should modify intermediate state that the update code can then take into consideration when it comes time to update entity positions and states.当事件触发时,它们应该修改中间状态,然后更新代码可以在更新实体位置和状态时考虑这些状态。

Specifically, you could use flags that represent which keys were pressed, flip them on whenever a keydown event fires, and flip them off whenever a keyup event fires.具体来说,您可以使用表示按下了哪些键的标志,在触发keydown事件时将其打开,并在触发keyup事件时将其关闭。 Then, the key will be processed regardless of any operating system delay buffering in the update loop.然后,无论更新循环中的任何操作系统延迟缓冲如何,都会处理密钥。

Rather than a boolean for every key, simply add the keys to a set when pressed and remove them when they're released.而不是每个键的布尔值,只需在按下时将键添加到集合中,并在释放时删除它们。

Here's a minimal example:这是一个最小的例子:

 const keysPressed = new Set(); document.addEventListener("keydown", e => { keysPressed.add(e.code); }); document.addEventListener("keyup", e => { keysPressed.delete(e.code); }); (function update() { requestAnimationFrame(update); document.body.innerHTML = ` <p>These keys are pressed:</p> <ul> ${[...keysPressed] .map(e => `<li>${e}</li>`) .join("") } </ul> `; })();

The above code works as a drop-in to implement rudimentary game movement, with some default prevention as needed:上面的代码可以作为实现基本游戏动作的插件,并根据需要进行一些默认预防:

 const keysPressed = new Set(); const preventedKeys = new Set([ "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", ]); document.addEventListener("keydown", e => { if (preventedKeys.has(e.code)) { e.preventDefault(); } keysPressed.add(e.code); }); document.addEventListener("keyup", e => { keysPressed.delete(e.code); }); const player = { x: 0, y: 0, speed: 2, el: document.querySelector("#player"), render() { this.el.style.left = player.x + "px"; this.el.style.top = player.y + "px"; }, actions: { ArrowLeft() { this.x -= this.speed; }, ArrowDown() { this.y += this.speed; }, ArrowUp() { this.y -= this.speed; }, ArrowRight() { this.x += this.speed; }, }, update(keysPressed) { Object.entries(this.actions) .forEach(([key, action]) => keysPressed.has(key) && action.call(this) ) ; }, }; (function update() { requestAnimationFrame(update); player.update(keysPressed); player.render(); })();
 .wrapper { position: relative; } #player { width: 40px; height: 40px; background: purple; position: absolute; }
 <p>Use arrow keys to move</p> <div class="wrapper"> <div id="player"></div> </div>

If you want some sort of cooldown/retrigger period (for example, the player is holding down a "fire" key but a gun should not fire a new bullet on every frame), I suggest handling that in each entity rather than in the logic for the above key-handling code.如果您想要某种冷却/重新触发时间(例如,玩家按住“开火”键但枪不应该在每一帧上发射新子弹),我建议在每个实体中而不是在逻辑中处理对于上面的键处理代码。 The key logic is responsible for identifying which keys are being pressed and nothing else.按键逻辑负责识别正在按下的按键,而不是其他按键。

Note that keyboards have limits to how many keys they register as pressed at once.请注意,键盘对它们一次注册的按键数量有限制。

setTimeout and setInterval are imprecise . setTimeoutsetInterval 不精确 Rely on requestAnimationFrame as much as possible for games and animations.游戏和动画尽可能依赖requestAnimationFrame You can use a tick counter to determine elapsed time such that all entities in the game are synchronized to the same clock.您可以使用刻度计数器来确定经过的时间,以便游戏中的所有实体都同步到同一个时钟。 Of course, much is application-dependent.当然,很大程度上取决于应用程序。


See also:也可以看看:

由于此事件是将whatever从一个位置移动到一个位置,为什么不使用onkeypress事件,所以如果用户键按下up键,则whatever都会继续向上移动,因为Pressed(e)将被多次调用,直到用户释放密钥。

<body onLoad="Load()" onkeypress="Pressed(event)">

This here is Lucas' solution in a more abstract version:这是卢卡斯在一个更抽象的版本中的解决方案:

http://jsfiddle.net/V2JeN/4/ http://jsfiddle.net/V2JeN/4/

Seems you can only press 3 keys at a time to my surprise.令我惊讶的是,似乎您一次只能按 3 个键。

I'm a total novice at this, but why not combine KeyDown with KeyUp?我在这方面完全是新手,但为什么不将 KeyDown 与 KeyUp 结合起来呢? I am working on a similar project right now and, after checking out quirksmode I am going to set forth at figuring out how to combine the two events such that the whole time between a Down and Up realizes the desired affect.我现在正在做一个类似的项目,在检查了quirksmode之后,我将着手研究如何组合这两个事件,以便在 Down 和 Up 之间的整个时间实现所需的效果。

Modernized @bobince's great answer for my project as follows...现代化的@bobince 对我的项目的最佳答案如下......

// Keyboard.js
'use strict'

class KeyboardController {
  constructor(keys, repeat) {
    this.keys = keys;
    this.repeat = repeat;
    this.timers = {};

    document.onkeydown = event => this.keydown(event);
    document.onkeyup = event => this.keyup(event);
    window.onblur = () => this.blur;
  }

  keydown(event) {
    event.stopPropagation();
    const code = event.code;
    if (!(code in this.keys)) return true;
    if (!(code in this.timers)) {
      this.timers[code] = null;
      this.keys[code]();
      if (this.repeat) this.timers[code] = setInterval(this.keys[code], this.repeat);
    }
    return false;
  }

  keyup(event) {
    const code = event.code;
    if (code in this.timers) {
      if (this.timers[code]) clearInterval(this.timers[code]);
      delete this.timers[code];
    }
  }

  blur() {
    for (let key in this.timers)
      if (this.timers[key]) clearInterval(this.timers[key]);
    this.timers = {};
  }
}

Same caller pattern...相同的来电模式...

const keyboard = new KeyboardController({
  ArrowLeft:  () => {/* your logic here */},
  ArrowRight: () => {},
  ArrowUp:    () => {},
  ArrowDown:  () => {}
}, 65);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM