Pour créer un jeu comme un RTS en réseau, j'ai vu un certain nombre de réponses suggérer de rendre le jeu complètement déterministe; Ensuite, il vous suffit de transférer les actions des utilisateurs les uns aux autres et de décaler ce qui est affiché afin de "verrouiller" l'entrée de tout le monde avant le rendu de la prochaine image. Ensuite, des éléments tels que les positions de l'unité, la santé, etc., n'ont pas besoin d'être constamment mis à jour sur le réseau, car la simulation de chaque joueur sera exactement la même. J'ai également entendu la même chose suggérée pour faire des replays.
Cependant, puisque les calculs en virgule flottante ne sont pas déterministes entre les machines, ou même entre différentes compilations du même programme sur la même machine, est-ce vraiment possible? Comment pouvons-nous empêcher ce fait de causer de petites différences entre les joueurs (ou les replays) qui se répercutent tout au long du jeu ?
J'ai entendu dire que certaines personnes suggèrent d'éviter complètement les nombres à virgule flottante et d'utiliser int
pour représenter le quotient d'une fraction, mais cela ne me semble pas pratique - que faire si j'ai besoin, par exemple, de prendre le cosinus d'un angle? Dois-je sérieusement réécrire toute une bibliothèque de mathématiques?
Notez que je suis principalement intéressé par C # qui, autant que je sache, a exactement les mêmes problèmes que C ++ à cet égard.
la source
Réponses:
Les points flottants sont-ils déterministes?
Il y a quelques années, j’ai beaucoup lu sur cette question lorsque je voulais écrire un RTS en utilisant la même architecture lockstep que celle que vous utilisez.
Mes conclusions sur les points flottants du matériel sont les suivantes:
STREFLOP
bibliothèque)J'ai conclu qu'il était impossible d'utiliser les types à virgule flottante intégrés dans .net de manière déterministe.
Solutions de contournement possibles
Ainsi j'avais besoin de solutions de contournement. J'ai considéré:
FixedPoint32
en C #. Bien que ce ne soit pas trop difficile (ma mise en œuvre est presque terminée), la très petite plage de valeurs rend son utilisation gênante. Vous devez faire attention à tout moment pour ne pas déborder, ni perdre trop de précision. En fin de compte, j'ai trouvé que ce n'était pas plus facile que d'utiliser des entiers directement.FixedPoint64
en C #. J'ai trouvé cela plutôt difficile à faire. Pour certaines opérations, des entiers intermédiaires de 128 bits seraient utiles. Mais .net ne propose pas un tel type.Decimal
. Mais c'est lent, prend beaucoup de mémoire et jette facilement des exceptions (division par 0, débordements). C'est très bien pour une utilisation financière, mais pas bon pour les jeux.Mon softfloat
Inspiré par votre message sur StackOverflow , je viens de commencer à implémenter un logiciel de type virgule flottante 32 bits et les résultats sont prometteurs.
float
une addition / multiplication (Single thread sur un i66 à 2.66 GHz). Si quelqu'un a de bons points de repère en virgule flottante pour .net, envoyez-les-moi, car mon test actuel est très rudimentaire.Si quelqu'un souhaite contribuer aux tests ou améliorer le code, contactez-moi ou envoyez une demande de tirage sur github. https://github.com/CodesInChaos/SoftFloat
Autres sources d'indéterminisme
Il existe également d’autres sources d’indéterminisme dans .net.
Dictionary<TKey,TValue>
ouHashSet<T>
retourne les éléments dans un ordre indéfini.object.GetHashCode()
diffère d'une course à l'autre.Random
classe intégrée n'est pas spécifiée, utilisez la vôtre.WeakReference
s perdent leur cible, c'est indéterministe car le GC peut être exécuté à tout moment.la source
La réponse à cette question provient du lien que vous avez posté. Plus précisément, vous devriez lire la citation de Gas Powered Games:
Et puis en dessous de celui-ci se trouve ceci:
Un jeu déterministe ne sera déterministe que lorsque vous utiliserez des fichiers compilés de manière identique et que celui-ci sera exécuté sur des systèmes conformes aux normes IEEE. Les simulations de réseau multiplates-formes ou les relances ne seront pas possibles.
la source
Utilisez des arithmétiques à point fixe. Ou choisissez un serveur faisant autorité et synchronisez l'état du jeu de temps en temps - c'est ce que font les MMORTS. (Du moins, Elements of War fonctionne comme ceci. Il est également écrit en C #.) De cette façon, les erreurs ne risquent pas de s’accumuler.
la source
Edit: un lien vers une classe à point fixe (attention acheteur - je ne l'ai pas utilisé ...)
Vous pouvez toujours avoir recours à l' arithmétique en virgule fixe. Quelqu'un (qui fabrique des droits, pas moins) a déjà effectué le travail sur le stackoverflow .
Vous devrez payer une pénalité de performance, mais cela peut ne pas être un problème, car .net ne sera pas particulièrement performant ici car il n’utilisera pas les instructions simd. Référence!
NB Apparemment, quelqu'un chez intel semble avoir une solution pour vous permettre d’utiliser la bibliothèque de primitives de performances intel de c #. Cela peut aider à vectoriser le code en virgule fixe pour compenser les performances plus lentes.
la source
decimal
fonctionnerait bien pour cela aussi; mais, cela a toujours les mêmes problèmes que d’utiliserint
- comment puis-je déclencher avec elle?J'ai travaillé sur un certain nombre de gros titres.
Réponse courte: C'est possible si vous êtes incroyablement rigoureux, mais n'en valez probablement pas la peine. À moins que vous n'ayez une architecture fixe (lire: console), elle est complexe, fragile et présente une foule de problèmes secondaires, tels qu'une jointure tardive.
Si vous lisez certains des articles mentionnés, vous remarquerez que, si vous pouvez définir le mode de fonctionnement du processeur, vous rencontrerez des cas bizarres, par exemple lorsqu'un pilote d'imprimante bascule le mode de traitement parce qu'il se trouvait dans le même espace d'adressage. Dans certains cas, une application avait été verrouillée par une application sur un périphérique externe, mais un composant défectueux provoquait un étranglement du processeur en raison de la chaleur et des rapports différents le matin et l'après-midi, ce qui en faisait un ordinateur différent après le déjeuner.
Ces différences de plate-forme se trouvent dans le silicium, pas dans le logiciel. Par conséquent, pour répondre à votre question, C # est également concerné. Des instructions telles que multiplier-ajouter fusionné (FMA) vs ADD + MUL changent le résultat car il arrondit en interne une seule fois au lieu de deux. C vous donne plus de contrôle pour forcer le processeur à faire ce que vous voulez en excluant des opérations comme FMA pour rendre les choses «standard», mais au détriment de la vitesse. Les éléments intrinsèques semblent être les plus susceptibles de différer. Sur un projet, j'ai dû extraire un livre de tableaux acos vieux de 150 ans pour obtenir des valeurs à comparer afin de déterminer quel processeur était "correct". De nombreux processeurs utilisent des approximations polynomiales pour les fonctions trig, mais pas toujours avec les mêmes coefficients.
Ma recommandation:
Que vous optiez pour une synchronisation ou un pas à pas entier, gérez les mécanismes de jeu séparément de la présentation. Assurez la précision du jeu, mais ne vous inquiétez pas de la précision de la couche de présentation. N'oubliez pas non plus que vous n'avez pas besoin d'envoyer toutes les données mondiales en réseau au même débit. Vous pouvez hiérarchiser vos messages. Si votre simulation correspond à 99,999%, vous n'aurez pas besoin de transmettre aussi souvent pour rester apparié. (Prévention de la triche à part.)
Il y a un bel article sur le moteur source qui explique une façon de synchroniser: https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
Rappelez-vous que si vous êtes intéressé par une participation tardive, vous devrez quand même vous en prendre à la synchronisation.
la source
Oui, C # a les mêmes problèmes que C ++. Mais il a aussi beaucoup plus.
Par exemple, prenons cette déclaration de Shawn Hawgraves:
Il est "assez facile" de faire en sorte que cela se produise en C ++. En C # , cependant, ce sera beaucoup plus difficile à gérer. C'est grâce à JIT.
Que pensez-vous qu'il se passerait si l'interprète exécutait votre code une fois interprété, puis que JIT le répète une seconde fois? Ou peut-être qu'il l'interprète deux fois sur la machine de quelqu'un d'autre, mais JIT l'a-t-il après?
JIT n'est pas déterministe, car vous avez très peu de contrôle sur elle. C’est l’une des choses que vous abandonnez pour utiliser le CLR.
Et que Dieu vous aide si une personne utilise .NET 4.0 pour exécuter votre jeu, tandis que quelqu'un d'autre utilise Mono CLR (en utilisant les bibliothèques .NET bien sûr). Même .NET 4.0 contre .NET 5.0 pourrait être différent. Vous avez simplement besoin de davantage de contrôle sur les détails de bas niveau d'une plate-forme pour garantir ce genre de chose.
Vous devriez être capable de vous en sortir avec des maths à points fixes. Mais c'est à peu près tout.
la source
int
- comment puis-je faire du trig avec?Après avoir écrit cette réponse, je me suis rendu compte qu'elle ne répondait pas vraiment à la question, qui portait spécifiquement sur le non-déterminisme à virgule flottante. Mais peut-être que cela l’aidera quand même s’il va créer un jeu en réseau de cette manière.
En plus du partage d’entrée que vous diffusez à tous les joueurs, il peut être très utile de créer et de diffuser une somme de contrôle sur l’état important du jeu, telle que la position du joueur, la santé, etc. Lors du traitement de l’entrée, affirmez que tous les lecteurs distants sont synchronisés. Vous aurez la garantie de pouvoir corriger les bogues non synchronisés (OOS), ce qui facilitera la tâche - vous recevrez un avis préalable indiquant que quelque chose ne va pas (ce qui vous aidera à comprendre les étapes de la reproduction), et vous devriez pouvoir en ajouter davantage. consignation de l’état du jeu dans un code suspect pour vous permettre de mettre entre parenthèses la cause du problème.
la source
Je pense que l'idée du blog liée à la nécessité d'une synchronisation périodique est toujours viable - j'ai vu suffisamment de bugs dans les jeux RTS en réseau qui n'acceptent pas cette approche.
Les réseaux sont à perte, lents, ont du temps de latence et peuvent même polluer vos données. Le "déterminisme à virgule flottante" (ce qui semble assez prétentieux pour me rendre sceptique) est le moindre de vos soucis dans la réalité ... surtout si vous utilisez un pas de temps fixe. avec des pas de temps variables, vous devrez aussi interpoler entre des pas de temps fixes pour éviter les problèmes de déterminisme. Je pense que c'est généralement ce que l'on entend par comportement "déterministe" non déterministe - ce n'est que le fait que les pas de temps variables entraînent une divergence des intégrations - rien à voir avec les bibliothèques mathématiques ou les fonctions de bas niveau.
La synchronisation est la clé cependant.
la source
Vous les rendez déterministes. Pour un bon exemple, consultez la présentation de Dungeon Siege GDC sur la manière dont les sites du monde peuvent être mis en réseau.
Rappelez-vous également que le déterminisme s'applique également aux événements «aléatoires»!
la source