我试图开发一个JavaScript游戏引擎,我遇到了这个问题:

当我按空格键时,角色会跳跃。 当我按下→角色向右移动。

问题是,当我按右键,然后按空格键时,角色会跳跃,然后停止移动。

我使用keydown函数来按下键。如何检查是否同时按下了多个键?


当前回答

如果其中一个按键是Alt / Crtl / Shift,你可以使用这个方法:

document.body.addEventListener('keydown', keysDown(actions) );

function actions() {
   // do stuff here
}

// simultaneous pressing Alt + R
function keysDown (cb) {
  return function (zEvent) {
    if (zEvent.altKey &&  zEvent.code === "KeyR" ) {
      return cb()
    }
  }
}

其他回答

我喜欢使用这个片段,它对于编写游戏输入脚本非常有用

var keyMap = [];

window.addEventListener('keydown', (e)=>{
    if(!keyMap.includes(e.keyCode)){
        keyMap.push(e.keyCode);
    }
})

window.addEventListener('keyup', (e)=>{
    if(keyMap.includes(e.keyCode)){
        keyMap.splice(keyMap.indexOf(e.keyCode), 1);
    }
})

function key(x){
    return (keyMap.includes(x));
}

function checkGameKeys(){
    if(key(32)){
        // Space Key
    }
    if(key(37)){
        // Left Arrow Key
    }
    if(key(39)){
        // Right Arrow Key
    }
    if(key(38)){
        // Up Arrow Key
    }
    if(key(40)){
        // Down Arrow Key
    }
    if(key(65)){
        // A Key
    }
    if(key(68)){
        // D Key
    }
    if(key(87)){
        // W Key
    }
    if(key(83)){
        // S Key
    }
}

注意:keyCode现在已弃用。

如果您理解了这个概念,那么多次击键检测就很容易了

我是这样做的:

var map = {}; // You could also use an array
onkeydown = onkeyup = function(e){
    e = e || event; // to deal with IE
    map[e.keyCode] = e.type == 'keydown';
    /* insert conditional here */
}

这段代码非常简单:由于计算机一次只传递一个击键,因此创建一个数组来跟踪多个键。然后可以使用该数组一次检查一个或多个键。

为了解释一下,假设你按下A和B,分别触发一个keydown事件来设置map[e]。e.type == keydown的值,其计算结果为true或false。现在map[65]和map[66]都被设置为true。当你放开A时,keyup事件触发,导致同样的逻辑为map[65] (A)确定相反的结果,现在是假的,但由于map[66] (B)仍然是“down”(它没有触发keyup事件),它仍然是真。

映射数组,通过这两个事件,看起来像这样:

// keydown A 
// keydown B
[
    65:true,
    66:true
]
// keyup A
// keydown B
[
    65:false,
    66:true
]

现在你可以做两件事:

A)可以创建一个键记录器(示例),以供稍后快速找出一个或多个键代码时参考。假设您已经定义了一个html元素,并使用变量元素指向它。

element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
    if(map[i]){
        element.innerHTML += '<hr>' + i;
    }
}

注意:您可以通过元素的id属性轻松获取元素。

<div id="element"></div>

这将创建一个html元素,可以很容易地在javascript中引用element

alert(element); // [Object HTMLDivElement]

甚至不需要使用document.getElementById()或$()来获取它。但出于兼容性考虑,更广泛地推荐使用jQuery的$()。

只需确保脚本标记位于HTML主体之后。优化提示:大多数大牌网站都把script标签放在body标签之后进行优化。这是因为在脚本下载完成之前,script标记阻止加载更多的元素。把它放在内容的前面可以让内容提前加载。

B(这是你感兴趣的地方)你可以一次检查一个或多个键,其中/*insert conditional here*/ was,举个例子:

if(map[17] && map[16] && map[65]){ // CTRL+SHIFT+A
    alert('Control Shift A');
}else if(map[17] && map[16] && map[66]){ // CTRL+SHIFT+B
    alert('Control Shift B');
}else if(map[17] && map[16] && map[67]){ // CTRL+SHIFT+C
    alert('Control Shift C');
}

编辑:这不是最易读的片段。可读性很重要,所以你可以尝试这样做,让眼睛更容易看到:

function test_key(selkey){
    var alias = {
        "ctrl":  17,
        "shift": 16,
        "A":     65,
        /* ... */
    };

    return key[selkey] || key[alias[selkey]];
}

function test_keys(){
    var keylist = arguments;

    for(var i = 0; i < keylist.length; i++)
        if(!test_key(keylist[i]))
            return false;

    return true;
}

用法:

test_keys(13, 16, 65)
test_keys('ctrl', 'shift', 'A')
test_key(65)
test_key('A')

这样更好吗?

if(test_keys('ctrl', 'shift')){
    if(test_key('A')){
        alert('Control Shift A');
    } else if(test_key('B')){
        alert('Control Shift B');
    } else if(test_key('C')){
        alert('Control Shift C');
    }
}

(编辑结束)


