J'ai récemment lu cet article sur les boucles de jeu: http://www.koonsolo.com/news/dewitters-gameloop/
Et la dernière mise en œuvre recommandée me déroute profondément. Je ne comprends pas comment cela fonctionne, et cela ressemble à un gâchis complet.
Je comprends le principe: mettre à jour le jeu à une vitesse constante, avec ce qui reste rendre le jeu autant de fois que possible.
Je suppose que vous ne pouvez pas utiliser:
- Obtenez une entrée pour 25 ticks
- Jeu de rendu pour 975 ticks
Approche puisque vous obtiendriez une entrée pour la première partie de la seconde et que cela vous semblerait bizarre? Ou est-ce ce qui se passe dans l'article?
Essentiellement:
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)
Comment est-ce même valable?
Assumons ses valeurs.
MAX_FRAMESKIP = 5
Supposons que next_game_tick, qui a été affecté quelques instants après l'initialisation, avant que la boucle de jeu principale ne dise ... 500.
Enfin, comme j'utilise SDL et OpenGL pour mon jeu, avec OpenGL utilisé uniquement pour le rendu, supposons que GetTickCount()
renvoie le temps écoulé depuis l'appel de SDL_Init, ce qu'il fait.
SDL_GetTicks -- Get the number of milliseconds since the SDL library initialization.
Source: http://www.libsdl.org/docs/html/sdlgetticks.html
L'auteur suppose également ceci:
DWORD next_game_tick = GetTickCount();
// GetTickCount() returns the current number of milliseconds
// that have elapsed since the system was started
Si nous développons la while
déclaration, nous obtenons:
while( ( 750 > 500 ) && ( 0 < 5 ) )
750 car le temps s'est écoulé depuis qu'il a next_game_tick
été attribué. loops
est nul comme vous pouvez le voir dans l'article.
Nous avons donc entré la boucle while, faisons un peu de logique et acceptons une entrée.
Yada yada yada.
À la fin de la boucle while, je vous rappelle que notre boucle de jeu principale est la suivante:
next_game_tick += SKIP_TICKS;
loops++;
Mettons à jour à quoi ressemble la prochaine itération du code while
while( ( 1000 > 540 ) && ( 1 < 5 ) )
1000 car le temps s'est écoulé pour obtenir des données et faire des choses avant d'atteindre la prochaine inétération de la boucle, où GetTickCount () est rappelé.
540 car, dans le code 1000/25 = 40, donc, 500 + 40 = 540
1 parce que notre boucle a répété une fois
5 , vous savez pourquoi.
Donc, puisque cette boucle While est CLAIREMENT dépendante MAX_FRAMESKIP
et non pas TICKS_PER_SECOND = 25;
comment le jeu est-il censé fonctionner correctement?
Ce n'était pas une surprise pour moi que lorsque j'ai implémenté cela dans mon code, je pourrais ajouter correctement car j'ai simplement renommé mes fonctions pour gérer les entrées utilisateur et dessiner le jeu selon ce que l'auteur de l'article a dans son exemple de code, le jeu n'a rien fait .
J'ai placé un fprintf( stderr, "Test\n" );
à l'intérieur de la boucle while qui n'est pas imprimé jusqu'à la fin du jeu.
Comment cette boucle de jeu fonctionne-t-elle 25 fois par seconde, garantie, tout en rendant le plus rapidement possible?
Pour moi, à moins que je manque quelque chose d'énorme, cela ressemble à ... rien.
Et cette structure, de cette boucle while, n'est-elle pas censée fonctionner 25 fois par seconde puis mettre à jour le jeu exactement ce que j'ai mentionné au début de l'article?
Si tel est le cas, pourquoi ne pourrions-nous pas faire quelque chose de simple comme:
while( loops < 25 )
{
getInput();
performLogic();
loops++;
}
drawGame();
Et comptez pour l'interpolation d'une autre manière.
Pardonnez ma question extrêmement longue, mais cet article m'a fait plus de mal que de bien. Je suis gravement confus maintenant - et je n'ai aucune idée de comment implémenter une boucle de jeu appropriée en raison de toutes ces questions.
la source
Réponses:
Je pense que l'auteur a fait une petite erreur:
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)
devrait être
while( GetTickCount() < next_game_tick && loops < MAX_FRAMESKIP)
C'est-à-dire: tant qu'il n'est pas encore temps de dessiner notre prochaine image et bien que nous n'ayons pas ignoré autant d'images que MAX_FRAMESKIP, nous devons attendre.
Je ne comprends pas non plus pourquoi il se met
next_game_tick
à jour dans la boucle et je suppose que c'est une autre erreur. Puisqu'au début d'une trame, vous pouvez déterminer quand la trame suivante doit être (lors de l'utilisation d'une fréquence d'images fixe). Lenext game tick
ne dépend pas de combien de temps nous reste plus après la mise à jour et le rendu.L'auteur fait également une autre erreur courante
Cela signifie rendre le même cadre plusieurs fois. L'auteur en est même conscient:
Cela ne fait que faire du GPU un travail inutile et si le rendu prend plus de temps que prévu, cela pourrait vous faire commencer à travailler sur votre prochaine trame plus tard que prévu, il est donc préférable de céder au système d'exploitation et d'attendre.
la source
Peut-être mieux si je simplifie un peu:
while
loop inside mainloop est utilisé pour exécuter des étapes de simulation de l'endroit où elles se trouvaient jusqu'à l'endroit où elles devraient être maintenant.update_game()
La fonction doit toujours supposer que seul leSKIP_TICKS
temps s'est écoulé depuis le dernier appel. Cela permettra à la physique du jeu de fonctionner à vitesse constante sur un matériel lent et rapide.L'incrémentation
next_game_tick
par quantité deSKIP_TICKS
mouvements le rapproche de l'heure actuelle. Lorsque cela devient plus grand que l'heure actuelle, il se casse (current > next_game_tick
) et la boucle principale continue de rendre l'image actuelle.Après le rendu, le prochain appel à
GetTickCount()
retournerait une nouvelle heure actuelle. Si ce temps est supérieur ànext_game_tick
cela, cela signifie que nous sommes déjà derrière les étapes 1-N de la simulation et que nous devons rattraper le retard, en exécutant chaque étape de la simulation à la même vitesse constante. Dans ce cas, s'il est inférieur, il restituera simplement la même image (sauf s'il y a interpolation).Le code d'origine avait limité le nombre de boucles si on nous laisse trop loin (
MAX_FRAMESKIP
). Cela ne fait que montrer quelque chose et ne ressemble pas à être verrouillé si, par exemple, la suspension ou le jeu est suspendu pendant longtemps dans le débogueur (en supposantGetTickCount()
qu'il ne s'arrête pas pendant ce temps) jusqu'à ce qu'il ait rattrapé le temps.Pour se débarrasser du même rendu d'image inutile si vous n'utilisez pas d'interpolation à l'intérieur
display_game()
, vous pouvez l'envelopper à l'intérieur si une instruction comme:Ceci est également un bon article à ce sujet: http://gafferongames.com/game-physics/fix-your-timestep/
De plus, la raison pour laquelle vos
fprintf
sorties à la fin de votre jeu est peut-être simplement qu'elle n'a pas été éliminée.Désolé de mon anglais.
la source
Son code semble entièrement valide.
Considérez la
while
boucle du dernier ensemble:J'ai un système en place qui dit "pendant que le jeu est en cours, vérifiez l'heure actuelle - si elle est supérieure à notre nombre d'images en cours d'exécution, et nous avons ignoré le dessin de moins de 5 images, puis sautez le dessin et mettez simplement à jour l'entrée et physique: sinon dessinez la scène et lancez la prochaine itération de mise à jour "
À chaque mise à jour, vous incrémentez le temps "next_frame" de votre taux de rafraîchissement idéal. Ensuite, vous vérifiez à nouveau votre temps. Si votre heure actuelle est maintenant inférieure à la date à laquelle le next_frame doit être mis à jour, vous ignorez la mise à jour et dessinez ce que vous avez.
Si votre courant_heure est supérieur (imaginez que le dernier processus de dessin a pris très longtemps, car il y avait un hoquet quelque part, ou un tas de garbage-collection dans un langage géré, ou une implémentation de mémoire gérée en C ++ ou autre), alors draw est ignoré et
next_frame
est mis à jour avec une autre image supplémentaire, jusqu'à ce que les mises à jour rattrapent l'endroit où nous devrions être sur l'horloge, ou que nous ayons ignoré suffisamment d'images pour que nous devions absolument en dessiner une, afin que le joueur puisse voir ce qu'ils font.Si votre machine est super rapide ou que votre jeu est super simple, cela
current_time
peut être moins quenext_frame
fréquent, ce qui signifie que vous ne mettez pas à jour pendant ces points.C'est là que l'interpolation entre en jeu. De même, vous pouvez avoir un booléen distinct, déclaré en dehors des boucles, pour déclarer un espace "sale".
À l'intérieur de la boucle de mise à jour, vous définissez
dirty = true
, ce qui signifie que vous avez réellement effectué une mise à jour.Ensuite, au lieu de simplement appeler
draw()
, vous diriez:Ensuite, vous manquez toute interpolation pour un mouvement fluide, mais vous vous assurez que vous ne mettez à jour que lorsque des mises à jour se produisent réellement (plutôt que des interpolations entre les états).
Si vous êtes courageux, il y a un article intitulé "Fix Your Timestep!" par GafferOnGames.
Il aborde le problème un peu différemment, mais je le considère comme une solution plus jolie qui fait essentiellement la même chose (en fonction des fonctionnalités de votre langage et de l'importance que vous accordez aux calculs physiques de votre jeu).
la source