Como complemento à resposta de Journeyman Geek (porque minha edição foi rejeitada) para as pessoas que estão interessadas na perspectiva de parte / desenvolvedor de código:
Da perspectiva dos programadores, para aqueles que estão interessados, os tempos do DOS eram tempos em que cada tick do CPU era importante para que os programadores mantivessem o código o mais rápido possível.
Um cenário típico em que qualquer programa será executado na velocidade máxima da CPU é simples (pseudo C):
int main()
{
while(true)
{
}
}
isso será executado para sempre, agora, vamos transformar esse trecho de código em um pseudo-jogo DOS:
int main()
{
bool GameRunning = true;
while(GameRunning)
{
ProcessUserMouseAndKeyboardInput();
ProcessGamePhysics();
DrawGameOnScreen();
//close game
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
a menos que as funções DrawGameOnScreen
usem buffer duplo / V-sync (que era um pouco caro nos dias em que os jogos DOS eram feitos), o jogo rodará na velocidade máxima da CPU.
Em um dia moderno, o i7 móvel rodaria em torno de 1.000.000 a 5.000.000 de vezes por segundo (dependendo da configuração do laptop e do uso atual da cpu).
Isso significaria que se eu conseguisse qualquer jogo DOS trabalhando em meu CPU moderno em meu 64bit windows eu poderia obter mais de mil (1000!) FPS que é muito rápido para qualquer ser humano tocar se o processamento físico "assumir "corre entre 50-60 fps.
O que os desenvolvedores atuais podem fazer:
- Habilite o V-Sync no jogo (* não disponível para aplicativos em janelas ** [também disponível em aplicativos de tela inteira])
- Meça a diferença de tempo entre a última atualização e atualize a física de acordo com a diferença de tempo que efetivamente faz o jogo / programa rodar na mesma velocidade, independentemente da taxa de FPS
- Limitar a taxa de quadros de forma programática
*** dependendo da configuração da placa gráfica / driver / os, pode ser possível.
Para o ponto 1 não há exemplo que mostrarei porque não é realmente uma "programação". Está apenas usando os recursos gráficos.
Quanto aos pontos 2 e 3, mostrarei os snippets de código e explicações correspondentes:
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;
}
}
}
Aqui você pode ver a entrada do usuário e a física levar em consideração a diferença de tempo, mas você ainda pode obter mais de 1000 FPS na tela porque o loop está rodando o mais rápido possível. Como o mecanismo de física sabe quanto tempo passou, ele não precisa depender de "nenhuma suposição" ou "uma certa taxa de quadros" para que o jogo funcione na mesma velocidade em qualquer cpu.
3:
O que os desenvolvedores podem fazer para limitar a taxa de quadros para, por exemplo, 30 FPS não é nada mais difícil, basta dar uma olhada:
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;
}
}
}
O que acontece aqui é que o programa conta quantos milissegundos se passaram, se uma certa quantia for atingida (33 ms), então ele redesenha a tela do jogo, efetivamente aplicando uma taxa de quadros próxima de ~ 30.
Além disso, dependendo do desenvolvedor, ele / ela pode optar por limitar TODO o processamento a 30 fps com o código acima ligeiramente modificado para isso:
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;
}
}
}
}
Existem alguns outros métodos, e alguns deles eu realmente odeio.
Por exemplo, usando sleep(<amount of milliseconds>)
.
Eu sei que este é um método para limitar a taxa de quadros, mas o que acontece quando o processamento do seu jogo leva 3 milissegundos ou mais? E então você executa o sono ...
isso resultará em uma taxa de quadros menor do que aquela que somente sleep()
deveria estar causando.
Vamos, por exemplo, tirar um tempo de sono de 16 ms. isso faria o programa rodar a 60 hz. agora o processamento dos dados, entrada, desenho e todo o material leva 5 milissegundos. estamos em 21 milisegundos para um loop agora que resulta em pouco menos de 50 hz, enquanto você poderia facilmente estar em 60 hz, mas por causa do sono é impossível.
Uma solução seria fazer um sono adaptativo na forma de medir o tempo de processamento e deduzir o tempo de processamento do sono desejado, resultando na correção do nosso "bug":
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);
}
}
}