Cette question est juste de savoir comment un jeu peut gérer autant de personnages à la fois. Je suis nouveau dans le jeu, je vous demande pardon à l'avance.
Exemple
Je crée un jeu de tower defense dans lequel il y a 15 emplacements de tours où les tours sont construites et chaque tour éjecte le projectile à une certaine vitesse; disons que chaque seconde, 2 projectiles sont créés par chacune des tours et qu'il y a des ennemis marchant sur le champ de bataille, disons 70 (chacun avec 10 types d'attributs comme HP, mana, etc., qui changeront en se déplaçant autour du champ de bataille).
Sommaire
Tour Count = 15
Projectiles Créé par chaque tour par seconde = 2
Nombre total de Projectiles Créé par seconde = 30
unités dans Battlefield Count = 70
Maintenant, le jeu gère-t-il ces 30 projectiles et 70 unités en les manipulant sur 100 threads différents (ce qui est trop pour un PC) ou 1 thread qui les déplace tous, réduit leur valeur, etc. (ce qui sera un peu lent , Je pense)?
Je n'ai aucune idée à ce sujet, alors quelqu'un peut-il me guider sur la façon dont cela fonctionnera?
la source
Réponses:
Non, ne fais jamais ça. Ne créez jamais un nouveau thread par ressource, cela ne s'adapte pas au réseau, ni à la mise à jour des entités. (Quelqu'un se souvient-il des moments où vous aviez un thread pour lire par socket en Java?)
Oui, pour commencer, c'est la voie à suivre. Les "gros moteurs" répartissent le travail entre les threads, mais cela n'est pas nécessaire pour démarrer un jeu simple comme un jeu de tower defense. Il y a probablement encore plus de travail à faire à chaque tick que vous ferez également dans ce fil. Oh ouais, et le rendu bien sûr.
Eh bien ... Quelle est votre définition de lent ? Pour 100 entités, cela ne devrait pas prendre plus d'une demi-milliseconde, probablement même moins, selon la qualité de votre code et la langue avec laquelle vous travaillez. Et même si cela prend deux millisecondes complètes, c'est quand même assez bon pour atteindre les 60 tps (ticks par secondes, sans parler des frames dans ce cas).
la source
La règle numéro un du multithreading est la suivante: ne l'utilisez pas sauf si vous devez paralléliser sur plusieurs cœurs de processeur pour des performances ou une réactivité. Une exigence "x et y doivent se produire simultanément du point de vue des utilisateurs" n'est pas encore une raison suffisante pour utiliser le multithreading.
Pourquoi?
Le multithreading est difficile. Vous n'avez aucun contrôle sur le moment où chaque thread est exécuté, ce qui peut entraîner toutes sortes de problèmes impossibles à reproduire («conditions de concurrence»). Il existe des méthodes pour éviter cela (verrous de synchronisation, sections critiques), mais celles-ci s'accompagnent de leur propre ensemble de problèmes ("blocages").
Habituellement, les jeux qui traitent un aussi petit nombre d'objets que quelques centaines (oui, ce n'est pas beaucoup dans le développement de jeux) les traitent généralement en série chaque tick logique en utilisant une
for
boucle commune .Même les processeurs de smartphone relativement plus faibles peuvent exécuter des milliards d'instructions par seconde. Cela signifie que même lorsque la logique de mise à jour de vos objets est complexe et prend environ 1000 instructions par objet et tick, et que vous visez 100 généreuses ticks par seconde, vous avez une capacité CPU suffisante pour des dizaines de milliers d'objets. Oui, il s'agit d'un calcul au dos de l'enveloppe extrêmement simplifié, mais il vous donne une idée.
En outre, la sagesse courante dans le développement de jeux est que les logiques de jeu sont très rarement le goulot d'étranglement d'un jeu. La partie critique des performances est presque toujours le graphisme. Oui, même pour les jeux 2D.
la source
Les autres réponses ont géré le filetage et la puissance des ordinateurs modernes. Pour répondre à la plus grande question cependant, ce que vous essayez de faire ici est d'éviter les situations "n au carré".
Par exemple, si vous avez 1000 projectiles et 1000 ennemis, la solution naïve consiste à simplement les comparer les uns aux autres.
Cela signifie que vous vous retrouvez avec p * e = 1 000 * 1 000 = 1 000 000 de contrôles différents! C'est O (n ^ 2).
D'un autre côté, si vous organisez mieux vos données, vous pouvez éviter beaucoup de cela.
Par exemple, si vous listez sur chaque carré de la grille quels ennemis se trouvent dans cette case, vous pouvez parcourir vos 1000 projectiles et vérifier simplement le carré sur la grille. Maintenant, il vous suffit de vérifier chaque projectile par rapport au carré, c'est O (n). Au lieu d'un million de chèques par trame, vous n'en avez besoin que de mille.
Penser à organiser vos données et à les traiter efficacement grâce à cette organisation est la plus grande optimisation que vous puissiez faire.
la source
Ne créez pas de threads par ressource / objet mais par section de la logique de votre programme. Par exemple:
L'avantage de ceci est que votre interface graphique (par exemple des boutons) ne se bloque pas nécessairement si votre logique est lente. L'utilisateur peut toujours mettre en pause et enregistrer la partie. C'est aussi bon pour préparer votre jeu pour le multijoueur, maintenant que vous séparez le graphique de la logique.
la source
Même Space Invaders a géré des dizaines d'objets en interaction. Alors que le décodage d'une image de vidéo HD H264 implique des centaines de millions d'opérations arithmétiques. Vous disposez d'une grande puissance de traitement.
Cela dit, vous pouvez toujours ralentir si vous le gaspillez. Le problème n'est pas tant le nombre d'objets que le nombre de tests de collision effectués; l'approche simple consistant à comparer chaque objet les uns par rapport aux autres équivaut au nombre de calculs requis. Tester 1001 objets pour des collisions de cette façon nécessiterait un million de comparaisons. Souvent, ceci est résolu par exemple en ne vérifiant pas les projectiles pour une collision les uns avec les autres.
la source
Je vais être en désaccord avec certaines des autres réponses ici. Les threads logiques séparés sont non seulement une bonne idée, mais extrêmement bénéfiques pour la vitesse de traitement - si votre logique est facilement séparable .
Votre question est un bon exemple de logique qui est probablement séparable si vous pouvez ajouter un peu de logique supplémentaire par-dessus. Par exemple, vous pouvez exécuter plusieurs threads de détection de hit soit en verrouillant les threads dans des régions spécifiques de l'espace, soit en mutexant les objets impliqués.
Vous ne voulez probablement PAS un thread pour chaque collision possible, juste parce que cela risque de gâcher le planificateur; il existe également un coût associé à la création et à la destruction de threads. Mieux vaut faire un certain nombre de threads autour des cœurs du système (ou utiliser une métrique comme l'ancienne
#cores * 2 + 4
), puis les réutiliser lorsque leur processus se termine.Cependant, toute logique n'est pas facilement séparable. Parfois, vos opérations peuvent atteindre simultanément toutes les données de jeu, ce qui rendrait le threading inutile (en fait, dangereux, car vous auriez besoin d'ajouter des vérifications pour éviter les problèmes de threading). De plus, si plusieurs étapes de la logique dépendent fortement les unes des autres se produisant dans des commandes spécifiques, vous devrez contrôler l'exécution des threads de manière à garantir que cela ne donne pas de résultats dépendants de la commande. Cependant, ce problème n'est pas éliminé en n'utilisant pas de threads, les threads l'exacerbent simplement.
La plupart des jeux ne le font pas simplement parce que c'est plus complexe que le développeur de jeux moyen ne veut / ne peut gérer ce qui n'est généralement pas le goulot d'étranglement en premier lieu. La grande majorité des jeux sont limités en GPU et non en CPU. Bien que l'amélioration de la vitesse du processeur puisse globalement aider, ce n'est généralement pas l'objectif.
Cela dit, les moteurs physiques utilisent souvent plusieurs threads, et je peux nommer plusieurs jeux qui, selon moi, auraient bénéficié de plusieurs threads logiques (les jeux Paradox RTS comme HOI3 et autres, par exemple).
Je suis d'accord avec d'autres articles que vous n'auriez probablement pas besoin d'employer des fils dans cet exemple spécifique, même si cela pourrait être bénéfique. Le thread doit être réservé aux cas où vous avez une charge CPU excessive qui ne peut pas être optimisée via d'autres méthodes. C'est une entreprise énorme qui affectera la structure fondamentale d'un moteur; ce n'est pas quelque chose que vous pouvez aborder après coup.
la source
Je pense que les autres réponses manquent une partie importante de la question en se concentrant trop sur le filetage de la question.
Un ordinateur ne gère pas du tout tous les objets d'un jeu à la fois. Il les gère en séquence.
Un jeu informatique progresse en pas de temps discrets. Selon le jeu et la vitesse du PC, ces étapes sont généralement de 30 ou 60 pas par seconde, ou autant / peu d'étapes que le PC peut calculer.
Dans une telle étape, un ordinateur calcule ce que chacun des objets de jeu fera pendant cette étape et les met à jour en conséquence, l'un après l'autre. Il pourrait même le faire en parallèle, en utilisant des threads pour être plus rapide, mais comme nous le verrons bientôt, la vitesse n'est pas du tout un problème.
Un processeur moyen doit être de 2 GHz ou plus, ce qui signifie 10 9 cycles d'horloge par seconde. Si l' on calcule 60 par seconde, pas de temps que les feuilles 10 9 /60 cycles d'horloge = 16666666 cycles d'horloge par pas de temps. Avec 70 unités, il nous reste encore environ 2 400 000 cycles d'horloge par unité. Si nous devions optimiser, nous pourrions mettre à jour chaque unité en aussi peu que 240 cycles, selon la complexité de la logique du jeu. Comme vous pouvez le voir, notre ordinateur est environ 10 000 fois plus rapide qu'il ne devrait l'être pour cette tâche.
la source
Avertissement: Mon type de jeu préféré de tous les temps est basé sur le texte et j'écris ceci en tant que programmeur de longue date d'un ancien MUD.
Je pense qu'une question importante que vous devez vous poser est la suivante: avez-vous même besoin de fils? Je comprends qu'un jeu graphique utilise probablement plus de MT, mais je pense que cela dépend aussi de la mécanique du jeu. (Il peut également être utile de considérer qu'avec les GPU, les CPU et toutes les autres ressources que nous avons aujourd'hui sont beaucoup plus puissantes, ce qui rend vos problèmes de ressources aussi problématiques que cela puisse vous paraître; en effet, 100 objets est pratiquement zéro). Cela dépend également de la façon dont vous définissez «tous les caractères à la fois». Voulez-vous dire exactement au même moment? Vous n'aurez pas cela comme Peter le souligne à juste titre, donc tout à coup n'est pas pertinent au sens littéral; cela n'apparaît que de cette façon.
En supposant que vous irez avec des threads: vous ne devriez certainement pas considérer 100 threads (et je ne vais même pas me demander si c'est trop pour votre processeur ou non; je ne parle que des complications et de la praticité de celui-ci).
Mais rappelez-vous ceci: le multi-threading n'est pas facile (comme le souligne Philipp) et a de nombreux problèmes. D'autres ont beaucoup plus d'expérience (par beaucoup) que moi avec MT mais je dirais qu'eux aussi suggéreraient la même chose (même s'ils seraient plus capables que moi - surtout sans pratique de ma part).
Certains soutiennent qu'ils ne sont pas d'accord sur le fait que les threads ne sont pas bénéfiques et certains soutiennent que chaque objet devrait avoir un thread. Mais (et encore une fois, c'est tout du texte, mais même si vous considérez plus d'un thread, vous n'avez pas besoin - et ne devriez pas - le considérer pour chaque objet), car Philipp souligne que les jeux ont tendance à parcourir les listes. Mais ce n'est pas seulement (comme il le suggère bien que je réalise qu'il ne répond qu'à vos paramètres de si peu d'objets) pour si peu d'objets. Dans le MUD, je suis un programmeur car nous avons les éléments suivants (et ce n'est pas toute l'activité qui se produit en temps réel, gardez cela à l'esprit également):
(Le nombre d'instances varie bien sûr - plus haut et plus bas)
Mobiles (PNJ, c'est-à-dire personnage non joueur): 2614; prototypes: 1360 Objets: 4457; prototypes: 2281 chambres: 7983; prototypes: 7983. Chaque pièce a généralement sa propre instance mais nous avons aussi des pièces dynamiques c'est-à-dire des pièces à l'intérieur d'une pièce; ou des pièces à l'intérieur d'un mobile, par exemple l'estomac d'un dragon; ou des pièces dans des objets, par exemple, vous entrez dans un objet magique). Gardez à l'esprit que ces pièces dynamiques existent par objet / pièce / mobile qui les a réellement définies. Oui, cela ressemble beaucoup à l'idée d'instances de World of Warcraft (je ne le joue pas mais un ami me l'a fait jouer quand j'avais une machine Windows, pendant un certain temps) sauf que nous l'avions bien avant que World of Warcraft n'existe.
Scripts: 868 (actuellement) (curieusement, notre commande de statistiques ne montre pas combien de prototypes nous avons donc je vais l'ajouter). Toutes ces activités ont lieu dans des zones / zones et nous en avons 103. Nous avons également des procédures spéciales qui se déclenchent à différents moments. Nous avons également d'autres événements. Ensuite, nous avons également des prises connectées. Les mobiles se déplacent, font différentes activités (outre le combat), interagissent avec les joueurs, etc. (Il en va de même pour les autres types d'entités).
Comment gérons-nous tout cela sans délai?
sockets: select (), files d'attente (entrée, sortie, événements, autres), tampons (entrée, sortie, autres), etc. Ceux-ci sont interrogés 10 fois par seconde.
personnages, objets, salles, combat, tout: le tout dans une boucle centrale sur différentes impulsions.
Nous avons également (mon implémentation basée sur une discussion entre le fondateur / autre programmeur et moi-même) un vaste suivi de liste liée et des tests de validité de pointeur et nous avons plus qu'assez de ressources gratuites si nous en avons réellement besoin. Tout cela (sauf que nous avons élargi le monde) existait il y a des années quand il y avait moins de RAM, de puissance CPU, d'espace sur le disque dur, etc. Et même alors, nous n'avons eu aucun problème. Dans les boucles décrites (les scripts provoquent cela comme les réinitialisations / repeuplements de zone comme le font d'autres choses), des monstres, des objets (objets) et d'autres choses sont créés, libérés, etc. Les connexions sont également acceptées, interrogées et tout ce que vous attendez.
la source