Als Ergänzung zu Journeyman Geeks Antwort (weil meine Bearbeitung abgelehnt wurde) für die Leute, die sich für den Codierungsteil / die Entwicklerperspektive interessieren:
Aus Sicht der Programmierer waren die DOS-Zeiten für diejenigen, die interessiert sind, Zeiten, in denen jeder CPU-Tick wichtig war, damit die Programmierer den Code so schnell wie möglich hielten.
Ein typisches Szenario, in dem ein Programm mit maximaler CPU-Geschwindigkeit ausgeführt wird, ist das folgende einfache Szenario (Pseudo-C):
int main()
{
while(true)
{
}
}
das wird für immer laufen, jetzt wollen wir dieses Code-Snippet in ein Pseudo-DOS-Spiel verwandeln:
int main()
{
bool GameRunning = true;
while(GameRunning)
{
ProcessUserMouseAndKeyboardInput();
ProcessGamePhysics();
DrawGameOnScreen();
//close game
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
Sofern die DrawGameOnScreen
Funktionen nicht Double Buffering / V-Sync verwenden (was in den Tagen, in denen DOS-Spiele erstellt wurden, ziemlich teuer war), wird das Spiel mit maximaler CPU-Geschwindigkeit ausgeführt. Auf einem modernen mobilen i7 würde dies mit einer Geschwindigkeit von 1.000.000 bis 5.000.000 Mal pro Sekunde (abhängig von der Laptop-Konfiguration und der aktuellen CPU-Auslastung) ausgeführt.
Dies würde bedeuten, dass, wenn ich ein DOS-Spiel auf meiner modernen CPU in meinen 64-Bit-Fenstern zum Laufen bringen könnte, ich mehr als tausend (1000!) FPS bekommen könnte, was für jeden Menschen zu schnell ist, wenn die physikalische Verarbeitung davon ausgeht, dass es läuft zwischen 50-60 fps.
Was Entwickler heute tun können, ist:
- Aktiviere V-Sync im Spiel (* nicht verfügbar für fensterbasierte Anwendungen ** [auch bekannt als nur in Vollbild-Apps])
- Messen Sie die Zeitdifferenz zwischen der letzten Aktualisierung und der Aktualisierung der Physik anhand der Zeitdifferenz, die bewirkt, dass das Spiel / Programm unabhängig von der FPS-Rate mit derselben Geschwindigkeit ausgeführt wird
- Begrenzen Sie die Framerate programmgesteuert
*** Abhängig von der Konfiguration der Grafikkarte / des Treibers / des Betriebssystems ist dies möglicherweise möglich.
Für Punkt 1 gibt es kein Beispiel, das ich zeigen werde, weil es eigentlich keine "Programmierung" ist. Es werden nur die Grafikfunktionen verwendet.
Zu Punkt 2 und 3 zeige ich die entsprechenden Codefragmente und Erläuterungen:
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;
}
}
}
Hier können Sie sehen, dass die Benutzereingaben und die Physik den Zeitunterschied berücksichtigen. Sie können jedoch immer noch über 1000 FPS auf dem Bildschirm anzeigen, da die Schleife so schnell wie möglich ausgeführt wird. Da die Physik-Engine weiß, wie viel Zeit vergangen ist, muss sie nicht auf "keine Annahmen" oder "eine bestimmte Framerate" angewiesen sein, damit das Spiel auf jeder CPU mit der gleichen Geschwindigkeit funktioniert.
3:
Was Entwickler tun können, um die Framerate auf beispielsweise 30 FPS zu begrenzen, ist eigentlich nichts Schwierigeres.
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;
}
}
}
Was hier passiert, ist, dass das Programm zählt, wie viele Millisekunden vergangen sind. Wenn eine bestimmte Zeitspanne (33 ms) erreicht ist, wird der Spielbildschirm neu gezeichnet und eine Bildrate von ungefähr 30 angewendet.
Abhängig vom Entwickler kann er die ALL-Verarbeitung auf 30 fps beschränken, wobei der obige Code geringfügig geändert wird:
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;
}
}
}
}
Es gibt ein paar andere Methoden, und einige davon hasse ich wirklich.
Zum Beispiel mit sleep(<amount of milliseconds>)
.
Ich weiß, dass dies eine Methode ist, um die Framerate zu begrenzen, aber was passiert, wenn Ihre Spielverarbeitung 3 Millisekunden oder mehr dauert? Und dann führen Sie den Schlaf aus ...
Dies führt zu einer niedrigeren Bildrate als diejenige, die nur sleep()
verursacht werden sollte.
Nehmen wir zum Beispiel eine Ruhezeit von 16 ms. Dies würde das Programm auf 60 Hz laufen lassen. Jetzt dauert die Verarbeitung der Daten, der Eingaben, des Zeichnens und des gesamten Materials 5 Millisekunden. Wir sind jetzt bei 21 Millisekunden für eine Schleife, was etwas weniger als 50 Hz ergibt, während Sie leicht noch bei 60 Hz sein könnten, aber wegen des Schlafes ist es unmöglich.
Eine Lösung wäre, einen adaptiven Schlaf in Form der Messung der Verarbeitungszeit und Abzug der Verarbeitungszeit von dem gewünschten Schlaf zu machen, was zur Behebung unseres "Fehlers" führt:
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);
}
}
}