这个例子检查CtrlShiftA, CtrlShiftB和CtrlShiftC

就是这么简单:)

笔记

跟踪键盘代码

一般来说,记录代码是很好的做法,尤其是像键代码(如// CTRL+ENTER)这样的东西,这样你就可以记住它们是什么。

您还应该将键代码放在与文档相同的顺序(CTRL+ENTER => map[17] && map[13], NOT map[13] && map[17])。这样,当您需要返回并编辑代码时,就不会感到困惑。

用if-else链抓住你

如果检查不同数量的组合(如CtrlShiftAltEnter和CtrlEnter),将较小的组合放在较大的组合之后,否则较小的组合将覆盖较大的组合,如果它们足够相似的话。例子:

// Correct:
if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
    alert('Whoa, mr. power user');
}else if(map[17] && map[13]){ // CTRL+ENTER
    alert('You found me');
}else if(map[13]){ // ENTER
    alert('You pressed Enter. You win the prize!')
}

// Incorrect:
if(map[17] && map[13]){ // CTRL+ENTER
    alert('You found me');
}else if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
    alert('Whoa, mr. power user');
}else if(map[13]){ // ENTER
    alert('You pressed Enter. You win the prize!');
}
// What will go wrong: When trying to do CTRL+SHIFT+ENTER, it will
// detect CTRL+ENTER first, and override CTRL+SHIFT+ENTER.
// Removing the else's is not a proper solution, either
// as it will cause it to alert BOTH "Mr. Power user" AND "You Found Me"

明白了:“这个组合键一直激活,即使我没有按下键”

当处理警报或任何从主窗口转移焦点的事情时,您可能希望包括map =[]来在条件完成后重置数组。这是因为有些东西,如alert(),将焦点从主窗口移开,导致'keyup'事件无法触发。例如:

if(map[17] && map[13]){ // CTRL+ENTER
    alert('Oh noes, a bug!');
}
// When you Press any key after executing this, it will alert again, even though you 
// are clearly NOT pressing CTRL+ENTER
// The fix would look like this:

if(map[17] && map[13]){ // CTRL+ENTER
    alert('Take that, bug!');
    map = {};
}
// The bug no longer happens since the array is cleared

明白了:浏览器默认值

下面是我发现的一个恼人的问题,解决方案包括:

问题:由于浏览器通常在键组合上有默认操作(如ctrl键激活书签窗口,或ctrl键shiftc激活maxthon上的skynote),你可能还想在map =[]后添加返回false,这样你的网站用户就不会在“复制文件”功能时感到沮丧,被放在ctrl键组合上,书签页面。

if(map[17] && map[68]){ // CTRL+D
    alert('The bookmark window didn\'t pop up!');
    map = {};
    return false;
}

如果没有return false, Bookmark窗口就会弹出,这让用户很沮丧。

return语句(new)

你并不总是想在那个点退出函数。这就是event.preventDefault()函数存在的原因。它所做的是设置一个内部标志,告诉解释器不允许浏览器运行默认操作。之后,函数继续执行(而return将立即退出函数)。

在决定是使用return false还是e.preventDefault()之前,请先理解这个区别

事件。keyCode已弃用

用户SeanVieira在评论中指出。keyCode已弃用。

在那里,他给出了一个很好的替代方案:事件。key,它返回被按下的键的字符串表示形式,如"a"表示a,或"Shift"表示Shift。

我继续做了一个工具来检查这些字符串。

元素。onevent vs . element.addEventListener

使用addEventListener注册的处理程序可以堆叠,并按照注册的顺序调用,而直接设置.onevent则相当激进,它会覆盖之前的所有操作。

document.body.onkeydown = function(ev){
    // do some stuff
    ev.preventDefault(); // cancels default actions
    return false; // cancels this function as well as default actions
}

document.body.addEventListener("keydown", function(ev){
    // do some stuff
    ev.preventDefault() // cancels default actions
    return false; // cancels this function only
});

.onevent属性似乎覆盖了ev.preventDefault()的所有行为并返回false;可能是相当不可预测的。

在这两种情况下,通过addEventlistener注册的处理程序似乎更容易编写和推理。

还有来自ie的非标准实现的attachEvent(“onevent”,回调),但这超出了弃用,甚至不属于JavaScript(它属于一种叫做JScript的深奥语言)。尽可能避免使用多语言代码对您最有利。

helper类

为了解决困惑/抱怨,我写了一个“类”来做这个抽象(pastebin link):

