Progress in Framerate-Independent Platforming

Discussion and feedback on Construct 2

Post » Fri Aug 12, 2016 2:00 am

A while back, I stumbled upon a thread where some game developers noticed pixel-perfect platformers had different jump heights depending on the framerate. I’m pleased to announce I have a solution. Assuming your game doesn’t require changing gravity angles, add the following two events to your event sheet:

EDIT: I originally thought subtracting a value would correct your position. Adding is actually what we want, and I've changed the events accordingly.

:?: Player Platform is jumping -or- :?: Player Platform is falling
:arrow: :?: System Player.Platform.VectorY < Player.Platform.MaxFallSpeed
:arrow: :!: Player Set Y to Self.Y + ((0.5*Self.Platform.Gravity)*dt)*dt
(Sorry, I had a little trouble inserting the Screenshot)

I’ve nicknamed this trick “Platform Minus” (because "Platform Plus" is taken). C2’s built-in platform behavior makes an imperfect approximation of the physics equations behind projectile motion. This is usually close enough that the difference doesn’t matter, but it does flatten the top of the jump arc and reduce the jump height by a few pixels, and changing framerates makes the problem more pronounced. This quick fix moves the arc closer to the exact equation and is less sensitive to framerates. I built a test capx to illustrate my point:

Jump Height Testing

In the debug window, look at the array's third value after you jump and land. With the 650 px/sec jump strength and 1000 px/(sec^2) gravity, the jump height should actually be 211.25 px. Press the "0" and "2" keys to switch modes.

Platform movement in the X-direction and 8-Direction movement have the same problem, but it’s much less noticeable because max speeds are usually relatively low, and it’s a little harder to fix because the accelerations aren’t constant. I hope I can get around to modifying the original plugins, but I’m not an expert coder, and I wouldn’t complain if someone beat me to it.

Platform Minus is a simple step, but an important one, towards even better platforming with Construct 2. We’re that much closer to perfectly framerate-independent game design.

Edit: Introducing "8 Direction Minus!" This slight change to the 8 Direction behavior makes top-down movement a little smoother and a bit less-sensitive to framerate changes:
8 Direction Minus(Once you get to Google Drive, right-click the name of the whole folder and click "download" to download the whole folder)
[NOTE: If you downloaded 8 direction minus before 13 August on 5:26 PM, please download it again, as the first version actually made a worse approximation of physics than the built-in behavior.]

An additional. known error: When a sprite with 8 Direction Minus is moving in only one direction at max speed, speed in the other direction becomes a very, very small number close to zero. However, this doesn't actually move the sprite in the second direction at all.
Last edited by calebbennetts on Sat Aug 13, 2016 10:38 pm, edited 5 times in total.
Eh, Steve!
B
115
S
54
G
13
Posts: 112
Reputation: 16,150

Post » Fri Aug 12, 2016 8:45 am

Wow that's a pretty nice effort, and a lot of data analysis...

Good job!

It's always going to be difficult to get exact frame rate independence with behaviours - since by default the behaviours use some built-in form of 'dt'.

I think the only REAL way to do this is to create a COMPLETELY custom movement system (moving, jumping, crouching - everything) without using ANY behaviours at all. You then have complete control over your fps independence.

~Sol
Tired of crappy file hosts that are crappy? Get DROPBOX - https://db.tt/uwjysXJF
Moderator
B
83
S
34
G
40
Posts: 3,032
Reputation: 30,474

Post » Fri Aug 12, 2016 9:41 am

Double-Wow...

