[SOLVED] How to normalize force dependent on device fps

Hello,

I have a game that is physics based where the player controls an astronaut by applying either positive or negative force to the astronaut to transport it between planets. I have run into the issue that the rate at which the force applied to the astronaut varies due to the fps of the device the game is played upon. For example, the astronaut moves more slowly in lower fps than on devices with higher fps. This is due to the default update method within playcanvas. I thought I could resolve this by implementing a fixed updated method so the rate at which force is applied to the astronaut would be the same independent of device type but I have come to understand that this fixed update method will not be implemented within playcanvas. Can anyone provide a way to ensure that the rate force is applied to the astronaut will be the same across devices?

Here is example code I would need modified.

XXX.attributes.add('speed', {
    type: 'number',    
    default: 0.1,
    min: 0.01,
    max: 0.5,
    precision: 2,
    description: 'Controls the movement speed'
});
XXX.prototype.update = function(dt) {

if (this.app.keyboard.isPressed(pc.KEY_LEFT)) {

        forceX = -this.speed;

        forceZ += this.speed;

      

    } else if  (this.app.keyboard.isPressed(pc.KEY_RIGHT)) {

        forceX += this.speed;

        forceZ = -this.speed;

        

     

    }

this.force.x = forceX;
    this.force.z = forceZ;

    // if we have some non-zero force
    if (this.force.length()) {

        // calculate force vector
        var rX = Math.cos(-Math.PI * 0.25);
        var rY = Math.sin(-Math.PI * 0.25);
        this.force.set(this.force.x * rX - this.force.z * rY, 0, this.force.z * rX + this.force.x * rY);
        
        // clamp force to the speed
        if (this.force.length() > this.speed) {
            this.force.normalize().scale(this.speed);
        }
    }

    // apply impulse to move the entity
    this.entity.rigidbody.applyImpulse(this.force);

}

@LeXXik I was under the impression that the frame rate shouldn’t matter as force would apply acceleration to the entity?

As a kind of proof of concept when I set the refresh rate of my screen to 240 hz I find that when I apply constant force to the avatar over a 3.5 second interval I get a max velocity of

Vec3 {x: 6.189068998778673e-15, y: 0, z: -63.06977081298828}

versus when I set refresh rate to 60 hz

Vec3 {x: 1.5455574078115171e-15, y: 0, z: -15.749975204467773}

These numbers are not exact but you can see how speed and refresh rate are locked linearly, i.e. when refresh rate increases 4 fold so does speed given constant force. This is because the force is being applied to the avatar 4x faster with the 240 hz refresh rate compared to the 60 hz refresh rate.

But all force does is set acceleration (F = ma). Setting the acceleration more times per second doesn’t change the acceleration over that time.

The only thing I can think of is on a lower frame rate, acceleration decays more between frames due to friction for example.

@will Any ideas on this as well?

@yaustar you are correct, it is impulse that is the problem force * change in time which is drive by this code

this.entity.rigidbody.applyImpulse(this.force);

I think with high refresh rate the impulse is applied more frequently generating higher velocity.

Oooooh, I missed that you are using impulse, not force. In which case that does make sense that instantly giving a rigidbody energy more times per second would make it be faster.

The only thing I can think off right now is to rate limit the impulse so that you are only applying it 15fps /30fps by keeping track of the frame times

Could you provide an example of how I would do this?

I believe I found a solution but I am open to anything that may be better.

update = function(dt)
 var fps = this.app.stats.frame.fps;

if (this.app.keyboard.isPressed(pc.KEY_LEFT)) {

        forceX = -this.speed * (60/fps);

        forceZ += this.speed * (60/fps);

      

    } else if  (this.app.keyboard.isPressed(pc.KEY_RIGHT)) {

        forceX += this.speed * (60/fps);

        forceZ = -this.speed * (60/fps);

        

     

    }

This should normalize force dependent on device fps to what the force should be based on original coding of game using 60 hz refresh rate.

I was thinking something like:

Script.prototype.update = function(dt) {
    this._secsTillLastUpdate += dt;

    if (this._secsTillLastUpdate >= (1/30)) {
        this._secsTillLastUpdate -= (1/30);

        // Apply impluse
    }
}

Would this work with different refresh rates? My game works on mobile as well where refresh rates on some devices can be as low as 30 hz. I was thinking of trying to get it to dynamically adjust the speed based on the current refresh rate within the game so that performance is similar across devices.

Should do as it’s applying impulse at the same rate across different framerates. It would fail if the framerate drops below 30 fps though

1 Like

The issue is that the force and impulse application is cumulative. If you apply force 2 times in a row:

rigidbody.applyForce(0, 1, 0);
rigidbody.applyForce(0, 2, 0);

The total force applied on Y axis on rigidbody will be 3, not 2. On higher refresh rate monitors the result would be that you will apply force more times. Since the force accumulates during the simulation step, then it will be stronger.

