Quel est l'intérêt du rendu indépendant de la mise à jour dans une boucle de jeu?

74

Il existe des dizaines d'articles, de livres et de discussions sur les règles du jeu. Cependant, je rencontre assez souvent quelque chose comme ceci:

while(running)
{
    processInput();
    while(isTimeForUpdate)
    {
        update();
    }
    render();
}

Ce qui me dérange fondamentalement dans cette approche, c’est le rendu "indépendant de la mise à jour", par exemple le rendu d’une image quand il n’ya aucun changement. Ma question est donc pourquoi cette approche est souvent enseignée?

Sam
la source
6
Personnellement, j'ai trouvé l' explication utile sur gameprogrammingpatterns.com/game-loop.html
Niels
12
Tous les changements de rendu ne sont pas reflétés dans l'état du jeu. Et je soupçonne que vous comprenez mal l’intérêt de ce morceau de code: il vous permet de mettre à jour plusieurs fois par rendu, et non de rendre plusieurs fois par mise à jour.
Luaan
13
Notez que le code lit while (isTimeForUpdate), pas if (isTimeForUpdate). L’objectif principal n’est pas de render()ne pas avoir eu d’existence update(), mais de se update()trouver à plusieurs reprises entre render()les deux. Quoi qu'il en soit, les deux situations ont des utilisations valables. Le premier serait valide si l'état peut changer en dehors de votre updatefonction, par exemple, ce qui est rendu en fonction d'un état implicite tel que l'heure actuelle. Ce dernier est valable car il donne à votre moteur physique la possibilité d'effectuer de nombreuses mises à jour petites et précises, ce qui réduit par exemple les chances de «déformation» à travers les obstacles.
Thierry
Une question plus logique serait "quel est le point de la boucle de jeu de rendu dépendant de la mise à jour "
user1306322
1
Combien de fois avez-vous une mise à jour qui ne fait rien? Pas même la mise à jour des animations en arrière-plan ou des horloges à l'écran?
pjc50

Réponses:

113

Nous sommes depuis longtemps arrivés à cette convention commune, avec de nombreux défis fascinants en chemin, je vais donc essayer de le motiver par étapes:

1. Problème: les périphériques fonctionnent à différentes vitesses

Avez-vous déjà essayé de jouer à un vieux jeu de DOS sur un PC moderne et celui-ci est incroyablement rapide - juste un flou?

Beaucoup de vieux jeux avaient une boucle de mise à jour très naïve: ils collectaient les entrées, mettaient à jour l'état du jeu et rendaient le rendu aussi rapidement que le permettait le matériel, sans comptabiliser le temps écoulé. Ce qui signifie que dès que le matériel change, le gameplay change.

Nous voulons généralement que nos joueurs aient une expérience et une convivialité de jeu cohérentes sur toute une gamme d’appareils (dans la mesure où ils respectent certaines spécifications minimales), qu’ils utilisent le téléphone de l’année dernière ou le dernier modèle, un ordinateur de jeu haut de gamme ou un ordinateur. ordinateur portable de niveau intermédiaire.

En particulier, pour les jeux compétitifs (multijoueurs ou via des classements), nous ne voulons pas que les joueurs utilisant un appareil particulier aient un avantage sur les autres, car ils peuvent courir plus vite ou avoir plus de temps pour réagir.

La solution sûre ici est de bloquer le rythme auquel nous effectuons les mises à jour de l'état du jeu. De cette façon, nous pouvons garantir que les résultats seront toujours les mêmes.

2. Alors, pourquoi ne pas simplement verrouiller le framerate (par exemple, en utilisant VSync) et continuer à exécuter les mises à jour et le rendu du gameplay en même temps?

Cela peut fonctionner, mais n’est pas toujours acceptable pour le public. Il y a longtemps que courir à 30 fps était considéré comme la norme absolue pour les jeux. Maintenant, les joueurs s'attendent systématiquement à une vitesse minimale de 60 images par seconde, en particulier dans les jeux d'action multijoueurs, et certains titres plus anciens ont maintenant une apparence plus instable alors que nos attentes ont changé. Il y a aussi un groupe vocal de joueurs sur PC en particulier qui s'opposent à des verrouillages d'images par seconde. Ils ont beaucoup payé pour leur matériel à la fine pointe de la technologie et veulent pouvoir utiliser ce muscle informatique pour obtenir le rendu le plus doux et le plus fidèle possible.

