Discuss Scratch

Scratch137
Scratcher
1000+ posts

Acceleration-based movement independent from framerate

Hello,

I'm trying to make a movement system that uses a “velocity” variable to smoothly move a sprite up and down.
In addition to this, the movement should occur at the same rate regardless of frame rate; in other words, travelling from point A to point B should take the same amount of time at 30 FPS, 60 FPS, or in Turbo Mode.

These are the scripts I have been using in an attempt to accomplish this:

// in the stage:
when green flag clicked
set [deltaTime v] to (0)
forever
set [lastTick v] to (timer)
broadcast [movement v] and wait
set [deltaTime v] to ((timer) - (lastTick))
end

// in the sprite:
when green flag clicked
go to x: (0) y: (0)
set [velocity v] to (0)

when I receive [movement v]
if <mouse down?> then
change [velocity v] by ((15) * (deltaTime))
else
change [velocity v] by ((-15) * (deltaTime))
end

Unfortunately, this doesn't seem to work. Switching to 60 FPS causes a noticeable speed increase; starting from 0 velocity, the sprite can move from -150 Y to 150 Y in about 0.8 seconds (compared with about 1.1 seconds at 30 FPS). In Turbo Mode, the sprite immediately jumps from the starting point to the end point.

Strangely, this same deltaTime system appears to work flawlessly when the sprite is moving at a constant velocity:

when I receive [movement v]
if <mouse down?> then
change y by ((200) * (deltaTime))
else
change y by ((-200) * (deltaTime))
end

Is there any way I can make the movement work independently from the frame rate? (Working in Turbo Mode isn't necessary.)

Last edited by Scratch137 (Dec. 11, 2023 22:44:27)

clodpoll
Scratcher
16 posts

Acceleration-based movement independent from framerate

I don't know much about this, but I'm pretty sure that the timer variable is affected by the frame rate. Try using the “days since 2000”, as I've heard that is the most accurate measurement of time.

Wait, nvm I actually found something exactly about this. Here: Scratch Wiki
Scratch137
Scratcher
1000+ posts

Acceleration-based movement independent from framerate

clodpoll wrote:

(#2)
I don't know much about this, but I'm pretty sure that the timer variable is affected by the frame rate. Try using the “days since 2000”, as I've heard that is the most accurate measurement of time.

Wait, nvm I actually found something exactly about this. Here: Scratch Wiki
The timer is not affected by the frame rate; if it was, those FPS counters would not work properly.
I've already verified that my method works when moving at a constant speed, so the delta time calculation itself probably isn't the problem.

EDIT: Here's a project that shows off the discrepancy. Hold down ALT and click the green flag to toggle between 30 and 60 FPS.

Last edited by Scratch137 (Dec. 12, 2023 04:13:09)

RokCoder
Scratcher
1000+ posts

Acceleration-based movement independent from framerate

You can't simply accumulate acceleration in that manner. Consider you're running at 30fps and the acceleration is 10 pixels per frame. This would give the following velocities per frame over the first tenth of a second -
  1. 10
  2. 20
  3. 30
Whereas with 60 fps you would have -
  1. 5
  2. 10
  3. 15
  4. 20
  5. 25
  6. 30
Simply adding those velocities will give very different answers and hence the 60 fps test that you're running will be moving faster.

If acceleration is constant as in your example, you could simply use the equation - s = ut + 0.5 * a * t * 2. In your example, the initial speed is zero so you would have s = 0.5 * a * t * t

Last edited by RokCoder (Dec. 12, 2023 10:00:01)

Scratch137
Scratcher
1000+ posts

Acceleration-based movement independent from framerate

RokCoder wrote:

(#4)
You can't simply accumulate acceleration in that manner. Consider you're running at 30fps and the acceleration is 10 pixels per frame. This would give the following velocities per frame over the first tenth of a second -
  1. 10
  2. 20
  3. 30
Whereas with 60 fps you would have -
  1. 5
  2. 10
  3. 15
  4. 20
  5. 25
  6. 30
Simply adding those velocities will give very different answers and hence the 60 fps test that you're running will be moving faster.

If acceleration is constant as in your example, you could simply use the equation - s = ut + 0.5 * a * t * 2. In your example, the initial speed is zero so you would have s = 0.5 * a * t * t
Ah, thank you for the explanation! Now the bug makes sense.
The new equation seems to have solved the framerate issue.

However, I now have a different problem.
One thing I neglected to include in my example is that the movement direction at any given point is determined by the player; holding down the mouse button makes the sprite go up, and letting go makes it go down.
The new equation is quite floaty and unresponsive when changing direction. Additionally, it seems to require much greater numbers in order to achieve similar speeds.

This is what that part of the script looks like now:

...
if <mouse down?> then
change [velocity v] by (((velocity) * (deltaTime)) + (((0.5) * (600)) * ((deltaTime) * (deltaTime))))
else
change [velocity v] by (((velocity) * (deltaTime)) + (((0.5) * (-600)) * ((deltaTime) * (deltaTime))))
end
change y by (velocity)

Last edited by Scratch137 (Dec. 12, 2023 22:32:44)

RokCoder
Scratcher
1000+ posts

Acceleration-based movement independent from framerate

You can use s = ut + 0.5 * a * t² for the distance displacement and v = u + a * t for the velocity change

define movement with a: (a) dt: (dt)
change [y v] by (((v) * (dt)) + (([0.5] * (a)) * ((dt) * (dt))))
change [v v] by ((a) * (dt)

As you have constant acceleration through the period of dt this should give the result that you're after.
Scratch137
Scratcher
1000+ posts

Acceleration-based movement independent from framerate

RokCoder wrote:

(#6)
You can use s = ut + 0.5 * a * t² for the distance displacement and v = u + a * t for the velocity change

define movement with a: (a) dt: (dt)
change [y v] by (((v) * (dt)) + (([0.5] * (a)) * ((dt) * (dt))))
change [v v] by ((a) * (dt)

As you have constant acceleration through the period of dt this should give the result that you're after.
Looks like that did it. Thank you so much!

Powered by DjangoBB