问题
I have the beginnings of a 2d javascript game - at the moment the player can drive a triangle around the screen using the arrow keys.
The problem is that sometimes the triangle will get stuck rotating in one direction or moving forward until the corresponding control key is pressed again and the onkeyup event is fired again. This usually happens when more than one control key is pressed at the same time.
I can't work out why it's getting stuck in the first place unless the onkeyup events aren't getting fired for some reason. Any help would be much appreciated, thank you.
Here's some of the code, you can find a fully working example on 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;
}
}
...
回答1:
Problem description
The keyup
events are fired, but sometimes a final keydown
event fires directly after the keyup
. Weird? Yes.
The problem is the way how the repetition of the keydown
event for a held key is implemented:
the keydown
event for the last held key is fired repeatedly. Due to the asynchronous, single-threaded nature of optimized JavaScript code execution, it can happen sometimes that such a repeated keydown
event is fired after the corresponding keyup
event was fired.
See what happens when i hold and release the keys [UP] and [RIGHT] simultaneously in my JSFiddle example.
Observed with Firefox 13.0.1 on Windows 7:
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
You can see three things in this example console output:
- only the
keydown
event for [RIGHT] (38) is fired repeatedly. - The
keyup
event for [RIGHT] (38) is fired when i release the key. - A last scheduled
keydown
event is fired, after thekeyup
was executed.
Thats the reason why your rotation
state is "stuck" with value 1
even if no key is pressed.
Solution
To avoid this, you can keep track of the microseconds when a key is released. When a keydown event occurs just a few microseconds ahead, then we silently ignore it.
I introduced a helper object Key
into your code. See it working in this JSFiddle example.
Now the console output looks like:
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
My solution is inspired by this blog article, which goes a bit further and explains how to avoid the sloppyness and "one-direction-only" issues you have when using JavaScript key events for game development. It's definitely worth reading.
The workaround to ignore the delayed keydown
event basically looks like this:
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); };
Update #1 Find an updated (fixed) version of your code on JSFiddle.
来源:https://stackoverflow.com/questions/11225694/why-are-onkeyup-events-not-firing-in-javascript-game