That is one hell of an analysis you've done there! Sir, I applaud you with both hands.
Perhaps the proper equation will be corrected in a future C2 version? (If my understanding of the thing is that it's "simply" maths that can be adjusted.

Also, kinda related, something awesome I discovered a few days ago: https://www.youtube.com/playlist?list=P ... 61509B4EB5
Wish I could do like in Matrix and speed-learn all of it...
B
17
S
3
G
3
Posts: 89
Reputation: 2,825

Post » Fri Aug 12, 2016 10:43 am

For the jump height issue, wouldn't it be easier to simply, when the sprite is starting to fall for the first time, set it's Y Position to the jump height desired (which can be calculated) unless it hitted a roof?


viewtopic.php?f=146&t=128434&p=905081#p905081

Jump Height = (Jump Strength)*(Jump Strength)/(2*gravity)

Also there already is a platform + plugin existing so you might consider a different name

EDIT: Ahh I see what you have done (wasn't quite awake...), it might work actually but unsure it would work with obstacles.
Game design is all about decomposing the core of your game so it becomes simple instructions.
B
53
S
22
G
18
Posts: 2,122
Reputation: 17,123

Post » Fri Aug 12, 2016 11:13 am

It looks like your solution is just to subtract off the delta-time calculation C2 does, effectively removing dt from the formula.

There's a better way: just set a minimum framerate of 200. Then dt is always fixed so stepping will be deterministic. You'll have to adjust your speeds and accelerations though.
Scirra Founder
B
398
S
236
G
88
Posts: 24,433
Reputation: 194,635

Post » Sat Aug 13, 2016 1:22 am

@Ashley
There’s actually an error in the math for the Platform and 8-Direction behaviors.
The velocity calculates right:
V = V(old) + a*dt

But the position calculates as:
Y = Y(old) + V*dt

So when you substitute the velocity equation in, it comes out:
Y = Y(old) + (V(old) + a*dt)*dt
Y = Y(old) + V(old)*dt + a*(dt)^2

But since the actual physics equation is:
Y = Y(old) + V(old)*dt + (1/2)*a*(dt)^2

Subtracting out (1/2)*a*(dt)^2 puts the position to what the physics equation predicts.

Also, I realize the classic physics equation depends on the total time from t = 0, but it works out if you think of starting a new equation every tick with the last tick’s positions and velocities as the new Y0 and V0. Or, you can think of it as position at time t and time t+dt, then subtract the two from each other to get the change in position.

My change isn’t truly framerate-independent, but it loses accuracy more slowly as the framerate drops.

@Aphrodite Is "Platform Minus" taken? It kind of works, since I'm subtracting something from the current equations. Also, I made a demo game with the new events, and it looks like the behavior handles collisions, clipping, etc. just like it normally would.
Eh, Steve!
B
115
S
54
G
13
Posts: 112
Reputation: 16,150

Post » Sat Aug 13, 2016 12:14 pm

@calebbennetts - this is pretty confusing, but I don't think it's the case... it's not that C2 uses the wrong formula, it's that it treats acceleration differently to the kinematic equations.

The kinematic equation (d = v * t + 1/2 * a * t^2) assumes the acceleration keeps increasing throughout the given time t. So for example if you have a car that starts at a given speed and keeps a steady acceleration, this will tell you how far it will travel in say 30 seconds.

Construct 2 treats movement as a sequence of very small discrete steps that happen once per frame. Given a frame is typically just 16ms, I never thought to take in to account the effect of the continuing acceleration over that time. So C2 actually assumes the velocity is constant (not accelerating) for the duration of delta-time. However since it updates the velocity according to the acceleration after every step, you still get the effect of acceleration over time.

Should Construct 2 take in to account the acceleration during delta-time? I guess that would be more correct. However there are two issues: we probably can't change this in the engine itself without breaking loads of existing games (which will all become slightly off), and the acceleration can actually change every tick as well, for example alternating between 1000p/s/s and 0p/s/s every tick. In that case which acceleration should be used for each step, given that there is a different start and finish acceleration and the engine doesn't necessarily know what the finish acceleration is? I think strictly speaking if I were starting from scratch I'd take in to account the starting acceleration over the whole step, but we can't change this now, and I think this is one of those things where as the time step tends to zero it becomes closer to correct, so this is another one of those "discrete steps don't work quite like the continuous real world" quirks.

Anyway, this doesn't quite solve the stated problem: the jump height can vary depending on the framerate because the calculations take in to account delta-time, which is based off an imperfectly accurate timer in an imperfectly scheduled operating system, so it tends to have small random variations every step. Given the movement over time is a non-linear summation, the small random variations can accumulate and it can change by a couple of pixels by the crest of a jump. Your altered formula is still susceptible to the same problem, because it still uses a randomly-varying dt, although I can see it would reduce the accumulated error assuming a steady acceleration. My solution fixes the value of dt every step so there is no random variation and you are guaranteed identical results every time, which you would still need to do with your altered formula to guarantee identical results as well.

Still, I'm wondering if a setting to use more accurate acceleration calculations would be worthwhile...

tl;dr: the problem is a randomly-varying dt, but your formula only changes whether acceleration is taken in to account during the step or not.
Scirra Founder
B
398
S
236
G
88
Posts: 24,433
Reputation: 194,635

Post » Sat Aug 13, 2016 2:59 pm

Ashley wrote:The kinematic equation (d = v * t + 1/2 * a * t^2) assumes the acceleration keeps increasing throughout the given time t. So for example if you have a car that starts at a given speed and keeps a steady acceleration, this will tell you how far it will travel in say 30 seconds.


Using this analogy I some quick maths (using Excel) to see which equation would be most reliable. For a car accelerating at 2 m/s^2 from an initial speed of zero for 30 secs, with a 1.0 sec sample (dt) period, the final displacements are:

Using Platform Equation: 930m
Using Incremental Newtonian Equation: 900m


If the sample period is changed to 0.5 sec (ie analogous to using a 120 Hz monitor instead of a 60 Hz monitor) then we get the following answers:

Using Platform Equation: 915m
Using Incremental Newtonian Equation: 900m


If the dt is changed to 2 sec (ie analogous to using a 30 Hz refresh rate) then we get the following answers:

Using Platform Equation: 960m
Using Incremental Newtonian Equation: 900m


Conclusion (Revised) -

The Newtonian equations work, whereas the equation used in the Platform Behaviour produces inconsistent results.


Edited to correct some poor public maths...
A big fan of JavaScript.
B
74
S
20
G
71
Posts: 2,230
Reputation: 44,892

Post » Sat Aug 13, 2016 7:04 pm

@Ashley @Calebbennetts,

I updated my post above - I corrected my calculations and have to conclude that Newtonian equations should be used if consistent results are to be experienced across different browser refresh rates and with variations in dt....

For anyone who's interested - here's a copy of the spreadsheet.
A big fan of JavaScript.
B
74
S
20
G
71
Posts: 2,230
Reputation: 44,892

Post » Sat Aug 13, 2016 10:07 pm

@ Colludium Thanks for your data. We definitely want to get as close to Newton as possible.

However, neither my originally-proposed method nor the built-in calculation method is correct. We actually need to add (1/2)*g*t^2.

I made a mistake in my calculations. Due to the discrete/continuous difference @Ashley mentioned, I mismatched position and velocity by one tick. When I fixed the error, Platform Minus gave a worse error than the built-in Platform behavior.

However, ADDING (1/2)*g*t^2 seemed to give a smaller error than the built-in calculations. I built a test program in Construct 2 that measures the unchanged, added, and subtracted jump heights.

Thanks to @Aphrodite for the max height equation, the actual maximum height for a 650 jump strength and 1000 gravity should be 211.25 pixels.
The Platform behavior yields a 213.958 px jump height.
Subtracting (1/2)*g*t^2 gives a less-accurate jump height: 216.667 px.
But adding (1/2)*g*t^2 gives a jump height very close to the expected value: 211.285 px.

You can find the test .capx here.

To use it, go to debug and select the array. After you jump and land, the third array value will be your jump height. Press 0, 1, and 2 to toggle between the unchanged, subtracting, and adding calculation methods, respectively.
Eh, Steve!
B
115
S
54
G
13
Posts: 112
Reputation: 16,150

Next

Return to Construct 2 General

Who is online

Users browsing this forum: No registered users and 13 guests