3

Why doesn't the following keydown event slow down by 3000 mil's, when I am continuously pressing the keydown event (letter k)? If I keep my finger down, the count rapidly adds up as through there is no setTimeout on mcount. why is that? There should be a delay between each count but I can't get it to work...

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();

Any advice will be appreciated!

Thanks

sdk900
  • 493
  • 3
  • 15
klewis
  • 7,459
  • 15
  • 58
  • 102
  • You are **not** using a timer. Try passing in a function-object. Try this, which shows the same flaw: `setTimeout(alert("DONE NOW"), 86400 * 1000)` (That will show the alert immediately, but was told to "wait" a day :-) –  Sep 27 '12 at 20:39
  • Also, "stacking" setTimeouts is likely not desired; since clearTimeout is passed nothing it .. does nothing (as in, doesn't stop any timeouts). setTimouet will thus lag mcount here if used correctly with a function-object, but will still effectively increase at the same speed (barring quirks of excessively many timeouts). –  Sep 27 '12 at 20:43
  • pst, thank you for that tip! I'm learning how to reduce lag and be more efficient with setTimeout in the process. I simply want to apply a delay to the rapid count. should I be checking for other tactics such as if mcount is divisible by a numerical value instead, to slow things down? thanks for any advice! – klewis Sep 27 '12 at 20:47

5 Answers5

6

1.: setTimeout() returns a timeoutId which can be cleared with clearTimeout(timeoutId). You're not doing that... so, after your 3 second delay, all those timeouts are called back-to-back.

2.: your console.log is executed immediately because you didn't wrap it in a function like so:

setTimeout(function() { console.log(mcount++) }, 3000);
canon
  • 40,609
  • 10
  • 73
  • 97
4

setTimeout does not cause a delay, it starts a timer that fires an event after the specified amount of time.

You cannot "sleep" in Javascript, you need to refactor your code so it can work with events. For your code, it looks like you will need to set a flag at first keypress. Then return, and only allow new keypresses (ie. only respond to), when the flag is cleared. The flag can then be cleared automatically after a time with setTimeout.

Community
  • 1
  • 1
driis
  • 161,458
  • 45
  • 265
  • 341
  • 2
    It is not even doing that here .. `console.log` does not return a function-object. –  Sep 27 '12 at 20:38
  • ahhh I think I am getting a clear understanding now. the 3000 is a timer, not a delay mechanism. If that is the case, what javascript method will produce a delay based on setTimeOuts timer of 3000's? – klewis Sep 27 '12 at 20:40
4

To go with what @Norguard said, here's an implementation: 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;
    }
};

I have fireRate set up as an integer indicating how many times per second the player can fire.

In the demo, I set up 3 players each with different fire rates. If you hold the spacebar down, you can see this in action.

Shmiddty
  • 13,847
  • 1
  • 35
  • 52
2

While everyone here is right, what they're missing is that you need to put a delay on the firing, not on the event being called...

Inside of your keydown event, set a timestamp, have a previous-time and a current-time for the event. Inside of the function, have a time_limit.

So when you press the key (or it fires repeatedly), check for:

current_time - last_fired >= rate_limit;

If the current time is more than 3000ms since the last shot, then set the last_fired timestamp to the current time, and fire your weapon.

EDIT

Consider this trivial example:

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]; });

Inside of your gameloop, you're now going to do things a little differently:
Your player is going to update itself.
Inside of that update, it's asking the Keyboard if it's got the shoot key pressed down.
If it is, then it will call the shoot method.

This still isn't 100% correct, as Player shouldn't care about or know about Keyboard.
It should be handled through a service of some kind, rather than asking for window.Keyboard. Regardless...

Your controls are now wrapped inside of the player -- so you can define what those controls are, rather than asking by keyCode all over the place.

Your events are now doing what they should: setting the key and going away.
In your current iteration, every time the browser fires keydown, which might be 300x/sec, if it wanted to, that event ALSO has to call all of your player logic... 300x/sec...

In larger games, you could then take this a step further, and make components out of Controls and Health, each having all of the properties and all of the methods that they need to do their own job, and nothing else.

Breaking the code up this way would also make it dirt-simple to have different guns.
Imagine an Inventory component:
The inventory contains different guns.
Each gun has its own rateLimit, has its own lastFired, has its own bulletCount, does its own damage, and fires its own bulletType.

