Espaces Conteneurs d'objets de jeu utiles

Le point entier d'un espace est de tenir des objets de jeu. Les objets de jeu dans un espace ne doivent pas avoir tout moyen de communiquer avec les objets du jeu dans un autre espace, les espaces constituent donc un moyen simple de séparer différents groupes d’objets du jeu. Dans cet article, vous apprendrez les avantages d’une telle architecture..

Si vous souhaitez voir un exemple d'implémentation d'espaces, veuillez consulter le moteur de jeu open source SEL. Je suis moi-même un auteur actif de SEL et je suis fier de le présenter comme une ressource pleinement fonctionnelle pour les lecteurs de cet article..

Je voudrais remercier Sean Middleditch de m'avoir parlé des avantages des espaces.

Pointe: Le terme espace dans le contexte de cet article, fait référence à un conteneur spécial d’objets de jeu. Je ne suis pas au courant d'un terme officiel clairement défini. Si vous en connaissez un, veuillez commenter!


Gestion conventionnelle d'objets

Dans un moteur de jeu conventionnel, les objets de jeu sont généralement stockés dans un seul conteneur. Un tel conteneur peut être un allocateur avec un gestionnaire de poignées. Parfois, le conteneur est juste une liste chaînée. Quelle que soit l’implémentation réelle, il peut y avoir un seul conteneur contenant tous les objets du jeu, et chaque objet du jeu créé est toujours dans ce conteneur..

C’est parfait et fonctionne parfaitement, mais certains problèmes d’organisation se posent. Par exemple, imaginez un gestionnaire d'état de jeu traditionnel. Souvent, entre le passage d'un état à un autre, tous les objets de jeu actuellement chargés sont libérés et les nouveaux sont lus à partir du disque. En tant qu'optimisation, les objets de jeu pour le prochain état (ou niveau) peuvent être chargés à l'avance sur un thread séparé, afin que les transitions d'état soient instantanées..

Cependant, il y a un problème gênant qui se pose généralement: comment représenter les éléments d'interface graphique pour les menus? Peut-être que la palette du lecteur est codée à l'aide d'objets de jeu et de scripts attachés à ces objets de jeu. Une mise en œuvre naïve de la gestion par l’état exigerait que tous les éléments du HUD soient détruits et recréés lors de la transition d’état. Cela signifie qu'un code personnalisé sera nécessaire pour gérer la transition d'objets particuliers d'un état à un autre.

Ou peut-être une conception de jeu appelle-t-elle un décor de fond fou dans lequel une énorme bataille se déroule, mais cette bataille ne doit en aucun cas gêner le premier plan (ou le joueur).

Il arrive souvent que des solutions bizarres soient trouvées pour ce genre de choses, par exemple en représentant les éléments du HUD comme étant extrêmement éloignés du reste du jeu dans le monde. Les menus de pause et similaires sont simplement déplacés dans la vue si nécessaire, et s'éloignent autrement. En général, un code personnalisé est nécessaire pour gérer et organiser les objets de jeu, car ils résident tous dans une seule usine ou un seul conteneur..


Plusieurs conteneurs d'objets de jeu

Une solution acceptable à ce problème consisterait à utiliser des espaces (voir la description vidéo supplémentaire de mon collègue Sean Middleditch). Étant donné que tous les objets du jeu dans chaque espace n'ont aucune interaction, les espaces deviennent un moyen naturel d'aborder l'organisation des objets du jeu. Cela peut grandement réduire le besoin de code spécial pour maintenir un conteneur logique séparé dans un conteneur réel (comme mentionné dans la section précédente)..

Jetons un coup d'œil à un exemple de ce à quoi une implémentation spatiale pourrait ressembler dans un langage similaire à C ++:

 class Space public: GameObject CreateObject (nom de chaîne); chaîne const GetName (void) const; private: string m_name; // Peut être une forme d'allocateur, peut-être juste un conteneur std :: vector Container m_objects; ;

Il est souvent utile de pouvoir rechercher un espace par son nom. Ceci est particulièrement utile pour les scripts où un script peut utiliser du code comme ceci:

 // Exécuter une logique pour l’arrière-plan local space = GetSpace ("Background") tornado = space.CreateObject ("Tornado") // Ailleurs, nous pouvons faire quelque chose de complètement isolé de l’arrière-plan, // comme récupérer le joueur raison est morte) et en jouant une animation // death sur le dessus de l'espace local du lecteur = GetSpace ("CurrentLevel") player = space.GetPlayer () space.CreateObjectAt ("DeathAnimation", player)
Schéma montrant la simple organisation d'objets de jeu dans des conteneurs isolés. Tous les espaces n'ont pas la même quantité d'objets de jeu, ni même les mêmes objets de jeu.

Quels objets vont dans les espaces?

La réponse à cette question est: tous! Tout type d'objet de jeu sera placé dans un espace. Si vous êtes familier avec l'agrégation (ou la conception à base de composants), un objet de jeu et ses composants associés résideront tous dans le même espace..

L'idée est de créer une fonctionnalité architecturale permettant un moyen simple et efficace de regrouper des objets de jeu et de les isoler des autres groupes..

