User Functions
Don't have an account yet? Sign up as a New User
Lost your password?
Events
There are no upcoming events
|
 |
| Author: |
|
| Dated: |
Saturday, January 27 2007 @ 03:29 AM NZDT |
| Viewed: |
238 times |
|
It's been a long time since I've talked about anything technological. I've probably lost that audience entirely, in fact I'm not sure I have an audience at all any more, but I'm writing this regardless. I've mentioned how I'm learning a lot, and I'm going to talk about something I've 80% learned and 20% come up with. It's about time steps, and how to get your game looking as smooth as possible for the least processing cost. And it's very long.
Beginning game developers inevitably run into a problem. Their games run different speeds on different computers, because they just update as fast as possible. In fact I never ran into that problem, but never mind that. There are a couple of (sensible) ways to overcome this. One is to limit the frame rate of the game, so it can never draw more than, say, 60 frames per second. Not only will this not satisfy the hardware freaks, if intensive calculations or slow hardware cause it to fail to complete the 60 frames, the game will slow down. Matrix style. For networked games this is especially bad, because (if badly done) they could get out of sync. Another option, and my preferred method for quite a while (even when my engine was threaded) was to update as fast as possible, but to base the calculations on a high precision timer. This ensures that the game keeps up with real life, but this has problems of it's own, especially when tackling physics and collision detection. A common example is objects passing through each other or acting strangely on slow computers. The jump between frames could mean an object goes from one side of a wall to the other, without ever actually intersecting it. There are ways of overcoming it, but that's beyond the scope of this post.
So how do we cope with it? Well it's nice for the physics to have a fixed time step. But it's nice to be able to skip a few frames, and even add more in to smooth it out (if for no other reason than because people have different refresh rate settings). So obviously we need to decouple gameplay/physics and rendering. I've always done this to a degree, and even had them running in separate threads at one point, but for some reason I never actually fixed the physics time step. So, with or without threading (preferably with, to avoid processing spikes when people drag windows around etc.) we make the update time step fixed and leave the graphics free to fill in the gaps. Problem solved, right?
Sort of. It could be better. Say you picked 50fps for your update frequency, because it's an even number of milliseconds, and it's fast enough to look smooth. And you didn't get thrown off by the windows sleep function, which is almost reliably 10ms late. But the user has a refresh rate of 80Hz, and a graphics card that can keep up. In this case, what their card is actually doing is rendering update 1, update 2, update 2 again, update 3, update 4, update 4 again, and so on (going in and out of phase, but never mind that). I haven't tested extensively, but I'd guess this might be noticeable. I can tell when something drops to 30fps briefly because it can't keep up with vsync, after all. And what's the point in rendering the same data multiple times?
The common solution I've seen to this problem is interpolation. Keep two sets of location data for all your objects, and use that good old high resolution timer to smoothly interpolate between them when rendering. This works well. You're sometimes rendering frames an entire update old, but given human reaction times that's not significant. But what people don't seem to realise, is that what they're actually doing is integration. You can use the same basic method for normal physics calculations. By storing the previous location and the current one, you have implicitly stored the velocity. But most physics systems store only one location, and explicitly store the velocity, which tends to come out more elegant.
So what's the reason not to use that same data to smooth out our rendering? There isn't one. It's simple, you just find the difference in time between the render and the update, multiply by the velocity and add to the location. And it's cheap, even slightly cheaper than conventional interpolation I think. Although that method will actually extrapolate ahead of the current data. Personally, I've offset it by half an update, so it's sort of centred about the update, half guessing at the past, half predicting the future. As an added bonus, it's never more than half a frame away from the latest data. UPDATE: Actually, I think how you should do it depends on how you integrate normally. If you're doing good old euler, and you calculate position based on the velocity for the current frame, your render time should always be behind your update time. If you calculate position based on the velocity from the previous frame, you should always extrapolate ahead. If you use RK4, I'd guess you should do what I did.
A 'problem' with this system is that it will predict wrong, and things will theoretically jump slightly upon receiving a new update. It's not a real problem for 2 reasons: 1. Velocities mostly change fairly smoothly, and in those cases the jump becomes insignificant. 2. The jump was insignificant anyway, because we're interpolating between updates here. It's (usually) easily smaller than the jump from one frame to the next, and we should be updating fast enough to keep things smooth. And if it ever became a problem, hypothetically speaking, (slow motion?) all you would have to do is upgrade the quality of your integration. Store acceleration as well as velocity, and get your objects moving in a series of little curves!
Ok, so maybe we should leave physics to the physics update, but it's not like we're doing collision detection or solving constraints here. This stuff is cheap, and possibly most importantly, it lets us cut down on real updates (assuming the simulation is stable enough to handle it) and free up processing time. Thereby making time for more frames, or better frames, or more complex physics. And that's always a good thing, isn't it?
|
Trackback URL for this entry: http://razorcode.net/trackback.php/TimeStepping
No trackback comments for this entry.
|