What is the Best Practice for Javascript Game Loop



  • Hi all,

    I am using Javascript to launch me into game development for the very first time in my programming life (see my intro post in the getting to know thread).

    Rather than just relying on libraries I want to get to know the theory and the nuts and bolts of game development and there are so many tutorials online but many are out of date (eg. not using requestAnimationFrame) and some have some time delta trickery in them, and some are sosphisiticated enough to only run the update method and not the draw if it is not needed.

    I was wondering if someone could provide a best case game loop for me to look at and rip apart and understand as that would be really helpful.

    I have bought an impact license to make things easier to start, but now understanding games at a high level, I want to get down and dig deeper.

    Thanks for all your help in advance!


  • Tiger Hat

    I know what you mean about the older tutorials, luckily @Josue punched the entire internet in the face when he made this tutorial, as an added bonus I saw some webkit prefixes in there so it looks like he was covering backwards compatibility as well. Searching for snippet…done.

    // Wrap-up for the Request Anim frame API, with setTimeout fallback for old browsers.
    window.requestAnimFrame = (function(){
    return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function( callback ){
    window.setTimeout(callback, 1000 / 60);
    };
    })();

    He’s essentially covering for a member of the forum how to make a particular type of game so it’ll be more code than you maybe wanted but if you scroll down to the bottom of his first code snippet you’ll see his starting scaffold, he also details what he forked and where etc. when necessary. It all builds from the core Simple Canvas Game that LDG put up on their blog a while back.

    I appreciate you’re an experienced programmer already from your introduction post so I’m pretty sure that a lot of what you see will confirm things you already know, after you get your desired prototype running start googling some of you own code snippets looking for the times Stackoverflow picked some code apart until you get a handle on the fastest method of command x. Like always remember to use setAttribute(“thing”,“value”) unless you want to make a noob feel stupid, in which case you should use setAttributeNS(null,“thing”,“value”). Am I right folks! Zing!

    Welcome to the forums by the way, a lot of people here have been keen on finding out how Impact handles so we look forward to hearing what you get out of it.



  • Here’s the standard way:

    gameLoop();
    function gameLoop() {
      requestAnimationFrame(gameLoop, canvas);
      //Run all your game code here
    }
    

    If you want to get funky, you can.
    … see below!


  • Patron

    @bennyt said:

    but many are out of date (eg. not using requestAnimationFrame) and some have some time delta trickery in them, and some are sosphisiticated enough to only run the update method and not the draw if it is not needed.

    Yeah, I know what you mean.

    You don’t need to worry about only running the update method when it’s needed, as the RAF api is locked at 60fps maximum, no matter how fast you computer is.

    If your game detects that the computer in which it’s running is too slow to render the game at 60 fps, you can do something like this:

    
    var shouldRender = true;
    
    var render = function ()
    {
      // Draw stuff
    }
    
    var update = function ()
    {
      // Update stuff
    }
    
    var loop = function()
    {
      if(shouldRender === true)
      {
       render();
      }
    
      update();
    
      shouldRender != shouldRender;
    }
    

    Now, it’s totally recommendable to use deltaTime, or else things will get messy on slower computers.

    Just use this as an example:

    
    var xPosition = 0;
    
    var update = function ()
    {
     xPosition ++;
     ctx.clearRect(0,0,canvas.width,canvas.height);
     ctx.fillRect(xPosition,0,30,30);
     window.requestAnimationFrame(update);
    }
    
    update();
    

    In this case, the square should move at 60px per second, but if you’re running it on a slower computer it will move at a slower speed.

    So, what you should do is:

    
    var xPosition = 0;
    
    var last = new Date();
    
    var update = function ()
    {
     var now = new Date();
     var delta = now - last;
     xPosition += 1 * Math.floor(delta / 16);
     ctx.clearRect(0,0,canvas.width,canvas.height);
     ctx.fillRect(xPosition,0,30,30);
     window.requestAnimationFrame(update);
    }
    
    update();
    

    But you probably already knew about that, right?

    @Affordable_Desk said:

    @Josue punched the entire internet in the face when he made this tutorial

    Haha, I would looove to see a @Mew drawing of me punching the internet!

    Oh, that snippet actually came from Paul Irish’s amazing RAF article.



  • Thanks everyone for all your replies so far, I didn’t expect answers so quickly.

    Now that the long weekend is over, I will have some spare time and I will let you know how I go. I will upload a gist or something with my notes and comments.



  • @d13 said:

    Here’s the standard way:

    gameLoop();
    function gameLoop() {
      requestAnimationFrame(gameLoop, canvas);
      //Run all your game code here
    }
    

    If you want to get funky, you can.
    … but you don’t need to

    Interesting, I have never heard of Cargo Cult programming before.

    So I guess the take-away from this is that whilst some people abstract out the logic and rendering, it is really overkill until your game really needs it. Is that correct or did I completely miss the point?

    If @richtaur or @geoffb could way in on their experience that would be appreciated as well.

    I already have a follow up question ready to go but want to save it until I get this locked down.


  • LDG

    @bennyt said:

    If @richtaur or @geoffb could way in on their experience that would be appreciated as well.

    I was gonna but everybody else is doing great and @geoffb is more betterer than me.

    Sounds like I should probably update the Simple Canvas tutorial with RAF ;)


  • LDG

    The basic game loop in the examples above is basically what we use for our games. A couple things to consider:

    • As @Josue mentioned, calculating delta time (milliseconds since last frame) is pretty much mandatory
    • Not all browsers pass the same “time” parameter to your game loop function. Chrome passes the milliseconds since that page was opened and other browsers pass the standard UNIX time since epoch. These are radically different numbers, so depending on which browsers you’re targeting you’ll need to keep this in mind.
    • Consider “fixing” your timestep so that your game simulation ticks at a consistent rate.
    • We separate our game simulation updates from the scene graph render, but they both still occur insides the same callback from requestAnimationFrame. Something like this:
    function gameLoop (time) {
      var dt = calculateDeltaTime(time);
      updateGameWorld(dt);
      drawAllTheThings(dt);
    }
    

    In this example, the updateGameWorld function would “accumulate” time based on the delta time. Then, the game world would be updated N times based on the fixed timestep. For example, let’s say you want your game world to update in 10ms steps. If your updateGameWorld function received a dt of 16ms, it’d run the update once at 10ms and store the leftover 6ms. Next frame, another 16ms pass, so your game world accumulates 6ms + 16ms = 22ms. Since 22 / 10 = 2, the game update function is run twice, each at a fixed step of 10ms. The remaining 2ms is then stored for next frame. Rinse, repeat.

    For non-simulation purposes we use the more simple variable timestep method. Something like: var distance = dt * speedPerMillisecond;


  • LDG

    We also talked about game loops a bit in Lostcast 71! It’s mostly just more words about what I wrote here.


  • Patron

    Hey @geoffb, why should you use fixed timestep, exactly?

    I mean, sure, if your update function gets a delta too high, things will get messy because of floating point precision and stuff, but getting those high deltas probably means the player’s computer is crashing, so, he probably has more stuff to worry about other than losing his progress in AWL.

    Of course, if things get crashy, having a fixed time step will prevent crazy stuff from happening, but that’s not my point.

    My point is, if things get that messy, the computer probably isn’t fast enough to run the game anyways.


  • Patron

    @geoffb said:

    • Not all browsers pass the same “time” parameter to your game loop function. Chrome passes the milliseconds since that page was opened and other browsers pass the standard UNIX time since epoch. These are radically different numbers, so depending on which browsers you’re targeting you’ll need to keep this in mind.

    Well, but if you’re only calculating the delta time, that doesn’t matter, right?

    function gameLoop (time) {
      var dt = calculateDeltaTime(time);
      updateGameWorld(dt);
      drawAllTheThings(dt);
    }
    

    I have a question about this…

    If you’re using a 16ms timestep, and your update function gets a 32ms delta, do you execute both timesteps one right after the other, or do you first make sure that there was a delay of at least 16ms between them?

    Or maybe that doesn’t matter?


  • LDG

    @Josue said:

    getting those high deltas probably means the player’s computer is crashing

    We talked about this on the podcast yesterday: high deltas just happen, they’re aren’t necessarily always caused by the program itself. The computer might be able to handle the game fine normally, but something else took up resources (or whatever) so it has to deal with a large tick.

    Professional games need to be able to handle large ticks without exploding in some way.


  • LDG

    @Josue said:

    Hey @geoffb, why should you use fixed timestep, exactly?

    Well, even (relatively) small increases in delta time can have adverse effects on your simulation. Especially when you consider things like jumping or gravity. Here’s a semi-related article on how FPS can affect gravity. Also, the first article I linked, “Fix Your Timestep!” does a better job of explaining why a fixed timestep solves various issues.

    If you’re using a 16ms timestep, and your update function gets a 32ms delta, do you execute both timesteps one right after the other, or do you first make sure that there was a delay of at least 16ms between them?

    You execute two game world updates in succession. For example, the updateGameWorld function might look like this:

    var step = 16;
    var storedTime = 0;
    function updateGameWorld (dt) {
      storedTime += dt;
      while (storedTime >= step) {
        updateWorldState(step); // Update entities and other stuff as if "step" milliseconds have passed
        storedTime -= step;
      } 
    }
    


  • Here’s another good article on this topic;

    http://gameprogrammingpatterns.com/game-loop.html


  • Patron

    @richtaur said:

    @Josue said:

    getting those high deltas probably means the player’s computer is crashing

    high deltas just happen, they’re aren’t necessarily always caused by the program itself. The computer might be able to handle the game fine normally, but something else took up resources (or whatever) so it has to deal with a large tick.

    That’s what I meant Matt!
    You see, I didn’t say the GAME crashes, I said the COMPUTER crashes.

    Like a total crash. A system crash.

    That kind of stuff happens all the time on my crappy computer.

    E.G:

    You open a document on Excel.

    As you may (or may not) know, Microsoft’s office suite eats RAM like if it was nothing (to be fair, OpenOffice is also pretty heavy).

    Then you open AWL…

    In this case, four things might happen:

    1. AWL crashes. In this case, you’ll be very angry, you’ll close the game and open it again (or go do something else ;_; )
    2. Excel crashes. In this case, you’ll be VEERY ANGRY. Specially if you didn’t save your sheet.
    3. Your computer crashes. Like, EVERYTHING crashes. In this case, I usually just reset my computer, it takes way less time than waiting it to recover.
    4. Nothing. You’ve got a beefy computer to play on. In this case, you just keep on gaming.

    The gaming crashing isn’t that big of a deal.

    Now, losing progress on a important Excel sheet is.


  • Patron

    @geoffb said:

    @Josue said:

    Hey @geoffb, why should you use fixed timestep, exactly?

    Well, even (relatively) small increases in delta time can have adverse effects on your simulation.

    Yeah, but if they aren’t substantial enough, I don’t see any reason to care about them.

    I mean, your simulation will never look exactly the same on different computers anyways.

    Unless you’re using really large or really small floats, I don’t see why those anomalies would be substantial.

    Also, as far as I understand, that FPS can affect gravity article is talking about a integration problem, not about a variable timestep problem.

    @richtaur

    Professional games need to be able to handle large ticks without exploding in some way.

    But using fixed time-step doesn’t ensures that your game won’t explode.

    We’re talking about worst-case scenarios here, like, very slow computers.

    In these cases, I think professional games should display a “your computer is too slow to run this game” message instead of giving a bad gaming experience.

    You execute two game world updates in succession.

    Hum… Interesting…

    But why is it called “fixed-timestep”, if the delay between ticks is variable?


  • LDG

    A variable tick time is inevitable if you’re using rAF, and in many other environments. In a fixed timestep situation, your simulation is only executing as fast as you want it to. If 20 seconds passed since the last tick, a fixed timestep application would loop through that large chunk of time in whatever increment you want (e.g. 16ms/60fps) to prevent weird stuff from happening due to the large tick.


Log in to reply