Terminologie

Les espaces sont assez similaires à certains autres concepts qui circulent depuis un moment. On a dit que les espaces s'apparentaient à la liste d'affichage dans Flash. Si vous connaissez les portails pour l’abattage du rendu (particulièrement important dans les jeux 3D comportant de nombreuses pièces intérieures), l’idée est également similaire ici..

Cependant, il y a une distinction importante à faire ici. Les espaces sont ne pas une forme de partitionnement spatial comme c'est le cas avec les portails ou un autre partitionnement spatial (comme l'arbre BSP populaire) pour le rendu de l'occlusion. Les espaces sont un architectural fonctionnalité permettant d'isoler des objets de jeu généraux.

Personnellement, j'aime bien penser aux espaces comme les calques Photoshop: tous les calques peuvent être visualisés (ou entendus) simultanément, mais lors de la peinture d'un calque, aucun autre calque n'est directement affecté. chaque couche est unique et isolée.

Systèmes et espaces

Le concept de système, dans le cadre de cet article, peut être décrit comme un ensemble de fonctionnalités (fonctions ou méthodes) agissant sur les objets de jeu d'un espace. De cette façon, un espace est laissé à un système pour effectuer une action; un système particulier est global au jeu entier.

Schéma illustrant la simplicité permettant à un système de fonctionner sur un espace en tant qu'entrée.

Imaginez un système graphique contenant un void Graphics :: DrawWorld (Space space) une fonction. Ce DrawWorld la fonction ferait une boucle sur les objets dans l'espace donné qui sont restituables et les dessinait à l'écran.

L'idée est d'écrire maintenant du code (systèmes) qui agit sur une entrée donnée d'objets de jeu. Aucun suivi ou gestion spéciale des objets de jeu ne doit se produire au sein de tels systèmes. Un système ne doit rien faire d'autre que d'exécuter des opérations sur des objets de jeu.

Ce style vous donne de très bons avantages, comme détaillé dans la section suivante.


Avantages des espaces

L'avantage le plus immédiat de la mise en place d'espaces dans un moteur est que le traitement des éléments ou des menus de l'interface graphique devient trivial. Nous pouvons construire un espace dédié à un menu particulier et, chaque fois que ce menu est inactif, les systèmes n'ont tout simplement pas besoin d'opérer sur le contenu de l'espace. Lorsqu'un menu est inactif, il reste en mémoire (les objets du jeu qui composent la mémoire sont assis dans l'espace du menu) et ne fait rien. il n'est ni mis à jour par un système logique ni rendu par un système graphique.

Lorsque ce menu redevient actif, il peut être simplement transféré aux systèmes appropriés. Le menu peut ensuite être mis à jour et rendu de manière appropriée. Simultanément, le gameplay en cours derrière le menu peut cesser d'être mis à jour de quelque manière que ce soit, bien qu'il soit peut-être transmis au système graphique pour être rendu. Cela implémente trivialement une fonctionnalité élégante et robuste de type pause-reprise qui vient implicitement du fait de la façon dont les espaces sont définis.

Niveaux isolés

Souvent, dans des jeux de type RPG tels que Pokémon, le joueur entre et sort de maisons et de cabanes. En ce qui concerne le jeu, ces différentes maisons sont généralement complètement isolées; petites maisons ou des grottes sont un scénario idéal pour appliquer des espaces. Un espace entier peut être construit pour contenir les objets de jeu d'une maison particulière. Ceux-ci peuvent être chargés et initialisés en mémoire et se reposer jusqu'à ce que vous en ayez besoin. Des transitions instantanées peuvent être obtenues en échangeant simplement l'espace qui est laissé aux différents systèmes du moteur..

Une idée géniale pour les jeux 2D tels que les jeux de plateforme (ou même les jeux 3D) pourrait être de simuler les niveaux réels et les ennemis du jeu en arrière-plan. Cela pourrait donner vie au monde d’une manière qui n’exige pas de contenu supplémentaire ni de temps de développement supplémentaire. Rayman Legends en est le meilleur exemple.


Les vrais ennemis, comme ceux que le joueur voit normalement, pourraient sauter ou ramper sur les murs au loin. Les transitions entre ces différentes "couches" pourraient offrir des possibilités de conception très intéressantes.

Ce genre de possibilités est en fait assez rare pour trouver des exemples et l’idée d’espaces n’est pas vraiment adoptée par les studios ou les moteurs AAA modernes. Cependant, la conception est solide et les avantages sont réels.

Multijoueur local

Dans de nombreux jeux prenant en charge le mode multijoueur où les deux joueurs jouent avec le même client de jeu, il existe certaines limitations. Souvent, les joueurs ne peuvent pas aller dans une nouvelle zone sans que les deux ne soient proches l'un de l'autre. Parfois, les joueurs ne peuvent même pas quitter l'écran les uns des autres. Cela peut être dû à la conception de jeux, mais je soupçonne que cela est souvent dû à des limitations architecturales.