Two ways to mitigate it:

  1. Integrate delta time into your force calculation. On higher refresh rates the force will receive smaller gains.
  2. In my games I implemented a fixedUpdate method and apply any forces on dynamic bodies there. The default update method is used for moving kinematic and non-physical entities.

This article has a good description on how to implement a fixed update method. It does also explain how to interpolate leftover delta time, which is not performed in PlayCanvas. Perhaps could be a good first PR.

3 Likes

@yaustar @LeXXik thanks a lot for your responses. I have seen that fixed update method article and will have to dig through how to perhaps implement in my game. One thing that is unique to my game, than perhaps others but I may be wrong, is that I collect avatar position and acceleration data so I think I will test each method and see how the real time adjustments made by each approach facilitate similar game “feel” dependent on refresh rate. I use my game as part of my academic research so unity between game feel and data quality are two things I need to consider. I’ll try and post results here which hopefully will be helpful for the community.

Thanks again!

1 Like


Hello @yaustar and @LeXXik
I have given my first pass at trying to normalize game performance dependent on screen refresh rate. I have had some success using the below method. As you can see in the graph the position traces of my game avatar are more similar than if no control method is used. I will provide a graph of the difference with no control method later.

However, I still find that this method does not generate a similar enough performance between a standard and high refresh rate. I have tried modeling the data based on the code below and they should be similar in theory. This led me to thinking that perhaps a change in refresh rate also has some effect on how reactive the game may be to button presses. I began to think this because the rate at which the avatar builds up speed seems higher than that at a lower refresh rate. I began to look at alternative explanations as to why this may be and I came across this video

This person found that refresh rate correlated to his reaction time using the website human bench mark reaction time. The results of his tests demonstrate some validity to the time at which a person reacts to visual stimuli based on refresh rate, but my thinking is that refresh rate has less to do with human performance versus machine performance. Is it possible that a game running at a lower refresh rate would also receive inputs and initiate commands at a slower rate as well? This may explain why the feel of the game even after trying to control for refresh rate still has the high refresh rate game performing better but I am not sure if this is the case and if it is would have the effects I am still seeing now. Any input would be great! Thanks!

update = function(dt)
 var fps = this.app.stats.frame.fps;

if (this.app.keyboard.isPressed(pc.KEY_LEFT)) {

        forceX = -this.speed * (60/fps);

        forceZ += this.speed * (60/fps);

      

    } else if  (this.app.keyboard.isPressed(pc.KEY_RIGHT)) {

        forceX += this.speed * (60/fps);

        forceZ = -this.speed * (60/fps);

        

     

    }
1 Like

Have you tried using a fixed time update using rhe method above? How to normalize force dependent on device fps - #10 by yaustar

2 Likes

Here is an example of using a fixed timestep:
https://playcanvas.com/project/914980/overview/physics-fixed-update

3 Likes

Hello again @LeXXik and @yaustar,

I already owe you a huge debt of gratitude for sticking with me on this issue. @LeXXik thank you specifically for making the fixed updated script. I have performed a type of pilot test on it however, and it does not appear to remedy the issue. I recorded said test here. As you can see even with the fixed update method the box still falls at a different rate when refresh rate is changed. Additionally, if the fixed update timestep is greater than the refresh rate the box does not fall at all.

This video is more for how game “feel” may vary.

In the above video I varied the refresh rate and kept the fixed update constant. Below is data that demonstrates how varying refresh rate and time step impact distance the box fell after 3 seconds.

Data comparing falls with varying parameters.

fixed step at 1/10
refresh rate at 60 Hz

time 3.0000000000000013
position-35.51

fixed step at 1/10
refresh rate at 50 Hz

time 3.056000000000002
position -30.16

fixed step at 1/20
refresh rate at 60 Hz

time 3.033999999999999
position -26.20

fixed step at 1/20
refresh rate at 50 Hz

time 3.060000000000002
position -20.81

fixed step at 1/10
refresh rate at 240 Hz

time 3.056000000000002
position -38.13

fixed step at 1/20
refresh rate at 240 Hz

time 3.033999999999999
position -28.31

Graph of above data

It appears that change in timestep generates a change in intercept independent of screen refresh rate. Change in distance based on screen refresh rate changes at what appears to be at some negative logarithm, i.e. changes in physics are greater at differences at lower screen refresh than higher screen refresh. However, I don’t have 120 hz screen refresh to support this. The good news each of these could be controlled for by varying how force is applied based on screen refresh rate. However, the change in distance is not consistent across different fixed time steps. At 1/30 time step with 60 hz screen the distance is -20. As I showed in the video as the time step gets closer to the refresh rate the slower the box will fall.

