Je crée mon jeu vidéo E07 : cartes, données et systèmes à entités

Posté par  (Mastodon) . Édité par ZeroHeure et Benoît Sibaud. Modéré par claudex. Licence CC By‑SA.
Étiquettes :
29
16
déc.
2013
Jeu

« Je crée mon jeu vidéo » est une série d'articles sur la création d'un jeu vidéo, depuis la feuille blanche jusqu'au résultat final. On y parle de tout : de la technique, du contenu, de la joie de voir bouger des sprites, de la lassitude du développement solitaire, etc. Vous pouvez suivre cette série grâce au tag gamedev.

Dans l'épisode 06, on a vu comment on pouvait simplement générer des arbres procéduralement. Cet épisode invitait à des extensions grâce aux quelques liens donnés dans les commentaires et aussi grâce à d'autres lectures que j'ai pu faire depuis. Mais pas tout de suite. Aujourd'hui, on va s'intéresser à la carte du jeu et je vais partager mes réflexions sur les données du jeu et les systèmes à entités.

Sommaire

La version 0.0.3 d'Akagoria vient de sortir et avec elle, une gestion améliorée de la carte. Ces développements m'ont amené à me poser plein de questions sur les données du jeu et les systèmes à entités.

Gestion de carte

Tout d'abord, je signale que la carte actuellement présente n'est pas du tout la carte définitive. Il s'agit simplement d'une île pour tester les divers éléments qui sont ajoutés. La carte finale sera beaucoup plus grande et aura certainement un autre ensemble de tuiles (tileset). On peut voir cet espace de test comme la maison de Lara Croft dans les premiers opus de la série Tomb Raider (oui, ceux où on pouvait enfermer le majordome dans la chambre froide).

L'île de test

La dernière fois qu'on a parlé de la carte, j'avais dit que j'étais capable de l'afficher morceau par morceau, et pas en entier à chaque frame, ce qui améliorait les performances. Maintenant, la carte ne doit pas avoir qu'un seul niveau : il y aura des grottes, des maisons, des caves, bref tout plein d'endroits qui sont soit en hauteur, soit en profondeur. Il faut donc gérer plusieurs éléments :

  • comment on définit un niveau différent, c'est-à-dire où va-t-on mettre les données de ce niveau ?
  • comment on passe d'un niveau à un autre ?
  • comment on implémente tout ça ?

Les données des niveaux

