0

I have a simple game loop that goes like this:

function update(progress){
    //do something each second

    //do something else twice per second

    //do something else each two seconds
}

function draw() {
  //code here...
}

function gameLoop(timestamp) {

  update(progress)
  draw()
  
  var progress = (timestamp - lastRender)
  lastRender = timestamp

  window.requestAnimationFrame(gameLoop)
}

var lastRender = 0
window.requestAnimationFrame(gameLoop)

How can I make sure to execute some actions into the update function each helf second, second, or two seconds?

Thank you

1
  • With the help of sockets, you make message/callback queues to be rendered that can trigger an event. Let render be as fast as the browser supports but know when out of focus it is kept at a 1FPS limit. So determine on/off focus... Commented Apr 1, 2022 at 13:03

3 Answers 3

2

If you want to define the interval, you'll need to use setInterval. requestAnimationFrame will only update based on the refresh rate of the screen so you cannot define your own interval with this. setInterval has lots of downsides though, so it is recommended to sync up the interval with the refresh rate using something like this:

let doUpdate = false
setInterval(() => doUpdate = true), 1000)
const render = () => {
  if (doUpdate) {
    // your code
    doUpdate = false
  }
}
window.requestAnimationFrame(render)
Sign up to request clarification or add additional context in comments.

Comments

1

If you want to perform an action periodically when using game loop based on delta time, the basic idea is for you to keep a counter of the elapsed time. For each iteration, you then add the time difference until it reaches the intended period.

Applied to your code, it would look something like this:

let oneSecondCounter = 0
let twoSecondCounter = 0
function update(progress){
    oneSecondCounter += progress
    if (oneSecondCounter >= 1000) {
        // Code here will be executed every 1000ms
        oneSecondCounter = 0
    }

    twoSecondCounter += progress
    if (twoSecondCounter >= 2000) {
        // Code here will be executed every 2000ms
        twoSecondCounter = 0
    }
}

function draw() {}

function gameLoop(timestamp) {
  var progress = (timestamp - lastRender)

  update(progress)
  draw()

  lastRender = timestamp

  window.requestAnimationFrame(gameLoop)
}

var lastRender = performance.now()
window.requestAnimationFrame(gameLoop)

However, this means you have to make a counter variable for every periodic action you want to perform. Instead of creating a separate variable, we can bundle the counter together with the function using a closure. The closure lets each function to have their own independent counter.

The closure that we are going to use looks like this:

function initPeriodicFunction(fn, runEvery) {
    let counter = 0
    return function (deltaTime) {
        counter += deltaTime
        if (counter >= runEvery) {
            fn()
            counter = 0
        }
    }
}

Now, instead of having to create a counter variable for each action, we can just pass a function to the initPeriodicFunction and get a new function which will run only periodically.

//  You can use an arrow function
const runEverySecond = initPeriodicFunction(() => console.log('One second'), 1000)

//  Or you can pass a function
function runThis() {
    console.log('Two seconds')
}
const runEveryTwoSeconds = initPeriodicFunction(runThis, 2000)

function update(progress){
    runEverySecond(progress)
    runEveryTwoSeconds(progress)
}

Depending on your use case, the method above might be enough. If you're going to perform a more accurate tasks (e.g. physics engine), it would be better to separate the frame rate from the update rate. This is similar to how Unity's FixedUpdate works.

Imagine you want to perform a physics update every 100ms. If somehow the update call was delayed, for example 600ms after the last update, instead of performing a single update, we perform 6 updates, each with 100ms chunk. This results in more precise step-by-step calculation.

To perform this kind of fixed update, the initialization function need to be modified as follows:

function initPeriodicFunction(fn, runEvery) {
    let counter = 0
    return function (deltaTime) {
        counter += deltaTime
        while (counter >= runEvery) {
            fn()
            counter -= runEvery
        }
    }
}

Now, the function will be run either once or multiple times depending on how long has elapsed since the last update call.

4 Comments

Wow, thank you for the long explanation. My doubt is: let's say the progress is 16 each time and the onesecond counter wants to reach 1000. I'm not sure it'll take exactly one second, because 16 does not divide 1000, so I guess it'll take a bit less/more? If not, can you please explain why?
Since we're basically depending on the system's timestamp, yes, it will not be exact. If the update are called every 16ms exactly, it will trigger only after 63 frames (1008ms). That's why if we're working with something like physics engine, we often use the chunked (fixed update) approach, since in each chunk the time is exactly 16ms or however long you want each tick to be.
Ah get it. But if the update doesn't happen exactly after one second, it's the same thing as using the setInterval function, isn't it?
Both of them are inaccurate, since setInterval also can sometimes be called late due to system load or other scheduling problems. Using counter alongside rAF have the benefit that if you pause the game loop, all counters are basically frozen. If you continue the loop, it will return from the last counter. With setInterval, you have to clear the interval and reset it again from 0. Can I ask you what are you going to use the function for?
-1
setInterval(function {
  //code that happends every second goes here
}, 1000);

use setInterval().

This creates a timer and will call the function every x seconds.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.