They say neccessity is the mother of invention...
While working on my tank game, I ran into a little problem. In order for netplay to work properly, I need my physics simulation to run the same on different PC's.
With delta timing, the time for each physics frame varies. One frame might take 1/60th of a second, the next, 1/58th.
Now normally this is good enough. The game will run pretty much the same on different PC's. But with this method, tiny floating point errors can add up. On one PC, a bullet might hit a corner and bounce off it. While on the other, that bullet might just barely clear that corner and bounce off an entirely different wall, resulting in a miss of an enemy tank.
Also, using delta timing means that one cannot easily record demos, because when you play back the demo, the frames won't take exactly the same amount of time between each that they did when recording the demo.
Now, the other method one can use to time their game is frame tweening. In tweening, every physics loop is calculated for the same length of time. But since you're not calculating a new object position every frame, you have to tween the object positions between frames. But this leads to all sorts of headaches because you object transparency gets tweened as well, and hiding objects and such might not take place exaclty when you expect it to, and collisions can sometimes act funny as a result of this.
My new method has the benefits of each of these methods, in exchange for one drawback.
My method is as follows. For each frame, calculate the amount of time the frame took, just as one would do with delta timing. Then, and here's the trick... Divide this time into discreet chunks of fixed time. Then retain any remainder, and add that to the time for the next frame.
Here's an example.
I calculate a frame of graphics. It takes 17 milliseconds to render. On my next frame, when I calcualte the physics, I divide these 17 milliseconds into chunks which are 4 milliseconds each. For each 4 millisecond timespan, I calculate my physics once. Thus every physics loop is done on a 4 millisecond chunk of time on every PC. Then, because the frame time was not evenly divisible by 4, I have one millisecond left over, which I carry forward to the next frame. The reason I do this is so that I don't lose a bit of time every frame resulting in the objects moving more slowly. For example, if my framerate was 60fps, and my time chunks were 10ms, then if I did not carry forward leftover time, I would lose 6ms of movement time every frame... My objects would move 33% slower than normal as a result. But by carrying forward the leftover time, I avoid this problem.
But carrying forward leftover time can result in choppyness. That is why I only carve time into 4ms chunks. One cannot percieve any differences between frames in how fast a camera is scrolling over a scene even when it is scrolling very slowly with such small time chunks. However, if one has a first person shooter, one need not have such precision. One could then carve time up into 16ms chunks perhaps, one for each frame at 60fps, and get away with it, not noticing any choppiness as a result, because choppiness is much harder to detect in an FPS than a scrolling game.
Now, the drawback of this method, is that one may need to calculate their physics four times a frame if the game renders at 60fps, and they will need to calculate the physics 8 times per second at 30fps, and 16 times per second at 15fps. Unfportunately it is the slower systems with lower framerates that would suffer most from having to calculate the physics more than once per frame. However, the physics in Blitz seem pretty fast, and calculating them 16 tiems per frame on a system which runs your game at 30fps might not actually impact the framerate much.
But while this method is not perfect, it does provide the benefit of physics running at a constant rate, while avoiding the icky perils of frame tweening. And it is very very simple to plug into a game which already uses delta time. Just make sure to keep the non physics stuff like getting keyboard input and updating shadows, or skyboxes, out of the physics loop, and correct the delta_time# variable whatever you call it after you mess with it for updating the physics so your other stuff will still get the correct time per frame.
Here is the main loop of my tank game currently as an example of how to do this.
While working on my tank game, I ran into a little problem. In order for netplay to work properly, I need my physics simulation to run the same on different PC's.
With delta timing, the time for each physics frame varies. One frame might take 1/60th of a second, the next, 1/58th.
Now normally this is good enough. The game will run pretty much the same on different PC's. But with this method, tiny floating point errors can add up. On one PC, a bullet might hit a corner and bounce off it. While on the other, that bullet might just barely clear that corner and bounce off an entirely different wall, resulting in a miss of an enemy tank.
Also, using delta timing means that one cannot easily record demos, because when you play back the demo, the frames won't take exactly the same amount of time between each that they did when recording the demo.
Now, the other method one can use to time their game is frame tweening. In tweening, every physics loop is calculated for the same length of time. But since you're not calculating a new object position every frame, you have to tween the object positions between frames. But this leads to all sorts of headaches because you object transparency gets tweened as well, and hiding objects and such might not take place exaclty when you expect it to, and collisions can sometimes act funny as a result of this.
My new method has the benefits of each of these methods, in exchange for one drawback.
My method is as follows. For each frame, calculate the amount of time the frame took, just as one would do with delta timing. Then, and here's the trick... Divide this time into discreet chunks of fixed time. Then retain any remainder, and add that to the time for the next frame.
Here's an example.
I calculate a frame of graphics. It takes 17 milliseconds to render. On my next frame, when I calcualte the physics, I divide these 17 milliseconds into chunks which are 4 milliseconds each. For each 4 millisecond timespan, I calculate my physics once. Thus every physics loop is done on a 4 millisecond chunk of time on every PC. Then, because the frame time was not evenly divisible by 4, I have one millisecond left over, which I carry forward to the next frame. The reason I do this is so that I don't lose a bit of time every frame resulting in the objects moving more slowly. For example, if my framerate was 60fps, and my time chunks were 10ms, then if I did not carry forward leftover time, I would lose 6ms of movement time every frame... My objects would move 33% slower than normal as a result. But by carrying forward the leftover time, I avoid this problem.
But carrying forward leftover time can result in choppyness. That is why I only carve time into 4ms chunks. One cannot percieve any differences between frames in how fast a camera is scrolling over a scene even when it is scrolling very slowly with such small time chunks. However, if one has a first person shooter, one need not have such precision. One could then carve time up into 16ms chunks perhaps, one for each frame at 60fps, and get away with it, not noticing any choppiness as a result, because choppiness is much harder to detect in an FPS than a scrolling game.
Now, the drawback of this method, is that one may need to calculate their physics four times a frame if the game renders at 60fps, and they will need to calculate the physics 8 times per second at 30fps, and 16 times per second at 15fps. Unfportunately it is the slower systems with lower framerates that would suffer most from having to calculate the physics more than once per frame. However, the physics in Blitz seem pretty fast, and calculating them 16 tiems per frame on a system which runs your game at 30fps might not actually impact the framerate much.
But while this method is not perfect, it does provide the benefit of physics running at a constant rate, while avoiding the icky perils of frame tweening. And it is very very simple to plug into a game which already uses delta time. Just make sure to keep the non physics stuff like getting keyboard input and updating shadows, or skyboxes, out of the physics loop, and correct the delta_time# variable whatever you call it after you mess with it for updating the physics so your other stuff will still get the correct time per frame.
Here is the main loop of my tank game currently as an example of how to do this.
SeedRnd MilliSecs() Min_Frame_Time = Floor(1000.0/Float(MAX_FPS)) Frame_Period = Min_Frame_Time Average_Frame_Time# = Min_Frame_Time Time_Old = MilliSecs() Current_Time = Time_Old Game_Start_Time = Current_Time Gosub Parse_CommandLine ; Select a graphics mode. Setgfx() ; Load the game media and set certain things up. Gosub Initialize_Game ; Start the game. Gosub Temp_Startgame_Code GAME_STATE = 1 Repeat Repeat ; Calculate the total time which this frame took. Current_Time = MilliSecs() ; Get the time this frame started at. Time_Delta = Current_Time - Time_Old ; Calculate the diffrence in start time between this frame and the last one. Time_Old = Current_Time ; Store the time this frame started at. Time_Delta_Sec# = Float(Time_Delta)/1000.0 Until Time_Delta < 200 ; Handle unnaturally long pauses between frames, such as when alt-tabbing out of the game. Select GAME_STATE Case 0 Case 1 Gosub Get_Local_Input ; Get input from local players. Gosub Get_AI_Input ; Get input from local AI players. Gosub Get_Network_Input ; Get input from remote players. Gosub Parse_Input ; Update player avatars according to their input. Gosub Send_Position_Update_Packet ; Send a packet to the server telling it where you are currently located. ;Gosub Animate_World ; Move bullets, etc. Case 2 Case 3 Case 4 End Select ; Divide physics up into discreet chunks of fixed time. ; This is the number of milliseconds per chunk of time. At 60fps, each frame takes 16.67 milliseconds. TimeChunkSize = 4 PhysicsLoops = (Time_Delta + LeftOverTime) / TimeChunkSize ; If the current frame had some leftover time, carry that time over to the next frame. LeftOverTime = (Time_Delta + LeftOverTime) Mod TimeChunkSize Time_Delta_Sec# = Float(TimeChunkSize) / 1000.0 For LOOP_Physics = 1 To PhysicsLoops Gosub Animate_World ; Move bullets, etc. UpdateWorld ; Do collision checking. Next Time_Delta_Sec# = Float(Time_Delta)/1000.0 ; This other stuff is after updateworld because updateworld changes the position of an entity if it collides with a surface. Gosub Animate_Cameras ; Move the cameras. Gosub Update_Shadows ; Update the tank shadows. Gosub Render_GUI ; Update the heads up display. Gosub Update_Backdrop ; Update the sky or menu backgrounds. Gosub Update_Particles ; Update the particle system. RenderWorld ; Render the current view to the back buffer. Gosub Render_2D_Overlay ; Draw 2D elements over the 3D scene. Flip True ; Swap the back buffer with the front buffer. Time_Passed = MilliSecs() - Time_Old ; Figure out how long this frame took to render including the flip. Time_Left = Frame_Period-Time_Passed ; Delay for the amount of extra time needed to slow the framerate down to MAX_FPS. Delay(Time_Left) Until Quit_Game End