En VR, en particulier, le framerate est roi et la norme continue de ramper. Au début de la récente reprise de la réalité virtuelle, les jeux tournaient souvent autour de 60 images par seconde. Maintenant, 90 est plus standard et un logiciel comme le PSVR commence à en prendre en charge 120. Cela pourrait encore augmenter. Ainsi, si un jeu de réalité virtuelle limite son débit à ce qui est faisable et accepté aujourd'hui, il est susceptible d'être laissé pour compte, à mesure que le matériel et les attentes se développent.

(En règle générale, méfiez-vous quand "les joueurs ne perçoivent rien de plus vite que XXX" car il est généralement basé sur un type particulier de "perception", comme la reconnaissance d'une image en séquence. La perception de la continuité du mouvement est généralement beaucoup plus sensible. )

Le dernier problème ici est qu'un jeu utilisant un nombre d'images par seconde verrouillé doit également être prudent: si vous atteignez un moment du jeu où vous mettez à jour et affichez un nombre anormalement élevé d'objets, vous ne voudrez pas manquer votre cadre. délai et causer un bégaiement ou un accroc visible. Vous devez donc définir des budgets de contenu suffisamment bas pour laisser une marge supplémentaire ou investir dans des fonctions plus complexes d'ajustement dynamique de la qualité afin d'éviter de lier toute l'expérience de jeu à la performance la plus défavorable sur un matériel de spécification minimale.

Cela peut être particulièrement problématique si les problèmes de performances se manifestent tardivement dans le développement, lorsque tous vos systèmes existants sont construits et optimisés en supposant un taux de rendu de verrouillage pas à pas que vous ne pouvez plus toujours toucher. Le découplage des taux de mise à jour et de rendu offre plus de flexibilité pour faire face à la variabilité des performances.

3. La mise à jour à une heure fixe ne pose-t-elle pas les mêmes problèmes que (2)?

Je pense que c’est le fond de la question initiale: si nous découplons nos mises à jour et rendons parfois deux images sans mises à jour de l’état du jeu entre elles, le rendu de l’étape à la vitesse inférieure n’est pas identique, puisqu’il n’ya aucun changement visible l'écran?

Il existe en fait plusieurs façons dont les jeux utilisent efficacement le découplage de ces mises à jour:

a) Le taux de mise à jour peut être plus rapide que le framerate rendu

Comme le fait remarquer tyjkenn dans une autre réponse, la physique en particulier est souvent utilisée à une fréquence supérieure à celle du rendu, ce qui permet de minimiser les erreurs d'intégration et d'obtenir des collisions plus précises. Ainsi, plutôt que d'avoir 0 ou 1 mises à jour entre les images rendues, vous pourriez en avoir 5, 10 ou 50.

Maintenant, le rendu du lecteur à 120 ips peut obtenir 2 mises à jour par image, tandis que le rendu du matériel à spécification inférieure à 30 ips reçoit 8 mises à jour par image et leurs deux jeux s'exécutent à la même vitesse de jeu-ticks-par-seconde-temps réel. Le meilleur matériel le rend plus lisse, mais ne modifie pas radicalement le fonctionnement du gameplay.

Il existe un risque ici que, si le taux de mise à jour ne corresponde pas à la fréquence d'images, vous puissiez obtenir une "fréquence de battement" entre les deux . Par exemple. Pour la plupart des cadres, nous avons assez de temps pour 4 mises à jour de l'état du jeu et un peu de reste, puis de temps en temps, nous en avons assez pour effectuer 5 mises à jour dans un cadre, en faisant un petit saut ou un bégaiement dans le mouvement. Ceci peut être adressé par ...

b) Interpoler (ou extrapoler) l'état du jeu entre les mises à jour

