Je crée un jeu de physique impliquant des corps rigides dans lequel les joueurs déplacent des pièces et des pièces pour résoudre un puzzle / une carte. Un aspect extrêmement important du jeu est que, lorsque les joueurs démarrent une simulation, celle-ci est identique partout , quel que soit leur système d'exploitation, leur processeur, etc.
Il y a de la place pour beaucoup de complexité et les simulations peuvent durer longtemps. Il est donc important que le moteur physique soit complètement déterministe en ce qui concerne ses opérations en virgule flottante, sinon une solution peut sembler "résoudre" sur la machine d'un joueur et "échouer" sur un autre.
Comment puis-je atteindre ce déterminisme dans mon jeu? Je suis prêt à utiliser divers cadres et langages, notamment Javascript, C ++, Java, Python et C #.
Box2D (C ++) et ses équivalents dans d’autres langages me tentent, même si cela semble répondre à mes besoins, mais il manque un déterminisme en virgule flottante, en particulier pour les fonctions trigonométriques.
La meilleure option que j'ai vue jusqu'à présent est l'équivalent Java de Box2D (JBox2D). Il semble tenter de déterminer le déterminisme à virgule flottante en utilisant StrictMath
plutôt que Math
pour de nombreuses opérations, mais on ignore si ce moteur garantira tout ce dont j'ai besoin, car je n'ai pas encore construit le jeu.
Est-il possible d'utiliser ou de modifier un moteur existant pour répondre à mes besoins? Ou devrais-je construire un moteur moi-même?
EDIT: Ignorez le reste de ce post si vous ne vous souciez pas de savoir pourquoi quelqu'un aurait besoin d'une telle précision. Les personnes dans les commentaires et les réponses semblent croire que je cherche quelque chose que je ne devrais pas, et à ce titre, je vais expliquer davantage comment le jeu est censé fonctionner.
Le joueur reçoit un casse-tête ou un niveau contenant des obstacles et un but. Au départ, une simulation n'est pas en cours d'exécution. Ils peuvent ensuite utiliser des pièces ou des outils mis à leur disposition pour construire une machine. Une fois qu'ils ont appuyé sur démarrer, la simulation commence et ils ne peuvent plus modifier leur machine. Si la machine résout la carte, le joueur a battu le niveau. Sinon, ils devront appuyer sur Stop, modifier leur machine et réessayer.
La raison pour laquelle tout doit être déterministe est que je prévois de produire un code qui mappera chaque machine (un ensemble de pièces et d’outils qui tentent de résoudre un niveau) vers un fichier xml ou json en enregistrant la position, la taille et la rotation de chaque pièce. Il sera alors possible aux joueurs de partager des solutions (qui sont représentées par ces fichiers) afin de pouvoir vérifier les solutions, apprendre les unes des autres, organiser des compétitions, collaborer, etc. Bien sûr, la plupart des solutions sont résolues, en particulier la résolution simple ou rapide. ceux-ci, ne seront pas affectés par un manque de déterminisme. Mais les conceptions lentes ou complexes qui résolvent des niveaux très difficiles peuvent, et ce sont celles qui seront probablement les plus intéressantes et les plus intéressantes à partager.
la source
Réponses:
Sur le traitement des nombres à virgule flottante de manière déterministe
La virgule flottante est déterministe. Eh bien, ça devrait l'être. C'est compliqué.
Il y a beaucoup de littérature sur les nombres à virgule flottante:
Et comment ils sont problématiques:
Pour résumé. Au moins, sur un seul thread, les mêmes opérations, avec les mêmes données, se déroulant dans le même ordre, devraient être déterministes. Ainsi, nous pouvons commencer par nous préoccuper des entrées et de la réorganisation.
Le temps est l’une des choses qui posent problème.
Tout d’abord, vous devez toujours calculer le même pas de temps. Je ne dis pas de ne pas mesurer le temps, je dis que vous ne passerez pas de temps à la simulation physique, car les variations dans le temps sont une source de bruit dans la simulation.
Pourquoi mesurez-vous le temps si vous ne le passez pas à la simulation physique? Vous souhaitez mesurer le temps écoulé pour savoir quand une étape de simulation doit être appelée et, en supposant que vous utilisiez le sommeil, combien de temps pour dormir.
Ainsi:
Maintenant, réorganiser les instructions.
Le compilateur peut décider que
f * a + b
c'est la même choseb + f * a
, mais que le résultat peut être différent. Il pourrait également compiler pour fmadd , ou décider de prendre plusieurs lignes comme celles-ci et de les écrire avec SIMD , ou une autre optimisation à laquelle je ne peux pas penser pour le moment. Et rappelez-vous que nous voulons que les mêmes opérations se déroulent dans le même ordre. Il est donc logique de vouloir contrôler quelles opérations ont lieu.Et non, l'utilisation de double ne vous sauvera pas.
Vous devez vous préoccuper du compilateur et de sa configuration, en particulier pour synchroniser les nombres à virgule flottante sur le réseau. Vous devez obtenir que les versions acceptent de faire la même chose.
On pourrait soutenir que l'écriture d'assemblage serait idéale. De cette façon, vous décidez quelle opération faire. Cependant, cela pourrait poser un problème pour prendre en charge plusieurs plates-formes.
Ainsi:
Le cas des nombres à virgule fixe
En raison de la manière dont les flottants sont représentés en mémoire, les grandes valeurs vont perdre de la précision. Il va de soi que le fait de garder vos valeurs faibles (clamp) atténue le problème. Ainsi, pas de vitesses énormes et pas de grandes salles. Ce qui signifie également que vous pouvez utiliser la physique discrète car vous avez moins de risque de tunneling.
D'autre part, de petites erreurs vont s'accumuler. Donc, tronquer. Je veux dire, redimensionnez et convertissez en un type entier. De cette façon, vous savez que rien ne se construit. Il y aura des opérations que vous pouvez faire en restant avec le type entier. Lorsque vous devez revenir en virgule flottante, vous lancez et annulez la mise à l'échelle.
Notez que je dis l'échelle. L'idée est que 1 unité sera réellement représentée par une puissance de deux (16384 par exemple). Quoi que ce soit, faites-en une constante et utilisez-la. Vous l'utilisez essentiellement comme un nombre à virgule fixe. En fait, si vous pouvez utiliser beaucoup mieux les nombres à virgule fixe appropriés d'une bibliothèque fiable.
Je dis tronqué. À propos du problème d'arrondi, cela signifie que vous ne pouvez pas faire confiance au dernier bit de la valeur que vous avez obtenue après la distribution. Donc, avant la distribution, obtenez un peu plus que nécessaire et tronquez-le par la suite.
Ainsi:
Attends, pourquoi as-tu besoin de virgule flottante? Ne pourriez-vous pas travailler uniquement avec un type entier? Oh, c'est vrai. Trigonométrie et radication. Vous pouvez calculer des tables pour la trigonométrie et le rayonnement et les faire cuire dans votre source. Vous pouvez également implémenter les algorithmes utilisés pour les calculer avec un nombre à virgule flottante, à l'exception de l'utilisation de nombres à virgule fixe. Oui, vous devez équilibrer la mémoire, les performances et la précision. Pourtant, vous pouvez rester en dehors des nombres en virgule flottante et rester déterministe.
Saviez-vous qu'ils ont fait ce genre de choses pour la PlayStation d'origine? S'il vous plaît rencontrer mon chien, des correctifs .
En passant, je ne dis pas de ne pas utiliser de virgule flottante pour les graphiques. Juste pour la physique. Je veux dire, bien sûr, les positions dépendront de la physique. Cependant, comme vous le savez, un collisionneur ne doit pas nécessairement correspondre à un modèle. Nous ne voulons pas voir les résultats de la troncature des modèles.
Ainsi: UTILISEZ DES NUMÉROS DE POINTS FIXES.
Pour être clair, si vous pouvez utiliser un compilateur qui vous permet de spécifier le fonctionnement des points flottants et que cela vous suffit, vous pouvez le faire. Ce n'est pas toujours une option. De plus, nous le faisons pour le déterminisme. Les nombres à point fixe ne signifient pas qu’il n’ya pas d’erreurs, après tout, leur précision est limitée.
Je ne pense pas que "nombre de points fixe sont difficiles" est une bonne raison de ne pas les utiliser. Et si vous voulez une bonne raison de les utiliser, c'est le déterminisme, en particulier le déterminisme sur toutes les plateformes.
Voir également:
Addendum : Je suggère de garder la taille du monde petite. Cela dit, les deux OP et Jibb Smart soulignent le fait que s’éloigner des flotteurs d’origine ont moins de précision. Cela aura un effet sur la physique, un phénomène qui sera vu beaucoup plus tôt que les confins du monde. Les nombres de points fixes, ainsi, ont une précision fixe, ils seront également bons (ou mauvais, si vous préférez) partout. Ce qui est bon si nous voulons le déterminisme. Je tiens également à mentionner que la manière dont nous pratiquons habituellement la physique a la propriété d’amplifier de petites variations. Voir L'effet papillon - Physique déterministe dans The Incredible Machine and Maker .
Une autre façon de faire de la physique
Je pensais que la petite erreur de précision dans les nombres en virgule flottante s’expliquait parce que nous effectuons des itérations sur ces nombres. À chaque étape de la simulation, nous prenons les résultats de la dernière étape de la simulation et les remplaçons. Cumul d'erreurs après sommet d'erreurs. C'est ton effet papillon.
Je ne pense pas que nous verrons une seule construction utilisant un seul thread sur la même machine donner une sortie différente par la même entrée. Pourtant, sur une autre machine, cela pourrait ou une construction différente.
Il y a un argument pour tester là-bas. Si nous décidons exactement comment les choses devraient fonctionner et que nous pouvons tester sur du matériel cible, nous ne devrions pas proposer de versions ayant un comportement différent.
Cependant, il y a aussi un argument pour ne pas travailler ailleurs qui accumule tant d'erreurs. C'est peut-être une occasion de faire de la physique d'une manière différente.
Comme vous le savez peut-être, il existe une physique continue et discrète, les deux travaillent sur la progression de chaque objet sur le pas de temps. Cependant, la physique continue a le moyen de déterminer le moment de la collision au lieu de sonder différents instants possibles pour voir si une collision s'est produite.
Ainsi, je propose ce qui suit: utilisez les techniques de la physique continue pour déterminer quand la prochaine collision de chaque objet se produira, avec un pas de temps important, beaucoup plus important que celui d’une seule étape de simulation. Ensuite, prenez l’instant de collision le plus proche et déterminez où tout se trouvera à cet instant.
Oui, cela représente beaucoup de travail d’une seule étape de simulation. Cela signifie que la simulation ne démarrera pas instantanément ...
... Cependant, vous pouvez simuler les prochaines étapes de la simulation sans vérifier chaque fois la collision, car vous savez déjà quand la prochaine collision se produira (ou qu'aucune collision ne se produira dans le grand intervalle de temps). De plus, les erreurs accumulées dans cette simulation sont sans importance car une fois que la simulation a atteint le pas de temps important, nous ne faisons que placer les positions que nous avons calculées auparavant.
Nous pouvons maintenant utiliser le budget-temps que nous aurions utilisé pour vérifier les collisions à chaque étape de la simulation afin de calculer la collision suivante après celle trouvée. C’est-à-dire que nous pouvons simuler l’avance en utilisant le grand pas de temps. En supposant un monde de portée limitée (cela ne fonctionnera pas pour des jeux énormes), il devrait y avoir une file d’états futurs pour la simulation, puis chaque image que vous venez d’interpoler du dernier état à l’autre.
Je plaiderais pour une interpolation. Cependant, étant donné qu'il y a des accélérations, nous ne pouvons pas simplement tout interpoler de la même manière. Au lieu de cela, nous devons interpoler en prenant en compte l'accélération de chaque objet. D'ailleurs, nous pourrions simplement mettre à jour la position de la même manière que nous le faisons pour le grand pas de temps (ce qui signifie également qu'il est moins sujet aux erreurs parce que nous n'utiliserions pas deux implémentations différentes pour le même mouvement).
Remarque : Si nous faisons ces nombres à virgule flottante, cette approche ne résout pas le problème des objets se comportant différemment à mesure qu'ils s'éloignent de l'origine. Cependant, s'il est vrai que la précision est perdue au fur et à mesure que l'on s'éloigne de l'origine, cela reste déterministe. En fait, c’est la raison pour laquelle cela n’a même pas été abordé à l’origine.
Addenda
De OP en commentaire :
Donc, pas de format binaire, non? Cela signifie que nous devons également nous inquiéter de savoir si les nombres en virgule flottante récupérés correspondent à l'original. Voir: La précision du flotteur revisitée: portabilité du flotteur à neuf chiffres
la source
base64
éléments. Ce n'est pas un moyen efficace de représenter de grandes quantités de telles données, mais il est incorrect de laisser entendre qu'elles empêchent l'utilisation de représentations binaires.Je travaille pour une entreprise qui réalise un certain jeu de stratégie en temps réel et je peux vous affirmer que le déterminisme en virgule flottante est possible.
L'utilisation de différents compilateurs, ou du même compilateur avec des paramètres différents, ou même de versions différentes du même compilateur, peut briser le déterminisme.
Si vous avez besoin d'un jeu croisé entre les plates-formes ou les versions de jeu, alors vous devez aller en virgule fixe. Le seul jeu croisé possible dont je suis au courant avec virgule flottante se situe entre PC et XBox1, mais c'est assez fou.
Vous devrez soit trouver un moteur physique entièrement déterministe, soit utiliser un moteur open source et le rendre déterministe, ou lancer votre propre moteur. De prime abord, j'ai le sentiment qu'Unity a ajouté un moteur de physique déterministe, mais je ne suis pas sûr qu'il soit simplement déterministe sur la même machine ou sur toutes les machines.
Si vous voulez essayer de faire votre propre travail, voici quelques conseils utiles:
la source
rsqrtps
?Je ne sais pas si c'est le type de réponse que vous recherchez, mais une autre solution pourrait consister à exécuter les calculs sur un serveur central. Demandez aux clients d’envoyer la configuration à votre serveur, laissez-le effectuer la simulation (ou en récupérer une mise en cache) et renvoyer les résultats, qui sont ensuite interprétés par le client et traités sous forme de graphiques.
Bien sûr, cela supprime tout projet éventuel d’exécution du client en mode hors connexion et, selon l’intensité des calculs, vous aurez peut-être besoin d’un serveur très puissant. Ou plusieurs, mais au moins vous avez la possibilité de vous assurer qu'ils ont la même configuration matérielle et logicielle. Une simulation en temps réel peut être difficile mais pas impossible (pensez aux flux vidéo en direct - ils fonctionnent, mais avec un léger retard).
la source
Je vais faire une suggestion contre-intuitive selon laquelle, même si elle n'est pas fiable à 100%, devrait fonctionner correctement la plupart du temps et est très facile à mettre en œuvre.
Réduisez la précision.
Utilisez une taille de pas de temps constante prédéterminée, effectuez la physique sur chaque pas de temps dans un float double précision standard, puis quantifiez la résolution de toutes les variables pour simple précision (ou quelque chose de pire encore) après chaque pas. Ensuite, la plupart des écarts que la réorganisation en virgule flottante pourrait éventuellement introduire (par rapport à un cycle de référence du même programme) seront éliminés, car ces écarts se produisent sous forme de chiffres qui n'existent même pas avec une précision réduite. Ainsi, la déviation n’aura pas la chance d’une accumulation de Lyapunov (effet papillon) qui finirait par devenir notable.
Bien sûr, la simulation sera légèrement moins précise que ce qu’elle pourrait être (comparée à la physique réelle), mais ce n’est pas vraiment remarquable tant que toutes les exécutions de votre programme sont inexactes. de la même manière .
Maintenant, techniquement parlant, il est bien sûr possible qu'un réordonnancement provoque une déviation qui atteigne un chiffre de signification supérieure, mais si les déviations ne sont réellement causées que par un flottant et que vos valeurs représentent des quantités physiques continues, cela est extrêmement improbable. Notez qu'il existe un demi-milliard de
double
valeurs entre deux valeurs quelconquessingle
, de sorte que l'on peut s'attendre à ce que la grande majorité des pas de temps dans la plupart des simulations soient exactement les mêmes d'une exécution à l'autre. Les rares cas où une déviation réussit à travers la quantification seront, espérons-le, dans des simulations qui ne durent pas aussi longtemps (du moins pas avec une dynamique chaotique).Je vous recommanderais également d’envisager l’approche complètement opposée à ce que vous demandez: embrasser l’incertitude ! Si le comportement est légèrement non déterministe, il s'agit en fait d'une approche plus proche des expériences réelles en physique. Alors, pourquoi ne pas délibérément randomiser les paramètres de départ pour chaque exécution de simulation et en faire une obligation pour que la simulation réussisse de manière cohérente sur plusieurs essais? Cela en apprendra beaucoup plus sur la physique et sur la façon de concevoir des machines suffisamment robustes / linéaires, plutôt que sur des machines ultra-fragiles qui ne sont réalistes que dans une simulation.
la source
Créez votre propre classe pour stocker des nombres!
Vous pouvez forcer un comportement déterministe si vous savez exactement comment les calculs seront effectués. Par exemple, si les seules opérations que vous traitez sont la multiplication, la division, l’addition et la soustraction, il suffirait alors de représenter tous les nombres comme un simple nombre rationnel. Pour ce faire, une simple classe Rational ferait l'affaire.
Mais si vous souhaitez gérer des calculs plus complexes (éventuellement des fonctions trigonométriques, par exemple), vous devrez alors écrire ces fonctions vous-même. Si vous voulez pouvoir prendre le sinus d'un nombre, vous devez pouvoir écrire une fonction qui se rapproche du sinus d'un nombre tout en n'utilisant que les opérations mentionnées ci-dessus. Tout cela est faisable et, à mon avis, contourne une grande partie des détails velus dans les autres réponses. Le compromis est que vous aurez plutôt à faire face à des calculs.
la source
*
/
+
-
les dénominateurs deviendront de plus en plus grands avec le temps.Il y a une certaine confusion de terminologie ici. Un système physique peut être complètement déterministe, mais impossible à modéliser pour une période de temps utile car son comportement est extrêmement sensible aux conditions initiales, et une modification infiniment petite des conditions initiales produira des comportements complètement différents.
Voici une vidéo d'un appareil réel dont le comportement est intentionnellement imprévisible, sauf d'un point de vue statistique:
https://www.youtube.com/watch?v=EvHiee7gs9Y
Il est facile de construire des systèmes mathématiques simples (utilisant seulement l'addition et la multiplication) où le résultat après N étapes dépend de la N'-ème décimale des conditions de départ. Écrire un logiciel pour modéliser un tel système de manière cohérente, quel que soit le matériel informatique et les logiciels que l'utilisateur peut avoir, est quasiment impossible - même avec un budget suffisamment grand pour le test l'application sur toutes les combinaisons possibles de matériel et de logiciel.
Le meilleur moyen de résoudre ce problème est d’attaquer le problème à sa source: assurez-vous que la physique de votre jeu est aussi déterministe que nécessaire pour obtenir des résultats reproductibles.
L’autre solution consiste à essayer de le rendre déterministe en modifiant le logiciel afin de modéliser quelque chose qui n’est pas conforme à la physique. Le problème est que vous avez introduit plusieurs couches de complexité supplémentaires dans le système, par rapport à une modification explicite de la physique.
Par exemple, supposons que votre jeu implique des collisions de corps rigides. Même si vous ignorez les frictions, la modélisation exacte des collisions entre des objets de formes arbitraires susceptibles de tourner à mesure qu'ils se déplacent est pratiquement impossible. Mais si vous modifiez la situation de sorte que les seuls objets soient des briques rectangulaires non rotatives, la vie devient beaucoup plus simple. Si les objets de votre jeu ne ressemblent pas à des briques, masquez-le avec des graphismes "non physiques" - par exemple, cachez littéralement l'instant de collision derrière de la fumée ou des flammes, ou une bulle de texte de dessin animé "Ouch" ou peu importe.
Le joueur doit découvrir la physique du jeu en jouant. Peu importe si ce n'est pas «totalement réaliste», à condition que ce soit cohérent avec soi-même et suffisamment similaire à l'expérience du sens commun pour être plausible.
Si vous faites que la physique elle-même se comporte de manière stable, un modèle informatique de celle-ci peut également produire des résultats stables, du moins en ce sens que les erreurs d'arrondi n'auront aucune pertinence.
la source
Utilisez la précision double en virgule flottante au lieu de la précision en virgule flottante simple . Bien que pas parfait, il est suffisamment précis pour être considéré comme déterministe dans votre physique. Vous pouvez envoyer une fusée sur la Lune avec une précision double, mais pas avec une précision unique.
Si vous avez vraiment besoin d'un déterminisme parfait, utilisez les mathématiques à points fixes . Cela vous donnera moins de précision (si vous utilisez le même nombre de bits), mais des résultats déterministes. Je ne suis au courant d'aucun moteur physique qui utilise le calcul mathématique à point fixe. Vous devrez peut-être écrire le vôtre si vous souhaitez emprunter cette voie. (Quelque chose que je déconseille.)
la source
Utilisez le modèle de mémento .
Lors de votre exécution initiale, sauvegardez les données de position de chaque image ou de tout autre point de repère nécessaire. Si c'est trop peu performant, ne le faites que toutes les n images.
Ensuite, lorsque vous reproduisez la simulation, suivez la physique arbitraire, mais mettez à jour les données de position toutes les n images.
Pseudo-code trop simplifié:
la source