Manière appropriée de gérer la destruction des entités de jeu

10

Imaginez un monde de jeu où des charges et des charges d'entités sont chargées dynamiquement tout le temps, je représenterais cela comme une liste d'entités peut-être, mais qu'en est-il de les supprimer?

Alors que lors de l'ajout de i, je pourrais repousser la nouvelle entité, je pourrais avoir besoin de supprimer n'importe où dans le conteneur. Pour éviter de rechercher l'élément pour trouver sa position de suppression, quels choix ai-je?

Je pensais que je pouvais stocker l'identifiant d'entité comme étant sa position dans le conteneur, créant un chemin pour la suppression directe, mais cela ne générerait-il pas une sorte de «trouble» de dépendance mutuelle?

Je sais que la bonne façon serait quelque chose comme List.RemoveAt (whereToRemove); mais si seulement l'entité sait quand elle doit mourir?

Ou suis-je en train de manquer quelque chose et un conteneur de liste saurait quand un objet est détruit et réduira sa propre taille?

Grimshaw
la source

Réponses:

10

Voici vos conditions:

  • D'autres objets peuvent encore dépendre de votre entité supprimée, après sa suppression.

  • Vous souhaitez que l'entité spécifie sa propre suppression.

Vous ne pouvez pas avoir les deux. Pourquoi? Parce que le code à un niveau supérieur à votre entité elle-même (voir les exemples ci-dessous) décide quand cette entité doit être utilisée. Par conséquent, seul le code de ce même niveau peut déterminer si votre entité est apte à être supprimée ou non.

Cependant , ce qui peut arriver, c'est que l'entité peut demander sa propre suppression, en déclenchant un événement que le code de niveau supérieur écoute. Ce niveau supérieur stocke ensuite cette demande de suppression dans une liste.


Exemple 1: sans événements

Vous contrôlez les collisions entre les entités de votre monde. Ceci est géré plus haut, généralement dans votre boucle de jeu principale, qui vérifie chaque entité les unes par rapport aux autres. Dans cet exemple en particulier, lorsqu'une entité entre en collision avec une autre, seule la logique interne de cette entité peut déterminer les dommages qu'elle a subis et si elle a «expiré» ou non. Permet donc de suivre le flux logique des collisions où vous avez quatre entités dans votre monde, A, B, C et D. A est notre entité qui nous intéresse.

Nous vérifions A pour une collision avec B. Il y a une collision. A subit 50% de dégâts.

Nous vérifions A pour une collision avec C. Il y a une collision. A subit 50% de dégâts. Parce que les dégâts atteignent 0, A détermine qu'il est "mort". Il se retire de la liste.

Nous vérifions A pour une collision avec D. Il n'y aurait pas eu de collision, mais vous n'irez jamais jusque-là: vous obtenez une exception d'exécution car votre liste d'entités a été modifiée au milieu d'une opération de traveral.

Exemple 2: avec événements

Même configuration que précédemment.

Nous vérifions A pour une collision avec B. Il y a une collision. A subit 50% de dégâts.

Nous vérifions A pour une collision avec C. Il y a une collision. A subit 50% de dégâts. Parce que les dégâts atteignent 0, A détermine qu'il est "mort". Il déclenche un événement dans le code de gestion d'entité pour dire «Supprimez-moi dès que possible». Le code de gestion d'entité examine la référence d'entité envoyée dans le cadre de l'événement et stocke cette référence dans une liste d'entités à supprimer.

Nous vérifions A pour la collision avec D. Il n'y a pas de collision, et la vérification fonctionne très bien.

À la toute fin de l'itération de boucle de jeu en cours , parcourez la liste des entités à supprimer et supprimez chacune d'elles de votre liste d'entités principale.


Vous pouvez voir comment cela évite complètement le problème. Vous n'avez pas besoin d'utiliser des événements, vous pouvez utiliser des signaux ou autre chose, mais le principe est le même - ne supprimez pas d'entités avant de pouvoir le faire en toute sécurité. Le revers de cette approche, pour garder les choses propres et ordonnées, fait de même avec les entités à ajouter - assurez-vous de conserver des références à elles, et ne les ajoutez qu'au début de la prochaine itération de boucle de jeu.

Enfin, n'oubliez pas de vider vos listes à supprimer et à ajouter, chaque fois que vous les utilisez pour effectuer des ajouts / suppressions sur votre liste d'entités principale.

PS. N'ayez pas peur de chercher dans votre liste principale des déménagements individuels. Cela fait partie intégrante de la gestion des entités, et même des listes massives ont tendance à être très rapides à parcourir - après tout, c'est pour cela qu'elles sont conçues.

Ingénieur
la source
0

Vous êtes certainement à la recherche d'un HashMap / HashTable . Une table de hachage est une carte qui associe une clé à une valeur spécifique. La clé peut être n'importe quoi (comme l'ID d'entité).

Setheron
la source
0

Je suppose que vous pourriez utiliser l' idée de smartpointer pour gérer les désallocations pour vous, dans ce cas, il ne sera pas nécessaire de conserver une liste de toutes les entités dans votre code.

dans certains cas, vous avez besoin d'une liste pour parcourir tous les objets de votre jeu. cette liste pourrait être simplement une liste de liens où l'insertion et la suppression d'objets prendront exactement O (1).

même pour augmenter votre vitesse, vous pouvez utiliser un tableau statique (éventuellement un vecteur). dans ce cas, vous devrez garder une trace de 2 listes liées à l'intérieur du même vecteur, l'une itérera sur les objets valides et l'autre sur les objets libres. chaque fois que smartpointer marque un endroit à supprimer, vous supprimez simplement ce pointeur et ajoutez son espace à la liste des espaces libres. et chaque fois que vous ajoutez une entité, il vous suffit de supprimer le premier espace libre, de la remplir avec un pointeur d'entité, puis de l'ajouter à la liste d'objets valides.

Ali1S232
la source