Pour chaque jeu que j'ai créé, je finis par placer tous mes objets de jeu (balles, voitures, joueurs) dans une seule liste de tableaux, que je boucle pour dessiner et mettre à jour. Le code de mise à jour de chaque entité est stocké dans sa classe.
Je me demandais: est-ce la bonne façon de procéder ou est-ce une manière plus rapide et plus efficace?
Réponses:
Il n'y a pas qu'une seule bonne façon. Ce que vous décrivez fonctionne et est probablement assez rapide pour que cela n'ait pas d'importance car vous semblez demander parce que vous êtes préoccupé par la conception et non parce que cela vous a causé des problèmes de performances. C'est donc une bonne façon, bien sûr.
Vous pouvez certainement le faire différemment, par exemple en conservant une liste homogène pour chaque type d'objet ou en vous assurant que tout dans votre liste hétérogène est regroupé de sorte que vous avez amélioré la cohérence du code en cache ou pour permettre des mises à jour parallèles. Il est difficile de dire si vous verriez une amélioration appréciable des performances en faisant cela sans connaître la portée et l'échelle de vos jeux.
Vous pouvez également prendre en compte les responsabilités de vos entités - il semble que les entités se mettent à jour et se rendent en ce moment - pour mieux suivre le principe de responsabilité unique. Cela peut augmenter la maintenabilité et la flexibilité de vos interfaces individuelles en les découplant les unes des autres. Mais encore une fois, il est difficile pour moi de dire si vous verriez un avantage du travail supplémentaire que cela impliquerait, sachant ce que je sais de vos jeux (ce qui n'est fondamentalement rien).
Je recommanderais de ne pas trop insister à ce sujet et gardez à l'esprit que cela pourrait être un domaine d'amélioration possible si vous remarquez des problèmes de performances ou de maintenabilité.
la source
Comme d'autres l'ont dit, si c'est assez rapide, alors c'est assez rapide. Si vous vous demandez s’il existe un meilleur moyen, la question à vous poser est
Est-ce que je me retrouve à parcourir tous les objets mais à n'opérer que sur un sous-ensemble d'entre eux?
Si vous le faites, cela indique que vous souhaiterez peut-être avoir plusieurs listes contenant uniquement des références pertinentes. Par exemple, si vous parcourez chaque objet de jeu pour les rendre mais que seul un sous-ensemble d'objets doit être rendu, il peut être utile de conserver une liste de rendu distincte uniquement pour les objets qui doivent être rendus.
Cela réduirait le nombre d'objets que vous parcourez et saute les vérifications inutiles car vous savez déjà que tous les objets de la liste doivent être traités.
Vous devez le profiler pour voir si vous obtenez un gain de performances important.
la source
Cela dépend presque entièrement de vos besoins de jeu spécifiques. Il peut parfaitement fonctionner pour un jeu simple, mais échouer sur un système plus complexe. Si cela fonctionne pour votre jeu, ne vous inquiétez pas ou n'essayez pas de le sur-concevoir jusqu'à ce que le besoin s'en fasse sentir.
Quant à savoir pourquoi l'approche simple peut échouer dans certaines situations, il est difficile de résumer cela dans un seul article, car les possibilités sont infinies et dépendent entièrement des jeux eux-mêmes. D'autres réponses ont mentionné, par exemple, que vous souhaitiez peut-être regrouper des objets partageant des responsabilités dans une liste distincte.
C'est l'un des changements les plus courants que vous pourriez apporter en fonction de la conception de votre jeu. Je prendrai la chance de décrire quelques autres exemples (plus complexes), mais n'oubliez pas qu'il existe encore de nombreuses autres raisons et solutions possibles.
Pour commencer, je soulignerai que la mise à jour et le rendu de vos objets de jeu peuvent avoir des exigences différentes dans certains jeux, et doivent donc être traités séparément. Quelques problèmes possibles avec la mise à jour et le rendu des objets de jeu:
Mise à jour des objets de jeu
Voici un extrait de Game Engine Architecture que je recommanderais de lire:
En d'autres termes, dans certains jeux, il est possible que les objets dépendent les uns des autres et nécessitent un ordre de mise à jour spécifique. Dans ce scénario, vous devrez peut-être concevoir une structure plus complexe qu'une liste pour stocker les objets et leurs interdépendances.
Rendre les objets du jeu
Dans certains jeux, les scènes et les objets créent une hiérarchie de nœuds parents-enfants et doivent être rendus dans le bon ordre et avec des transformations par rapport à leurs parents. Dans ces cas, vous pourriez avoir besoin d'une structure plus complexe comme un graphique de scène (une structure arborescente) au lieu d'une seule liste:
D'autres raisons peuvent être, par exemple, l'utilisation d'une structure de données de partitionnement spatial pour organiser vos objets d'une manière qui améliore l'efficacité de la suppression des troncs de vue.
la source
La réponse est "oui, ça va." Lorsque j'ai travaillé sur de grandes simulations de fer où les performances n'étaient pas négociables, j'ai été surpris de tout voir dans un tableau global de gros gras (BIG et FAT). Plus tard, j'ai travaillé sur un système OO et j'ai pu comparer les deux. Bien qu'elle soit super moche, la version "un seul tableau pour les gouverner tous" dans un vieux C simple à thread unique compilé dans le débogage était plusieurs fois plus rapide que le "joli" système OO multithread compilé -O2 en C ++. Je pense que cela était en partie dû à l'excellente localité du cache (à la fois dans l'espace et dans le temps) dans la version big-fat-array.
Si vous voulez des performances, un grand tableau C global gros sera la limite supérieure (par expérience).
la source
Fondamentalement, ce que vous voulez faire est de créer des listes selon vos besoins. Si vous redessinez des objets de terrain en une seule fois, une liste de ceux-ci pourrait être utile. Mais pour que cela en vaille la peine, il vous en faudrait des dizaines de milliers et des milliers de choses qui ne sont pas du terrain. Sinon, parcourir votre liste de tout et utiliser un "si" pour retirer les objets du terrain est assez rapide.
J'ai toujours eu besoin d'une liste principale, quel que soit le nombre d'autres listes que j'ai eues. Habituellement, j'en fais une carte, un tableau à clés ou un dictionnaire, afin de pouvoir accéder de manière aléatoire à des éléments individuels. Mais une simple liste est beaucoup plus simple; si vous n'accédez pas au hasard aux objets du jeu, vous n'avez pas besoin de la carte. Et la recherche séquentielle occasionnelle de quelques milliers d'entrées pour trouver la bonne ne prendra pas longtemps. Je dirais donc, quoi que vous fassiez d'autre, prévoyez de vous en tenir à la liste principale. Envisagez d'utiliser une carte si elle devient grande. (Bien que si vous pouvez utiliser des index au lieu de clés, vous pouvez vous en tenir aux tableaux et avoir quelque chose de bien meilleur qu'une carte. J'ai toujours eu de gros écarts entre les numéros d'index, donc j'ai dû y renoncer.)
Un mot sur les tableaux: ils sont incroyablement rapides. Mais quand ils récupèrent environ 100 000 éléments, ils commencent à s'adapter aux Garbage Collectors. Si vous pouvez allouer l'espace une fois et ne pas le toucher par la suite, c'est bien. Mais si vous étendez continuellement le tableau, vous allouez et libérez continuellement de gros morceaux de mémoire et vous êtes susceptible de faire bloquer votre jeu pendant quelques secondes à la fois et même d'échouer avec une erreur de mémoire.
Si rapide et plus efficace? Sûr. Une carte pour un accès aléatoire. Pour les très grandes listes, une liste liée battra un tableau si et quand vous n'avez pas besoin d'un accès aléatoire. Plusieurs cartes / listes pour organiser les objets de différentes manières, selon vos besoins. Vous pouvez multi-threader la mise à jour.
Mais c'est beaucoup de travail. Ce tableau unique est rapide et simple. Vous ne pouvez ajouter d'autres listes et éléments qu'en cas de besoin, et vous n'en aurez probablement pas besoin. Sachez simplement ce qui peut mal tourner si votre jeu devient gros. Vous voudrez peut-être avoir une version de test avec 10 fois plus d'objets que dans la version réelle pour vous donner une idée du moment où vous vous dirigez vers des problèmes. (Ne faites cela que si votre jeu est commence à être grande.) (Et ne pas essayer le multi-threading sans lui donner beaucoup de pensée. Tout le reste est facile de démarrer avec et vous pouvez faire autant ou aussi peu que vous aimez et reculer à tout moment. Avec le multi-thread, vous paierez un prix élevé pour entrer, les frais de séjour sont élevés et il est difficile de sortir.)
En d'autres termes, continuez à faire ce que vous faites. Votre plus grande limite est probablement votre propre temps, et vous en faites le meilleur usage possible.
la source
J'ai utilisé une méthode quelque peu similaire pour les jeux simples. Tout stocker dans un tableau est très utile lorsque les conditions suivantes sont remplies:
Dans tous les cas, j'ai implémenté cette solution comme un tableau de taille fixe qui contient une
gameObject_ptr
structure. Cette structure contenait un pointeur sur l'objet de jeu lui-même et une valeur booléenne représentant si l'objet était "vivant" ou non.L'objectif du booléen est d'accélérer les opérations de création et de suppression. Au lieu de détruire les objets du jeu lorsqu'ils sont retirés de la simulation, je définirais simplement le drapeau "vivant" sur
false
.Lorsque vous effectuez des calculs sur des objets, le code passe en boucle dans le tableau, effectuant des calculs sur des objets marqués comme "vivants", et seuls les objets marqués "vivants" sont rendus.
Une autre optimisation simple consiste à organiser le tableau par type d'objet. Toutes les puces, par exemple, pourraient vivre dans les n dernières cellules du tableau. Ensuite, en stockant un int avec la valeur de l'index ou un pointeur vers l'élément de tableau, des types spécifiques pourraient être référencés à un coût réduit.
Il va sans dire que cette solution n'est pas bonne pour les jeux avec de grandes quantités de données, car le tableau sera trop grand. Il n'est pas non plus adapté aux jeux avec des contraintes de mémoire qui interdisent aux objets inutilisés de prendre de la mémoire. L'essentiel est que si vous avez une limite supérieure connue sur le nombre d'objets de jeu que vous aurez à un moment donné, il n'y a rien de mal à les stocker dans un tableau statique.
Ceci est mon premier message posté! J'espère que cela répond à votre question!
la source
C'est acceptable, quoique inhabituel. Des termes comme «plus rapide» et «plus efficace» sont relatifs; En règle générale, vous organisez les objets de jeu en catégories distinctes (donc une liste pour les balles, une liste pour les ennemis, etc.) pour rendre la programmation plus organisée (et donc plus efficace), mais ce n'est pas comme si l'ordinateur parcourait tous les objets plus rapidement .
la source
Vous dites tableau unique et objets.
Donc (sans votre code à regarder), je suppose qu'ils sont tous liés à 'uberGameObject'.
Alors peut-être deux problèmes.
1) Vous pouvez avoir un objet «dieu» - fait des tonnes de choses, pas vraiment segmenté par ce qu'il fait ou est.
2) Vous parcourez cette liste et le casting pour taper? ou déballer chacun. Cela a une grosse pénalité de performance.
envisager de diviser différents types (puces, méchants, etc.) en leurs propres listes fortement typées.
la source
update()
).Je peux proposer un compromis entre la liste unique générale et les listes séparées pour chaque type d'objet.
Gardez la référence à un objet dans plusieurs listes. Ces listes sont définies par des comportements de base. Par exemple, l'
MarineSoldier
objet sera référencé dansPhysicalObjList
,GraphicalObjList
,UserControlObjList
,HumanObjList
. Ainsi, lorsque vous traitez la physique, vous parcourezPhysicalObjList
, lorsque le processus endommage le poison -HumanObjList
, si le soldat meurt - enlevez-le,HumanObjList
mais continuezPhysicalObjList
. Pour que ce mécanisme fonctionne, vous devez organiser l’insertion / suppression automatique des objets lors de leur création / suppression.Avantages de l'approche:
AllObjectsList
avec casting lors de la mise à jour)MegaAirHumanUnderwaterObject
serait inséré dans les listes correspondantes au lieu de créer une nouvelle liste pour son type)PS: Quant à la mise à jour automatique, vous rencontrerez un problème lorsque plusieurs objets interagissent et chacun d'entre eux dépend des autres. Pour résoudre ce problème, nous devons affecter l'objet à l'extérieur, et non à l'intérieur de la mise à jour automatique.
la source