5

我有一个 2d Javascript 游戏的开始 - 目前玩家可以使用箭头键在屏幕上驱动一个三角形。

问题是有时三角形会卡在一个方向旋转或向前移动,直到再次按下相应的控制键并再次onkeyup触发事件。这通常发生在同时按下多个控制键时。

我无法弄清楚为什么它首先会被卡住,除非onkeyup事件由于某种原因没有被触发。任何帮助将不胜感激,谢谢。

这是一些代码,您可以在 JSFiddle 上找到一个完整的示例

...

function init(){
    var canvas = document.getElementById('canvas');
    if(canvas.getContext){
        setInterval(play, 50);
    }
}

function play(){
    printTriState();
    updateTriAcceleration();
    applyTriVelocity();
    updateTriRotation();
    draw();
}

document.onkeydown = function(event){

if(!event.keyCode){
    keycode = window.event.keyCode;
} else {
    keycode = event.keyCode;
}
console.log(event.keyCode);
switch(keycode){
    //left
    case 37:
    tri.rotation = -1;
    break;

    //up
    case 38:
    tri.throttle = true;
    break;

    //right
    case 39:
    tri.rotation = 1;
    break;

    //down
    case 40:
    tri.currentSpeed = 0;
    break;

    default:
    break;
}
};

document.onkeyup = function(event){

if(!event.keyCode){
    keycode = window.event.keyCode;
} else {
    keycode = event.keyCode;
}
console.log(event.keyCode);
switch(keycode){
    //left
    case 37:
    tri.rotation = 0;
    break;

    //up
    case 38:
    tri.throttle = false;
    break;

    //right
    case 39:
    tri.rotation = 0;
    break;

    //down
    case 40:

    break;

    default:
    break;
}
};

function updateTriRotation(){
    if(tri.rotation == 1){
        tri.orientation += tri.rotationSpeed;
    } else if (tri.rotation == -1){
        tri.orientation -= tri.rotationSpeed;
    } else {
        tri.orientation += 0;
    }
}

...
4

2 回答 2

12

问题描述

事件触发,但有时最终keyup事件直接在. 奇怪的?是的。keydownkeyup

问题在于如何keydown实现对持有的键的事件的重复:
最后keydown持有的键的事件被重复触发。由于优化的 JavaScript 代码执行的异步、单线程特性,有时可能会在触发相应事件keydown触发此类重复事件。keyup

看看当我在我的 JSFiddle 示例中同时按住并释放键 [UP] 和 [RIGHT] 时会发生什么。

在 Windows 7 上使用 Firefox 13.0.1 观察到

rotation: 1 key down: 39
rotation: 1 key down: 38
rotation: 1 key down: 38
rotation: 1 key down: 38
rotation: 1 key up: 38
rotation: 0 key up: 39
rotation: 1 key down: 38

您可以在此示例控制台输出中看到三件事:

  1. 只有keydown[RIGHT] (38) 的事件被重复触发。
  2. keyup当我释放键时,[RIGHT] (38)的事件触发。
  3. 在执行完之后keydown,会触发最后一个计划的事件。keyup

这就是即使没有按下任何键,您的rotation状态也会“卡住”价值的原因。1


解决方案

为避免这种情况,您可以在释放键时跟踪微秒。当 keydown 事件发生在几微秒前时,我们会默默地忽略它。

Key我在您的代码中引入了一个辅助对象。在这个 JSFiddle 示例中查看它的工作原理。

现在控制台输出如下所示:

rotation: 1 key down: 40 time: 1340805132040
rotation: 1 key down: 40 time: 1340805132071
rotation: 1 key down: 40 time: 1340805132109
rotation: 1 key up: 40 time: 1340805132138
rotation: 0 key up: 39 time: 1340805132153
key down: 40 release time: 1340804109335
keydown fired after keyup .. nothing to do.
rotation: 0 key down: 39 time: 1340805132191

我的解决方案受到这篇博客文章的启发,这篇文章更进一步,解释了如何避免在使用 JavaScript 键事件进行游戏开发时遇到的草率和“单向”问题。这绝对值得一读。

忽略延迟事件的解决方法keydown基本上如下所示:

var Key = {
    _pressed: {},
    _release_time: {},

    MAX_KEY_DELAY: 100,

    onKeydown: function(event) {

        // avoid firing of the keydown event when a keyup occured
        // just +/- 100ms before or after a the keydown event.
        // due to the asynchronous nature of JS it may happen that the
        // timestamp of keydown is before the timestamp of keyup, but 
        // the actual execution of the keydown event happens *after* 
        // the keyup.   Weird? Yes. Confusing!

        var time = new Date().getTime();
        if ( this._release_time[event.keyCode] &&
             time < this._release_time[event.keyCode] + this.MAX_KEY_DELAY ) {
            console.log('keydown fired after keyup .. nothing to do.');
            return false;
        }

        this._pressed[event.keyCode] = true;

        // LOGIC FOR ACTUAL KEY CODE HANDLING GOES HERE
    },

    onKeyup: function(event) {
        delete this._pressed[event.keyCode];
        this._release_time[event.keyCode] = new Date().getTime();

        // LOGIC FOR ACTUAL KEY CODE HANDLING GOES HERE
    }
};

document.onkeydown = function(event) { return Key.onKeydown(event); };
document.onkeyup = function(event) { return Key.onKeyup(event); };

更新 #1 在 JSFiddle 上找到您的代码的更新(固定)版本

于 2012-06-27T13:50:17.023 回答
4

这个问题已经有将近 8 年的历史了,但我遇到了类似的问题并偶然发现了这个页面,所以以防万一它对其他人有帮助:

我在我的代码中遇到了干扰问题,它在 keydown 上使用Setadd键盘事件,delete在 keyup 上使用它们。我觉得使用event.key字段来区分按键而不是 ,keyCode来维护用户键盘上写的内容和我的代码解释的内容之间的语义映射是一种很好的做法。(例如,我相信 QWERTY 键盘的 W 会与keyCodeAZERTY 键盘的 Z 报告相同,但没有法式键盘可供验证。)

这工作正常(我没有看到其他人报告的乱序事件的问题),直到修改器出现在图片中。我发现这些有两个问题:

  • ...举行 Shift 的地方。如果用户击键的顺序是(hold-shift、hold-R、release-shift、release-R),那么JS事件的顺序就是wrt。会event.key去(keydown-shift,keydown-R,keyup-shift,keyup-r)。换句话说, anR被按下,但 anr被释放,所以 mySetR卡住了。我对此的解决方案是将Set变成Map,其中键是event.keyCode',值是event.key在 keydown 事件发生时报告的 '。这样,当我得到一个 keyup 事件时,我只是delete对应于keyCode和 键的映射键不会卡住。我希望 option/alt 需要相同的处理。
  • ... Meta 键所在的位置(我的 Mac 上的命令;我猜是 Windows 上的控制?)。在这种情况下,一个序列(hold-meta、hold-K、release-meta、release-K)将生成事件(keydown-meta、keydown-k、keyup-meta)。所以 - K 的 keyup 事件确实丢失了,即使我确定这样做event.preventDefault是为了防止系统窃取我的事件。我希望我有一个更好的解决方案,但是当我看到 Meta 关键事件时,我现在正在做的是对整个地图进行核对。我应该郑重声明,我的代码实际上并没有使用 Meta 作为修饰符;我只是想确保它是安全的,并且当用户快速键入时,我的密钥处理程序似乎很容易卡住。

哦,还有,关于模糊——我正在对地图进行核弹。那么所有的赌注都取消了...

于 2020-04-18T18:48:10.497 回答