En complément de la réponse de Journeyman Geek (parce que mon édition a été rejetée) pour les personnes intéressées par la partie codage/point de vue du développeur :
Du point de vue des programmeurs, pour ceux que cela intéresse, l'époque du DOS était une époque où chaque tic du processeur était important, les programmeurs devaient donc garder le code aussi rapide que possible.
Un scénario typique où tout programme s'exécutera à la vitesse maximale du CPU est ce simple pseudo C :
int main()
{
while(true)
{
}
}
Cela durera toujours.
Maintenant, transformons cet extrait de code en un jeu pseudo-DOS :
int main()
{
bool GameRunning = true;
while(GameRunning)
{
ProcessUserMouseAndKeyboardInput();
ProcessGamePhysics();
DrawGameOnScreen();
// close game
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
À moins que le DrawGameOnScreen
utilise le double buffering/V-sync (ce qui était assez coûteux à l'époque où les jeux DOS étaient réalisés), le jeu fonctionnera à la vitesse maximale du CPU. Sur un portable i7 moderne, cette fonction s'exécuterait entre 1 000 000 et 5 000 000 de fois par seconde (selon la configuration de l'ordinateur portable et l'utilisation actuelle du CPU).
Cela signifie que si je pouvais faire fonctionner n'importe quel jeu DOS sur mon processeur moderne dans mon Windows 64 bits, je pourrais obtenir plus de mille (1000 !) FPS - ce qui est trop rapide pour qu'un humain puisse jouer - si le traitement physique "suppose" qu'il fonctionne entre 50 et 60 FPS.
Ce que les développeurs modernes (peuvent) faire
- Activer V-Sync dans le jeu (non disponible pour les applications fenêtrées* - c'est-à-dire uniquement disponible dans les applications plein écran)
- Mesure le temps écoulé depuis la dernière mise à jour et ajuste le traitement physique en conséquence, ce qui permet au jeu/programme de fonctionner à la même vitesse, quelle que soit la vitesse du CPU.
- Limiter le framerate de façon programmatique
* En fonction de la configuration de la carte graphique, du pilote et du système d'exploitation, il est possible d'obtenir des informations sur l'état du système. かもしれません être possible.
Pour la première option, je ne montrerai pas d'exemples car il ne s'agit pas vraiment de "programmation" - il s'agit simplement d'utiliser les fonctions graphiques.
Quant aux deux autres options, je vous montrerai les extraits de code et les explications correspondantes.
Mesurer le temps écoulé depuis la dernière mise à jour
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
while(GameRunning)
{
TimeDifference = GetCurrentTime() - LastTick;
LastTick = GetCurrentTime();
// process movements based on time passed and keys 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;
}
}
}
Ici, vous pouvez voir que l'entrée de l'utilisateur et la physique prennent en compte la différence de temps, et pourtant vous pouvez obtenir plus de 1000 FPS à l'écran parce que la boucle tourne aussi vite que possible. Comme le moteur physique sait combien de temps s'est écoulé, il n'a pas besoin de dépendre de "pas d'hypothèses" ou d'"une certaine fréquence", de sorte que le jeu fonctionnera au même framerate sur n'importe quel CPU.
Limiter le framerate de manière programmatique
Ce que les développeurs peuvent faire pour limiter le framerate à, par exemple, 30 FPS n'est pas plus difficile - il suffit de jeter un coup d'œil :
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
double DESIRED_FPS = 30;
// how many milliseconds need to pass before the next draw so we get the framerate we want
double TimeToPassBeforeNextDraw = 1000.0/DESIRED_FPS;
// note to geek programmers: this is pseudo code, so I don't care about variable types and return types
double LastDraw = GetCurrentTime();
while(GameRunning)
{
TimeDifference = GetCurrentTime() - LastTick;
LastTick = GetCurrentTime();
// process movements based on time passed and keys pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
// pass the time difference to the physics engine, so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
// if certain number of milliseconds pass...
if(LastTick-LastDraw >= TimeToPassBeforeNextDraw)
{
// draw our game
DrawGameOnScreen();
// and save when we last drew the game
LastDraw = LastTick;
}
// close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
Le programme compte les millisecondes passées et, lorsqu'un certain nombre de millisecondes est atteint (33 ms), il redessine l'écran du jeu, appliquant ainsi un taux de rafraîchissement proche de 30 FPS.
De même, le développeur peut choisir de limiter tous à 30 FPS en modifiant légèrement le code ci-dessus :
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
double DESIRED_FPS = 30;
// how many milliseconds need to pass before the next draw so we get the framerate we want
double TimeToPassBeforeNextDraw = 1000.0/DESIRED_FPS;
// note to geek programmers: this is pseudo code, so I don't care about variable types and return types
double LastDraw = GetCurrentTime();
while(GameRunning)
{
LastTick = GetCurrentTime();
TimeDifference = LastTick - LastDraw;
// if certain number of milliseconds pass...
if(TimeDifference >= TimeToPassBeforeNextDraw)
{
// process movements based on time passed and keys 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 drew the game
LastDraw = LastTick;
// close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
}
Autres alternatives
Il y a quelques autres méthodes, et certaines d'entre elles me font vraiment horreur. Par exemple, l'utilisation de sleep(NumberOfMilliseconds)
.
Je sais que c'est une méthode pour limiter le framerate, mais que se passe-t-il lorsque le traitement de votre jeu prend 3 millisecondes ou plus et que vous exécutez ensuite le sleep ? Il en résultera un framerate plus faible que celui qui ne prend que 3 millisecondes. sleep()
devrait causer.
Prenons, par exemple, un temps de sommeil de 16 ms. Ainsi, le programme fonctionnera à 60 Hz. Maintenant, disons que le traitement des données, l'entrée, le dessin et tout le reste prend 5 millisecondes. Cela nous amène à 21 millisecondes pour une boucle, ce qui donne un peu moins de 50 Hz, alors que vous pourriez facilement être encore à 60 Hz, mais à cause du sommeil codé en dur, c'est impossible.
Une solution serait d'effectuer un "sommeil adaptatif", sous la forme d'une mesure du temps de traitement et d'une déduction du temps de traitement du sommeil souhaité, ce qui permettrait de corriger notre "bug" :
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
long long NeededSleep;
while(GameRunning)
{
TimeDifference = GetCurrentTime() - LastTick;
LastTick = GetCurrentTime();
// process movements based on time passed and keys 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);
}
}
}