Como complemento à resposta do Journeyman Geek (porque minha edição foi rejeitada) para as pessoas interessadas na parte de codificação / perspectiva do desenvolvedor:
Do ponto de vista dos programadores, para os interessados, os tempos do DOS eram momentos em que cada escala de CPU era importante; portanto, os programadores mantinham 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 vai durar para sempre, agora, vamos transformar esse trecho de código em um jogo pseudo-DOS:
int main()
{
bool GameRunning = true;
while(GameRunning)
{
ProcessUserMouseAndKeyboardInput();
ProcessGamePhysics();
DrawGameOnScreen();
//close game
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
a menos que as DrawGameOnScreen
funções usem buffer duplo / sincronização V (que era meio cara nos dias em que os jogos do DOS eram criados), o jogo será executado na velocidade máxima da CPU. Nos dias atuais, o i7 móvel seria executado 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 pudesse fazer com que qualquer jogo DOS funcionasse na minha CPU moderna em minhas janelas de 64 bits, poderia obter mais de mil (1000!) FPS, o que é muito rápido para qualquer ser humano jogar se o processamento físico "assumir" que ele é executado entre 50-60 fps.
O que os desenvolvedores do dia atual (podem) fazem é:
- Ativar V-Sync no jogo (* não disponível para aplicativos em janela ** [também disponível apenas em aplicativos em tela cheia])
- 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 programaticamente
*** dependendo da configuração da placa gráfica / driver / sistema operacional, pode ser possível.
Para o ponto 1, não há exemplo que mostrarei porque não há realmente nenhuma "programação". É apenas usando os recursos gráficos.
Como nos pontos 2 e 3, mostrarei os trechos 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 horário, mas ainda assim você pode obter mais de 1000 FPS na tela porque o loop está sendo executado 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 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 passou, se uma certa quantia for atingida (33 ms) e redesenha a tela do jogo, aplicando efetivamente 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 qps 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>)
.
Sei que esse é um método para limitar a taxa de quadros, mas o que acontece quando o processamento do 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, dormir 16 ms. isso faria o programa rodar a 60 hz. agora o processamento dos dados, entrada, desenho e todo o material leva 5 milissegundos. agora estamos em 21 milissegundos para um loop, o que resulta em pouco menos de 50 hz, enquanto você ainda pode facilmente estar a 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 de 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);
}
}
}