I do concede that this issue is not hugely significant when you consider that screen refresh rates do not vary that much. My own data from a sample of 273 individuals who have played my game supports this. 211 out of 273 played with a refresh rate between 55 and 65 hz. Although about 10% of players did play the game at a refresh rate below 49 Hz. With about 4% even playing at below 20 Hz. Yikes. So overall the vast majority play around 60 Hz and I would suspect that give or take 5 Hz, game feel doesn’t considerably vary.

However, my data does demonstrate a linear relationship between a participant’s refresh rate and their game high score (see below). This is more of an issue as a researcher, as I want to measure a person’s performance without too much interference or noise from the device with which they play. Overall, I can either remove people with extreme refresh rates from my dataset or control for refresh rate in a statistical model. However, screen refresh rates, especially for mobile, may have 120 hz become the new standard.

Thanks again for all your help and sorry for the very long reply. I hope these types of tests and posts can be informative to others in the community.

UPDATE: using applyimpulse has different effects compared to applyforce.

Additionally if there is no fixed updated method and you just let the box fall in the default environment this is how far it falls within 3 seconds

60 Hz screen refresh
time 3.000124
position-42.36

50 Hz screen refresh
time 3.009767
position-42.54

This is confusing as now there is no difference in fall between screen refresh rate…

There is no difference if I use apply impulse instead of apply force. Note this is only when force is set to 0.

This is the code I used on the box within the original app with the box.js and fixed-update.js script turned off.

 var time = 0;
// update code called every frame
BoxFall.prototype.update = function(dt) {
   
        this.entity.rigidbody.applyForce(0,0,0);
        time += dt;
        console.log("time" + time);
        console.log("position" + this.entity.localPosition.y.toFixed(2).toString());

};

However, once I change the y parameter using apply impulse things get weird

CODE with apply 1 unit of force upward

var time = 0;
// update code called every frame
BoxFall.prototype.update = function(dt) {
        //var force = new pc.Vec3(0,1,0);
   
        this.entity.rigidbody.applyImpulse(0,1,0);
        time += dt;
        console.log("time" + time);
        console.log("position" + this.entity.localPosition.y.toFixed(2).toString());

};

50 Hz
time 3.00304600001192
position 186.05

60 Hz
time 3.0043130000119183
position 229.73

So it appears with no fixed update method that apply impulse is what causes the issue. Give the equation for impulse this is understandable. When I do the same thing with apply force there is no difference. Thus, the root cause is with the apply impulse function. Makes sense.

Interesting when I change @LeXXik code from apply force to apply impulse I get the intended result, similar distance traveled with varying refresh rate.

@LeXXik method at 1/30 fixed timestep
50 Hz
time3.0000000000000013
position 86.41

time3.015999999999996
position 87.85

Although when I increase the timestep to 1/60 things get weird again and are similar to if no method is being used suggesting the timestep picked is of importance. However, when I used a timestep above the refresh rate the box still moves which is good and different from what I found in my original video.

50 Hz
time 3.0000000000000018
position 170.36

60 Hz
time 3.0129999999999937
position 214.27

For comparison here is how my previously posted method compares

My method - normalize force to 60 fps

50 Hz
time 3.007188000023842
position 77.60

60 Hz
time 3.002173
position 77.35

Of note: My method requires detecting if yf = infinity because when the app runs the fps counter is 0 for several samples to it logs infinity which shoots the box into oblivion. I included an if statement to set yf to 0 first so the box falls momentarily until the fps counter picks up. This is why its distance is smaller than the other methods. This isn’t an issue for my current game because there is a login screen and the fps counter is running fine once they get past the login screen.

CODE

var time = 0;
var yf = 1;

// update code called every frame
BoxFall.prototype.update = function(dt) {
        var fps = this.app.stats.frame.fps;
        yf = 1*60/fps;
        if(yf===Infinity){
            yf=0;
        }
        console.log(yf);
        this.entity.rigidbody.applyImpulse(0,yf,0);
        time += dt;
        console.log("time" + time);
        console.log("position" + this.entity.localPosition.y.toFixed(2).toString());

};

If I change the if statement from 0 to 1 then the result is not quire the same

50 Hz
time 3.007188000023842
position 206

60 Hz
time 3.002173
position 229

This is probably because during that delay while the counter ramps up more impulse is applied during the 60 hz than the 50 hz.

I will need to conduct another test with the very high refresh rate to see how things diverge.

Finished some high resolution tests.

@LeXXik method with 1/30 time step works great
50 Hz
87

60 Hz
85

240 Hz
88

My method
50 Hz
79

60 Hz
74

240 Hz
78

Again, mine is lower because the box is in free fall before the the fps counter kicks in. However, the box starts in the same position in each test because I had previously established that when the argument is 0 there is no difference in distance dependent on refresh rate.

It gives me pause to think that the fixed update method may resolve the issue I see in my game. I hope this has been informative on the discrepancies in game performance based on apply impulse and screen refresh rate.