Jako dodatek do odpowiedzi Journeyman Geek (ponieważ moja edycja została odrzucona) dla osób, które są zainteresowane częścią kodowania / perspektywą programisty:
Z punktu widzenia programistów, dla tych, którzy są zainteresowani, czasy DOS były czasami, w których każdy takt procesora był ważny, więc programiści zachowali kod tak szybko, jak to możliwe.
Typowy scenariusz, w którym dowolny program będzie działał z maksymalną szybkością procesora, jest taki prosty (pseudo C):
int main()
{
while(true)
{
}
}
to będzie działać wiecznie, teraz zmieńmy ten fragment kodu w pseudo-grę DOS:
int main()
{
bool GameRunning = true;
while(GameRunning)
{
ProcessUserMouseAndKeyboardInput();
ProcessGamePhysics();
DrawGameOnScreen();
//close game
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
chyba że DrawGameOnScreen
funkcje używają podwójnego buforowania / synchronizacji V (co było dość drogie w czasach, gdy powstawały gry DOS), gra będzie działać z maksymalną szybkością procesora. Na współczesnym telefonie komórkowym i7 działałoby to z prędkością od 1 000 000 do 5 000 000 razy na sekundę (w zależności od konfiguracji laptopa i bieżącego użycia procesora).
Oznaczałoby to, że gdybym mógł uruchomić dowolną grę DOS działającą na moim nowoczesnym procesorze w 64-bitowych oknach, mógłbym uzyskać więcej niż tysiąc (1000!) Klatek na sekundę, co jest zbyt szybkie dla każdego człowieka, jeśli proces fizyki „zakłada”, że działa pomiędzy 50-60 fps.
Programiści (mogą) na dzień dzisiejszy to:
- Włącz V-Sync w grze (* niedostępne dla aplikacji okienkowych ** [inaczej dostępne tylko w aplikacjach pełnoekranowych])
- Zmierz różnicę czasu między ostatnią aktualizacją i zaktualizuj fizykę zgodnie z różnicą czasu, która skutecznie sprawia, że gra / program działa z tą samą prędkością, niezależnie od prędkości FPS
- Programowo ograniczaj liczbę klatek na sekundę
*** w zależności od konfiguracji karty graficznej / sterownika / systemu operacyjnego może być to możliwe.
Dla punktu 1 nie pokażę żadnego przykładu, ponieważ tak naprawdę nie jest to „programowanie”. Po prostu korzysta z funkcji graficznych.
Jeśli chodzi o pkt 2 i 3, pokażę odpowiednie fragmenty kodu i objaśnienia:
2:
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
while(GameRunning)
{
TimeDifference = GetCurrentTime()-LastTick;
LastTick = GetCurrentTime();
//process movement based on how many time passed and which keys are pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
//pass the time difference to the physics engine so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
DrawGameOnScreen();
//close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
Tutaj możesz zobaczyć, jak dane wejściowe użytkownika i fizyka uwzględniają różnicę czasu, ale wciąż możesz uzyskać ponad 1000 FPS na ekranie, ponieważ pętla działa tak szybko, jak to możliwe. Ponieważ silnik fizyki wie, ile czasu minęło, nie musi on zależeć od „żadnych założeń” lub „pewnej liczby klatek na sekundę”, więc gra będzie działać z tą samą prędkością na dowolnym procesorze.
3:
Co programiści mogą zrobić, aby ograniczyć liczbę klatek na sekundę, na przykład do 30 klatek na sekundę, jest w rzeczywistości niczym trudniejszym, wystarczy spojrzeć:
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
double FPS_WE_WANT = 30;
//how many milliseconds need to pass before we need to draw again so we get the framerate we want?
double TimeToPassBeforeNextDraw = 1000.0/FPS_WE_WANT;
//For the geek programmers: note, this is pseudo code so I don't care for variable types and return types..
double LastDraw = GetCurrentTime();
while(GameRunning)
{
TimeDifference = GetCurrentTime()-LastTick;
LastTick = GetCurrentTime();
//process movement based on how many time passed and which keys are pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
//pass the time difference to the physics engine so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
//if certain amount of milliseconds pass...
if(LastTick-LastDraw >= TimeToPassBeforeNextDraw)
{
//draw our game
DrawGameOnScreen();
//and save when we last drawn the game
LastDraw = LastTick;
}
//close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
To, co się tutaj dzieje, polega na tym, że program liczy, ile milisekund minęło, jeśli określona ilość zostanie osiągnięta (33 ms), wówczas przerysowuje ekran gry, skutecznie stosując częstotliwość klatek bliską ~ 30.
Ponadto, w zależności od programisty, może on ograniczyć przetwarzanie WSZYSTKIE do 30 fps z powyższym kodem nieco zmodyfikowanym do tego:
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
double FPS_WE_WANT = 30;
//how many miliseconds need to pass before we need to draw again so we get the framerate we want?
double TimeToPassBeforeNextDraw = 1000.0/FPS_WE_WANT;
//For the geek programmers: note, this is pseudo code so I don't care for variable types and return types..
double LastDraw = GetCurrentTime();
while(GameRunning)
{
LastTick = GetCurrentTime();
TimeDifference = LastTick-LastDraw;
//if certain amount of miliseconds pass...
if(TimeDifference >= TimeToPassBeforeNextDraw)
{
//process movement based on how many time passed and which keys are pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
//pass the time difference to the physics engine so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
//draw our game
DrawGameOnScreen();
//and save when we last drawn the game
LastDraw = LastTick;
//close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
}
Jest kilka innych metod, a niektórych z nich naprawdę nienawidzę.
Na przykład za pomocą sleep(<amount of milliseconds>)
.
Wiem, że jest to jedna z metod ograniczania liczby klatek na sekundę, ale co się dzieje, gdy przetwarzanie gry trwa 3 milisekundy lub dłużej? A potem wykonujesz sen ...
spowoduje to niższą liczbę klatek na sekundę niż ta, która sleep()
powinna tylko powodować.
Weźmy na przykład czas snu 16 ms. spowoduje to, że program będzie działał przy 60 Hz. teraz przetwarzanie danych, danych wejściowych, rysowania i wszystkich innych zajmuje 5 milisekund. mamy teraz 21 milisekund na jedną pętlę, co powoduje nieco mniej niż 50 Hz, podczas gdy możesz łatwo być przy 60 Hz, ale z powodu snu jest to niemożliwe.
Jednym z rozwiązań byłoby wykonanie adaptacyjnego snu w formie pomiaru czasu przetwarzania i odliczenia czasu przetwarzania od pożądanego snu, co skutkuje naprawieniem naszego „błędu”:
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
long long NeededSleep;
while(GameRunning)
{
TimeDifference = GetCurrentTime()-LastTick;
LastTick = GetCurrentTime();
//process movement based on how many time passed and which keys are pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
//pass the time difference to the physics engine so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
//draw our game
DrawGameOnScreen();
//close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
NeededSleep = 33 - (GetCurrentTime()-LastTick);
if(NeededSleep > 0)
{
Sleep(NeededSleep);
}
}
}