0

Function PS.Tick() is called every 100 milliseconds and its job is to call AI function of NPCs so they can move:

PS.Tick = function ()
{
"use strict";
for (NPCid = 0; NPCid < NPCnumber; NPCid++)
{
    NPCAI(NPCid);
};
}; 

But I want the NPCs to not move simultaneously every 100 millisecond, but do it at their own frequency, so I tried this code:

PS.Tick = function ()
{
"use strict";
for (NPCid = 0; NPCid < NPCnumber; NPCid++)
{
    var timeout = 0;
    timeout = PS.Random (1000);
    setTimeout("NPCAI(NPCid)",timeout);
};
};

Now, they don't move at all. Why? How do I make them move at different time intervals?

Alex
  • 34,899
  • 5
  • 77
  • 90
DerpTomi
  • 1
  • 2
  • 5
  • I want to point out that this could have very strange timing. Basically, every 100 milliseconds, you are deciding how far into the future you want to call `NPCAI(NPCid)`. What if, on the first time around for `NPCid=0`, the variable `timeout` is `1000`. Then, the next time `PS.Tick` is called, `timeout` is `0`. The second `NPCAI(NPCid)` would be called before the first. I'm not sure if this is an issue for your program, but I wanted to call it out... – lbstr Jul 09 '12 at 15:52
  • If you desired affect is to have each `NPC` move "at their own frequency" like you say, then you will need a different approach. If you want them to move at random intervals, this should work fine. – lbstr Jul 09 '12 at 15:54
  • lbstr - yes, they move quite odd, for a moment they are slugish and then they burst in 3 or 4 very fast moves and are slugish again. How would I fix that? – DerpTomi Jul 10 '12 at 11:55
  • well, if you reduce your random count, there will be a smaller range of intervals. Right now, it could move anywhere between 100 and 1100 milliseconds. If you reduce 1000 to 100, for example, it could move anywhere between 100 and 200 milliseconds. It will still be random, but less jumpy. – lbstr Jul 10 '12 at 15:34

3 Answers3

2

make that

for (NPCid = 0; NPCid < NPCnumber; NPCid++) {
    var timeout = 0;
    timeout = PS.Random (1000);
    (function (id) {
        setTimeout(function(){NPCAI(id)},timeout);
    })(NPCid); };

That extra function is needed to capture the ID in a closure. Without it, only the last ID of the loop will be passed in each instance.

Joe
  • 46,419
  • 33
  • 155
  • 245
  • I'm sorry I can't really understand your answer. – DerpTomi Jul 09 '12 at 15:53
  • 1
    @user1510009 See http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example for an introduction to closures and why they apply in this case (namely, function-creation inside a loop). – apsillers Jul 09 '12 at 16:02
0

Now, they don't move at all. Why?

A couple of possible reasons:

  • If NPCid isn't declared anywhere: Your code is throwing a ReferenceError.

  • If NPCid is declared somewhere but it's not a global: When you pass a string into setTimeout, it doesn't get evaluated in the current execution context and doesn't have access to NPCid. In general, don't pass strings into setTimeout.

  • If NPCid is a global: When the delayed code is executed, they'll all see the same value for NPCid, which is its value at the end of the loop.

Instead: If you're doing this on NodeJS (I'm just inferring this from what you're doing), you can do this):

PS.Tick = function ()
{
    "use strict";

    // (I'm assuming NPCid is defined somewhere; if not, add `var NPCid;` here)

    for (NPCid = 0; NPCid < NPCnumber; NPCid++)
    {
        var timeout = 0;
        timeout = PS.Random (1000);
        setTimeout(NPCAI, timeout, NPCid); // NodeJS (and Firefox) ONLY!!
    }
};

That works because on NodeJS (and Firefox), setTimeout can accept arguments to pass to the function to call.

If you're not using NodeJS or Firefox, but you do have access to ES5's Function#bind, you can do this:

PS.Tick = function ()
{
    "use strict";

    // (I'm assuming NPCid is defined somewhere; if not, add `var NPCid;` here)

    for (NPCid = 0; NPCid < NPCnumber; NPCid++)
    {
        var timeout = 0;
        timeout = PS.Random (1000);
        setTimeout(NPCAI.bind(undefined, NPCid), timeout);
    }
};

Function#bind returns a function that, when called, will call the original function with a specific this value and the arguments you give it.

If not, you can write your own bind, or do it like this:

PS.Tick = function ()
{
    "use strict";

    // (I'm assuming NPCid is defined somewhere; if not, add `var NPCid;` here)

    for (NPCid = 0; NPCid < NPCnumber; NPCid++)
    {
        var timeout = 0;
        timeout = PS.Random (1000);
        setTimeout(makeHandler(NPCid), timeout);
    }

    function makeHandler(id) {
        return function() {
            NPCAI(id);
        };
    }
};

That works by creating a function that, when called, turns around and calls NPCAI with the value we pass into it.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
-1

Your setTimeout doesn't call any functions,

try this:

setTimeout(function(){ NPCAI(NPCid); }, timeout);

or better use set interval for each NPC independently, however using lots of timeouts and intervals is highly inefficient so be aware and ready for lag.

Dpolehonski
  • 948
  • 6
  • 11
  • You need a closure around the function; otherwise, the `NPCAI` function will be called repeatedly with the last `NPCid` value. (See @Joe's answer.) – apsillers Jul 09 '12 at 15:54
  • I agree, I'd vote for joes awnser above mine but stackoverflow thinks i'm too new to do it. – Dpolehonski Jul 10 '12 at 11:07