I have a sprite moving horizontally from left to right. For each iteration I want to start decreasing the speed.
This works ok, and the sprite will stop at some point, however depending on the frame rate it won’t always stop
at the same place. What’s wrong with my code?
Speed.X = 50;
public override void Update(GameTime theGameTime)
float time = (float) theGameTime.ElapsedGameTime.TotalSeconds;
Position += Speed * time;
Speed.X-=10; // Reduce speed for next iteration
if ( Speed.X < 0 ) Speed.X = 0;
Speed * time to update position, which takes frame rate into account. However you’re also reducing speed by a constant amount every frame. That update needs to account for frame rate as well.
Yes, I had thought about that, and I tried
Speed.X-=10 * time;
but that didn’t resolve it. Do you know what formula I should use?
tl;dr look at the final line of code if you don’t care for the explanation.
I think it’s because that would still give you different sized steps for an approximation of an integral, which is the continuous form of the deceleration. I don’t know what your math background is like, but I’ll try to explain this simply:
- Hopefully you understand that integrating acceleration gives you velocity, and integrating velocity gives you position.
- Changing position by a constant speed creates a line when graphing position vs. time.
- Changing position by a speed with constant acceleration creates a parabola when graphing position vs. time.
- In a game that runs at a finite frame rate, the best we can do is approximate the graph.
Speed.X -= 10 * time would accomplish that by sampling speed at discrete times, but then you still lose precision because that needs to be integrated again to get position. What you can do instead is just directly sample position at discrete times.
Acc = -10 gives
Speed = StartSpeed - 10 * time
Speed = StartSpeed - 10 * time gives
Position = StartPosition + StartSpeed * time - 5 * time ^ 2;
I’d say try using that directly.
EDIT: In general, it’s a commonly known and used formula to calculate position of an object with constant acceleration:
p(t) = p0 + v0 * t - (a0 / 2) * t^2
EDIT 2: By the way these formulas all use TotalGameTime, not ElapsedGameTime.
Thanks so much for your detailed answer. You’re right, I should have used that formula that includes that initial speed. I got it to work by doing:
Speed.X-= StartSpeed * 10 * time;
I still need to understand the logic better though. I should have taken Physics more seriously when I was in high school.
Is that a typo? Because I can’t imagine that line of code working… haha.
Anyway, glad it works.
No, it’s correct. It works with that. I’m still not too convinced why…
If I try
> Speed = StartSpeed - 10 * time
still doesn’t work.
Well I still think you should be setting Position directly if you want it to be independent of frame rate, but I’m not going to complain if it’s working. Did you change
time to be Total rather than Elapsed?
Yeah, I tried that, but it seems it messes up other things because they stopped working, so I’m afraid to touch it.
You only need 3 variables since you have a constant frame rate…
As soon as you let go of the forward button or press it. Increase your players velocity or decrease it by a acceleration amount that is a constant. Add that velocity to your position. If you want to compensate for lag or skips use a target elapsed time.
this is just pseudo code to give the idea.
timeNow = (float)gameTime.TotalGameTime.TotalSeconds;
timeElapsed = timeNow - timeLast;
timeLast = timeNow;
// deal with user pressing keys say
Vector2 Right = new Vector2(1f,0f); // this is always -1 or 1 or 1's and 0's
Vector2 Left = new Vector2(-1f,0f);
Direction = Right;
// deal with increasing movement over time.
// acceleration is a amount you define its a small value.
Velocity += Direction * Acceleration * timeElapsed;
Velocity -= Direction * Acceleration * timeElapsed;
// you might want to define some velocity limits.
Vector2 maxVelocity = new Vector(10f,10f);
if(Velocity > maxVelocity )
Velocity = maxVelocity;
if(Velocity < Vector2.Zero )
Velocity = Vector2.Zero;
// move your player.
Position += velocity * timeElapsed;
Velocity could be a float instead with some tweeking like leftRightSpeed were its just += to go right and -= to go left;
If you get a really big lag spike or your elapsed time is really big you might want to cut your velocity down to zero. You may want to also or instead multiply velocity by the elapsed time when you add it to the position. Though if you get a really really bad skip no formula can really do a perfect job to make sense of it.
Sorry for the late reply, willmotil. I was out of town because of the holidays.
Thanks so much for the code. It made me understand much better what I need to do and how it works.
I’ll try the code later today and I’ll let you know if I have any problems but I think that’s the way to go!
Did you get it working? Regardless, I have three pieces of advice that I thought I’d share, which I believe constitute the root of your problem, and are good to uphold at all times. I recommend these to everyone.
Factor in time wherever applicable.
In each calculation, if something is supposed to happens over a period of time (such as your deceleration), be sure that the calculation knows how much time is actually passing.
Speed.X -= 10; // incorrect, because the speed will decrease by 10 each update, regardless of how much time has passed
Speed -= 10 * time; //correct, because the speed will decrease by a total of 10 per second, divided amongst all updates, regardless of how many.
Watch your units.
Even though programs don’t use units themselves, you should make sure your equations’ units balance; if not, there’s something wrong!
Position += Speed; //incorrect, because position (m) is not the same unit as speed (m/s).
Position += Speed * time; //correct, because position (m) is the same unit as speed (m/s) * time (s).
Your update is to represent the total of all changes that supposedly occurred over that time interval.
This one is a little harder to catch, but if something is changing based on something else that’s changing, make sure you’re taking into consideration that changing amount.
Position += Speed * time;
Speed -= 10 * time;
Although the units balance, the above is incorrect because the position is changing based on the initial speed of the update only, even though the speed is supposedly decelerating throughout that entire interval. What you want is to change based on all the various speeds combined. This is the integration that @jnoyola described earlier.
Position += Speed * time + .5 * -10 * time * time;
Speed -= 10 * time;
The “Speed * time”, as mentioned above, factors in the initial speed, and the rest - the integration - is how much the change in speed (the acceleration or deceleration) contributes.
Thanks for that information, ed022! Yeah, it seems I was doing everything totally wrong from the beginning.
I appreciate your help so I can understand better the right way to do it.