繁体   English   中英

删除 Javascript 中的按键延迟

[英]Remove key press delay in Javascript

我有以下问题:我正在尝试编写一个 Javascript 游戏,并且角色由箭头键控制。
问题是,当一个人按住按键时,在触发第一次keypress和重复keypress之间会有短暂的延迟。
此外,当一个人按下“右箭头键”并保持按下状态,然后按下“向上箭头键”时,角色不会移动到右上角,而是停止向右移动并开始向上移动。
这是我正在使用的代码:

<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);
    }

有人有想法吗?

如果您希望以可控的方式重复按键,则必须自己实现它,因为按键事件的触发取决于操作系统对按键应如何重复的想法。 这意味着可能存在可变的初始延迟和后续延迟,并且同时按住两个键只会导致其中一个重复。

您必须记录当前是否按下每个键,并在键已按下时忽略keydown事件。 这是因为当自动重复发生时,许多浏览器会触发keydownkeypress事件,如果您要自己复制键重复,则需要抑制它。

例如:

// 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= {};
    };
};

然后:

// 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);

虽然,大多数基于动作的游戏都有一个固定时间的主框架循环,您可以将按键上/下处理绑定到其中。

我已经这样解决了:

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 上结束它吗?

这与@bobince 的出色回答几乎相同

我已经稍微修改了它以允许间隔的单个值

// 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= {};
    };
};

由于这个问题中给出的原因,我还计划使用 setTimeout 而不是 setInterval: setTimeout 或 setInterval?

修改和测试后,我将更新此答案。

在游戏中触发动作的典型解决方案是添加一个间接层:在游戏循环运行到下一帧之前,不要让用户的动作更新实体状态。 (是的,这适用于鼠标事件以及在大多数情况下影响游戏状态的任何其他事件)

按下按键后立即触发游戏事件可能具有直观意义; 毕竟,这就是您通常对事件的响应方式:立即在侦听器回调中。

然而,在游戏和动画中,更新/渲染循环是唯一应该发生实体更新(如移动)的地方。 处理渲染循环之外的位置会绕过如下所示的正常流程:

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

当事件触发时,它们应该修改中间状态,然后更新代码可以在更新实体位置和状态时考虑这些状态。

具体来说,您可以使用表示按下了哪些键的标志,在触发keydown事件时将其打开,并在触发keyup事件时将其关闭。 然后,无论更新循环中的任何操作系统延迟缓冲如何,都会处理密钥。

而不是每个键的布尔值,只需在按下时将键添加到集合中,并在释放时删除它们。

这是一个最小的例子:

 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> `; })();

上面的代码可以作为实现基本游戏动作的插件,并根据需要进行一些默认预防:

 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>

如果您想要某种冷却/重新触发时间(例如,玩家按住“开火”键但枪不应该在每一帧上发射新子弹),我建议在每个实体中而不是在逻辑中处理对于上面的键处理代码。 按键逻辑负责识别正在按下的按键,而不是其他按键。

请注意,键盘对它们一次注册的按键数量有限制。

setTimeoutsetInterval 不精确 游戏和动画尽可能依赖requestAnimationFrame 您可以使用刻度计数器来确定经过的时间,以便游戏中的所有实体都同步到同一个时钟。 当然,很大程度上取决于应用程序。


也可以看看:

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

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

这是卢卡斯在一个更抽象的版本中的解决方案:

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

令我惊讶的是,似乎您一次只能按 3 个键。

我在这方面完全是新手,但为什么不将 KeyDown 与 KeyUp 结合起来呢? 我现在正在做一个类似的项目,在检查了quirksmode之后,我将着手研究如何组合这两个事件,以便在 Down 和 Up 之间的整个时间实现所需的效果。

现代化的@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 = {};
  }
}

相同的来电模式...

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