我有以下问题:
我正在尝试编写一个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); }
有没有人有想法?
答案 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)
答案 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>
如果您想要某种冷却/重新触发时间(例如,玩家按住“开火”键但枪不应该在每一帧发射新子弹),我建议在每个实体中处理它,而不是在上述密钥处理代码的逻辑中。按键逻辑负责识别正在按下的按键,而不是其他任何东西。
setTimeout
和 setInterval
是 imprecise。游戏和动画尽可能依赖 requestAnimationFrame
。您可以使用滴答计数器来确定经过的时间,以便游戏中的所有实体都同步到同一时钟。当然,很多都依赖于应用程序。
另见: