J'étais un peu curieux de savoir comment la relecture pourrait être implémentée dans un jeu.
Au départ, je pensais qu'il y aurait juste une liste de commandes de chaque action joueur/ai qui aurait été prise dans le jeu, puis il "rejouer" le jeu et laisser le moteur restituer comme d'habitude. Cependant, j'ai regardé les replays dans les jeux FPS/RTS, et après une inspection minutieuse, même des choses comme les particules et les défauts graphiques/sonores sont cohérents (et ces problèmes sont généralement in cohérents).
Alors, comment cela se produit-il? Dans les jeux à angle de caméra fixe, je pensais qu'il pourrait simplement écrire chaque image de la scène entière dans un flux qui est stocké, puis rejouer le flux, mais cela ne semble pas suffisant pour les jeux qui vous permettent de suspendre et de déplacer la caméra autour. Vous devez stocker les emplacements de tout dans la scène à tout moment (non?). Donc, pour des choses comme les particules, c'est beaucoup de données à transmettre, ce qui semble être un tirage important sur les performances du jeu tout en jouant.
Je pense que votre pensée initiale était correcte. Pour créer une relecture, vous stockez toutes les entrées reçues de l'utilisateur (ainsi que le numéro de trame auquel elles ont été reçues) ainsi que les graines initiales de tout générateur de nombres aléatoires. Pour rejouer le jeu, vous réinitialisez vos PRNG en utilisant les graines enregistrées et alimentez le moteur de jeu la même séquence d'entrée (synchronisée avec les numéros de trame). Étant donné que de nombreux jeux mettront à jour l'état du jeu en fonction du temps qui s'écoule entre les images, vous devrez peut-être également stocker la longueur de chaque image.
Starcraft et Starcraft: Brood War avaient une fonction de relecture. Une fois le match terminé, vous pouvez choisir d'enregistrer la rediffusion pour l'afficher ultérieurement. Pendant la lecture, vous pouvez faire défiler la carte et cliquer sur les unités et les bâtiments, mais pas changer leurs comportements.
Je me souviens d'avoir regardé une rediffusion d'un match qui avait été joué dans le jeu original, mais la rediffusion était visionnée dans Brood War. Pour ceux qui ne sont pas familiers, Brood War contient toutes les unités et bâtiments d'origine, ainsi qu'une variété de nouveaux. Dans le jeu original, le joueur avait vaincu l'ordinateur en créant des unités que l'ordinateur ne pouvait pas facilement contrer. Lorsque j'ai joué la rediffusion dans Brood War, l'ordinateur avait accès à différentes unités, qu'il a créées et utilisées pour vaincre le joueur. Ainsi, le même fichier de relecture exact a donné lieu à un gagnant différent selon la version de Starcraft qui lisait le fichier.
J'ai toujours trouvé le concept fascinant. Il semblerait que la fonction de relecture fonctionne en enregistrant toutes les entrées du lecteur et suppose que l'ordinateur répondra à ces stimuli de la même manière à chaque fois. Lorsque les entrées des joueurs ont été introduites dans le replayer original de Starcraft, le jeu s'est déroulé exactement comme lors du match d'origine. Lorsque la même entrée exacte a été introduite dans le replayer de Brood War, l'ordinateur a réagi différemment, a créé des unités plus fortes et a gagné la partie.
Quelque chose à garder à l'esprit si vous écrivez un moteur de relecture.
Il existe deux méthodes principales:
Cela dépend de ce que vous voulez faire. Parfois, le stockage d'événements est préférable, car cela prend généralement beaucoup moins de mémoire. D'un autre côté, si vous souhaitez fournir des rediffusions qui peuvent être lues à différentes vitesses et à partir de différents points de départ, il est préférable de stocker les états. Lors du stockage des états, vous pouvez également décider de les stocker après chaque événement ou par exemple. seulement 12 ou 25 fois par seconde - cela pourrait réduire la taille de votre relecture et faciliter leur rembobinage/avance rapide.
Notez que "état" ne signifie pas état graphique. Plus quelque chose comme les positions des unités, l'état des ressources, etc. Des éléments comme les graphiques, les systèmes de particules, etc., sont généralement déterministes et peuvent être stockés sous la forme "animation X, temps Y: Z".
Parfois, les rediffusions sont utilisées comme schéma anticheat. Ensuite, le stockage des événements est probablement le meilleur ici.
Techniquement, vous devez écrire votre moteur pour qu'il soit déterministe, ce n'est pas un hasard. En supposant qu'un personnage dans le jeu vise le bras d'un adversaire et tire une arme, le même montant de dégâts devrait être appliqué à l'adversaire dans tous les cas.
En supposant qu'une bombe explose à l'emplacement X, les particules produites par cette explosion devraient toujours donner le même résultat visuel. Si vous avez besoin d'aléatoire, créez un ensemble de nombres aléatoires, sélectionnez une valeur de départ lorsque le jeu est joué et enregistrez cette valeur de départ dans la relecture.
En général, avoir un caractère aléatoire dans un jeu est une mauvaise idée. Même pour des choses comme le multijoueur, vous ne pouvez pas voir la moitié de vos joueurs autour d'une explosion tandis que les autres ne peuvent pas simplement parce qu'ils n'ont pas obtenu la bonne valeur aléatoire.
Rendez tout déterministe et tout ira bien.
Compte tenu de la état initial et d'une série d'actions avec horodatages, il suffit de parcourir la séquence car les actions enregistrées sont censées se produire.
Afin que les événements aléatoires se reproduisent exactement de la même manière, tilisez des nombres pseudo-aléatoires prédéfinis et enregistrez le germe dans le fichier de relecture.
Tant que vous utilisez le même algorithme pour générer les nombres aléatoires à partir de la graine, vous pouvez recréer tous les événements tels qu'ils se sont produits dans le jeu en direct sans avoir besoin d'instantanés complets de l'état du jeu.
Cela nécessitera que les rediffusions soient surveillées séquentiellement, mais c'est assez normal pour les rediffusions de jeu (voir Starcraft 2). Si vous souhaitez autoriser l'accès aléatoire à la chronologie, vous pouvez prendre des instantanés d'état complet à intervalles définis (disons toutes les minutes), pour sauter autour de la chronologie à une granularité définie.
NVidia PhysX (un moteur de simulation physique qui est souvent utilisé dans les jeux) est capable d'enregistrer l'état complet de la scène physique au fil du temps. Cela intègre toutes les entrées de conduite du moteur de jeu, ce qui signifie que vous n'avez pas besoin de suivre les graines de nombres aléatoires comme d'autres l'ont suggéré. Si vous prenez ce vidage de scène, vous pouvez le rejouer dans un outil extérieur (fourni par NVidia), ce qui est très pratique pour détecter les problèmes avec vos modèles physiques. Cependant, vous pouvez également utiliser le même flux physique pour piloter votre moteur graphique, ce qui vous permettrait alors d'avoir un contrôle de caméra normal, car seule la physique pilotant les graphiques a été enregistrée. Dans de nombreux jeux, cela inclut les effets de particules (PhysX inclut des systèmes de particules très sophistiqués.) Quant au son, je suppose que c'est enregistré textuellement (sous forme de flux sonore), mais je ne suis pas sûr.
Votre idée originale est juste, et pour les effets vraiment complexes, ils ne se souviennent pas exclusivement. Par exemple, le système de relecture de Warcraft 3 ne stocke pas l'état des animations ou des effets de particules dans le cas d'effets aléatoires, etc. En outre, la plupart des choses peuvent être calculées par calcul à partir d'un point de départ de manière déterministe, donc pour la plupart des systèmes qui utilisent des variables aléatoires (une explosion de particules qui donne un décalage aléatoire, par exemple), tout ce dont vous avez besoin est le temps de l'effet et la graine aléatoire. Vous pouvez ensuite recréer l'effet sans vraiment savoir à quoi il ressemblera. Sachant qu'il passe par un chemin de code déterministe.
En y pensant purement conceptuellement, pour rejouer une chronologie des événements, tout ce dont vous avez besoin sont les actions de l'utilisateur. Le programme réagira exactement de la même manière, sauf dans le cas de variables aléatoires. Dans ce scénario, vous pouvez soit ignorer le caractère aléatoire (est-ce vraiment important si les effets sont exactement les mêmes, ou peuvent-ils être générés de manière aléatoire), soit stocker la valeur de départ et simuler le caractère aléatoire.
Jetez mes deux sous.
Dépend de ce que vous voulez, la relecture peut être effectuée via
La plupart du temps, les gens veulent une relecture interactive, c'est donc le chemin à parcourir. Ensuite, en fonction de vos contraintes, il existe plusieurs façons d'optimiser ce processus
C'est vraiment un sujet fascinant. Je me souviens qu'un titre de lancement pour la Xbox originale Wreckless avait une bonne fonction de lecture. Malheureusement, à plus d'une occasion, la rediffusion aurait échoué;)
oh ouais, comment quelqu'un pourrait-il oublier BlinxTime Sweeper ! super relecture interactive qui a été incorporée dans la mécanique de jeu actuelle!
* = il semble qu'il y ait des commentaires concernant le pas de temps. j'utilise la "simulation" ici pour capturer cette fonctionnalité. au cœur, votre moteur doit être capable de produire des cadres de temps discrets. même si un cadre de relecture prend plus ou moins de temps à traiter que l'original, le système doit percevoir que le même delta de temps s'est écoulé. cela signifie enregistrer le pas de temps de trame avec chaque entrée enregistrée et fournir ce delta à l'horloge de votre moteur.
Peut-être pourriez-vous simplement enregistrer une pile de commandes envoyées par chaque joueur. Ainsi, au lieu de sauver qu'une bombe explose à un certain moment et à un certain moment, ou qu'une certaine voiture soit détruite, vous enregistrez simplement les pressions de touches envoyées par chaque joueur. Ensuite, dans la relecture, vous simulez simplement le jeu comme cela se serait produit avec ces presses. J'ai l'impression que cela a le potentiel de prendre moins de place, mais je n'ai jamais travaillé sur un système de rejeu comme celui-ci.
Question intéressante, cependant. Je serais intéressé par la façon dont cela se fait dans les jeux professionnels.
Dan Bryant
En outre, l'enregistrement de graines aléatoires ne serait pas suffisant pour le support de rembobinage, car la progression aléatoire n'est pas une procédure réversible sans support spécial dans toute la logique reposant sur le caractère aléatoire. Il est plus flexible d'enregistrer les résultats des opérations aléatoires dans le cadre du flux d'événements.
C'est exactement ce que je pensais au début quand j'essayais de comprendre comment ils faisaient pour que le jeu soit toujours le même à chaque fois. Avec Doom, je me suis demandé à quel point les tournages étaient aléatoires: D. Enregistrer n'importe quel nombre aléatoire utilisé, j'ai découvert que cela pourrait être une solution. C'était avant de tomber sur un document pdf sur la technologie Crysis. Certaines textures, le bruit et la disposition de l'herbe ou des arbres, semblaient utiliser la pseudo-randomisation avec des graines fixes réversibles pour que vous ne voyiez pas la disposition altérée du bruit, des arbres et de l'herbe à chaque fois que vous regardez!
Éviter en même temps, de stocker des millions d'arbres et de positionner les puits d'herbe. Apparemment, une séquence pseudo-aléatoire peut rejouer la même chose à tout moment, car la logique est fixe, pour ne faire qu'une fausse séquence de nombres statistiquement aléatoire.
Le problème d'avoir une relecture cohérente est le même (enfin, plus facile) que d'avoir un jeu multijoueur cohérent.
Comme d'autres l'ont mentionné précédemment, les rediffusions dans les jeux RTS sont stockées en enregistrant toutes les entrées (qui ont un effet. Le défilement n'a aucun effet.) Le multijoueur transmet également toutes les entrées
L'enregistrement de toutes les entrées n'est pas seulement une supposition - il existe une bibliothèque pour lire les replays de Warcraft3 avec cela.
l'entrée inclut des horodatages pour cette réponse.