Jump to content

Removing game logic from requestAnimationFrame

Shaun Dreclin

Recommended Posts

So I've found out that rAF is not very stable FPS-wise. People using monitors with higher refresh rates will have higher frame rates, making the game logic go way too quickly.

What should I do to convert my existing project? I'm imagining just taking everything out of the main function and only having:

function main() {
} requestAnimationFrame(main);

(On an unrelated node, should rAF be at the top or the bottom of my main function? Not sure on the difference there if there is any.)

Then I would put all my game logic in a function like this:

function logic() {
	//Do lots of stuff
	setTimeout(logic, 1000 / 60);
} logic();

But at this point, is there even any point in having a rAF loop going? Does having the logic loop running as well negate the performance boost?

And what about situations where there might be two logic ticks and only a single render tick, or two render ticks with only one logic?

Link to comment
Share on other sites

You could listen to Pixis ticker and use the deltatime in your logic update loop to speed/slow things up. I usually do this approach if the logic loop is pretty light.

setInterval could be one solution also. Just remember to calculate deltatime (using performance.now() or date.now()) as setInterval and setTimeout are not very reliable in their precision. If you end up using setTimeout/interval -based solution, then those intervals keep running even if RAF is not. This happens for example when user changes the tab.

You could also listen to RAF (either directly or through ticker) and then in the main-loop calculate if the logic loop should be run or not. This method (though in c++) has been explained here in detail http://gafferongames.com/game-physics/fix-your-timestep/

[Edit] Having RAF loop to do rendering is always good. As then the browser can control when rendering is happening.

Link to comment
Share on other sites

There is no problem is decoupling the render and logic loop/s.

RAF is simply to call your rendering before the browser is ready to repaint.

Most physics libraries do not rely on RAF/timeout/interval to do their updating, they rely on the time between ticks (delta) and make calculations based on time. It is the time that is important, not necessarily the frequency of the function you are calling.

RAF gets called all the time, the same as interval. The problem with this is that if your function takes too long to execute it will not have finished by the time you call the next one.

Using setTimeout and calling another one at the end of your function ensures no overlap but its frequency will deviate based on how long the function takes to run. To keep animations smooth this is dreadful, but for logic, where you are working out positions etc based on timing, this is absolutely fine.

You can run multiple logic loops if you like, the only problem you may have is syncing up all the interactions that occur between calls. Bare in mind that really clever solutions rely on knowing how long they have to run, for example, if you have 1000 objects to update, but, each object update takes 10ms, then it takes 10 secs to update them all so you'd want to stagger how many of those 1000 objects get updated during each frame. This is very complicated, more so in JS due to the single-threaded thing.

With RAF there is no difference if you call it early or late in your update code, for setTimeout there is. e.g. if your update function takes 100ms but your setTimeout is set to 16ms then you're going to be stacking up calls if you call setTimeout early, if you call it at the end of your (synchronous) function then your timeout will run at roughly 116 ms.

Link to comment
Share on other sites

So my code above is really all I need in my render loop, then. It'll just update as fast as it can and draw the screen to reflect the changes made in logic.

I guess the next thing is trying to make logic ticks happen at a precise interval. The logic loop up there is giving me around 58-63 ticks per second, but that's not so great. I guess my question is, is there a good way of saying "Do stuff, then call this function again precisely (1000/60) ms after the function started"? I could calculate how long to make my timeout for that, but the timeout isn't very accurate on when it actually fires. So would that mean I want a loop running as fast as possible, checking the timestamps, then call the logic function at appropriate intervals? Is there a while() loop in JS that doesn't lock up the browser?

Or am I coming at this from a totally wrong angle haha

Link to comment
Share on other sites

20 is probably fine, just went with 60 because that's what it was already running at.

Mind you the game logic is also going to be updating sprites' animation frames, so maybe faster ticks would be better.. or should sprite animations be calculated in the render loop?

tbh I would probably be better off using a tick/timing library that already exists, rather than reinventing the wheel. is there one built into pixi or do I have to add another library to my project?


Edit: Oh nice, ticker is built into pixi. Is there an example/tutorial of using it to set up a game logic loop? I'm not super brilliant when it comes to understanding documentation haha

Link to comment
Share on other sites

I usually end up using something very similar to raf-stream or raf-loop, because I like the patterns of emitters/streams

To use is super simple

function onUpdate( dt ) {
  renderer.render( stage )
  engine.update( dt )

raf( canvas ).on( 'data', onUpdate )

The premise here is that the event emitter emits every animation frame, which triggers the `onUpdate` function with the time elapsed (delta, dt). I then let pixi handle rendering the stage and I give the delta to the engine to do its updates.

Many times this is all I need but, as Ivan pointed out, often logic doesnt need to be computed so often (or is too expensive), at which point I'll create a new loop but limit the number of frames it emits (I still tie it to raf but you wouldnt have to). I've even written a small wrapper to handle frame-limiting, I'm using it here (sorry, that example isnt the cleanest, plenty of work to do in there yet). 


Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...