Ici, nous laisserons souvent l’état du jeu vivre selon un pas de temps fixe dans l’avenir et stockerons suffisamment d’informations provenant des 2 états les plus récents pour pouvoir afficher un point arbitraire entre eux. Ensuite, lorsque nous sommes prêts à afficher un nouveau cadre à l'écran, nous nous fondons au moment approprié à des fins d'affichage uniquement (nous ne modifions pas l'état de jeu sous-jacent ici).

Lorsque vous le faites correctement, le mouvement est lisse comme du beurre, et aide même à masquer certaines fluctuations du nombre d'images par seconde, à condition de ne pas tomber trop bas.

c) Ajout de finesse aux changements d'état non liés au gameplay

Même sans interpoler l'état de jeu, nous pouvons toujours obtenir des gains en douceur.

Les changements purement visuels tels que l'animation de personnages, les systèmes de particules ou VFX, et les éléments d'interface utilisateur tels que HUD, sont souvent mis à jour séparément du pas de temps fixe de l'état de jeu. Cela signifie que si nous cochons notre état de jeu plusieurs fois par image, nous ne payons pas leur coût à chaque tick, mais uniquement lors du rendu final. Au lieu de cela, nous modifions la vitesse de lecture de ces effets pour les adapter à la longueur de l'image. Ils sont donc joués avec la même fluidité que le rend le rendu sans nuire à la vitesse ou à l'équité du jeu, comme indiqué dans (1).

Le mouvement de la caméra peut également le faire - en particulier en réalité virtuelle, nous affichons parfois plusieurs fois le même cadre, mais nous le reprojetons pour prendre en compte le mouvement de la tête du joueur entre les deux , de manière à améliorer la latence et le confort perçus, même si nous le pouvons. ne rendez pas natif tout ce que rapide. Certains systèmes de diffusion de jeu (où le jeu est exécuté sur un serveur et le lecteur n’exécute qu’un client léger) utilisent également une version de celui-ci.

4. Pourquoi ne pas simplement utiliser ce style (c) pour tout? Si cela fonctionne pour l'animation et l'interface utilisateur, ne pouvons-nous pas simplement adapter les mises à jour de l'état de notre gameplay à la fréquence d'images actuelle?

Oui * c'est possible, mais non ce n'est pas simple.

Cette réponse est déjà un peu longue donc je ne vais pas entrer dans tous les détails sanglants, mais juste un bref résumé:

  • La multiplication par des deltaTimetravaux permet de s’ajuster aux mises à jour de longueur variable pour les changements linéaires (par exemple, un mouvement à vitesse constante, le compte à rebours d’un chronomètre ou la progression le long d’une ligne temporelle d’animation).

  • Malheureusement, de nombreux aspects des jeux ne sont pas linéaires . Même quelque chose d'aussi simple que la gravité nécessite des techniques d'intégration plus sophistiquées ou des sous-étapes de résolution plus élevée pour éviter des résultats divergents sous des cadences de rendu variables. La saisie et le contrôle du lecteur sont en soi une source énorme de non-linéarité.

  • En particulier, les résultats de la détection et de la résolution des collisions discrètes dépendent de la vitesse de mise à jour, ce qui entraîne des erreurs de tunnelling et de jitter si les trames deviennent trop longues. Ainsi, un framerate variable nous oblige à utiliser des méthodes de détection de collision continue plus complexes / coûteuses sur une plus grande partie de notre contenu, ou à tolérer une variabilité de notre physique. Même la détection de collision continue pose des problèmes lorsque des objets se déplacent en arcs, ce qui nécessite des pas de temps plus courts ...

Ainsi, dans le cas général d'un jeu de complexité moyenne, maintenir un comportement cohérent et une justice intégralement grâce à la deltaTimemise à l' échelle se situe entre très difficile et demande beaucoup de maintenance, voire irréalisable.

La standardisation d'un taux de mise à jour nous permet de garantir un comportement plus cohérent dans diverses conditions , souvent avec un code plus simple.

En conservant ce taux de mise à jour découplé du rendu, nous avons la possibilité de contrôler la fluidité et les performances de l'expérience sans modifier la logique de jeu .

