How can we handle framerate inconsistencies (despite using deltatime)

0 favourites
  • 10 posts
From the Asset Store
With this template you will learn how to use the GooglePlay Games native plugin
  • I've noticed there's a couple of things that appear to be framerate dependent and there doesn't seem to be a way to actually solve this. Take this setup for example:

    wackytoaster.at/parachute/framerateindep.c3p

    It's as simple as it gets. Sprite1 collides with sprite2 and sprite2 vectorY is logged in the console. (then layout restarts and does it again)

    While the results are consistent over and over again for the same framerate (ignoring minute differences), it gets ugly once I switch it. My screen supports up to 240 fps, so I can easily test it.

    And there we have it. A difference in vectorY of 20! I knew beforehand that deltatime has some imprecision, but this is too large to build around.

    60fps -> 875

    240fps -> 894

    4500fps -> 897 (unlimited mode)

    Based on that it seems that the error gets exponentially smaller at higher fps, but the important ranges of 60-240hz (which I think 99.99% of screens fall into) have quite large differences.

    My project (or part of it) relies on this specific interaction actually, and the compounding error over multiple of these interactions is very noticable. I only see two options. Either I figure out some kind of formula that accounts for the differences somehow or I ship it with unlimited mode, simply assuming most computers will manage to run it at 240+ fps.

    The third option is to use a fixed tickrate for the engine which is not possible in Construct from my understanding. Though maybe it is considering there's an unlimited (ticks only) mode?

    So... what can I do? Any ideas?

  • I don't really have a proper solution but:

    You can set a minimum framerate, this clamps dt to a maximum. Making games run at slow motion if they fall below that, this is set to 30 fps by default. So this is basically a fixed tickrate, at least from one way.

    I think ideally you don't want to design the game around extremely tight timings and precision. Not only for dt but other imprecisions like input delay etc. in case the player has to react.

    Alternatively you could instead animate something like this with the timeline, which I think will be much more consistent across framerates.

  • You can set a minimum framerate, this clamps dt to a maximum. Making games run at slow motion if they fall below that, this is set to 30 fps by default. So this is basically a fixed tickrate, at least from one way.

    This doesn't solve the problem. If I set it to 60, everyone with a quicker screen has different results. If I set it to anything higher, it will run in slow-mo for slower screens. That's no good.

    I think ideally you don't want to design the game around extremely tight timings and precision. Not only for dt but other imprecisions like input delay etc. in case the player has to react.

    I understand, but I'm not even doing that. It's just that the difference is so big that it ramps up quickly and noticeably. If there was a difference of a few pixels I'd not have noticed it. The specific part I have made simply requires 2 of those interactions, and the jump is significantly harder to pull off at low framerates, as the character simply goes ~48-64 pixels lower than it should.

    Alternatively you could instead animate something like this with the timeline, which I think will be much more consistent across framerates.

    That is not gonna work in my case, I'm not gonna animate the player character in a platformer :V

    I guess my last option would be to hardcode the values but I think it would be more fun if I don't? Also who knows where else that issue creeps up again in a different form. Afaik other engines avoid this usually by being able to lock the tickrate e.g. the physics always tick at 30 fps regardless of the games framerate.

  • Ugh... so after some more experimenting I could find something odd. I decided to try and hardcode the value, to my surprise, there still is a difference in height. Double-checked the code, nothing that could interfere. Made a small repro and...

    Enabling the platform behavior... is NOT framerate independent?

    Excuse me?

    This probably is at least part of the issue!

    EDIT: Turns out that objects that are suspended in the air are the issue and it might not be a bug. Oh no how am I gonna work around that...

    EDIT2: After some pondering I do think this is actually a bug. Applying a vectorY to an object that's in the air is framerate dependant somehow.

    github.com/Scirra/Construct-3-bugs/issues/6544

  • I think your first post is mainly due to how the time of collision will vary according to the framerate. With a low framerate the objects can be overlapping a fair bit when a collision is triggered. With a high framerate the collision will be triggered closer to when the object just come into contact.

    Framerate independence mainly just ensures the motion is consistent between collisions for any framerate.

    Here's a test of different ways to do the integration for projectile motion, aka jumping. It also tests what they do at some different timesteps. The first method undershoots at lower fps, the second overshoots, the third is perfect at all fps, and the last is wrong.

    I also compared it with the platform behavior, and it is using the perfect method when jumping off the ground, and actually is improved over how it was in C2.

    The difference between jumping in the air, and jumping on the ground could have something to do with the logic to handle moving platforms but that's just a guess.

    Actually looks like it's using method 3 when on the ground and method 4 when in the air.

    dropbox.com/s/a5qzfvv7y18c3hy/plat_dt_test.capx

  • R0J0houndThat's an interesting project, though I admit I don't really understand the math behind it all that well :V

    What I described in the first post was my first suspicion as to why my game was framerate dependent, but I'm pretty sure the second problem is the actual main culprit.

    I'm not sure if you saw this but I found a workaround for the second issue:

    Now all the cubes in method 3 align almost perfectly at all framerates. It's possible that it's still just an approximation resulting in some minor error, but that's as close as I could get. How would you interpret this? Why do I have to add -gravity*dt to the vector for the cube in the air? As I believe, the game processes the gravity first, moving the cube down gravity*dt pixels for a single tick, then applying vectorY to it. At a gravity of 1500 this would result in 25px at 60fps, but only 6px at 240fps, as such turning it framerate dependent. Am I not right? Man, I wish I could actually look at the behavior code at hand and verify this myself.

    I've also found that the platform behaviors build-in doublejump suffers this issue of being framerate dependent for this exact reason. I've filed the issue again, as the old one was closed down and I do really believe that I'm correct.

    There's some more details and measurements in the bugreport if anyone is curious.

    github.com/Scirra/Construct-3-bugs/issues/6550

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • I mean the gist of method 3 is it updates the position first with gravity, then the velocity. What you’re doing seems to be undoing a velocity update and it would be 100% accurate if the next frame has the same dt.

    Can’t say why that’s needed.

  • But your method 3 does work properly in the air, even if I disable the part that sets the vY to 0 when it's in the air. I think that's quite logical as how the eventsheet works.

    1. Process velocity

    2. Set velocity (jump)

    3. next tick

    The behavior however seems to be processing gravity and velocity in a different way. Perhaps what it does is to first process velocity, then apply the new velocity after. I think behaviors are usually run after events, so it would look a bit like this:

    1. Events

    2. Process velocity

    3. Set velocity

    4. next tick

    So it would first process the velocity at vY=0+gravity*dt, resulting in 1 tick of falling, then set the new velocity, which is then processed 1 tick later. To correct this it would have to set velocity before processing it as such:

    1. Events

    2. Set velocity

    3. Process velocity

    4. next tick

    It's just a messed up order of operations I believe.

    EDIT: It seems that actually, the objects velocity before jumping has no impact. If I set it to -9000000 the cube should shoot into the air. It just does the same, framerate dependent jump. So it's not that the cubes velocityY is processed first before setting the new velocityY, it's that at some point for some reason, a tick of gravity is added deliberatly. So it can very well be some kind of edge case bugfix, which the platform behavior is full off.

  • Probably some edge case as you say. Behaviors can run before or after the event sheet, or both. That is done per behavior.

    If you are really curious about what the platform behavior is doing you can look at the source of it in c2 as it’s similar. Or you can find it in c3 when you preview. Open the browser debug console, sources tab and it’s one of the files.

  • Well, I did dig into the code and I solved it :^)

    This is a replication of the behavior code into your project. It perfectly simulates along with the actual behavior. In the screenshot mode 2 = the correct behavior. Mode 3 = how construct handles it.

    The solution is as simple as switching it around so it's in the correct order. Though I do not know if this has any unwanted sideeffects downstream in terms of bugfixes and workarounds that have been implemented previously.

Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)