为什么 JavaScript 的 setTimeout 不会减缓 keydown 的速率?

3
为什么当我不断按下键盘事件(字母k)时,以下的keydown事件没有减速3000毫秒?如果我一直按着不放,计数会快速累加,好像在mcount上没有setTimeout。为什么会这样呢?每次计数之间应该有一个延迟,但我无法使其起作用...
var mcount = 0;
function playershoot() {
if(!game.playerHit){ 
      $(document).keydown(function(e){ 
        switch(e.keyCode){
        case 75: 
        clearTimeout();
        setTimeout(console.log(mcount++), 3000);
        break;
        }
    });
}

}
playershoot();

任何建议都将不胜感激!
谢谢

你并没有使用计时器。尝试传递一个函数对象。尝试这个,它展示了同样的缺陷:setTimeout(alert("DONE NOW"), 86400 * 1000) (这会立即显示警报,但是要求“等待”一天 :-)) - user166390
此外,“堆叠”setTimeouts可能不是所期望的;因为clearTimeout没有传递任何参数,它...什么都不做(例如,不会停止任何超时)。如果正确地使用一个函数对象,setTimouet将在这里延迟mcount,但仍然以相同的速度有效地增加(除非有过多的超时问题)。 - user166390
Pst,谢谢你的提示!我正在学习如何通过setTimeout来减少延迟并提高效率。我只是想对快速计数应用一些延迟。我是否应该检查其他策略,例如如果mcount可以被数字值整除,以减慢速度?感谢任何建议! - klewis
5个回答

6

1.: setTimeout() 返回一个可以用clearTimeout(timeoutId)清除的timeoutId。你没有那样做...所以,在三秒延迟之后,所有这些超时都会依次被调用。

2.: 因为你没有像这样将其包装在函数中,所以你的console.log会立即执行:

setTimeout(function() { console.log(mcount++) }, 3000);

所以我想要影响的项目,需要被包装在一个函数中?就这样?这就是我的问题? - klewis
1
@blachawk 一个问题之一。还请参阅第一个要点。 - user166390

4
为了配合@Norguard所说的,这是一个实现: http://jsfiddle.net/apu3P/
this.fire = function(){
    var cFire = new Date();

    if ((cFire - lastFire) / 1000 > 1/me.fireRate){            
        // code to fire the projectile
        lastFire = cFire;
    }
};

我将fireRate设置为一个整数,表示玩家每秒可以射击的次数。

在演示中,我设置了3个不同射速的玩家。如果你按住空格键,你可以看到这一点。


4
setTimeout并不会造成延迟,它会启动一个计时器,在指定的时间后触发一个事件。
在Javascript中,你无法进行"sleep"操作,需要重构代码以适应事件。对于你的代码,看起来你需要在第一次按键时设置一个标志位。然后返回,并且只有在标志位被清除时才允许新的按键(即只响应)。可以使用setTimeout自动在一段时间后清除该标志位。请参考这里

2
这里甚至没有执行.. console.log 不会返回一个函数对象。 - user166390
啊,我想我现在有了清晰的理解。3000是一个计时器,而不是延迟机制。如果是这样,哪个JavaScript方法会基于setTimeOuts 3000毫秒的计时器产生延迟? - klewis

2
虽然这里的每个人都是正确的,但他们忽略了你需要在触发事件之前设置延迟,而不是在调用事件上设置延迟......
在你的keydown事件内,设置一个时间戳,为事件设置一个previous-time和current-time。在函数内部,有一个time_limit。
所以当你按下键(或它重复触发)时,请检查:
current_time - last_fired >= rate_limit;

如果距离上一次射击已经超过3000毫秒,则将last_fired时间戳设置为当前时间,并开火武器。 编辑 考虑这个微不足道的例子:
var Keyboard = {};

var player = (function () {
    var gun = {
            charging  : false,
            lastFired : 0,
            rateLimit : 3000
        },

        controls = { shoot : 75 },

        isHit = false,
        public_interface;


    function shoot () {
        var currentTime = Date.now();

        if (gun.rateLimit > currentTime - gun.lastFired) { return; }
        /* make bullet, et cetera */

        gun.lastFired = currentTime;
    }

    function update () {
        if (Keyboard[controls.shoot] || gun.charging) { this.shoot(); }
        // if key was released before this update, then the key is gone...
        // but if the gun was charging, that means that it's ready to be fired

        // do other updates
    }

    function draw (ctx) { /* draw player */ }

    public_interface = {
        shoot : shoot,
        damage : function (amt) { isHurt = true; /* rest of your logic */ }
        draw : draw,
        update : update
    };

    return public_interface;

}());


