删除javascript中的按键延迟

时间:2010-09-11 15:01:09

标签: javascript events javascript-events

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

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

有没有人有想法?

8 个答案:

答案 0 :(得分:28)

如果你想以可控的方式进行密钥重复,你必须自己实现它,因为按键事件是根据操作系统关于密钥重复方式的想法而触发的。这意味着可能存在可变的初始延迟和跟随延迟,并且同时按住两个键将导致其中只有一个重复。

您必须记录当前是否按下每个键,并在键已关闭时忽略keydown个事件。这是因为许多浏览器会在发生自动重复时触发keydown以及keypress事件,如果您正在重现密钥重复,则需要对其进行抑制。

例如:

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

尽管如此,大多数基于动作的游戏都有一个固定时间的主框架循环,您可以将按键上/下操作绑定到。

答案 1 :(得分:4)

我已经解决了这个问题:

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)">

答案 2 :(得分:2)

你可以开始onkeydown的运动并且只在keyup上结束它?

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

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

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

似乎你一次只能按3个按键让我感到惊讶。

答案 5 :(得分:1)

我在这方面完全是新手,但为什么不将KeyDown与KeyUp结合使用?我正在研究一个类似的项目,在结账quirksmode之后,我将着手解决如何将这两个事件结合起来,以便在Down和Up之间的整个时间实现所需的影响。 / p>

答案 6 :(得分:1)

这与@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 or setInterval?

我修改并测试后,我会更新此答案。

答案 7 :(得分:0)

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

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

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

[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>

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

setTimeoutsetIntervalimprecise。游戏和动画尽可能依赖 requestAnimationFrame。您可以使用滴答计数器来确定经过的时间,以便游戏中的所有实体都同步到同一时钟。当然,很多都依赖于应用程序。


另见: