Como una adición a la respuesta de Journeyman Geek (porque mi edición fue rechazada) para las personas interesadas en la parte de codificación / perspectiva del desarrollador:
Desde la perspectiva de los programadores, para aquellos que estén interesados, los tiempos de DOS fueron momentos en los que cada tic de CPU era importante, por lo que los programadores mantuvieron el código lo más rápido posible.
Un escenario típico donde cualquier programa se ejecutará a la velocidad máxima de la CPU es este simple (pseudo C):
int main()
{
while(true)
{
}
}
esto se ejecutará para siempre, ahora, vamos a convertir este fragmento de código en un juego pseudo-DOS:
int main()
{
bool GameRunning = true;
while(GameRunning)
{
ProcessUserMouseAndKeyboardInput();
ProcessGamePhysics();
DrawGameOnScreen();
//close game
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
a menos que las DrawGameOnScreen
funciones utilicen doble búfer / sincronización V (que era un poco costoso en los días en que se creaban los juegos de DOS), el juego se ejecutará a la velocidad máxima de la CPU. En un i7 móvil moderno, esto funcionaría entre 1,000,000 y 5,000,000 veces por segundo (dependiendo de la configuración de la computadora portátil y el uso actual de la CPU).
Esto significaría que si pudiera hacer que un juego de DOS funcione en mi CPU moderna en mis ventanas de 64 bits, podría obtener más de mil (1000) FPS, que es demasiado rápido para que cualquier humano lo juegue si el procesamiento de física "asume" que se ejecuta entre 50-60 fps.
Lo que los desarrolladores actuales (pueden) hacer es:
- Habilite V-Sync en el juego (* no disponible para aplicaciones con ventana ** [también conocido como disponible en aplicaciones de pantalla completa])
- Mida la diferencia horaria entre la última actualización y actualice la física de acuerdo con la diferencia horaria que efectivamente hace que el juego / programa se ejecute a la misma velocidad, independientemente de la tasa de FPS
- Limite la velocidad de fotogramas programáticamente
*** dependiendo de la configuración de la tarjeta gráfica / controlador / sistema operativo puede ser posible.
Para el punto 1 no hay ningún ejemplo que mostraré porque en realidad no es ninguna "programación". Solo está usando las características gráficas.
En cuanto a los puntos 2 y 3, mostraré los fragmentos de código y las explicaciones correspondientes:
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;
}
}
}
Aquí puede ver la entrada del usuario y la física teniendo en cuenta la diferencia horaria, pero aún puede obtener más de 1000 FPS en la pantalla porque el ciclo se ejecuta lo más rápido posible. Debido a que el motor de física sabe cuánto tiempo pasó, no tiene que depender de "ninguna suposición" o "cierta velocidad de cuadros", por lo que el juego funcionará a la misma velocidad en cualquier CPU.
3:
Lo que los desarrolladores pueden hacer para limitar la velocidad de cuadros a, por ejemplo, 30 FPS en realidad no es nada más difícil, solo eche un vistazo:
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;
}
}
}
Lo que sucede aquí es que el programa cuenta cuántos milisegundos han pasado, si se alcanza una cierta cantidad (33 ms), vuelve a dibujar la pantalla del juego, aplicando efectivamente una velocidad de fotogramas cercana a ~ 30.
Además, dependiendo del desarrollador, él / ella puede elegir limitar TODO el procesamiento a 30 fps con el código anterior ligeramente modificado para esto:
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;
}
}
}
}
Hay algunos otros métodos, y algunos de ellos realmente los odio.
Por ejemplo, usando sleep(<amount of milliseconds>)
.
Sé que este es un método para limitar la velocidad de fotogramas, pero ¿qué sucede cuando el procesamiento de tu juego tarda 3 milisegundos o más? Y luego ejecutas el sueño ...
esto dará como resultado una velocidad de fotogramas más baja que la que solo sleep()
debería estar causando.
Tomemos por ejemplo un tiempo de sueño de 16 ms. esto haría que el programa se ejecute a 60 hz. ahora el procesamiento de los datos, la entrada, el dibujo y todo lo demás lleva 5 milisegundos. Estamos a 21 milisegundos para un ciclo ahora, lo que resulta en un poco menos de 50 hz, mientras que fácilmente podría estar a 60 hz, pero debido al sueño es imposible.
Una solución sería hacer un sueño adaptativo en forma de medir el tiempo de procesamiento y deducir el tiempo de procesamiento del sueño deseado, lo que da como resultado la reparación de nuestro "error":
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);
}
}
}