OnWindows, the
GetTickCount and
GetTickCount64 functions are completely useless in the terms of calculating delta. These functions are based on the system time which is refreshed only 60 times per second. This causes the delta calculated in this way:
begin
endTime := GetTickCount;
proc(endTime - begintime);
beginTime := endTime;
end;
will always be 0, 16, or 17. I can see that it keeps repeating itself and suggests the same nonsense that these functions are supposed to be useful for game development. No,
they are not — in absolute most cases they are useless (at least on Windows). Still don't believe me? Try this:
uses
SysUtils;
var
TicksOld, TicksNew: Int64;
Tries: Integer;
begin
TicksNew := GetTickCount64();
for Tries := 0 to 19 do
begin
TicksOld := TicksNew;
WriteLn(TicksOld);
repeat
TicksNew := GetTickCount64();
until TicksOld <> TicksNew;
end;
ReadLn();
end.
The result in the console will be the following:
149456484
149456500
149456515
149456531
149456546
149456562
149456578
149456593
149456609
149456625
149456640
149456656
149456671
149456687
149456703
149456718
149456734
149456750
149456765
149456781
As you can clearly see, the value of the counter changes every 15-17 milliseconds. Bravo.
If the main game loop is to freeze the thread between frames, no matter what framerate it has to keep (this can be a constant 25fps, 30fps, 60fps, 120fps or dynamic but limited), you need to calculate the freeze time with high precision and carry out the freezing with high accuracy. Some frames can be generated in 1ms and some in 15ms. At 60fps, in the first case you will have to wait a little over 15ms, and in the second only about 2ms. As I wrote, a little over, because at 60fps the time for one frame is
1000/60=16.667ms, so the calculations cannot be rounded to milliseconds.
This type of computation is easily done — on Windows, you can determine counter resolution using
QueryPerformanceFrequency function (precision is always nanosecond on Unix). To get the current counter status, use
QueryPerformanceCounter on Windows and
FPGetTimeOfDay on Unix-based systems. Take the time before and after generating the frame and then calculate their difference. Knowing the precision of the clock and how many ticks it took to generate the frame, it is easy to calculate how many nanoseconds to freeze the thread.
It's easy on Unix-based systems to freeze the thread with high accuracy, because there is the
FPNanoSleep function, which has a very high precision (microsecond, although the argument is the number of nanoseconds). It is more difficult on Windows, because
Sleep works with millisecond precision. Therefore, in order to maintain a very high precision (more or less microseconds, which is enough for the needs of typical video games), you can divide the freezing into two stages — first, using
Sleep, suspend the thread's work for a specified number of milliseconds, and the rest with busy waiting and especially for this purpose, the assembly instruction
pause was designed.
Important! If your game does not need super-high precision (you don't create e.g. an emulator), you can safely use only millisecond intervals. It is important not to round the time, but to keep it in a tank so that the remaining, less than a millisecond fraction, accumulates and is used in subsequent freezes. However, it is still necessary to rely on a high-precision counter (i.e. a hardware counter, not a shitty system clock) in order to be able to correctly calculate the waiting time and delta, as discussed below.
Another bad example is delta usage in the form of elapsed milliseconds. This is wrong because such a value is unintuitive and very difficult to use. It is very good for delta to be a floating point number, representing a fraction of a second, in the range
0 to
1. Assuming the game is running at 60fps and there is no lag, the delta should be
~0.01667. At 100fps it should be
0.01 and at 187fps it should be around
0.005348. With a variable frame rate, the delta will contain different values, but still between
0 and
1. If there is a huge lag, for example 5 seconds, the delta will be
5 and still be able to be used in the calculations, so there is no problem.
Given this delta, you determine the speed of each object and other element in the game in terms of units per second (e.g. 100px per second) and multiply it by the delta. No matter what the speed is, and no matter what the framerate (no matter if it's constant or dynamic), it's very easy to configure the behavior of objects.
If your game is going to be able to manipulate the gameplay speed (i.e. support slow-motion or accelerated-motion), you can use the second floating point number, which is the speed of the game, and use it in the delta calculation. At normal speed, this number should be
1, at slow-motion the value should be in the range
(0,1), and with accelerated motion in the range
(1,n>, where
n is any value greater than
1 (
x=2 is an acceleration of 2x).
Everything I wrote above will allow you to:
- keep any constant and any variable framerate (with the ability to change the behavior of the game while it is running),
- correctly calculate the waiting time between frames,
- correctly calculate the delta and keep it simple to use in your game engine,
- the mechanics are resistant to lags (the game does not slow down),
- maintain a very high precision of timing (depends of the needs and implementation),
- it is very easy to determine the speed of objects and elements in the game,
- it is very easy to support slow-motion and accelereted-motion regardless of the framerate.
I know all of this can seem complicated, but it's all limited to using a few functions and performing very simple calculations. There is nothing to be afraid of, because such very simple calculations can create an incredibly functional game core mechanics, not only easy to use by the programmer, but also giving players the ability to adapt its behavior to their hardware (limit framerate, safe CPU power and so on).
Everything I described will be implemented in the game I am currently working on, so if I have the timing module finished, I will gladly give you a demo for fun and source for analysis.