0

I'm trying to write my first html5 game. However, the game loop causes my browser to become unresponsive (eventually being shut down by the browser). I created a state machine:

while(state != State.EXIT){
  switch(state){
    case State.SPLASH:
        break;
    case State.HOW_TO:
        break;
    case State.PLAY:
        oldTime=Date.now();
        state=gameLoop();
        break;
    case State.GAME_OVER:
        break;
    default:
        state=State.EXIT;
    }
}

That seems to be working okay. So, then, here's the game loop:

function gameLoop(){
var newTime=Date.now();
var delta=newTime-oldTime;

update(delta/1000); 
render();

oldTime=newTime;

return state;
}

This is where the crash happens. If I take out the return statement, it returns null or whatever javascript returns. And, that's fine. It runs once and exits. However, if I leave it in there, this is where the browser seizes up. The update function gives my character the ability to move and the render function draws one image to the screen. Very simple stuff.

NOTE: This is being written in the canvas element if that matters.

SOLUTION! I created a stateSelector() function which contains the switch statement above(without the while). However, rather than state=gameLoop, I used interval=setInterval(gameLoop, 1). Then, I use clearInterval(interval) when I want to stop, followed immediately by stateSelector(). Obviously, if I want to change the state, I do that before calling the stateSelector function. I could probably have it take in a parameter containing the state I want to go into, but that a small change that I could evaluate later. I just wanted to announce my solution in case anyone else runs into this.

Steven Detweiler
  • 151
  • 4
  • 17
  • where is the statements that create a loop ? – Diode Feb 29 '12 at 05:38
  • most answers are right, but a `setTimeOut(0)` is all you really need. It frees up the processing to push another instruction (external to your loop) onto the stack. – vol7ron Feb 29 '12 at 05:52
  • Thanks, vol. I placed that in right after the "state=gameLoop();" line and it prevented everything from crashing. However, it seems I can't get the game to respond to any input. Are you aware of any drawbacks from your method? Or is this likely a problem elsewhere in my code? – Steven Detweiler Feb 29 '12 at 18:19

4 Answers4

3

JavaScript is single-threaded and runs (in effect) in the GUI thread in all common browser environments. When you're JavaScript, the UI of the browser is not updated until the JavaScript finishes running.

You're using a while loop that will never finish, and so the UI will never get updated. To fix this, you need to restructure a little: render a frame and then tell the browser you want to render another frame soon; it can update the UI and do other browsery things and then it can get back to you to render another frame.

Implementation

There's an experimental new function called requestAnimationFrame that can do this. Since it's still experimental, to use it, you need to check for browser-specific versions of it, or if it's not available at all, provide a fallback. Here are some of the names of the browser-specific versions:

  • mozRequestAnimationFrame for Gecko (Firefox)
  • webkitRequestAnimationFrame for WebKit (Chrome and Safari)
  • msRequestAnimationFrame for Trident (Internet Explorer)

So if an unprefixed requestAnimationFrame is available, use that. If that's not available but a prefixed one is, use that. If none of those work, you can use a fallback:

function fallbackRequestAnimationFrame(func) {
    setTimeout(func, 10); // Schedule func to be run in 10 milliseconds.
}

Here's a slightly-modified version of the code found on MDN:

var myRequestAnimationFrame =
       window.requestAnimationFrame
    || window.mozRequestAnimationFrame
    || window.webkitRequestAnimationFrame
    || window.msRequestAnimationFrame
    || fallbackRequestAnimationFrame;

Once you've figured out which requestAnimationFrame function you can use, you can change your game loop (which seems to be not the gameLoop function, which has no loops, but rather the while loop) to look like this:

function runFrame() {
    switch(state) {
        // state handling code
    }
    if(state != State.EXIT) {
        myRequestAnimationFrame(runFrame);
    }
}

Then start it off:

runFrame();
icktoofay
  • 126,289
  • 21
  • 250
  • 231
  • I suppose I should mention that the benefit of `requestAnimationFrame` over `setTimeout(..., 0)` as [vol7ron](http://stackoverflow.com/users/183181) suggested in a comment on the question is that the browser can reduce the framerate (and therefore, CPU usage) if the tab is in the background and the user isn't looking. – icktoofay Feb 29 '12 at 05:55
0

I think you may need some sort of a pause, if you're looping with no pause it will consume all of the CPU processing the loop over and over, preventing the page from rendering.

Developer
  • 2,021
  • 12
  • 11
0

Generally, programmers make loops play nice by adding sleep(s) or yield() calls, but since javascript's event driven model lacks these, you would instead replace your loop with a setInterval() which could call a function containing something like your loop body every specified interval, say, every 33 milliseconds, for a 30 fps experience.

zero323
  • 322,348
  • 103
  • 959
  • 935
Umbrella
  • 4,733
  • 2
  • 22
  • 31
0

JavaScript runs on the same thread the browser uses to render the page, so if you write an infinite loop the browser never gets control back to refresh the page. (Modern browsers detect "long running" loops and offer the user a chance to abort them, but that doesn't help you with your game.)

You need to use either setTimeout() or setInterval() with some variation on the following:

function gameLoop() {
   // do calculations
   // render
   // etc

   if (!gameOver)
       setTimeout(gameLoop, 30);
}

gameLoop();

(Note: your original gameLoop() function doesn't actually loop - your loop is controlled outside the function - whereas what I've just showed does loop.)

The setTimeout() function queues up a function to be run later and then immediately continues with the next line of code. When the current code finishes executing the browser then gets control back to update the display, etc. Then after (approximately) the specified interval (in milliseconds) the queued function is executed.

The effect above is similar to a recursive call where a function calls itself directly, except using setTimeout() yields control back to the browser in the meantime.

Outside the gameLoop() function you can then define event handlers for key and/or mouse events, and have those update variables that gameLoop() will use to decide how to, e.g., move the player's character, something like I said in this answer to another question.

Community
  • 1
  • 1
nnnnnn
  • 147,572
  • 30
  • 200
  • 241