URL: https://linuxfr.org/news/je-cree-mon-jeu-video-e09-techniques-de-c-11-appliquees-au-systeme-a-entites Title: Je crée mon jeu vidéo E09 : Techniques de C++11 appliquées au système à entités Authors: rewind palm123 et Benoît Sibaud Date: 2014年02月06日T16:55:50+01:00 License: CC By-SA Tags: gamedev Score: 38 «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 parlera de tout : de la technique, du contenu, de la joie de voir bouger des sprites, de la lassitude du développement solitaire, etc. Vous pourrez suivre cette série grâce au [tag gamedev](http://linuxfr.org/tags/gamedev/public). Dans le dernier épisode, on a parlé du livre de Jesse Schell, «L'Art du Game Design». Mais bon, comme il paraît qu'[on ne discute pas assez de technique sur LinuxFR](http://linuxfr.org/users/ffourcot/journaux/la-communaute-linuxfr-n-a-t-elle-plus-rien-de-technique-a-dire), donc cette fois, on va causer uniquement de technique. Du gros, du lourd, du C++ ! Et dans sa version 2011 pour que ça soit encore plus imbitable pour le commun des mortels. Comme ça, on discutera entre nous, techniciens divins et on laissera la bonne plèbe se vautrer dans la mélasse. (Est-il nécessaire que je rajoute une balise humour ?) ---- [Akagoria, la revanche de Kalista](http://www.akagoria.org/) [le tag gamedev](http://linuxfr.org/tags/gamedev/public) [C++11](http://en.wikipedia.org/wiki/C++11) ---- # C++11 mon amour Programmer un jeu en C++11 (connu auparavant comme C++0x) est l'occasion de tester des fonctionnalités de cette nouvelle norme. Et les systèmes à entités donnent l'occasion de faire des choses assez élégantes. Comme Bjarne Stroustrup, inventeur du C++, je ne suis pas loin de penser que [C++11 est presque un nouveau langage](http://www.stroustrup.com/C++11FAQ.html#think) tellement tout paraît plus facile et naturel. Une fois essayé, difficile de s'en passer. Voici donc deux exemples qui utilisent des fonctionnalités C++11 et qui sont intégrés à [`libes`](https://github.com/jube/libes) et utilisé dans mon jeu. ## Des gestionnaires d'événements vraiment génériques Dans le [patron de conception Observateur](http://fr.wikipedia.org/wiki/Observateur_%28patron_de_conception%29) qui est à la base de certains systèmes d'événements, l'observateur est censé hériter d'une classe `Observer` et implémenter une fonction `notify()` qui sera appelée au moment adéquat. En C++, cela se traduit souvent par une classe de base avec une fonction virtuelle pure : ```cpp class Observer { public: virtual void notify() = 0; } ``` Cette technique n'est pas très pratique, surtout comparé à Java où on aurait une [simple interface](http://docs.oracle.com/javase/7/docs/api/java/util/Observer.html) (et non une classe, même abstraite). Heureusement, C++11 vient avec un moyen encore plus puissant que Java : `std::function` ! [`std::function`](http://en.cppreference.com/w/cpp/utility/functional), c'est les pointeurs de fonction en démultiplié ! Bon d'accord, la comparaison n'est peut-être pas idéale mais disons que c'est l'idée. Les amateurs de langages fonctionnels trouveront sûrement que cette fonctionnalité est triviale, et implémentée de manière verbeuse et inélégante, mais en C++, c'est nouveau et c'est révolutionnaire ! Concrètement, comment peut-on s'en servir ? Et bien je vais prendre l'exemple de `libes` et de ses gestionnaires d'événements. Dans `libes`, un gestionnaire d'événement est défini de la manière suivante : ```cpp typedef std::function EventHandler; ``` Ce qui veut dire qu'un `EventHandler` est une «fonction» prenant en paramètre une entité (l'origine de l'événement), un type d'événement et un pointeur vers les données de cet événement et renvoyant un statut (que je ne détaille pas ici). Ça a l'air limité mais en fait, ça ne l'est pas, au contraire. Parce qu'on peut avoir une vraie fonction : ```cpp EventStatus monGestionnaire(Entity e, EventType t, Event *ev) { return EventStatus::KEEP; } ``` Mais on peut aussi avoir un lambda : ```cpp auto monGestionnaireLambda = [](Entity e, EventType t, Event *ev) { return EventStatus::KEEP; } ``` Mais on peut aussi avoir une méthode d'une classe ! Et c'est là que ça déchire : ```cpp class Foo { EventStatus maMethodeGestionnaire(Entity e, EventType t, Event *ev) { return EventStatus::KEEP; } } ``` Et dans ces cas-là, on peut l'associer à un objet en particulier via [`std::bind`](http://en.cppreference.com/w/cpp/utility/functional/bind) : ```cpp using namespace std::placeholders; Foo foo; auto gestionnaire = std::bind(&Foo::maMethodeGestionnaire, &foo, _1, _2, _3) ``` Ce qui signifie que, quand on appellera `gestionnaire` avec les trois arguments qui vont bien, en fait, on appellera la méthode `maMethodeGestionnaire` sur l'objet `foo` avec les trois arguments. On pourrait faire des choses encore plus drôles en ayant des méthodes qui ont les paramètres dans le désordre, ou dans laquelle il manque des paramètres. Bref, tout est possible avec `std::bind` ! Maintenant, on n'est donc plus limité par une classe de base, on peut avoir tout ce qu'on veut comme gestionnaire d'événements. Cerise sur le gâteau, comme ce dernier cas est plutôt courant, `libes` permet de spécifier un pointeur sur une méthode et un objet et [fait le bind automatiquement](https://github.com/jube/libes/blob/8905dfb55c3acd621856964ba85c8d3d15569880/src/include/es/Manager.h#L286). Magique ! ## Comment avoir des identifiants uniques en C++ ? Dans mon implémentation de `libes`, j'utilise des identifiants uniques pour les composants et les événements. Ces identifiants doivent être tous différents et différents de zéro (qui est l'identifiant qui représente le composant ou l'événement invalide). Évidemment, cette manière de faire est très utile pour le développeur de `libes` (moi) qui a un joli entier qu'il peut utiliser pour plein de choses (essentiellement ranger les composants/événements dans une table de hachage) mais pas très pratique pour l'utilisateur de `libes`. Et bien désormais, `libes` permet de ne pas avoir à trop se préoccuper de cet entier (notamment savoir s'il est différent des autres) grâce à la magie de C++. Pour cela, j'ai introduit un [littéral définis par l'utilisateur](http://en.cppreference.com/w/cpp/language/user_literal) qui, à partir d'une chaîne de caractère, permet d'avoir un entier. En fait, l'entier est obtenu à partir d'un hash de la chaîne, ce qui garantit (presque) que pour deux chaînes différentes, on aura deux identifiants différents (ou alors, c'est vraiment pas de bol !). Déjà, quel hash utiliser ? Ici, une fonction de hachage non-cryptographique et simple convient. Et même, si elle peut être suffisamment simple pour pouvoir être calculée à la compilation, ce serait parfait. À la compilation ? Oui, C++ permet, grace au [mot-clef constexpr](http://en.cppreference.com/w/cpp/language/constexpr) de calculer des choses à la compilation. Donc, notre fonction de hachage doit être `constexpr`, ce qui implique qu'elle doit tenir sur une seule ligne ! Heureusement, tout un tas de fonction de hachage sont comme ça. J'ai donc choisi [une variante d'un hash FNV](http://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function). Voilà son implémentation en C++ : ```cpp constexpr uint64_t Hash(const char *str, std::size_t sz) { return sz == 0 ? 0xcbf29ce484222325 : (str[0] ^ Hash(str + 1, sz - 1)) * 0x100000001b3; } ``` On peut voir que la variante vient du fait qu'ici, à cause de l'appel récursif, on prend les données à l'envers, c'est-à-dire qu'on commence par la fin et on remonte jusqu'au début. Ce n'est pas très grave, ça donne les mêmes résultats en terme de collisions potentielles. Ensuite, il n'y a plus qu'à définir un nouveau littéral. À noter que les littéraux définis pas les utilisateurs doivent commencer par `_`, les littéraux commençant par une lettre étant réservés pour un usage futur (comme en C++14 où on aura plusieurs littéraux de ce genre dans la bibliothèque standard). Ici, on choisit `_type` : ```cpp constexpr uint64_t operator"" _type(const char *str, std::size_t sz) { return Hash(str, sz); } ``` Maintenant, pour définir un identifiant d'un composant (par exemple), on peut faire : ```cpp struct Foo { static const es::ComponentType type = "Foo"_type; } ``` Et cette constante est calculée à la compilation, pas à l'exécution. On a l'avantage d'avoir un identifiant clair sous forme de chaîne de caractères et un identifiant entier pour le développeur, sans aucun surcoût à l'exécution, bref que des avantages. Après, on pourrait s'amuser à définir des macros pour encapsuler tout ça, ou le générer automatiquement (ce que je fais dans mon jeu), mais tout ça est laissé à l'utilisateur, la bibliothèque ne fournit que le mécanisme de base et c'est déjà pas mal. # Des nouvelles du front Pas grand chose de nouveau par rapport à la dernière fois. Je continue [ma réflexion sur les dialogues](http://linuxfr.org/news/je-cree-mon-jeu-video-e08-fiche-de-lecture-de-l-art-du-game-design-par-jesse-schell#les-dialogues) et malgré l'excellent lien qui m'a été fourni à propos du jeu [Andor's Trail](https://code.google.com/p/andors-trail/), il y a encore des zones d'ombre que je souhaite éclaircir avant de me lancer dans un début d'implémentation. Mais j'ai vraiment hâte d'attaquer cette partie. Par ailleurs, j'ai terminé un gros refactoring (nécessaire) sur le chargement de la carte et j'ai commencé à [spécifier un peu proprement](https://gitorious.org/akagoria/akagoria/source/develop:doc/map.md) la manière dont j'allais construire ma carte graĉe à [Tiled](http://www.mapeditor.org/).

AltStyle によって変換されたページ (->オリジナル) /