document.addEventListener("keydown", function (e) {
    // if key is already down, exit
    if (!!Keyboard[e.keyCode]) { return; }
    // else, set the key to the time the key was pressed
    // (think of "charging-up" guns, based on how long you've held the button down)
    Keyboard[e.keyCode] = e.timeStamp;
});

document.addEventListener("keyup", function (e) { delete Keyboard[e.keyCode]; });

在你的游戏循环中,现在你需要做一些不同的事情:
你的玩家将更新自己。
在这个更新中,它会询问键盘是否按下了射击键。
如果是的话,那么它将调用射击方法。

这仍然不是100%正确的,因为玩家不应该关心或知道键盘。
它应该通过某种服务来处理,而不是请求window.Keyboard。 无论如何...

现在,你的控制已经包含在玩家内部——因此你可以定义这些控制,而不是到处询问keyCode

现在你的事件正在做它们应该做的事情:设置键并离开。
在你目前的迭代中,每当浏览器触发keydown时,可能是300次/秒,如果它想的话,那个事件也必须调用所有的玩家逻辑... 300次/秒...

在更大的游戏中,你可以进一步采取这一步,将ControlsHealth组件分别制作出来,每个组件都有它们所需的所有属性和方法,而没有其他东西。

以这种方式分解代码也会使得拥有不同枪支变得非常简单。
想象一下一个Inventory组件:
库存包含不同的guns
每个gun都有自己的rateLimit,有自己的lastFired,有自己的bulletCount,做自己的damage,并发射自己的bulletType

然后你会调用player.shoot();,在内部,它将调用inventory.equipped.shoot();
那个内部函数将负责所有装备枪支的逻辑(因为你会inventory.add(Gun);到你的枪支中,并inventory.equip(id);你想要的枪支)。


1
@blachawk:看一下这个更新,看看它是否能帮助你。它比你现在可能正在处理的要高级一些,但是查询(轮询)状态变化...并使用DOM事件来设置状态(键盘和鼠标对象),将使你的生活在长期内变得更加轻松。特别是当你涉及到多人游戏或处理大量不同操作的更新时,或者当与许多移动敌人/子弹/平台的时间同步变得重要时。 - Norguard
谢谢Norguard提供如此丰富和无懈可击的反馈。您是否有任何关于学习JavaScript中有关状态变化轮询的网站推荐?我会使用您提供的示例并进行实验,以了解其在我的特定情况下的操作。再次感谢!即使这意味着我需要花更多的时间进行DOM事件设置状态的培训,我也希望能让我的生活变得更加轻松。 - klewis
1
就阅读而言,我大部分的读物都是Douglas Crockford的《JavaScript: the Good Parts》和Stoyan Stefanov的《Javascript Patterns》。Nicholas Zakas在模块化系统设计方面有一些令人瞩目的演讲。在YouTube上搜索他的关键词,如“Sandbox”或“Scalable Architecture”。他有很多幻灯片,还有一个博客,但我发现那更像是标准食品,在他/Crockford/Stefanov的书中已经涵盖了。 - Norguard
1
为了学习目的,绝对要保留它的字面意义。但请记住任何人都可以在控制台中键入player.rateLimit = 100;,以获得惊人的效果。因此,在将您的原型(完全可以混合在一起,并且可能应该这样做)转化为公共版本时,请将这些属性封装为变量。在闭包中,要么返回一个公共接口来访问这些变量,要么返回一个具有访问静态变量(所有返回的对象都可以读取相同的值/帮助函数)能力的函数,或者两者兼备。 - Norguard
1
使用两个闭包——一个用于“静态”辅助函数(例如takeDamage),另一个在构造函数内部用于该实例的私有变量(例如healthbombsLeft等)。现在不必担心这个,但是在准备好时请记住它。 - Norguard
显示剩余6条评论

1

你需要把setTimeout返回的值传给clearTimeout来取消它。

var mcount = 0,timeout;
    function playershoot() {
    if(!game.playerHit){ 
          $(document).keydown(function(e){ 
            switch(e.keyCode){
            case 75: 
            clearTimeout(timeout );
            timeout = setTimeout(function(){
                       console.log(mcount++);
               }, 3000);
            break;
            }
        });
    }

    }
    playershoot();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接