Je suis tombé sur cette question lorsque je concevais un jeu vidéo en C #.
Si nous considérons des jeux tels que Battlefield ou Call of Duty , des centaines voire des milliers de balles volent en même temps. Les événements sont déclenchés constamment, et d'après ce que je sais, cela consomme beaucoup de puissance de traitement… ou le fait-il? Je souhaite savoir comment divers développeurs de jeux gèrent les puces (2D et 3D) et quelle est la méthode la plus efficace pour chacune d’elles.
J'ai lu la question Comment les balles sont-elles simulées dans les jeux vidéo? mais cela ne concerne pas le fonctionnement des puces du point de vue de la conception du programme.
J'ai eu quelques idées, mais chacune a ses inconvénients:
Méthode la plus efficace à laquelle je pouvais penser (pour les jeux 2D):
Supposons que je crée une classe appelée Bullet et que, aussi longtemps que l'utilisateur maintienne un bouton enfoncé, un objet Bullet sera créé toutes les 0,01 seconde. Cette balle a:
1 vélocité
2 Position de départ d'où il est tiré
3 texture Sprite
4 Un effet de frappe
Puisque la balle serait sa propre classe, elle pourrait gérer elle-même les écouteurs dessinant, bougeant et agissant.
Ne serait-il pas difficile pour le processeur de traiter des milliers de ces objets en cours d'instanciation, puis de destruction (lorsqu'un effet de frappe est déclenché)? Espace RAM?
Méthode efficace pour les jeux 3D - Une autre pensée que j'avais était la suivante:
Disons que je crée une classe d'arme. Cette arme a diverses caractéristiques, dont certaines:
1 Détecter le point de visée de l'arme et déterminer si elle vise une cible
2 Déclenchez une animation du tir au fusil
3 Possède une méthode doDamage () qui indique quelque chose à soustraire la santé quel que soit le type de pistolet visé
4 Notifie une classe d'animation de balle lorsque le bouton est enfoncé
Je pourrais ensuite créer une classe statique, disons BulletAnimation, qui pourrait recevoir une notification indiquant l'origine du pistolet, l'emplacement de ce pistolet (pour la destination de la balle) et des informations sur un sprite et une vitesse appropriés à utiliser pour la balle. . Cette classe dessine ensuite les images-objets (sur un nouveau fil, idk) en fonction des deux positions et de l'image-objet souhaitée, afin de simuler le tir d'une balle.
Ce dernier semble beaucoup plus difficile à coder, et ne faudrait-il pas beaucoup de puissance de traitement pour appeler constamment le statique afin de le faire pour des milliers de balles à la fois? Obtenir des mises à jour constantes sur les positions de départ et d'arrivée serait également difficile.
Ma question est la suivante: quelle est la méthode la plus efficace pour les créateurs de jeux? Cette méthode change-t-elle des jeux 2D aux jeux 3D?
pew-pew-pew
technologie :)Réponses:
Je peux certainement comprendre pourquoi vous pensez qu’il serait difficile de les simuler, mais il ya suffisamment de contraintes sur les balles (vraiment tous les projectiles) pour les rendre plus faciles.
Ils sont généralement simulés comme un seul point, plutôt que comme un élément de volume. Cela facilite considérablement la détection des collisions, car maintenant je n'ai plus besoin que de collisions contre des surfaces très simples, comme une ligne contre un cercle.
Nous savons comment ils vont se déplacer, il n’ya donc pas beaucoup d’informations à stocker ou à calculer pour eux. Votre liste était assez précise. Nous aurons généralement quelques éléments supplémentaires, comme par exemple qui a tiré la balle et quel est son type.
Étant donné que tous les projectiles seront très similaires, nous pouvons les pré-allouer afin d’éviter toute surcharge de les créer dynamiquement. Je peux allouer un tableau de 1 000 projectiles, et maintenant, ils ne sont accessibles qu’avec un index et ils sont tous séquentiels en mémoire, leur traitement sera donc rapide.
Ils ont une durée de vie / plage fixe, ce qui me permet de supprimer les anciennes puces et de recycler la mémoire en nouvelles puces très rapidement.
Une fois qu'ils ont touché quelque chose, je peux aussi les faire expirer, leur vie est donc finie.
Puisque nous savons à quel moment ils ont été créés, si nous en avons besoin de nouveaux et que nous n'avons pas de billets gratuits dans notre liste préaffectée, je peux simplement récupérer les plus anciens et les recycler, et les gens ne remarqueront pas que les balles expirent un peu plus tôt .
Ils sont rendus sous forme de sprites (généralement) ou de modèles low poly et occupent très peu d’espace à l’écran. Ils sont donc rapides à restituer.
Tenant compte de toutes ces choses, les balles ont tendance à être relativement bon marché. Si notre budget est déjà consommé par les balles et les rend, nous le remanierions généralement pour limiter le nombre de tirs pouvant être tirés à la fois (vous le constaterez dans de nombreux jeux d'arcade anciens), utilisez des armes à rayon qui se déplacent instantanément. , ou ralentissez le taux d’incendie pour vous assurer de respecter le budget.
la source
num_characters * max_bullets_per_character
L’un des moyens les plus efficaces d’implémenter des puces consiste probablement à utiliser ce que l’on appelle des hitscan . Sa mise en œuvre est plutôt simple: lorsque vous tirez, vous vérifiez si le pistolet vise (éventuellement en utilisant un rayon pour trouver l'entité / l'objet / le maillage le plus proche), puis vous le frappez, lui infligeant des dégâts. Si vous voulez donner l’impression que vous avez tiré une balle invisible invisible à déplacement rapide, vous pouvez la simuler en ajoutant un léger retard en fonction de la distance avant d’endommager.
Cette approche suppose essentiellement que le projectile tiré a une vitesse infinie et est généralement utilisé pour des types d’armes tels que les lasers et les faisceaux de particules / canons et peut-être même certaines formes de fusils de tireur d’ élite .
L’approche suivante consisterait à modéliser la balle tirée comme un projectile, modélisé comme sa propre entité / objet sujet à la collision et éventuellement à la gravité et / ou à la résistance de l’air qui modifie sa direction et sa vitesse. Il est plus complexe que l'approche de numérisation à la volée en raison des équations de physique supplémentaires, et nécessite plus de ressources en raison de la présence d'un objet puce, mais peut fournir des puces plus réalistes.
En ce qui concerne la gestion des collisions entre des balles à base de projectiles et d’autres objets dans le jeu, la détection des collisions peut être grandement simplifiée en triant vos objets en quad ou octrees . Les octrees sont principalement utilisés dans les jeux en 3D, tandis que les quadtrees peuvent être utilisés dans les jeux en 2D ou en 3D. L'utilisation de l'un de ces arbres présente l'avantage de réduire considérablement le nombre de contrôles de collision possibles. Par exemple, si vous avez 20 objets actifs dans le niveau, sans utiliser l'une de ces arborescences, vous devrez vérifier que tous les 20 sont en conflit avec la puce. En divisant les 20 objets entre les feuilles (nœuds d'extrémité) de l'arborescence, vous pouvez réduire le nombre de contrôles au nombre d'entités présentes dans la même feuille que la puce.
En ce qui concerne ces approches - hitscan et projectile, les deux peuvent être utilisés librement dans des jeux 2D ou 3D. Cela dépend plus de la nature de l'arme et de la manière dont le créateur a décidé que l'arme fonctionnerait.
la source
Je ne suis en aucun cas un expert, mais pour répondre à votre question, oui, vous auriez besoin de beaucoup de ces choses que vous mentionnez.
Pour votre exemple 2D, vous pourriez avoir une position et une vitesse pour une balle. (Vous pouvez également avoir besoin d'une durée de vie ou d'une distance maximale, en fonction de la manière dont vous avez implémenté vos puces.) Cela impliquerait généralement 2 valeurs (x, y). Si elles étaient des flotteurs, c'est 16 octets. Si vous avez 100 balles, cela ne représente que 1600 octets ou environ 1,5k. Ce n'est rien sur une machine aujourd'hui.
Ensuite, vous mentionnez les sprites. Vous n'aurez besoin que d'un seul sprite pour représenter chaque puce. Sa taille dépend de la profondeur de bits sur laquelle vous dessinez et de la taille à laquelle elle doit apparaître à l'écran. Même non compressé à, disons, 256x256 en 32 bits par canal, cela représente 1 Mo pour le sprite. (Et ce serait très gros!) Vous dessineriez le même sprite à chaque emplacement de balle, mais cela ne prendrait pas de mémoire supplémentaire pour chaque copie du sprite. Ce serait similaire pour un effet de frappe.
Vous parlez de tirer toutes les 0,01 secondes. Ce serait 100 balles par seconde de votre arme. Même pour une arme futuriste, c'est beaucoup! Selon cet article de Wikipédia :
Donc, ce serait le taux d'un hélicoptère d'attaque!
Pour un grand monde comme vous le mentionnez dans Battlefield / Call of Duty / etc., ils peuvent calculer toutes ces positions de balle, mais ne pas les dessiner toutes si l'action est éloignée. Ou ils peuvent ne pas les simuler jusqu'à ce que vous soyez proches. (Je dois admettre que je devine un peu sur cette partie car je n'ai pas travaillé sur quelque chose d'aussi gros.)
la source
Je pense que vous sous-estimez à quel point les ordinateurs sont rapides. Ce fut parfois un problème sur les systèmes des années 80 et 90. C'est en partie pourquoi les Space Invaders d'origine ne vous permettent pas de tirer une autre balle tant que la balle actuelle n'est pas tombée. Certains jeux ont souffert du "ralentissement" s’il y avait trop de sprites à l’écran.
De nos jours, cependant? Vous disposez de suffisamment de puissance de traitement pour des milliers d'opérations par pixel nécessaires à la texturation et à l'éclairage. Il n'y a pas de problème avec des milliers d'objets en mouvement; Cela vous permet de créer un terrain destructible (par exemple, Red Faction) où chaque fragment traite les collisions avec d'autres fragments et suit une courbe balistique.
Vous devez faire preuve de prudence en matière d'algorithme - vous ne pouvez pas utiliser l'approche naïve consistant à vérifier chaque objet par rapport à un autre objet lorsque vous avez des milliers d'objets. Les puces ne vérifient généralement pas les collisions avec d'autres balles.
Petite anecdote: la première version de Doom en réseau (l’original des années 90) envoyait un paquet sur le réseau pour chaque balle tirée. Lorsqu'un ou plusieurs joueurs ont la mitrailleuse, le réseau pourrait être submergé. Les années 90 étaient pleines de gens jouant illicitement à Doom sur des réseaux universitaires ou professionnels qui rencontraient des problèmes avec leurs administrateurs réseau lorsque le réseau devenait inutilisable.
la source
Je suis loin d'être un expert mais je travaille sur un jeu de tir 2D multijoueur pendant mon temps libre.
Ma méthode
Il existe différentes classes de puces entre le client et le serveur (même en mode hors connexion, une instance de serveur est démarrée sur un processus distinct auquel le jeu "principal" est connecté).
À chaque tick (60 par seconde), le client établit un relèvement entre le pointeur de la souris du joueur et le centre de l'écran (où se trouve leur personnage) et fait partie des informations envoyées au serveur. Si le joueur tire également à ce moment (en supposant que l'arme est chargée et prête), une instance de balle côté serveur est créée, avec simplement quelques coordonnées et un dommage de base (qui découle des statistiques de l'arme qui a tiré il). L’instance de balle utilise ensuite certaines fonctions mathématiques pour calculer les vitesses X et Y à partir du relèvement que nous avons collecté auprès du client.
Pour chaque coup suivant, la balle se déplace de ces coordonnées et réduit les dégâts de base d'un montant prédéfini. Si cette valeur passe au-dessous de 1 ou frappe un objet solide dans le monde, l'instance de balle est supprimée et, comme les collisions de points de test sont incroyablement peu coûteuses en 2D, même les armes à tir rapide ont un impact négligeable sur les performances.
En ce qui concerne le client, les informations par balle ne sont pas réellement reçues sur le réseau (cela s’est avéré inutile dans les tests), mais dans le cadre de la mise à jour par tick, chaque caractère dispose d’un booléen "déclenché" qui, si true, est créé par un client local. objet bullet qui fonctionne presque exactement comme les serveurs, la seule différence est qu’il a un sprite.
Cela signifie que bien que la balle que vous voyez ne soit pas une représentation tout à fait exacte de celle-ci sur le serveur, toute différence serait à peine perceptible, voire pas du tout, pour un joueur, et les avantages du réseau l'emportent sur les incohérences.
Note sur les différentes méthodes
Certains jeux, y compris le mien, déplacent les balles à chaque coche comme s'il s'agissait d'objets physiques, tandis que d'autres créent simplement un vecteur dans le sens de la prise de vue ou calculent le trajet complet de la balle dans le coche où elle a été créée, par exemple dans Contre- Jeux de grève. Il existe quelques petites astuces côté client pour masquer cela, telles qu'une animation du tir de la balle, mais à toutes fins pratiques, chaque balle n'est qu'un laser .
Avec les modèles 3D qui peuvent avoir des hitbox complexes, il est standard de tester les collisions contre un simple bornier FIRST, et si cela réussit, passez à une détection de collision plus «détaillée».
la source
C'est ce qu'on appelle la détection de collision. Pour ce faire, les ordinateurs 8 bits utilisaient du matériel graphique lecteur-missile. Les moteurs de jeu modernes utilisent des moteurs physiques et de l’algèbre linéaire. La direction actuelle d'une arme est représentée par un vecteur 3D. Cela fournit une ligne infinie dans la direction du feu. Chaque objet en mouvement a une ou plusieurs sphères de délimitation car c'est l'objet le plus simple à détecter une collision avec une ligne. Si les deux se croisent, c'est un succès, sinon ce n'est pas le cas. Mais la scène peut être gênante, il faut donc la vérifier également (à l’aide de volumes hiérarchiques). L'objet le plus proche qui a une intersection est celui qui a été touché.
la source