[英]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
事件。 這是因為當自動重復發生時,許多瀏覽器會觸發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);
雖然,大多數基於動作的游戲都有一個固定時間的主框架循環,您可以將按鍵上/下處理綁定到其中。
我已經這樣解決了:
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>
如果您想要某種冷卻/重新觸發時間(例如,玩家按住“開火”鍵但槍不應該在每一幀上發射新子彈),我建議在每個實體中而不是在邏輯中處理對於上面的鍵處理代碼。 按鍵邏輯負責識別正在按下的按鍵,而不是其他按鍵。
請注意,鍵盤對它們一次注冊的按鍵數量有限制。
setTimeout
和setInterval
不精確。 游戲和動畫盡可能依賴requestAnimationFrame
。 您可以使用刻度計數器來確定經過的時間,以便游戲中的所有實體都同步到同一個時鍾。 當然,很大程度上取決於應用程序。
也可以看看:
由於此事件是將whatever
從一個位置移動到一個位置,為什么不使用onkeypress
事件,所以如果用戶鍵按下up
鍵,則whatever
都會繼續向上移動,因為Pressed(e)
將被多次調用,直到用戶釋放密鑰。
<body onLoad="Load()" onkeypress="Pressed(event)">
我在這方面完全是新手,但為什么不將 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.