Après avoir lu quelques documents sur le système entité-composant, j'ai décidé de mettre en œuvre le mien. Jusqu'à présent, j'ai une classe mondiale qui contient les entités et le gestionnaire de système (systèmes), une classe d'entité qui contient les composants sous forme de std :: map et quelques systèmes. Je tiens des entités en tant que vecteur std :: dans World. Aucun problème jusqu'ici. Ce qui me déroute, c'est l'itération des entités, je ne peux pas avoir un esprit clair à ce sujet, donc je ne peux toujours pas implémenter cette partie. Chaque système devrait-il contenir une liste locale d'entités qui l'intéressent? Ou dois-je simplement parcourir les entités de la classe World et créer une boucle imbriquée pour parcourir les systèmes et vérifier si l'entité a les composants qui intéressent le système? Je veux dire :
for (entity x : listofentities) {
for (system y : listofsystems) {
if ((x.componentBitmask & y.bitmask) == y.bitmask)
y.update(x, deltatime)
}
}
mais je pense qu'un système de masques de bits bloquera un peu la flexibilité en cas d'incorporation d'un langage de script. Ou avoir des listes locales pour chaque système augmentera l'utilisation de la mémoire pour les classes. Je suis terriblement confus.
la source
Réponses:
C'est un compromis spatio-temporel traditionnel .
Bien que l'itération à travers toutes les entités et la vérification de leurs signatures soit directement du code, cela peut devenir inefficace à mesure que votre nombre de systèmes augmente - imaginez un système spécialisé (laissez-le entrer) qui recherche son entité probablement unique parmi des milliers d'entités non liées .
Cela dit, cette approche peut toujours être suffisante en fonction de vos objectifs.
Bien que, si vous vous inquiétez de la vitesse, il existe bien sûr d'autres solutions à envisager.
Exactement. Il s'agit d'une approche standard qui devrait vous donner des performances décentes et est relativement facile à mettre en œuvre. La surcharge de mémoire est négligeable à mon avis - nous parlons de stocker des pointeurs.
Maintenant, comment maintenir ces "listes d'intérêt" peut ne pas être si évident. En ce qui concerne le conteneur de données,
std::vector<entity*> targets
la classe système interne est parfaitement suffisante. Maintenant, ce que je fais, c'est ceci:Chaque fois que j'ajoute un composant à une entité:
itérer à travers tous les systèmes du monde et s'il y a un système dont la signature ne correspond signature courante de l'entité et ne correspond à la nouvelle signature, il devient évident que nous devons push_back le pointeur à notre entité là - bas.
La suppression d'une entité est entièrement analogue, à la seule différence que nous supprimons si un système correspond à notre signature actuelle (ce qui signifie que l'entité était là) et ne correspond pas à la nouvelle signature (ce qui signifie que l'entité ne devrait plus être là ).
Maintenant, vous envisagez peut-être d'utiliser std :: list car la suppression du vecteur est O (n), sans mentionner que vous devrez déplacer un gros morceau de données chaque fois que vous supprimez du milieu. En fait, vous n'êtes pas obligé - puisque nous ne nous soucions pas du traitement de l'ordre à ce niveau, nous pouvons simplement appeler std :: remove et vivre avec le fait qu'à chaque suppression, nous n'avons qu'à effectuer une recherche O (n) pour notre entité à supprimer.
std :: list vous donnerait O (1) supprimer mais de l'autre côté vous avez un peu de surcharge de mémoire supplémentaire. Souvenez-vous également que la plupart du temps, vous allez traiter des entités et non les supprimer - et cela se fait sûrement plus rapidement en utilisant std :: vector.
Si vous êtes très critique en termes de performances, vous pouvez envisager un autre modèle d'accès aux données , mais dans les deux cas, vous conservez une sorte de "listes d'intérêt". N'oubliez pas que si vous conservez votre API Entity System suffisamment abstraite, cela ne devrait pas être un problème pour améliorer les méthodes de traitement des entités des systèmes si votre taux de rafraîchissement baisse à cause d'eux - donc pour l'instant, choisissez la méthode la plus facile à coder pour vous - uniquement puis profilez et améliorez si nécessaire.
la source
Il y a une approche qui mérite d'être considérée où chaque système possède les composants qui lui sont associés et où les entités s'y réfèrent uniquement. Fondamentalement, votre
Entity
classe (simplifiée) ressemble à ceci:Lorsque vous avez dit un
RigidBody
composant attaché à unEntity
, vous le demandez à votrePhysics
système. Le système crée le composant et laisse l'entité garder un pointeur sur celui-ci. Votre système ressemble alors à:Cela peut sembler un peu contre-intuitif au début, mais l'avantage réside dans la façon dont les systèmes d'entités composants mettent à jour leur état. Souvent, vous parcourrez vos systèmes et leur demanderez de mettre à jour les composants associés
La force d'avoir tous les composants appartenant au système dans la mémoire contiguë est que lorsque votre système parcourt chaque composant et le met à jour, il n'a essentiellement qu'à faire
Il n'a pas à parcourir toutes les entités qui n'ont potentiellement pas de composant à mettre à jour et il a également un potentiel de très bonnes performances de cache car les composants seront tous stockés de manière contiguë. C'est l'un, sinon le plus grand avantage de cette méthode. Vous aurez souvent des centaines et des milliers de composants à un moment donné, autant essayer et être aussi performant que possible.
À ce stade, vous
World
ne parcourez que les systèmes et les appelezupdate
sans avoir à réitérer les entités également. C'est (à mon humble avis) une meilleure conception, car les responsabilités des systèmes sont alors beaucoup plus claires.Bien sûr, il existe une myriade de tels modèles, vous devez donc évaluer soigneusement les besoins de votre jeu et choisir le plus approprié, mais comme nous pouvons le voir ici, ce sont parfois les petits détails de conception qui peuvent faire la différence.
la source
À mon avis, une bonne architecture consiste à créer une couche de composants dans les entités et à séparer la gestion de chaque système dans cette couche de composants. Par exemple, le système logique possède certains composants logiques qui affectent leur entité et stockent les attributs communs qui sont partagés avec tous les composants de l'entité.
Après cela, si vous souhaitez gérer les objets de chaque système en différents points ou dans un ordre particulier, il est préférable de créer une liste de composants actifs dans chaque système. Toutes les listes de pointeurs que vous pouvez créer et gérer dans les systèmes sont moins d'une ressource chargée.
la source