So then you'd call player.shoot();, and inside, it would call inventory.equipped.shoot();.
That inner function would take care of all of the logic for firing the equipped gun (because you'd inventory.add(Gun); to your guns, and inventory.equip(id); the gun you want.

Norguard
  • 26,167
  • 5
  • 41
  • 49
  • ok, i'm gonna try this tactic and get back to you. Thanks Norguard. – klewis Sep 27 '12 at 21:08
  • 1
    @blachawk : have a look at the update, and see if this helps you out any. It's a little more advanced than what you're probably working on right now, but querying (polling) for state-changes... ...and using the DOM events to set state (Keyboard and Mouse objects), will make your life a billion times easier in the long-run. Especially when you get into multiplayer, or dealing with updating a *lot* of different actions, or when syncing up timing with a lot of moving enemies/bullets/platforms becomes important. – Norguard Sep 27 '12 at 22:53
  • thank you Norguard for this rich and Infallible feedback. Would you happen to have any web site recommendations on learning more about polling for state changes in javascript? I will take the example you did and play with it to get feel on its operations for my specific case. Thanks again! I do want to make my life a billion times easier, even it means I take more training time with DOM events to set states. – klewis Sep 28 '12 at 13:46
  • Here's where I add a major-disclaimer. Polling is for time-sensitive stuff, or stuff which will fire many times over. Typically, this is for games, and is how most console games have worked for ages -- you're running a 60fps loop, so instead of doing an update mid-frame, just because that was the millisecond the button was pushed, you wait until your next update, and then ask the controller which keys are pressed. For UI stuff, use events -- but again, do the bare-minimum inside of the DOM-event, itself. Call someone else to handle it -- look to mediator/observer patterns and promise/deferred – Norguard Sep 28 '12 at 14:24
  • Nobody needs to set up a 60fps loop, just for the purpose of checking if somebody clicked the `submit` button of a form, for example, or entered a blog comment. Valid times for polling, however, would be for things like `onscroll`, `onresize`, `onkeydown` (if you want to make a custom text-editor app in-browser, or other game/app response), `onmouseover` -- if you call big long program chains over and over again in these cases, you will suffer, as each one will be called constantly. So keep the setup small, and either poll, or (more normal in traditional UI) set a limiter inside your program. – Norguard Sep 28 '12 at 14:30
  • A very quick short-circuit, which says `if (timestamp - lastFired < min_delay) { return; }`, right at the top of your function, or in a helper function of the program, so that you waste no time in there. This also lets you build a mediator, so that on scroll, say, the event listener calls the mediator and says ("I'm scrolling, here's my info"), the mediator loops through and calls anybody who cares to listen. Those people all do that very quick test and leave if not needed, because each has their own timing needs, but for the same event. Again, for events which fire continuously. – Norguard Sep 28 '12 at 14:34
  • 1
    As for reading, most of mine was Douglas Crockford's "JavaScript: the Good Parts" and Stoyan Stefanov's "Javascript Patterns". Nicholas Zakas had some eye-opening talks on modular system design. Do a YouTube search for him with terms like "Sandbox" or "Scalable Architecture". He's got a ton of slide decks out there, as well (and a blog, but I find that to be more standard-fare, covered in his/Crockford's/Stefanov's books). – Norguard Sep 28 '12 at 14:56
  • kind of off topic, for function shoot, function update and function draw, will it hurt anything if i make them object literal functions under the var player object? or is that a bad idea? – klewis Sep 28 '12 at 15:03
  • It's not a bad idea at all. Just make sure that you don't expect to do any initialization of the player, if you make him a literal. My two favourite reasons for the revealing-module are that I can have all kinds of private state, which end-users can't modify by hand, and because I can do any initialization I want, inside the function. Think of an RPG where you want to say `myHero = { str : 5, damage : this.str * 2.5, dex : 3, accuracy : this.dex * 2 };`, and instead you need to create an init function, or do more setup by hand, after. Inside, they can be simple vars. – Norguard Sep 28 '12 at 16:33
  • 1
    For learning purposes, absolutely keep it as a literal. But remember that anybody can go `player.rateLimit = 100;` in the console, to awesome effect. So when it comes to taking your prototype, which can totally be hacked together (and probably should be) to a public release, make those properties enclosed variables. In a closure, either return a public interface, which accesses those vars, or return a function which has access to static vars(all returned objects can read the same values/helper functions), or both. – Norguard Sep 28 '12 at 16:44
  • 1
    (using two closures - an outer for "static" helpers, like `takeDamage`, and an inner, inside the constructor, for private vars to that instance, like `health`, `bombsLeft`, etc). Don't worry about that now, but remember it for when you are ready for it. – Norguard Sep 28 '12 at 16:45
1

You have to pass returned value of setTimeout to clearTimeout . to cancel it.

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();
Anoop
  • 23,044
  • 10
  • 62
  • 76