Même dans ce cas, nous n'obtenons jamais une indépendance en matière de framerate vraiment «parfaite», mais, comme beaucoup d'approches dans les jeux, cela nous donne une méthode contrôlable permettant de sélectionner «assez bien» pour répondre aux besoins d'un jeu donné. C'est pourquoi il est généralement enseigné comme un point de départ utile.

DMGregory
la source
2
Dans les cas où tout utilisera la même fréquence d'images, la synchronisation de tout peut réduire le délai entre le moment où le contrôleur est échantillonné et celui où l'état du jeu réagit. Pour de nombreux jeux sur des machines plus anciennes, le temps le plus défavorable serait inférieur à 17 ms (les contrôles sont lus au début du blanc vertical, puis les changements d'état du jeu sont appliqués et l'image suivante est rendue tandis que le faisceau se déplace vers le bas de l'écran) . Le découplage entraîne souvent une augmentation significative du pire des cas.
Supercat
3
S'il est vrai que des pipelines de mise à jour plus complexes facilitent l'introduction involontaire de latence, ce n'est pas une conséquence nécessaire de l'approche découplée lorsqu'elle est correctement implémentée. En fait, nous pourrions même être en mesure de réduire la latence. Prenons un jeu qui rend à 60 images par seconde. Avec un pas fixe read-update-render, notre pire latence est de 17 ms (en ignorant le pipeline graphique et la latence d'affichage pour le moment). Avec une (read-update)x(n>1)-renderboucle découplée à la même fréquence d'images, notre latence dans le pire des cas ne peut être identique ou meilleure car nous vérifions et agissons sur les entrées aussi souvent que plus. :)
DMGregory
3
Sur une note latérale intéressante sur les vieux jeux ne prenant pas en compte le temps réel écoulé, l'arcade d'origine de Space Invaders présentait un problème lié au pouvoir de rendu. , dans la courbe de difficulté emblématique du jeu.
Oskuro
1
@DMGregory: si les choses sont faites de manière asynchrone, il sera possible qu'un changement de contrôle se produise juste après que la boucle de jeu interroge les contrôles, le cycle d'événements de jeu suivant celui en cours se terminant juste après la boucle de rendu saisissant l'état de jeu, et pour que la boucle de rendu suivant la boucle actuelle se termine juste après que le système de sortie vidéo ait saisi le tampon d'images actuel, le pire des scénarios finit donc par être juste en deçà de deux temps de boucle de jeu plus deux temps de rendu plus deux périodes d'image. Synchroniser les choses correctement pourrait réduire ce temps de moitié
Supercat
1
@Oskuro: Ce n'était pas un problème. La vitesse de la boucle de mise à jour reste constante quel que soit le nombre d’envahisseurs affichés à l’écran, mais le jeu n’attire pas tous les envahisseurs à chaque boucle de mise à jour.
Supercat
8

Les autres réponses sont bonnes et expliquent pourquoi la boucle de jeu existe et doit être séparée de la boucle de rendu. Cependant, comme pour l'exemple spécifique de "Pourquoi rendre un cadre alors qu'il n'y a eu aucun changement?" Tout se résume au matériel et à la complexité.

Les cartes vidéo sont des machines à états et elles sont vraiment douées pour faire la même chose encore et encore. Si vous ne restituez que les choses qui ont changé, c'est en fait plus cher, pas moins. Dans la plupart des scénarios, il n'y a pas grand-chose de statique, si vous vous déplacez légèrement vers la gauche dans un jeu FPS, si vous modifiez les données en pixels de 98% des éléments à l'écran, vous pouvez également effectuer le rendu de la totalité du cadre.

Mais surtout, la complexité. Garder une trace de tout ce qui change lors de la mise à jour coûte beaucoup plus cher, car il faut tout retravailler ou garder le résultat de certains algorithmes, le comparer au nouveau résultat et ne restituer ce pixel que si le changement est différent. Cela dépend du système.

La conception du matériel, etc. est largement optimisée pour les conventions actuelles, et une machine à états est un bon modèle.

