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?
while (isTimeForUpdate)
, pasif (isTimeForUpdate)
. L’objectif principal n’est pas derender()
ne pas avoir eu d’existenceupdate()
, mais de seupdate()
trouver à plusieurs reprises entrerender()
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 votreupdate
fonction, 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.Réponses:
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
deltaTime
travaux 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
deltaTime
mise à 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.
la source
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)-render
boucle 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. :)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.
la source
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.
la source
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 laupdate
méthode, mais vous pouvez également la concevoir de manière à ce que le changement de mouvement se produise pendantupdate
. Avec cette dernière approche, même si votre état de jeu ne changera pas avant le prochainupdate
, 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.
la source
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.la source