Il y a deux solutions que j'ai envisagées pour répondre à ce problème : soit mettre les niveaux dans des fichiers complètement différents, soit mettre les niveaux dans le même fichier. Dans le premier cas, il fallait faire une correspondance entre les différents fichiers et niveaux, par exemple savoir que quand on descend à tel endroit, on se retrouve à tel autre endroit. L'avantage, c'est qu'on était plus flexible avec plusieurs cartes, dont certaines plus petites (genre le sixième sous-sol où se trouve le boss de fin et rien d'autre, il peut être assez petit). Dans le second cas, la correspondance entre les niveaux est bien plus aisée. En contrepartie, l'édition des niveaux est plus difficile étant donné qu'il faut jongler entre les calques pour avoir la bonne vue du niveau.

J'ai choisi la seconde solution parce que je préférais avoir la correspondance gratuitement. En effet, étant donné la dimension envisagée de la carte, il me paraît difficile de devoir gérer cette correspondance à la main, je m'y perdrai forcément au bout d'un moment. Et puis les calques ne sont pas si mal gérés dans Tiled, ce qui permet d'éditer assez facilement.

Le passage d'un niveau à l'autre

La question qui vient ensuite est de bien gérer le passage d'un niveau à l'autre. Pour ça, Box2D nous aide. Box2D nous permet de définir des zones de capture (sensor) qui n'ont aucune interaction avec les autres éléments mais qui provoquent des événements de contact qu'on peut écouter et traiter. Il suffit alors de définir une zone de passage au niveau inférieur qui permettra au héros de descendre d'un niveau quand il sera en contact avec celle-ci.

Une difficulté est que le traitement des événements de contact est global dans Box2D, et pas lié à un élément en particulier. Cela force à gérer toutes les zones de changement de niveau de manière globale, mais ce n'est pas trop un problème vu qu'elles vont toutes être définies dans la carte. Car, oui, on va se servir une nouvelle fois de la carte pour définir ces zones. On va utiliser en particulier ce qui est appelé « objets » dans la terminologie de Tiled, c'est-à-dire des formes arbitraires qu'on peut placer n'importe où sur la carte (pas juste aux intersections des tuiles).

L'implémentation

Maintenant qu'on a plusieurs niveaux, il faut être capable de n'afficher que les éléments du niveau courant. Il faut donc pouvoir mettre à jour le niveau courant. J'ai pour cela utilisé le nouveau système d'événements introduit dans la dernière version de ma bibliothèque de systèmes à entités, libes. Pourquoi avoir introduit ça dans cette bibliothèque ? En fait, c'est très complémentaire. J'en parle dans la suite de cet épisode.

Donc, il existe un événement qui va dire quand le héros change de niveau et tous ceux qui doivent mettre à jour leur comportement peuvent alors le faire. Ça concerne tout d'abord l'affichage. Mais pour ça, il faut un composant qui permet de dire à quel niveau se situent les entités concernées. C'est le rôle du composant Altitude qui a été ajouté à pas mal d'entités. Quand le héros passe sur une zone de changement de niveau, il envoie cet événement et tout est bien mis à jour et seules sont affichées les entités du bon niveau.

Reste le dernier problème, comment faire pour éviter les collisions entre objets de niveaux différents ? Heureusement, Box2D vient encore à notre rescousse puisque la bibliothèque prévoit de pouvoir filtrer les collisions en attribuant des masques de bits aux différents éléments. Je ne rentre pas dans les détails de ces filtres qui sont assez évolués et qui permettent de faire des choses très avancées. Le résultat est qu'on peut attribuer des masques suivant le niveau de chaque entité. Et pour le héros, on peut changer son masque dynamiquement suivant le niveau où il se trouve. La limite est qu'on ne peut avoir que seize niveaux mais c'est largement suffisant.

On voit donc que ce petit problème de pouvoir aller d'un niveau à un autre met en œuvre beaucoup de parties différentes qu'il faut coordonner de manière intelligente : la définition dans le fichier TMX, la traduction avec Box2D, le traitement avec libes.

Maintenant qu'on peut lire les zones de changement de niveau dans le fichier TMX, le travail pour lire d'autres informations est déjà bien entamé. Cela a deux conséquences : la première est que la fonctionnalité « lire les données directement dans la carte » qui était prévue un peu plus tard a été avancé dans la roadmap (puisqu'une grosse partie est déjà faite), la seconde est qu'on va aussi lire des données de collision avec des éléments du décor.

Quand je dis « éléments du décor », je ne parle pas des arbres ou des puits qu'on a vu la dernière fois, je parle uniquement des contraintes liées à la géographie de la carte, comme les rivières et les rochers infranchissables, les murs des grottes, le bord de mer. Encore une fois, Box2D permet de définir des formes constituées de segments de droite. Et là, on se dit que le monde est bien fait puisque le fichier TMX et Box2D ont une définition similaires de ces formes : un point de départ et un ensemble de coordonnées relatives à ce point de départ. Les deux font également la différence entre les formes closes (polygon) et les formes ouvertes (polyline). Donc, il suffit de lire le fichier TMX et de traduire les coordonnées du fichier TMX (en pixels) en coordonnées de Box2D (en mètres). Puis on crée le corps qui va bien et on l'ajoute au monde.

Un petit aperçu

Voici un petit aperçu de ce que ça donne sur la carte dans Tiled. Plus précisément, c'est le niveau du sol, on distingue une zone infranchissable en bleuté et une zone de changement de niveau en orangé.

le niveau du sol dans Tiled

Dans cette deuxième capture, on a exactement la même vue mais au premier niveau souterrain. On distingue à nouveau les zones infranchissables en bleuté (ici, les murs de la grotte), et la zone de changement de niveau en orangé (pour pouvoir remonter).

le premier niveau sous le sol dans Tiled

Une fois tout ceci défini, on peut voir ce que ça donne dans une vidéo disponible sur le site (attention, le chargement est assez lent) que vous pouvez télécharger directement si le cœur vous en dit.

Réflexion sur les données

J'aimerais maintenant avoir une réflexion sur les données dans les jeux en général, du point de vue du développeur. L'utilisation d'un système à entités pousse à avoir cette réflexion, pour savoir quoi mettre où et comment faire.

Données statiques et données dynamiques

Tout d'abord, dans un jeu, on peut distinguer deux types de données. Premièrement, les données statiques, c'est-à-dire les données qui ne changeront pas au cours du jeu. Le fond de carte est un bon exemple de donnée statique. Deuxièmement, les données dynamiques, c'est-à-dire les données qui apparaissent et/ou évoluent au cours du jeu. La position du joueur est un bon exemple de données dynamique.

Si on imagine ce qu'est l'état courant du jeu, c'est-à-dire ce qu'on va devoir sauvegarder, on voit bien qu'il s'agit uniquement des données dynamiques. Toutes les données dynamiques ? Non, sinon ça serait trop simple. Si on regarde dans le détail, on voit bien qu'on a plusieurs types de données dynamiques. Par exemple pour une animation, on a un numéro de frame courant qui est une donnée dynamique (on la sauvegardera pour retrouver le même état au prochain chargement), et on a l'image courante, qui est une donnée dynamique induite par le numéro de frame courant. Et celle là, on ne devra pas la sauvegarder, puisqu'on peut la retrouver à partir du reste.

Dernier problème, c'est le mélange entre données statiques et dynamiques. Par exemple, quand on définit le corps d'un objet, on a tout un tas de données statiques (la densité, la friction, etc) et on a quelques données dynamiques (position, angle), le tout réuni dans une seule structure qu'on ne maîtrise pas :b2Body.

Données et systèmes à entités

Viennent alors les systèmes à entités. La théorie nous dit que les entités ont plusieurs composants et que ces composants représentent l'état du système. Et quand on veut sauvegarder l'état, il faut « juste » sauvegarder les composants, ça suffit. Donc, on se dit qu'un composant est une donnée dynamique. D'accord, mais je le met où mon b2Body ? Si je le met dans un composant, déjà je vais avoir un problème vu que je n'aurai qu'un pointeur, et je ne vais certainement pas sauvegarder un pointeur ! Bon, je vais donc sauvegarder la position et l'angle contenu dans le b2Body. Mais au moment du chargement, je fais comment ? Je les récupère comment les données statiques contenu dans le b2Body et qui sont nécessaires à sa construction ?

D'ailleurs, dans la version actuelle d'Akagoria, j'ai fait une grosse erreur. En effet, pour afficher ma carte, j'avais tout un tas de tuile, que je voulais gérer de manière efficace, c'est-à-dire n'afficher que ce qui est nécessaire. J'ai donc définit un composant Tile que j'ai utilisé pour mettre toutes les informations nécessaires (position de la tuile, coordonnées sur le tileset). Puis j'ai fait un système MapRender qui prend toutes les tuiles situés vers le héros et qui les affiche. Ça marche très bien, mais ça ne va pas du tout ! En effet, mes tuiles sont des données statiques, elles ne font pas partie de l'état du jeu. Si je charge une sauvegarde, je n'ai pas besoin d'avoir tout ça dans la sauvegarde puisque c'est commun à toutes les sauvegardes.

Bref, définir des composants, ce n'est jamais aussi simple qu'on le croit. On peut rapidement tomber dans un piège.

Données et traitements

Dernier point dans cette réflexion, l'ajout d'un système d'événements au système à entités. Souvent, dans la littérature, on présente les systèmes à entités et au détour d'un transparent ou d'une phrase, on voit : « ha oui, tiens il y a aussi un système d'événement mais c'est pas important ». Il peut s'appeler « système de messages » également, mais l'idée est la même : qu'il puisse y avoir une communication directe entre deux entités ou entre l'environnement extérieur et une entité. Je pense et j'affirme que cet élément n'est pas qu'un à-côté qu'on doit traiter comme une note de bas de page, mais qu'il est nécessaire à tout système à entité, il en fait partie intégrante !

Comment en suis-je arrivé à cette conclusion ? Déjà, le fait qu'il y ait systématiquement un système d'événements associé à un système à entités m'a mis la puce à l'oreille. Je trouvais bizarre que personne n'en parle vraiment, sans doute parce que la programmation événementielle est un paradigme bien connu, tandis que les systèmes à entités sont relativement nouveau. Puis, quand on implémente un jeu complet, on tombe forcément sur un cas où on en a besoin. Dans mon cas, c'était le changement de niveau, mais j'avais déjà en tête d'autres cas d'utilisation.

Alors, j'ai poussé ma réflexion. En quoi un système d'événements est-il complémentaire d'un système à entités pur ? La réponse est assez évidente : un système à entités met à jour ses données de manière régulière via les systèmes (environ soixante fois par secondes en simplifiant) tandis qu'un système d'événement n'agit que s'il y a un événement. On retrouve la dichotomie bien connue polling/event, sauf qu'ici, on a mis les données au centre de la réflexion. Ma première conclusion, c'est qu'on a besoin des deux. On pourrait sans doute avoir un composant Event et un système EventHandler qui regarde toutes les entités avec un Event et agit en fonction. Mais d'une part, on serait limité par ce qu'on peut mettre comme données dans l'événement (à moins de définir des composants spéciaux pour chaque type d'événement), et d'autre part, on ferait en fait du polling pour implémenter un gestionnaire d'événement, ce qui est un peu contradictoire. Sans compter que ces événements ne font pas partie de l'état du jeu !

La suite, elle est simple. Avec ces nouveaux outils (entités/événements), il va falloir refaire tout ce qui a été fait pour d'autres paradigme, c'est-à-dire trouver des design pattern adaptés à ces outils, de manière à éviter d'être en permanence en train de se demander comment faire telle ou telle chose. Construire un jeu permet d'avoir des cas très concrets d'utilisation et il suffit alors d'en extraire l'essence. Ça sera un des effets de bord de la création de ce jeu.

Version 0.0.3 et la suite

La version 0.0.3 est sortie le 15 décembre avec les nouveautés décrites précédemment. Dans la version suivante, je vais m'attaquer à la gestion des dialogues, ce qui amènera nécessairement à s'intéresser aux traductions. Il y aura également une petite mise à jour du sprite de Kalista que Naha m'a soumise (mon premier patch par courriel !) mais qui n'a pas été intégrée à cette version 0.0.3.

Aller plus loin

  • # Données des niveaux

    Posté par  . Évalué à 6.

    Pour ce que ça vaut, je crois que j'aurais choisi l'autre option quant à ta représentation des niveaux. Vu que :

    • concrètement, tu géres les changement de niveau à la main et dans les deux sens (et non pas juste en définissant une zone "tampon" commune à tous les calques), sauf si j'ai rien compris. Donc que ça pointe vers un calque différent ou un fichier différent, ça ne change pas grand chose.
    • de fait, l'ordre des calques est une information superflue pour toi, tes "zones" ne disent pas "passe au calque du dessus", mais "passe au calque ", si?
    • et donc du coup, avec des fichiers différents, tu pourrais te permettre des choses beaucoup plus rigolotes pour pas plus cher : genre dimensions intérieures et extérieures sans aucun rapport (TARDIS?).

    Bon après, c'est avec ce que j'ai compris de ton exposé (que je lis toujours avec plaisir).

    • [^] # Re: Données des niveaux

      Posté par  (Mastodon) . Évalué à 4.

      concrètement, tu géres les changement de niveau à la main et dans les deux sens (et non pas juste en définissant une zone "tampon" commune à tous les calques), sauf si j'ai rien compris. Donc que ça pointe vers un calque différent ou un fichier différent, ça ne change pas grand chose.

      Si, ça change beaucoup de choses en fait. La zone tampon n'est pas commune aux deux niveaux, il y en a deux et elles sont légèrement décalées parce que sinon, tu passes d'un niveau à l'autre puisque tu retombes dans une zone de changement de niveau. Et avoir des calques, c'est bien pratique pour bien positionner ces deux zones pour qu'elles soient assez éloignées.

      J'ai eu un bug la première fois que j'ai défini mes zones. Elles n'étaient pas assez éloignées et j'avais le phénomène décrit. Parce qu'il faut bien comprendre comment ça se passe. La collision est détectée dès qu'il y a collision, donc dès que les deux corps sont en contact. Mais le corps de mon héros, il est assez gros, donc en passant au niveau inférieur, il peut toucher l'autre zone de changement de niveau s'il n'y a pas assez d'écart. Déjà là, je met les deux zones suffisamment éloignées mais je vois ce que je fais grâce aux calques. Sans calques, je serais obligé de largement surestimer la corpulence de mon héros pour être sûr que ça va passer.

      de fait, l'ordre des calques est une information superflue pour toi, tes "zones" ne disent pas "passe au calque du dessus", mais "passe au calque ", si?

      Oui, l'ordre importe peu, l'information du niveau est stockée dans une propriété des calques. L'ordre importe uniquement pour celui qui va créer les niveaux, ça lui permet de savoir où il en est et à quel niveau il est.

      et donc du coup, avec des fichiers différents, tu pourrais te permettre des choses beaucoup plus rigolotes pour pas plus cher : genre dimensions intérieures et extérieures sans aucun rapport (TARDIS?).

      L'un n'empêche pas l'autre je dirais. Pour le cas général de la carte de mon univers, je vais procéder avec des calques. Mais si jamais mon héros était transporté dans une dimension parallèle, j'ajouterais des zones de changement de dimension ;)

  • # Souvenirs

    Posté par  (site web personnel) . Évalué à 3.

    oui, ceux où on pouvait enfermer le majordome dans la chambre froide

    Petit instant de nostalgie de ces bons moments.

    Merci pour cette dépêche instructive et de bonne qualité.

    kentoc'h mervel eget bezan saotred

    • [^] # Re: Souvenirs

      Posté par  (Mastodon) . Évalué à 3.

      Petit instant de nostalgie de ces bons moments.

      Je savais bien que j'allais toucher le cœur de certains :P

  • # solutions ?

    Posté par  (site web personnel) . Évalué à 2.

    Tu poses beaucoup de problème et ne proposes pas vraiment de solution. Alors les messages se sont des entités ou des composants ? Leur durée de vie est éphémère ou ils sont recyclés ? Il y a broadcast ou seulement du point à point ?

    Sinon, je voulais savoir aussi comment on gère une information dupliquée dans un système à entité. Par exemple un nom au dessus de chaque personnage, plus un encart détaillé sur le personnage qui a le focus. Dans un système objet classique, il y a un paquet d'adapter ou de listener collé un peu partout, surtout si la donné est modifiable, il faut pouvoir propager l'information dans tous les sens (ex: modification du nom).

    Comment rendre cela plus simple ? Avoir une entité-composant pour la boite de propriété qui est rattaché dynamiquement au personnage qui a le focus ?

    "La première sécurité est la liberté"

    • [^] # Re: solutions ?

      Posté par  (Mastodon) . Évalué à 3.

      Tu poses beaucoup de problème et ne proposes pas vraiment de solution.

      Effectivement, parce que je n'ai pas encore toutes les réponses. Mais ça viendra, c'est un travail en cours.

      Alors les messages se sont des entités ou des composants ?

      Ni l'un, ni l'autre, ils vivent en dehors du système à entités pure. Ils font partie du système d'événements.

      Leur durée de vie est éphémère ou ils sont recyclés ?

      Nécessairement éphémère. En plus, ça simplifie la gestion de la mémoire.

      Il y a broadcast ou seulement du point à point ?

      Heu, c'est une sorte de publish/subscribe, les gestionnaires d'événements s'abonnent à un type d'événement et reçoivent tous les événements de ce type. Donc, c'est du multicast ;)

      Sinon, je voulais savoir aussi comment on gère une information dupliquée dans un système à entité. Par exemple un nom au dessus de chaque personnage, plus un encart détaillé sur le personnage qui a le focus. Dans un système objet classique, il y a un paquet d'adapter ou de listener collé un peu partout, surtout si la donné est modifiable, il faut pouvoir propager l'information dans tous les sens (ex: modification du nom).

      On essaie, comme partout ailleurs, de ne pas dupliquer l'information. Et justement, avoir des événements permet de propager l'information. Maintenant, c'est vrai qu'avec une conception objet, on a les bons design patterns pour mettre tout ça en branle facilement. Mais l'intéraction entre un système à entité pure et un système d'événements est encore mal cernée, de mon point de vue, et c'est justement là dessus que je veux me pencher.

      Comment rendre cela plus simple ? Avoir une entité-composant pour la boite de propriété qui est rattaché dynamiquement au personnage qui a le focus ?

      Je ne sais pas si ça sera plus simple (et j'en doute même). Mais pour le cas que tu présentes là (très rapidement), je pense que ça peut se faire. Il faudrait décrire le problème de manière plus détaillé.

      De manière générale, je dirais que les systèmes à entités ne proposent pas de simplifier ou de changer tout, ils proposent un autre point de vue sur le traitement de données hétérogènes mais qui partagent des traits similaires. Ce n'est pas une solution miracle à des problèmes bien connus depuis longtemps, c'est juste une manière de faire différente, rien de plus. Il ne faut pas en attendre trop. Pour l'instant, je dirais que j'aurais pu faire tout ce que j'ai fait avec une bonne vieille architecture objet des familles, et sans doute que ça aurait été plus facile parce que j'y suis plus habitué, mais j'aime bien le challenge intellectuel que ça représente de travailler avec des systèmes à entités et c'est aussi pour ça que je partage mes réflexions sur le sujet.

      • [^] # Re: solutions ?

        Posté par  (site web personnel) . Évalué à 2. Dernière modification le 16 décembre 2013 à 13:50.

        Je bosse avec des technos Java eclipse qui fonctionne à base de "Publish-Subscribe", ce sont les adapters et les listners dont je parlais avant. Et c'est juste chiant, lourd, plein de bug, lent, cela a du mal pour le passage à l'échelle,… etc

        Dans un outil, tu as un domaine métier qui existe et un "certain nombre" de représentation (schéma, navigateur),… De base tout est statique, et des machins sont ajoutés pour tout mettre à jour. J'aimais beaucoup le principe du jeu vidéo qui est à l'inverse : tout est mis à jour tout le temps (fonction cyclique), sauf optimisations volontaires faite dans les systèmes. Je trouvais cela super sexy.

        Au lieu d'avoir plusieurs arbres parallèle avec des liaisons plus ou moins standard, on a un arbre avec un objet, et un ou plusieurs composant définissent des représentations à mettre à jour, quand l'objet métier est mis à jour. Il est facile d'imaginer un bit "dirty", pour éviter une mise à jour couteuse.

        L'avantage est aussi de ne plus avoir besoin de communication entre 2 types d'objet, puisqu'ils sont ici "fusionné". L'avantage aussi d'avoir des messages-entités, est de pouvoir attacher n'importe quel composant dessus. En java, on passe son temps à poser des "instanceof" sur des EObject (je caricatures), cela paraitrait tellement facile d'utiliser des entités à la place.

        "La première sécurité est la liberté"

        • [^] # Re: solutions ?

          Posté par  (Mastodon) . Évalué à 2.

          Savoir si les systèmes à entités peuvent servir à autre chose que des jeux vidéos est, pour moi, une question encore ouverte. Mais d'après ce que tu dis (et ce que j'en comprends), la réponse a l'air d'être plutôt oui. Après, j'avoue que je n'ai pas bien compris.

          • [^] # Re: solutions ?

            Posté par  (site web personnel) . Évalué à 5.

            Je bosse pour un éditeur de logiciel, et on utilise Eclipse comme base. C'est lourd. Ta présentation des entités m'a vraiment intéressé.

            Si tu prends un IDE de codage par exemple, tu te rends compte que de plus en plus de choses sont dynamiques et faite "en live". Ce qui les fait ressembler de plus en plus au jeu vidéo. On peut parler de la coloration syntaxique, la correction orthographique, la complétion automatique, …

            Le principe est toujours le même : tu as des données (genre arbre sémantique) qui est représenté par l'outil ("vue" ou objet graphique). Si une vue modifie l'objet, il faut mettre à jour toutes les autres vues visibles. C'est cette propagation qui est très "manuel" et difficile à faire correctement. La difficulté augmente avec l'augmentation du "dynamisme" des interfaces.

            Ce genre d'outil à des problématiques commune, comme le copier-coller, le undo/redo et la sérialisation, voir la gestion de version (l'historique ?). Les entités systèmes semblent à première vue pouvoir offrir ces fonctionnalités, de façon générique. Il me reste à régler le problème concernant ses mise à jour parallèles, cela doit bien exister dans un jeu : la barre de vie qui diminue et le sprite qui change pour montrer les dégâts ?

            "La première sécurité est la liberté"

            • [^] # Re: solutions ?

              Posté par  (Mastodon) . Évalué à 2.

              Ha oui, je comprends mieux.

              Il me reste à régler le problème concernant ses mise à jour parallèles, cela doit bien exister dans un jeu : la barre de vie qui diminue et le sprite qui change pour montrer les dégâts ?

              Et bien c'est là que je mettrais un événement (et donc un listener, enfin deux ici). Ou, comme dit dans l'article et un peu plus bas dans les commentaires, tu peux utiliser des composants pour tes événements et des systèmes pour les traiter.

          • [^] # Re: solutions ?

            Posté par  (site web personnel) . Évalué à 1.

            Savoir si les systèmes à entités peuvent servir à autre chose que des jeux vidéos est, pour moi, une question encore ouverte

            Je me permets de la clore: ça sert dans les simulations, mais aussi chaque fois que tu as une entité polymorphe, par exemple un devis qui devient un bon de commande puis enfin un dossier avec à chaque étape plus ou moins de composants (options, bon de réduction, avis, assurances, garanties…).

            Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

            • [^] # Re: solutions ?

              Posté par  (Mastodon) . Évalué à 2.

              Tu l'as vu en vrai ou c'est juste le principe ? Et c'est fait avec une lib ES ou c'est juste que ça se rapproche dans l'idée de l'ES ? C'est juste de la curiosité, l'exemple que tu donnes montre que oui, c'est possible dans ce domaine là.

              • [^] # Re: solutions ?

                Posté par  (site web personnel) . Évalué à 3.

                Je l'ai vu en direct live, avec des implémentations maisons, car c'était avant qu'on appelle ça "Entity System".

                Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

  • # Niveau supplémentaires en 3D

    Posté par  (site web personnel) . Évalué à 1.

    …et pourquoi ne pas implémenter quelques niveaux sous-terrains (donjons) supplémentaires en 3D pas à pas ? (un peu comme dans Ultima III, IV et V), cela rendrai le jeu plus intéressant au niveau joueur et au niveau coding.

  • # Systèmes à entités et événements

    Posté par  . Évalué à 3.

    Dans le moteur E/S que je développe j'ai choisi de ne pas avoir de système d'événements :)

    La raison principale étant : pour avoir un flux de traitement organisé et facile à suivre, plutôt que des listeners d'événements qui vont déclencher d'autres événements en chaîne etc…

    Les événements ont été remplacé soit par du polling tout bête (ex : le composant Button a un booléen 'clicked') ou par des entités éphémères (ex : le joueur clique sur un bouton, création d'une entité avec un composant Attaque et le vrai traitement de l'attaque est fait dans l'update de l'AttackSystem et non dans un listener).

    Le blog des développeur du moteur de jeu bitsquid contient pleins d'articles intéressants sur les E/S ; cet article parle des événements par exemple.

    • [^] # Re: Systèmes à entités et événements

      Posté par  (Mastodon) . Évalué à 2.

      Très intéressant. Et que penses-tu de mes remarques alors ? Notamment que ces événements ne font pas vraiment partie de l'état du système (d'ailleurs, je suis surpris de voir un composant Button) ?

      • [^] # Re: Systèmes à entités et événements

        Posté par  . Évalué à 1.

        Et que penses-tu de mes remarques alors ? Notamment que ces événements ne font pas vraiment partie de l'état du système

        Je ne suis pas sûr d'avoir un avis bien tranché là dessus. À vrai dire c'est surtout les listeners que je rejette pour conserver la cohérence (ex : le changement des niveau est toujours traité au même endroit du code, et au même moment dans chaque frame).

        Par contre, utiliser un «système de messages» basé sur des entités a plusieurs intérêts :
        * les messages font partie du design E/S
        * la synchro de ses entités messages suffit pour implémenter un mode réseau basique :)

        d'ailleurs, je suis surpris de voir un composant Button

        Oui, de ce que j'ai pu voir du code de libes nos composants sont très différents.
        Dans mon cas un composant fournit une fonctionnalité complète, par exemple : Button, Rendering, Transformation, Sound, Grid, etc

        Ensuite chaque composant déclare ses propriétés, ce qui permet :
        * de les afficher pour du debug (en utilisant AntTweakBar)
        * de les charger depuis un format de fichier texte
        * de sérialiser/désérialiser les entités persistantes pour sauvegarder l'état du jeu et le restaurer plus tard
        * de sérialiser/désérialiser les entités (celles avec un composant Network) pour implémenter un mode réseau

        • [^] # Re: Systèmes à entités et événements

          Posté par  (Mastodon) . Évalué à 3.

          Par contre, utiliser un «système de messages» basé sur des entités a plusieurs intérêts :
          * les messages font partie du design E/S
          * la synchro de ses entités messages suffit pour implémenter un mode réseau basique :)

          Donc, tu ne rejettes pas mes réflexions en bloc ;)
          Je crois que tu résumes bien les avantages qu'on peut en tirer.

          Dans mon cas un composant fournit une fonctionnalité complète, par exemple : Button, Rendering, Transformation, Sound, Grid, etc

          Tu as un dépôt public ? Parce que là, je trouve ça étonnant et ça m'intrigue vraiment.

          de sérialiser/désérialiser les entités persistantes pour sauvegarder l'état du jeu et le restaurer plus tard

          Tu fais donc une différence entre les entités persistantes et celles qui ne le sont pas, je comprends mieux du coup. J'essaie justement de réduire l'utilisation du système à entités à ce qui est persistant, en enlevant tout ce qui n'a pas à l'être. Mais je suis vraiment curieux de ton approche du coup.

          • [^] # Re: Systèmes à entités et événements

            Posté par  . Évalué à 1.

            Tu as un dépôt public ? Parce que là, je trouve ça étonnant et ça m'intrigue vraiment.

            Si tu n'as pas froid au yeux, tu peux regarder par là : git://git.damsy.net/sac.git pour le code du moteur et là pour un de nos jeux qui l'utilise : git://git.damsy.net/sac/heriswap.git :-)

            (un jour il faudra que je publie aussi les sources de notre 2nd jeu…)

            Tu fais donc une différence entre les entités persistantes et celles qui ne le sont pas

            Oui. Par ex : une entité avec le composant Particule va générer N particules/sec. Quand on veut sauvegarder l'état du jeu on surtout intéressé par le générateur de particules … hum… en écrivant ça je me dis que c'est vraiment arbitraire comme choix et qu'on pourrait tout aussi bien sauvegarder toutes les entités et donc n'avoir que des entités persistantes :)

            • [^] # Re: Systèmes à entités et événements

              Posté par  (Mastodon) . Évalué à 2.

              Si tu n'as pas froid au yeux, tu peux regarder par là : git://git.damsy.net/sac.git pour le code du moteur et là pour un de nos jeux qui l'utilise : git://git.damsy.net/sac/heriswap.git :-)

              Je vais aller voir ça ;)

              en écrivant ça je me dis que c'est vraiment arbitraire comme choix et qu'on pourrait tout aussi bien sauvegarder toutes les entités et donc n'avoir que des entités persistantes :)

              Ha, tu vois, tu doutes aussi et finalement, rien n'est clair dans tout ça.

    • [^] # Re: Systèmes à entités et événements

      Posté par  (site web personnel) . Évalué à 2.

      Dans le cas de champ numérique d'input, est-ce qu'il y a des moyens simples de ne pas relancer tout un calcul avec un boolean 'dirty' ?

      Concernant les messages qui ne sont donc pas des événement lié à des modifications d'états, est-ce qu'il sont gérer par des systèmes particuliers ? Est-ce qu'il des propriétés à conserver ?

      "La première sécurité est la liberté"

      • [^] # Re: Systèmes à entités et événements

        Posté par  . Évalué à 1.

        Dans le cas de champ numérique d'input, est-ce qu'il y a des moyens simples de ne pas relancer tout un calcul avec un boolean 'dirty' ?

        J'ai essayé plusieurs approches pour ça, avec comme seul règle : pas de complexité pour le développeur qui l'utilise (et donc pas un flag 'dirty' à modifier à la main quand on modifie le champs).
        La plus intéressante était celle avec un système qui définissait un composant public: MonComposant et un composant privé MonComposantPrivé qui hérite de MonComposant.
        L'idée étant : tous les utilisateurs externes au système ne voit que MonComposant, alors que le système manipule MonComposantPrivé
        Appliqué à ton exemple, on pourrait faire :

        MonComposant { float value; }
        MonComposantPrivé : MonComposant { float oldValue; }
        

        Et il est trivial de ne déclencher le traitement coûteux que si la valeur a changé.

        Ou alors créer une entité qui aura un composant ValueChanged (?) par ex à chaque fois que le champs est modifié - ça pourrait avoir des effets secondaires intéressants pour gérer le Undo par ex.

        • [^] # Re: Systèmes à entités et événements

          Posté par  (site web personnel) . Évalué à 2.

          Les composants peuvent inclure uniquement des getter/setter qui gèrent les champs dirty. Cela évite de le gérer à chaque fois.

          Et concernant les messages, c'est plutot "event" ou "callback" ?

          "La première sécurité est la liberté"

  • # Systeme d'evenements

    Posté par  . Évalué à 3.

    Merci de partager ta progression sur le jeu. J'aime bien lire tes comptes rendus.
    J'aime aussi la video, bravo pour en etre arrive jusque la!

     

    J'ai quelques commentaires sur la depeche.

    Comme un commentaire au dessus, je suis un peu frustre par tes interrogations qui ne sont pas suivies de pistes de reflexion. Je comprends bien que la reflexion est en cours, ou meme pas encore bien definie, mais c'est justement le cheminement qui fait la richesses de cette serie de depeches. Si tu exposes tes reflexions en cours (ou meme tes ebauches de reflexion) avant d'exposer le choix realise, tu vas beaucoup faire beaucoup plus reagir/faire reflechir les lecteurs qu'en leur presentant le resultat final. Si tu ne fais pas ca, je me demande pourquoi tu nous expose tes interrogations. A vrai dire c'est un peu frustrant pour moi. Ceci dit, tu fais comme tu veux bien entendu :) Je suis deja content que tu partages ton experience et de voir la progression de ton jeu.

     

    Concernant le systeme a evenement, cela me semble un prerequis avec des systemes tres complexes ou tu veux totalement decoupler la mise a jour des donnees et les actions a entreprendre suite a la mise a jour de ces donnees.
    Cela se voit aussi bien intra-application (ex: bus d'evenements dans Eclipse) que inter-applications (ex: dbus), voire de maniere distribuee (ex: Enterprise Service Bus). C'est une problematique difficile, mais indispensable pour decoupler du code, autoriser les systemes de plugins riches, ou autoriser des changements rapides sans impact sur le code central a une application. Le bus d'evenements, c'est le pattern observer aux steroides: il est generalise a toute application sans devoir repeter le pattern partout.
    En gros, cela permet de centraliser la mise a jour des donnees et de factoriser les notifications liees a ces mises a jour.
    Malheureusement, comme signale plus haut, les performances peuvent en patir selon l'implementation. Je pense qu'une implementation correcte et de typer les evenements et de se baser sur un systeme publish/subscribe ou le subscribe s'appuie sur le type de chaque evenement afin de limiter les notifications indesirables et la liste des subsriber a parcourir.
    Un autre probleme auquel on se heurte parfois est lies a l'ordre d'execution des listeners: il peut etre desirable de les executer dans un ordre precis afin de mettre a jour une donnee avant qu'elle ne soit lue par un autre listener. La combinatoire explose, mais reste plus simple a gerer qu'en ajoutant l'appel de methode X partout ou la donnee Y peut etre mise a jour. Le principal probleme reste l'ajout d'un ou plusieurs niveau d'indirection dans les appels de methodes qui rendent le debogage plus complique. Ce probleme peut etre reduit par l'utilisation de "traces" dans un mode debug qui permettent de comprendre que l'evenement E a ete concomme par le listener L, qui a en retour leve l'evenement F, etc.

    Bref, ce n'est pas tres simple mais bien mieux que l'alternative a mon avis.

    Pour finir, le choix entre l'utilisation du polling ou du publish/subscribe n'est pas seulement dans les mains du developpeur: est ce que les donnees doivent etre mises a jour au fil de l'eau (le fameux real time des analystes metiers qui n'a rien a voir avec le real time des programmeurs :) ), ou bien est ce qu'un delai entre la mise a jour des donnees et la repercussion de cette mise a jour est acceptable? Est ce que le systeme est transactionnel? quel est le volume de donnees a traiter? etc.
    Imaginez la complexite lorsque le systeme est transactionnel, avec un fort volume de donnees et que les regles metiers sont extrement complexes :)
    Bref, bienvenu dans le monde la bancassurance!

    • [^] # Re: Systeme d'evenements

      Posté par  (Mastodon) . Évalué à 2.

      Comme un commentaire au dessus, je suis un peu frustre par tes interrogations qui ne sont pas suivies de pistes de reflexion. Je comprends bien que la reflexion est en cours, ou meme pas encore bien definie, mais c'est justement le cheminement qui fait la richesses de cette serie de depeches. Si tu exposes tes reflexions en cours (ou meme tes ebauches de reflexion) avant d'exposer le choix realise, tu vas beaucoup faire beaucoup plus reagir/faire reflechir les lecteurs qu'en leur presentant le resultat final. Si tu ne fais pas ca, je me demande pourquoi tu nous expose tes interrogations. A vrai dire c'est un peu frustrant pour moi. Ceci dit, tu fais comme tu veux bien entendu :) Je suis deja content que tu partages ton experience et de voir la progression de ton jeu.

      Je prend note. Je dois dire que j'aurais aimé aller plus loin mais par manque de temps, j'en suis resté là. J'espère ne pas avoir suscité trop de frustration.

      Malheureusement, comme signale plus haut, les performances peuvent en patir selon l'implementation. Je pense qu'une implementation correcte et de typer les evenements et de se baser sur un systeme publish/subscribe ou le subscribe s'appuie sur le type de chaque evenement afin de limiter les notifications indesirables et la liste des subsriber a parcourir.

      Dans l'implémentation que j'ai faite, le subscribe est bien par type d'événement, pas pour tous les événements, et les événements ont bien un type. L'autre choix majeur, qui simplifie énormément le code et la gestion mémoire, est que les événements doivent être consommés immédiatement, ils ne sont pas mis dans une file d'attente. Je pense que ça simplifiera le debug aussi.

      Ce probleme peut etre reduit par l'utilisation de "traces" dans un mode debug qui permettent de comprendre que l'evenement E a ete concomme par le listener L, qui a en retour leve l'evenement F, etc.

      Je commençais à avoir des traces, donc j'ai mis en place un système de log pour hiérarchiser l'information un minimum. Je vais assurément m'en servir pour ça aussi.

      Merci pour ce retour encourageant !

      • [^] # Re: Systeme d'evenements

        Posté par  . Évalué à 3.

        J'espère ne pas avoir suscité trop de frustration.

        Ne t'inquietes pas, je survivrai :)
        Je comprend le manque de temps. Merci encore de partager ton experience.

        L'autre choix majeur, qui simplifie énormément le code et la gestion mémoire, est que les événements doivent être consommés immédiatement, ils ne sont pas mis dans une file d'attente. Je pense que ça simplifiera le debug aussi.

        Oh oui! Ca va beaucoup simplifier le debug!

        Nous avions une implementation permettant de:

        • retarder la consommation des evenements jusqu'a la fin d'une transaction, afin d'eviter de traiter inutilement plusieurs fois les memes evenements survenant lors de la meme transaction
        • consommer les evenements de maniere synchrone (comme toi)
        • consommer les evenements dans une transaction separee.

         

        Ca repondait a pas mal de use case :)
        En contrepartie, les evenements retardes etaient une plaie a debugger.

        Je commençais à avoir des traces, donc j'ai mis en place un système de log pour hiérarchiser l'information un minimum. Je vais assurément m'en servir pour ça aussi.

        Attention a l'explosion de la taille des logs!

        • [^] # Re: Systeme d'evenements

          Posté par  (Mastodon) . Évalué à 2.

          Je comprend le manque de temps.

          En fait, cet épisode est en retard par rapport aux autres ;) Normalement, j'essaie de publier toutes les deux semaines, mais là ça n'était pas possible. Du coup, j'ai retardé d'une semaine. Mais ça m'arrange parce que comme ça, je vais publier le prochain début janvier (parce que pendant les vacances, il n'y aura sans doute pas beaucoup de lecteurs, et j'en profiterai pour avancer)

          Attention a l'explosion de la taille des logs!

          Pour l'instant, tout va sur stderr et ça reste encore lisible.

  • # Il y a évènement et évènement...

    Posté par  (site web personnel) . Évalué à 2.

    Sans compter que ces événements ne font pas partie de l'état du jeu !

    Je viens de ticker sur cette phrase ;-)

    Il faut distinguer au moins:

    • les interactions avec l'extérieur: avec le joueur (appuie sur une touche de direction, mise en pause du jeu…) ou le système (message reçu via le réseau dans le cadre d'un jeu en ligne, texture chargée…).
    • les actions d'une entité sur d'autres (un PNJ qui en frappe un autre…).
    • les événements du jeu (une quête qui se débloque, la nuit tombe…).

    Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

    • [^] # Re: Il y a évènement et évènement...

      Posté par  (Mastodon) . Évalué à 2.

      Tu as tout à fait raison, je pensais essentiellement au second type. Le premier est traité de manière particulière (via du polling d'ailleurs). Et le troisième n'a pas besoin de réactivité et provoque assez peu d'événements, donc on peut aussi faire du polling via des composants et des systèmes (et ça fait bien partie de l'état du jeu pour le coup).

  • # Code du système d'événements

    Posté par  . Évalué à 1.

    Très intéressant, comme les autres articles de la série.

    Je souhaitais regarder la manière dont tu as implémenté les événements, mais sur le github de libes, il semblerait que tu n'aies pas poussé le code (ou alors je suis miro, c'est encore de l'ordre du possible), ou bien est-il hébergé ailleurs ?

    • [^] # Re: Code du système d'événements

      Posté par  (Mastodon) . Évalué à 2.

      En fait, il est sur la branche develop mais je sais pas pourquoi je l'ai pas mis sur la branche master.

      https://github.com/jube/libes/tree/develop/src/include/es

      Et puis de toute façon, j'ai changé un peu ce code, j'ai remplacé EventHandler et sa fonction virtuelle par un std::function, mais j'ai pas encore pushé sur la branche develop. Si ça t'intéresse, je pushe ça le plus vite possible.

      • [^] # Re: Code du système d'événements

        Posté par  . Évalué à 2.

        évidemment, sur une autre branche, stupide moi :)
        Ça m'intéresse, j'ai fait un petit prototype de système entités/événements basé sur des acteurs. Je suis donc intéressé de voir comment tu as implémenté la chose.

        Dans mon prototype, le tick est un message périodique envoyé à un acteur moteur qui délègue aux systèmes qu'il gère.
        Lorsqu'un système publie un événement, c'est un message comme un autre envoyé au moteur, il est donc séquencé parmi au milieu des tick standards.
        Le modèle d'acteurs m'assure qu'un seul message est traité à la fois par un engin donné.
        Si ça marche pas mal, je ferai potentiellement un journal dessus :)

        • [^] # Re: Code du système d'événements

          Posté par  (Mastodon) . Évalué à 2.

          J'ai mis à jour (et j'ai fait une nouvelle version 0.4.0) avec les événements.

          Je suis très intéressé par ton implémentation et j'attends avec impatience ton journal :)

  • # Nouvelle version 0.0.3.1

    Posté par  (Mastodon) . Évalué à 3.

    Bon, pour ceux qui ont essayé de compiler la version 0.0.3, il y avait un petit souci, puisqu'il fallait prendre la branche develop de la libes (qui contenait justement les événements), ce qui n'était pas très pratique. Du coup, j'ai sorti une nouvelle version 0.4.0 de la libes (qui modifie le type d'EventHandler par rapport à ce qu'il y avait dans Akagoria) et j'ai sorti une version 0.0.3.1 d'Akagoria qui va avec (il y avait une mise à jour du code à faire forcément). Donc voilà, maintenant, tout devrait être en ordre, tout est dans les branches master.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.