Pagaie
la source
5
Il faut faire une distinction ici entre rendre (tout) uniquement lorsque quelque chose a changé (objet de la question) et uniquement les parties modifiées (description de votre réponse).
DMGregory
C'est vrai et j'ai essayé de faire la distinction entre les premier et deuxième paragraphes. Il est rare qu'un cadre ne change pas du tout. J'ai donc pensé qu'il était important de suivre ce point de vue parallèlement à votre réponse globale.
Waddles
En plus de cela, je ferais remarquer qu'il n'y a aucune raison de ne pas rendre. Vous savez que vous avez toujours du temps pour vos appels de rendu à chaque image (vous feriez mieux de savoir que vous avez toujours du temps pour vos appels de rendu à chaque image!). Il n'y a donc que très peu de mal à effectuer un rendu "inutile" - en particulier essentiellement ne jamais venir dans la pratique.
Steven Stadnicki
@StevenStadnicki Il est vrai que le rendu de chaque image ne présente pas un coût important en temps (vous devez avoir du temps dans votre budget pour le faire quand de nombreux états changent en même temps). Toutefois, pour les appareils portables tels que les ordinateurs portables, les tablettes, les téléphones ou les systèmes de jeux portables, il peut être judicieux d'éviter un rendu redondant pour utiliser efficacement la batterie du lecteur . Cela s'applique principalement aux jeux à interface utilisateur intense où de grandes parties de l'écran peuvent rester inchangées pour les images entre les actions du joueur et ne sont pas toujours pratiques à implémenter en fonction de l'architecture du jeu.
DMGregory
6

Le rendu est généralement le processus le plus lent de la boucle de jeu. Les humains ne remarquent pas facilement une différence de fréquence d'images supérieure à 60. Il est donc souvent moins important de perdre du temps à effectuer un rendu plus rapide. Cependant, il existe d'autres processus qui bénéficieraient davantage d'un taux plus rapide. La physique en est une. Un changement trop important dans une boucle peut entraîner un blocage des objets au-delà des murs. Il existe peut-être des moyens de contourner les erreurs de collision simples avec des incréments plus importants, mais pour de nombreuses interactions physiques complexes, vous n'obtiendrez tout simplement pas la même précision. Toutefois, si la boucle physique est exécutée plus fréquemment, les risques de problèmes sont moins importants, car les objets peuvent être déplacés par incréments plus petits sans être restitués à chaque fois. Plus de ressources sont affectées au moteur physique sensible et moins de gaspillage est consacré au dessin de plus de cadres que l'utilisateur ne peut pas voir.

Ceci est particulièrement important dans les jeux à plus forte intensité graphique. S'il y avait un rendu pour chaque boucle de jeu et qu'un joueur ne disposait pas de la machine la plus puissante, il se peut qu'il y ait des points dans la partie où le nombre de fps chute à 30 ou 40. le jeu commencerait à devenir assez lent si nous essayions de garder chaque changement physique assez petit pour éviter les problèmes. Le joueur serait ennuyé que son personnage ne marche que la moitié de la vitesse normale. Cependant, si le taux de rendu était indépendant du reste de la boucle, le joueur pourrait rester à une vitesse de marche fixe malgré la baisse du taux de trame.