Avec des espaces, nous pouvons soutenir deux joueurs, peut-être avec des écrans divisés, voyageant d’un niveau ou d’un bâtiment à l’autre. Chaque joueur peut résider dans le même espace ou dans deux espaces séparés. Lorsque chaque joueur se trouve dans un bâtiment différent, ils peuvent tous deux se trouver dans deux espaces distincts. Une fois qu'ils convergent sur la même zone, le moteur peut transférer l'un des objets de jeu du joueur dans l'espace adverse..

Support de l'éditeur

Ceci est certainement mon exemple préféré de la façon dont les espaces sont géniaux. Dans les éditeurs, il existe souvent des sections dans lesquelles vous pouvez développer un nouveau type d'objet. Cet objet dans la création aura généralement une fenêtre pour afficher un aperçu de la création au fur et à mesure du développement..

Il peut être impossible pour la plupart des moteurs de supporter naturellement un tel éditeur. Que se passe-t-il si l'utilisateur modifie la position et que celle-ci se heurte soudainement à la simulation et renverse les choses, ou active une IA? Un code personnalisé doit être créé pour pouvoir gérer l'objet en mémoire d'une manière ou d'une autre. Soit le code d’isolement des cas spéciaux, soit un format intermédiaire, doit être édité et traduit de l’éditeur à la simulation réelle. Les étapes intermédiaires peuvent être une forme de sérialisation ou un "objet proxy" factice non intrusif complexe. Les objets mandataires peuvent souvent nécessiter une introspection avancée du code pour être implémentés de manière utile. Ces options peuvent être coûteuses ou inutiles pour de nombreux projets..

Cependant, si on dispose d'espaces, un objet caméra et l'objet en création peuvent être placés dans un espace isolé. Cet espace peut ensuite être confié aux systèmes nécessaires et géré de manière isolée. Aucun code de cas spécial ou création supplémentaire ne serait nécessaire dans un tel scénario.

Plusieurs niveaux peuvent être maintenus facilement dans les éditeurs. Plusieurs fenêtres et simulations peuvent être exécutées simultanément de manière isolée. Et si un développeur voulait scinder en deux le développement de deux niveaux pour permuter rapidement? Cela pourrait être une tâche de génie logiciel difficile, ou l'architecture de l'éditeur pourrait implémenter une certaine forme d'espaces.


Idées de mise en œuvre

Gestion de l'espace

Qu'est-ce qui gère tous les espaces? De nombreux développeurs de jeux ont peut-être l'habitude que tout doit pouvoir être "possédé" par un gestionnaire. Tous les espaces doivent pouvoir être gérés par cette entité unique, non? En fait, ce genre de paradigme n'est tout simplement pas nécessaire tout le temps.

Dans le moteur SEL, les espaces sont construits à partir d'un emplacement et peuvent être recherchés par leur nom à l'aide d'un dictionnaire, mais il serait peut-être préférable que la plupart des projets laissent simplement les espaces gérés, au cas par cas. Souvent, il est logique de créer un espace dans un script aléatoire, de le conserver pendant un moment, puis de le libérer. D'autres fois, un espace est créé et reste en mémoire pendant toute la durée de l'exécution du jeu..

Une bonne recommandation serait de laisser l’utilisateur allouer un espace et le libérer à sa guise. Il serait probablement bon d'utiliser un seul allocateur pour ce comportement. Cependant, la gestion de l'instance spatiale elle-même, telle qu'elle a été constatée par expérience, pourrait ne pas être inquiétante; laisser à l'utilisateur.

Remarque: quand un espace est détruit, il devrait nettoyer tous les objets qu'il contient! Ne confondez pas le manque de manager avec un manque de gestion à vie.

Composants et sous-espaces

Les composants sont souvent enregistrés avec leurs systèmes respectifs dans un moteur à base de composants. Cependant, avec des espaces cela devient inutile. Au lieu de cela, chaque espace devrait contenir ce qu'on appelle un sous-espace. Un sous-espace peut être très simple à implémenter, en tant que vecteur d’objets composants. L'idée est de contenir simplement différents types de conteneurs de composants dans chaque espace. Chaque fois qu'un composant est construit, il demande à quel espace il doit être associé et s'enregistre avec le sous-espace..

Un espace ne doit pas nécessairement contenir chaque type de sous-espace en lui-même. Par exemple, un espace de menu n'aura probablement pas besoin de simulation physique et, par conséquent, pourrait ne pas avoir une instance entière d'un monde de la physique représentant un sous-espace de la physique..

Enfin, il convient de noter que dans une architecture à base de composants, vos objets de jeu doivent avoir une poignée ou un pointeur sur l'espace dans lequel ils résident. De cette manière, l'espace devient une partie de l'identificateur unique de l'objet de jeu lui-même..


Conclusion

Les espaces sont extrêmement simples à mettre en œuvre et offrent de nombreux avantages importants. Pour pratiquement tous les jeux et moteurs de jeu existants, l'ajout d'espaces sera un avantage en raison de la facilité de mise en œuvre. Utilisez des espaces, même pour de très petits projets et de très petits jeux!

En tant que ressource, ma propre implémentation d'espaces est open source pour un affichage public dans le moteur de jeu SEL..