function Input(el){
    var parent = el,
        map = {},
        intervals = {};
    
    function ev_kdown(ev)
    {
        map[ev.key] = true;
        ev.preventDefault();
        return;
    }
    
    function ev_kup(ev)
    {
        map[ev.key] = false;
        ev.preventDefault();
        return;
    }
    
    function key_down(key)
    {
        return map[key];
    }

    function keys_down_array(array)
    {
        for(var i = 0; i < array.length; i++)
            if(!key_down(array[i]))
                return false;

        return true;
    }
    
    function keys_down_arguments()
    {
        return keys_down_array(Array.from(arguments));
    }
    
    function clear()
    {
        map = {};
    }
    
    function watch_loop(keylist, callback)
    {
        return function(){
            if(keys_down_array(keylist))
                callback();
        }
    }

    function watch(name, callback)
    {
        var keylist = Array.from(arguments).splice(2);

        intervals[name] = setInterval(watch_loop(keylist, callback), 1000/24);
    }

    function unwatch(name)
    {
        clearInterval(intervals[name]);
        delete intervals[name];
    }

    function detach()
    {
        parent.removeEventListener("keydown", ev_kdown);
        parent.removeEventListener("keyup", ev_kup);
    }
    
    function attach()
    {
        parent.addEventListener("keydown", ev_kdown);
        parent.addEventListener("keyup", ev_kup);
    }
    
    function Input()
    {
        attach();

        return {
            key_down: key_down,
            keys_down: keys_down_arguments,
            watch: watch,
            unwatch: unwatch,
            clear: clear,
            detach: detach
        };
    }
    
    return Input();
}

这个类不会做所有事情,也不会处理所有可能的用例。我不喜欢去图书馆。但是对于一般的交互使用来说应该没问题。

要使用这个类,创建一个实例,并将其指向你想要关联键盘输入的元素:

var input_txt = Input(document.getElementById("txt"));

input_txt.watch("print_5", function(){
    txt.value += "FIVE ";
}, "Control", "5");

这将做的是将一个新的输入侦听器附加到元素#txt(让我们假设它是一个文本区域),并为组合键Ctrl+5设置一个观察点。当同时按下Ctrl和5时,将调用传入的回调函数(在本例中,将“FIVE”添加到文本区域的函数)。回调函数与名称print_5相关联,因此要删除它,只需使用:

input_txt.unwatch("print_5");

将input_txt从txt元素中分离出来:

input_txt.detach();

通过这种方式,垃圾收集可以在对象(input_txt)被丢弃时拾取它,并且不会留下一个旧的僵尸事件侦听器。

为了彻底起见,这里有一个类的API的快速参考,以C/Java风格呈现,以便您知道它们返回什么以及它们期望的参数。

Boolean key_down (String key); Returns true if key is down, false otherwise. Boolean keys_down (String key1, String key2, ...); Returns true if all keys key1 .. keyN are down, false otherwise. void watch (String name, Function callback, String key1, String key2, ...); Creates a "watchpoint" such that pressing all of keyN will trigger the callback void unwatch (String name); Removes said watchpoint via its name void clear (void); Wipes the "keys down" cache. Equivalent to map = {} above void detach (void); Detaches the ev_kdown and ev_kup listeners from the parent element, making it possible to safely get rid of the instance

为了响应将本文发布到github的请求,我创建了一个主旨。

我已经使用声明式编程有一段时间了,现在这种方式是我个人的最爱:小提琴,pastebin

一般来说,它将与您实际需要的情况(ctrl, alt, shift)一起工作,但如果您需要同时点击,例如,a+w,那么将这些方法“组合”到一个多键查找中并不太难。


我希望这篇详细解释的微博对你有帮助:)

这是布雷登斯答案的一个实现。

Var键= {} handleKeyPress(evt) { let {keyCode, type} = evt ||事件;//处理IE let isKeyDown = (type == 'keydown'); keys[keyCode] = isKeyDown; // test: enter键被按下,shift键没有被按下 if(isKeyDown && keys[13] && !keys[16]){ Console.log('用户按下enter没有shift') } }; 窗口。addEventListener(“弹起”,handleKeyPress); 窗口。addEventListener(“keydown”,handleKeyPress);

我会试着在按键时添加一个按键事件处理程序。例句:

window.onkeydown = function() {
    // evaluate key and call respective handler
    window.onkeypress = function() {
       // evaluate key and call respective handler
    }
}

window.onkeyup = function() {
    window.onkeypress = void(0) ;
}

这只是为了说明一个模式;这里我就不详细介绍了(尤其是浏览器特定的level2+事件注册)。

请回复这是否有帮助。

Easiest, and most Effective Method

//check key press
    function loop(){
        //>>key<< can be any string representing a letter eg: "a", "b", "ctrl",
        if(map[*key*]==true){
         //do something
        }
        //multiple keys
        if(map["x"]==true&&map["ctrl"]==true){
         console.log("x, and ctrl are being held down together")
        }
    }

//>>>variable which will hold all key information<<
    var map={}

//Key Event Listeners
    window.addEventListener("keydown", btnd, true);
    window.addEventListener("keyup", btnu, true);

    //Handle button down
      function btnd(e) {
      map[e.key] = true;
      }

    //Handle Button up
      function btnu(e) {
      map[e.key] = false;
      }

//>>>If you want to see the state of every Key on the Keybaord<<<
    setInterval(() => {
                for (var x in map) {
                    log += "|" + x + "=" + map[x];
                }
                console.log(log);
                log = "";
            }, 300);