tyjkenn
la source
1
Ou, par exemple, mesurer le temps entre les ticks et calculer jusqu'où le personnage devrait aller dans ce temps? Le temps des animations de sprites fixes est révolu!
Graham
2
Les humains ne peuvent pas percevoir les choses plus rapidement que 60 images par seconde sans crénelage temporel, mais la cadence de défilement nécessaire pour obtenir un mouvement fluide en l'absence de flou de mouvement peut être beaucoup plus élevée que cela. Au-delà d'une certaine vitesse, une roue en rotation devrait apparaître comme un flou, mais si le logiciel n'applique pas de flou directionnel et que la roue tourne de plus d'un demi-rayon par image, la roue peut sembler défectueuse même si la cadence est de 1 000 images par seconde.
Supercat
1
@ Graham, vous rencontrez le problème de mon premier paragraphe, où des choses telles que la physique se comportent mal avec le plus grand changement par tick. Si le nombre d'images par seconde diminue suffisamment, une compensation par des modifications plus importantes peut amener le joueur à passer à travers un mur, manquant ainsi sa zone de frappe.
jeudi
1
Humain ne peut généralement pas percevoir plus vite que 60 i / s, il est donc inutile de gaspiller des ressources pour un rendu plus rapide. Je suis en désaccord avec cette déclaration. Les HMD VR sont rendus à 90Hz, et ça monte. Croyez-moi quand je vous dis que vous pouvez CERTAINEMENT percevoir la différence entre 90Hz et 60Hz sur un casque. En outre, j'ai récemment vu autant de jeux liés au processeur que liés au GPU. Dire que "le rendu est généralement le processus le plus lent" n'est pas nécessairement vrai.
3Dave
@DavidLively, si "inutile" a peut-être été trop exagéré. Ce que je voulais dire, c'est que le rendu a tendance à être un goulot d'étranglement et que la plupart des jeux ont l'air bien à 60 ips. Il existe certes des effets importants dans certains types de jeux, comme avec la réalité virtuelle, que vous ne pouvez obtenir qu'avec des taux de trame plus rapides, mais ceux-ci semblent être l'exception plutôt que la norme. Si je courais un jeu sur une machine plus lente, je préférerais de beaucoup travailler avec la physique plutôt qu’une vitesse de défilement rapide à peine perceptible.
jeudi
4

Une construction comme celle de votre question peut avoir un sens si le sous-système de rendu a une notion du "temps écoulé depuis le dernier rendu" .

Prenons, par exemple, une approche dans laquelle la position d'un objet dans le monde du jeu est représentée par des (x,y,z)coordonnées fixes avec une approche qui stocke en outre le vecteur de mouvement actuel (dx,dy,dz). Vous pouvez maintenant écrire votre boucle de jeu de manière à ce que le changement de position se produise dans la updateméthode, mais vous pouvez également la concevoir de manière à ce que le changement de mouvement se produise pendant update. Avec cette dernière approche, même si votre état de jeu ne changera pas avant le prochain update, unerender-fonction appelée à une fréquence plus élevée pourrait déjà dessiner l'objet à une position légèrement mise à jour. Bien que cela conduise techniquement à une discordance entre ce que vous voyez et ce qui est représenté en interne, la différence est suffisamment petite pour ne pas avoir d’importance pour la plupart des aspects pratiques, tout en permettant aux animations d’être beaucoup plus fluides.

Prédire "l'avenir" de votre état de jeu (malgré le risque de vous tromper) peut être une bonne idée lorsque vous prenez par exemple en compte les latences des entrées réseau.

Thomas
la source
4

En plus d'autres réponses ...

La vérification du changement d'état nécessite un traitement important. S'il faut un temps de traitement similaire (ou plus long) pour vérifier les modifications, par rapport au traitement en cours, vous n'avez vraiment pas amélioré la situation. Dans le cas du rendu d'une image, comme le dit @Waddles, une carte vidéo est vraiment efficace pour faire la même chose stupide encore et encore, et il est plus coûteux de vérifier chaque bloc de données pour des modifications que de le transférer simplement. à la carte vidéo pour le traitement. Aussi , si le rendu est le jeu alors il est vraiment peu probable que l'écran n'avoir changé dans la dernière tique.

Vous supposez également que le rendu prend beaucoup de temps au processeur. Cela dépend beaucoup de votre processeur et de votre carte graphique. Depuis de nombreuses années, l’accent est mis sur l’imputation progressive d’un travail de rendu de plus en plus sophistiqué sur la carte graphique et sur la réduction de l’entrée de rendu requise du processeur. Dans l'idéal, l' render()appel du processeur devrait simplement mettre en place un transfert DMA et c'est tout. L'obtention de données sur la carte graphique est ensuite déléguée au contrôleur de mémoire et la production de l'image est déléguée à la carte graphique. Ils peuvent le faire à leur rythme, alors que le processeur en parallèlecontinue avec la physique, le moteur de jeu et toutes les autres choses qu'un processeur fait mieux. De toute évidence, la réalité est bien plus compliquée que cela, mais le fait de pouvoir se décharger de son travail sur d’autres parties du système est également un facteur important.

Graham
la source